Route
Route
Route는 길찾기 라인을 표시하기 위해 LOD처리를 적용한 라인입니다. 따라서 Route는 레벨별로 라인이 간소화되거나, 짧은 Segment가 생략됩니다. 또한 Route는 라인에 컬러값 뿐만 아니라 패턴, 심볼을 적용하여 다양하게 표시할 수 있습니다.
하나의 세그먼트와 스타일로 이루어진 라인 | 여러 세그먼트와 스타일로 이루어진 라인 | 자동생성 곡선 라인 | 스타일에 패턴이 있는 라인 |
---|---|---|---|
RouteManager
Route를 추가/삭제하는 등의 control은 모두 RouteManager를 통해서 이루어집니다. RouteManager는 KakaoMap 객체 안에 종속되어 있으며, KakaoMap이 삭제된 뒤에 사용하지 않도록 주의해야 합니다.
RouteManager에서는 Route를 관리합니다. Layer는 Route를 추가해서 일종의 폴더처럼 route를 관리할 수 있는 단위입니다. Style은 Route가 어떻게 표시될 지 정의합니다. Route를 추가하기 전에 style을 먼저 생성해놓고, 해당 스타일의 ID를 레퍼런스로 객체를 어떻게 표시할 지 정의합니다.
RouteLayer
RouteManager를 통해 RouteLayer를 생성할 수 있습니다. 특정 성격을 가진 여러개의 Route를 Layer 단위로 묶어서 편리하게 관리할 수 있습니다.
Route는 사용자가 직접 객체를 생성할 수 없으며, RouteLayer에 Route를 생성하기 위한 옵션들로 생성된 Route 객체를 받아올 수 있습니다.
RouteLayer를 생성하기 위한 옵션은 아래와 같습니다. RouteManager를 통해 특정 옵션을 주어서 RouteLayer를 생성하고, RouteLayer를 통해 Route를 생성할 수 잇습니다. 이 레이어는 레이어 안에 속한 Route를 관리하며, Route간의 렌더링 우선순위를 조절할 수 있습니다.
Property | Description |
---|---|
layerID | RouteLayer의 식별자 |
zOrder | RouteLayer의 렌더링 우선순위 |
특정 목적을 가진 Route를 하나의 Layer로 묶어서 한꺼번에 표시하거나, 사라지게 할 수 있습니다. 레이어를 지우면 해당 레이어에 속한 Route도 함께 제거됩니다.
RouteLayer는 사용자가 생성한 RouteLayer간의 렌더링 우선순위를 zOrder를 통해 조절할 수 있습니다. 단, 다른 종류의 View Object(Label, Shape, Gui)와는 그리는 순서가 고정적이므로 렌더링 우선순위를 조절할 수 없습니다.
RouteLayer 생성하기
아래 예제 코드는 RouteManager를 통해 RouteLayer를 생성하는 예제입니다.
let mapView = mapController?.getView("mapview") as! KakaoMap
let manager = mapView.getRouteManager()
// 레이어의 이름과 렌더링 우선순위를 지정하여 RouteLayer를 생성한다.
// 생성된 RouteLayer가 리턴된다.
let layer = manager.addRouteLayer(layerID: "RouteLayer", zOrder: 0)
RouteStyleSet
RouteStyleSet은 Route가 어떻게 표시될지를 정의합니다. RouteStyleSet은 RouteManager를 통해 생성할 수 있으며, 같은 styleID로는 overwrite할 수 없습니다. RouteStyleSet은 하나 이상의 RouteStyle과 RoutePattern으로 이루어져 있습니다. 또한 RouteStyle은 하나 이상의 레벨별 스타일(PerLevelRouteStyle)로 구성됩니다. RoutePattern은 RouteStyle에 적용할 패턴을 추가할 수 있습니다. 그림으로 표시하면 RouteStyleSet의 구성은 아래와 같습니다.
StyleSet에 여러개의 style을 추가하면 추가한 순서대로 index가 부여되고, 여러개의 RouteSegment로 이루어진 Route에 해당 StyleSet을 적용하여 각 RouteSegment마다 styleIndex를 지정하여 원하는 세그먼트마다 다르게 표시할 수 있습니다.
또한, RouteStyleSet은 추가적으로 RoutePattern도 추가할 수 있습니다. 각 RouteStyle에 적용할 패턴들을 추가할 수 있습니다. 이 패턴 역시 추가한 순서대로 Pattern Index가 부여되고, PerLevelRouteStyle마다 StyleSet에 추가된 패턴 인덱스를 지정할 수 있습니다.
주의
Style이 지정되지 않은 Route, 혹은 지정되었어도 표시 레벨 범위 밖에 있는 레벨에서는 Route가 표시되지 않습니다.RouteStyle
RouteStyle의 단위 레벨 구성 요소는 아래와 같습니다.
Property | Description |
---|---|
width | Route의 두께 |
color | Route의 컬러 |
storkeWidth | Route의 외곽선 두께 |
storkeColor | Route의 외곽선 컬러 |
level | 해당 스타일이 표출되기 시작하는 레벨 |
patternIndex | 해당 스타일에서 사용할 RouteStyleSet에 추가된 RoutePattern 인덱스. -1로 세팅하면 패턴을 사용하지 않습니다. |
RoutePattern
RoutePattern의 구성요소는 아래와 같습니다.
Property | Description |
---|---|
pattern | 사용할 패턴이미지 |
symbol | 패턴이미지 외 부가적으로 패턴의 속성을 표시하는 심볼 이미지 |
distance | 패턴이 표시되는 간격(px) |
pinStart | 패턴이 Segment 시작지점에 고정적으로 표시될지에 대한 여부 |
pinEnd0 | 패턴이 Segment 끝지점에 고정적으로 표시될지에 대한 여부 |
RouteStyleSet 생성하기
RouteStyle1 | RouteStyle2 | RouteStyle3 | RouteStyle4 |
---|---|---|---|
패턴 사용안함 | 화살표 패턴 사용 | dot 패턴 사용 | dot패턴과 symbol 사용 |
아래 예제는 위 스크린샷과 같은 형태의 4개의 RouteStyle과 3개의 RoutePattern을 구성하여 하나의 RouteStyleSet을 생성하는 예제입니다.
// RouteStyleSet을 생성한다.
func createRouteStyleSet() {
let mapView = mapController?.getView("mapview") as? KakaoMap
let manager = mapView?.getRouteManager()
let _ = manager?.addRouteLayer(layerID: "RouteLayer", zOrder: 0)
let patternImages = [UIImage(named: "route_pattern_arrow.png"), UIImage(named: "route_pattern_walk.png"), UIImage(named: "route_pattern_long_dot.png")]
// StyleSet에 pattern을 추가한다.
let styleSet = RouteStyleSet(styleID: "routeStyleSet1")
styleSet.addPattern(RoutePattern(pattern: patternImages[0]!, distance: 60, symbol: nil, pinStart: false, pinEnd: false))
styleSet.addPattern(RoutePattern(pattern: patternImages[1]!, distance: 6, symbol: nil, pinStart: true, pinEnd: true))
styleSet.addPattern(RoutePattern(pattern: patternImages[2]!, distance: 6, symbol: UIImage(named: "route_pattern_long_airplane.png")!, pinStart: true, pinEnd: true))
let colors = [ UIColor(hex: 0x7796ffff),
UIColor(hex: 0x343434ff),
UIColor(hex: 0x3396ff00),
UIColor(hex: 0xee63ae00) ]
let strokeColors = [ UIColor(hex: 0xffffffff),
UIColor(hex: 0xffffffff),
UIColor(hex: 0xffffff00),
UIColor(hex: 0xffffff00) ]
let patternIndex = [-1, 0, 1, 2]
// 총 4개의 스타일을 생성한다.
for index in 0 ..< colors.count {
let routeStyle = RouteStyle()
// 각 스타일은 1개의 표출 시작 레벨 = 0 인 PerLevelStyle을 갖는다. 즉, 전 레벨에서 동일하게 표출된다.
// Style의 패턴인덱스가 -1로 지정되는 경우, 패턴을 사용하지 않고 컬러만 사용한다.
routeStyle.addPerLevelStyle(PerLevelRouteStyle(width: 18, color: colors[index], strokeWidth: 4, strokeColor: strokeColors[index], level: 0, patternIndex: patternIndex[index]))
styleSet.addStyle(routeStyle)
}
manager?.addRouteStyleSet(styleSet)
}
Route 생성하기
Route 객체는 사용자가 직접 생성할 수 없기때문에, RouteLayer를 통해 생성할 Route의 property를 전달하면 API 내부적으로 해당 특성을 가진 Route 객체를 생성하여 리턴합니다.
func createRouteline() {
let mapView = mapController?.getView("mapview") as! KakaoMap
let manager = mapView.getRouteManager()
// Route 생성을 위해 RouteLayer를 생성한다.
let layer = manager.addRouteLayer(layerID: "RouteLayer", zOrder: 0)
// Route 생성을 위한 RouteSegment 생성
let segmentPoints = routeSegmentPoints()
var segments: [RouteSegment] = [RouteSegment]()
var styleIndex: UInt = 0
for points in segmentPoints {
// 경로 포인트로 RouteSegment 생성. 사용할 스타일 인덱스도 지정한다.
let seg = RouteSegment(points: points, styleIndex: styleIndex)
segments.append(seg)
styleIndex = (styleIndex + 1) % 4
}
// Route 생성을 위해 ID, styleID, zOrder, segment를 전달한다.
let route = layer?.addRoute(routeID: "routes", styleID: "routeStyleSet1", zOrder: 0, segments: segments)
route?.show()
}
Route를 생성하기 위해서는 아래와 같은 Property를 설정해주어야합니다.
Property | Description |
---|---|
routeID | Route의 고유 ID. 같은 Layer안에서 중복 ID를 가질 수 없습니다. |
styleID | Route가 사용할 RouteStyleSet ID. Route를 생성하는 시점에서 미리 StyleSet이 생성되어있어야 합니다. |
zOrder | Route의 렌더링 우선순위. Layer내에서 먼저 그려질지 결정하는 기준이 됩니다. |
segments | Route를 구성할 하나 이상의 RouteSegment 배열 |
주의
Route를 생성할 때 경유지를 포함하는 라인의 경우, 각 경유지마다 Route를 별도로 생성하는것을 권장합니다. 중간경로가 여러개 포함된 route line의 경우 최적화가 어려우므로 각 경유지 라인마다 Route를 별도로 생성해야합니다. 예를 들어, 시작->경유지1->도착으로 구성된 route line인 경우, 시작->경유지1 로 구성된 Route와 경유지1->도착으로 구성된 Route, 총 2개의 Route를 생성해야합니다.RouteSegment
RouteSegment는 Route의 구성요소입니다. Route는 하나 이상의 RouteSegment로 이루어져 있습니다. RouteSegment의 Property는 아래와 같습니다.
Property | Description |
---|---|
points | segment를 구성하는 MapPoint 배열. 2개 이상의 point로 이루어집니다 |
styleIndex | RouteSegment가 사용할 RouteStyleSet에 속한 RouteStyle 인덱스 |
위 스크린샷의 경우, 3개의 RouteSegment가 하나의 Route를 구성하며, 각 segment마다 다른 스타일을 적용하여 표시할 수 있습니다.
RouteSegment의 points구성 주의 사항
RouteSegment의 points를 입력할 때의 주의사항은 아래와 같습니다.
- points는 2개 이상의 MapPoint로 이루어져야 합니다.
1.1. points의 개수가 2개인 경우, 두 포인트는 위치가 달라야 합니다.
1.2. points가 2개 이상인 경우에도 위치가 동일한 포인트를 넣는 것은 권장하지 않습니다. - Segment가 2개 이상인 경우, 순서상 인접한 segment의 시작과 끝 point는 동일한 위치를 가져야 합니다.
2.1. 인접한 segment의 시작과 끝 point가 동일하지 않으면, 경우에 따라 정상적으로 그려지지 않을 수 있습니다.
Style 및 Segment 업데이트
Route는 Style과 RouteSegment를 함께 바꾸는 인터페이스를 제공합니다.
changeStyleAndData(styleID:segments:)
Route의 style은 유지하고, RouteSegment만 업데이트하고자 하는 경우나 segment는 유지하되 style만 바꾸는 경우에 해당 인터페이스를 사용합니다. 예를들어, 길찾기 검색 결과 위에 교통정보를 표시할 때 교통정보가 업데이트 되면 교통정보 컬러값들로 이루어진 styleSet은 유지하고, 바뀐 교통정보에 따라 segment만 업데이트 할 수 있습니다. 또한 길찾기 결과에서 enable 결과와 disabled 결과를 바꿀땐 segment를 유지하고, styleID만 바꿔서 스타일을 변경할 수 있습니다.
이 인터페이스는 Route 객체 자체의 id는 유지되므로, Route가 가리키는 본질은 변하지 않지만 위와 같은 예시처럼 데이터나 스타일을 업데이트 할 경우 해당 함수를 사용합니다. 만약 이 경우가 아니라면, 객체를 하나 별도로 만들어서 show/hide 등으로 컨트롤 하는것을 권장합니다.
아래 예제는 4개의 길찾기 검색 결과에서 선택한 경로만 highlight하도록 style을 변경하고, 제일 위에 그려질 수 있도록 zOrder를 변경하는 예제입니다. 아래 예제 코드는 샘플프로젝트에도 포함되어 있습니다.
// RouteLines을 표시할 Layer를 생성한다.
func createRouteLayer() {
let mapView = mapController?.getView("mapview") as? KakaoMap
let manager = mapView?.getRouteManager()
let _ = manager?.addRouteLayer(layerID: "RouteLinesLayer", zOrder: 0)
}
// Eanbled Style을 생성한다. => 제일 위에 그려지는 Route의 Style
func createEnabledStyleSet() {
let mapView = mapController?.getView("mapview") as? KakaoMap
let manager = mapView?.getRouteManager()
let patternImages = [UIImage(named: "route_pattern_arrow.png"), UIImage(named: "route_pattern_walk.png"), UIImage(named: "route_pattern_long_dot.png")]
// pattern
let styleSet = RouteStyleSet(styleID: "enabledRoute")
styleSet.addPattern(RoutePattern(pattern: patternImages[0]!, distance: 60, symbol: nil, pinStart: false, pinEnd: false))
styleSet.addPattern(RoutePattern(pattern: patternImages[1]!, distance: 6, symbol: nil, pinStart: true, pinEnd: true))
styleSet.addPattern(RoutePattern(pattern: patternImages[2]!, distance: 6, symbol: UIImage(named: "route_pattern_long_airplane.png")!, pinStart: true, pinEnd: true))
let colors = [ UIColor(hex: 0x7796ffff),
UIColor(hex: 0x343434ff),
UIColor(hex: 0x3396ff00),
UIColor(hex: 0xee63ae00) ]
let strokeColors = [ UIColor(hex: 0xffffffff),
UIColor(hex: 0xffffffff),
UIColor(hex: 0xffffff00),
UIColor(hex: 0xffffff00) ]
let patternIndex = [-1, 0, 1, 2]
for index in 0 ..< colors.count {
let routeStyle = RouteStyle()
routeStyle.addPerLevelStyle(PerLevelRouteStyle(width: 18, color: colors[index], strokeWidth: 4, strokeColor: strokeColors[index], level: 0, patternIndex: patternIndex[index]))
styleSet.addStyle(routeStyle)
}
manager?.addRouteStyleSet(styleSet)
}
// Disabled Style을 생성한다. 밑에 그려질 Route들의 style.
func createDisabledStyleSet() {
let mapView = mapController?.getView("mapview") as? KakaoMap
let manager = mapView?.getRouteManager()
// pattern
let styleSet = RouteStyleSet(styleID: "disabledRoute")
styleSet.addPattern(RoutePattern(pattern: UIImage(named: "route_pattern_arrow.png")!, distance: 60, symbol: nil, pinStart: false, pinEnd: false))
let routeStyle = RouteStyle()
routeStyle.addPerLevelStyle(PerLevelRouteStyle(width: 16, color: UIColor(hex: 0x8d8d8dff), strokeWidth: 2, strokeColor: UIColor(hex: 0xffffffff), level: 0, patternIndex: 0))
styleSet.addStyle(routeStyle)
manager?.addRouteStyleSet(styleSet)
}
// 4개의 Route를 생성한다.
func createRouteLines() {
let mapView = mapController?.getView("mapview") as! KakaoMap
let manager = mapView.getRouteManager()
let layer = manager.getRouteLayer(layerID: "RouteLinesLayer")
for index in 0 ... 3 {
var styleID: String
var zOrder: Int = 0
if index == _enabledIndex {
styleID = "enabledRoute"
zOrder = _enabledZOrder
}
else {
styleID = "disabledRoute"
zOrder = index
}
// 해당 예제에서는 route를 구성하는 points를 임의로 offset을 주어 4개의 다른 경로를 구성한다.
let segmentPoints = routeSegmentPoints(offset: Double(500 * index))
var styleIndex: UInt = 0
var segments = [RouteSegment]()
for points in segmentPoints {
if index != _enabledIndex {
styleIndex = 0
}
let segment = RouteSegment(points: points, styleIndex: styleIndex)
segments.append(segment)
styleIndex = (styleIndex + 1)%4
}
// route 추가
let route = layer?.addRoute(routeID: "route"+String(index+1), styleID: styleID, zOrder: zOrder, segments: segments)
route?.show()
}
}
func guiDidTapped(_ gui: GuiBase, componentName: String) {
guard _enabledIndex != Int(componentName)! - 1 else {
return
}
let mapView = mapController?.getView("mapview") as? KakaoMap
let manager = mapView?.getRouteManager()
let layer = manager?.getRouteLayer(layerID: "RouteLinesLayer")
// disabled -> enabled로 먼저 바뀌는게 자연스럽다.
let disabled = layer?.getRoute(routeID: "route"+componentName)
var segments = [RouteSegment]()
var styleIndex: UInt = 0
// style 과 data를 업데이트 하기 위해 route segment 재 생성
var segmentPoints = routeSegmentPoints(offset: Double(500 * (Int(componentName)!-1)))
for points in segmentPoints {
let segment = RouteSegment(points: points, styleIndex: styleIndex)
styleIndex = (styleIndex + 1)%4
segments.append(segment)
}
// 스타일과 데이터를 바꾸고, 제일 위에 표시되게 하기 위해 zOrder를 변경한다.
disabled?.changeStyleAndData(styleID: "enabledRoute", segments: segments)
disabled?.setZOrder(_enabledZOrder)
segments.removeAll()
// enabled로 표시되고 있던 route를 enabled로 바꾸기
let enabled = layer?.getRoute(routeID: "route"+String(_enabledIndex+1))
segmentPoints = routeSegmentPoints(offset: Double(500 * _enabledIndex))
for points in segmentPoints {
let segment = RouteSegment(points: points, styleIndex: 0)
segments.append(segment)
}
// 스타일과 데이터를 바꾸고, 다시 아래로 내리기 위해 zOrder를 변경한다.
enabled?.changeStyleAndData(styleID: "disabledRoute", segments: segments)
enabled?.setZOrder(_enabledIndex)
_enabledIndex = Int(componentName)!-1
}
Route Progress 표현
경로의 진행도를 Animator를 통해 표현할 수 있습니다.
//RouteAnimator 생성.
func createRouteAnimators() {
let mapView = mapController?.getView("mapview") as? KakaoMap
let manager = mapView?.getRouteManager()
let clearEffect = ProgressAnimationEffect(direction: .forward, type: .clearFromStart) //시작점에서부터 비워지는 효과
let fillEffect = ProgressAnimationEffect(direction: .forward, type: .fillFromStart) //시적점에서부터 채워지는 효과
clearEffect.interpolation = AnimationInterpolation(duration: 900000, method: .linear)
fillEffect.interpolation = AnimationInterpolation(duration: 900000, method: .linear)
let _ = manager?.addRouteAnimator(animatorID: "routeAnimator1", effect: clearEffect)
let _ = manager?.addRoute
Animator(animatorID: "routeAnimator2", effect: fillEffect)
}
// 진행을 표현하기 위해 fgRoute는 시작점에서부터 비워지는 효과를 사용하고, bgRoute는 시작점에서부터 채워지는 효과를 사용한다.
func runAnimation() {
let mapView = mapController?.getView("mapview") as? KakaoMap
let manager = mapView?.getRouteManager()
let animator1 = manager?.getRouteAnimator(animatorID: "routeAnimator1")
let animator2 = manager?.getRouteAnimator(animatorID: "routeAnimator2")
let fgRoutes = manager?.getRouteLayer(layerID: "RouteLayer")?.getRoute(routeID: "fgRoutes")
let bgRoutes = manager?.getRouteLayer(layerID: "RouteLayer")?.getRoute(routeID: "bgRoutes")
animator1?.addRoute(fgRoutes!)
animator2?.addRoute(bgRoutes!)
animator1?.start()
animator2?.start()
}
직접 Route의 진행도를 표현하고 싶은 경우, Route 객체의 setProgress를 사용하여 진행도를 지정할 수 있습니다. setProgress는 현상태에서 새로 지정된 상태로 지정된 시간동안 변경됩니다. 아래 예제는 Route의 진행도를 표현하는 예제입니다.
func progressBtnPressed() {
let mapView = mapController?.getView("mapview") as? KakaoMap
let manager = mapView?.getRouteManager()
let fgRoutes = manager?.getRouteLayer(layerID: "RouteLayer")?.getRoute(routeID: "fgRoutes")
let bgRoutes = manager?.getRouteLayer(layerID: "RouteLayer")?.getRoute(routeID: "bgRoutes")
//setProgress는 현상태에서 새로 지정된 상태로 지정된 시간동안 변경됨. 기본 상태는 .clearFromStart의 progress 0 상태.
//bgRoute가 비워진 상태에서 시작점에서부터 채워지도록 하기 위해 .fillFromStart의 progress 0 으로 먼저 지정.
bgRoutes?.setProgress(progress: 0.0, type: .fillFromStart, duration: 0, callback: { (rt: Route?) in
//progress 지정이 완료되면 변경될 상태로 지정. 진행도중 setProgress를 중복실행하면 진행중이던 내용은 무시되고 실행 시점의 상태에서 새로 지정된 상태로 변경을 진행함.
fgRoutes?.setProgress(progress: 0.5, type: .clearFromStart, duration: 100000)
bgRoutes?.setProgress(progress: 0.5, type: .fillFromStart, duration: 100000)
})
}