Route

KakaoMapsSDK의 길찾기 라인 사용 방법.

Route

Route는 길찾기 라인을 표시하기 위해 LOD처리를 적용한 라인입니다. 따라서 Route는 레벨별로 라인이 간소화되거나, 짧은 Segment가 생략됩니다. 또한 Route는 라인에 컬러값 뿐만 아니라 패턴, 심볼을 적용하여 다양하게 표시할 수 있습니다.

하나의 세그먼트와 스타일로 이루어진 라인 여러 세그먼트와 스타일로 이루어진 라인 자동생성 곡선 라인 스타일에 패턴이 있는 라인
route1 route1 route1 route1
[RouteLine 에 적용 된 다양한 스타일과 모양]

RouteManager


Route를 추가/삭제하는 등의 control은 모두 RouteManager를 통해서 이루어집니다. RouteManager는 KakaoMap 객체 안에 종속되어 있으며, KakaoMap이 삭제된 뒤에 사용하지 않도록 주의해야 합니다.

RouteManager

RouteManager에서는 Route를 관리합니다. Layer는 Route를 추가해서 일종의 폴더처럼 route를 관리할 수 있는 단위입니다. Style은 Route가 어떻게 표시될 지 정의합니다. Route를 추가하기 전에 style을 먼저 생성해놓고, 해당 스타일의 ID를 레퍼런스로 객체를 어떻게 표시할 지 정의합니다.

RouteLayer


RouteManager를 통해 RouteLayer를 생성할 수 있습니다. 특정 성격을 가진 여러개의 Route를 Layer 단위로 묶어서 편리하게 관리할 수 있습니다.

Route는 사용자가 직접 객체를 생성할 수 없으며, RouteLayer에 Route를 생성하기 위한 옵션들로 생성된 Route 객체를 받아올 수 있습니다.

routeLayer

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의 구성은 아래와 같습니다.

RouteStyleSet

StyleSet에 여러개의 style을 추가하면 추가한 순서대로 index가 부여되고, 여러개의 RouteSegment로 이루어진 Route에 해당 StyleSet을 적용하여 각 RouteSegment마다 styleIndex를 지정하여 원하는 세그먼트마다 다르게 표시할 수 있습니다.

또한, RouteStyleSet은 추가적으로 RoutePattern도 추가할 수 있습니다. 각 RouteStyle에 적용할 패턴들을 추가할 수 있습니다. 이 패턴 역시 추가한 순서대로 Pattern Index가 부여되고, PerLevelRouteStyle마다 StyleSet에 추가된 패턴 인덱스를 지정할 수 있습니다.

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
style1 style2 style3 style4
패턴 사용안함 화살표 패턴 사용 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 배열

RouteSegment

RouteSegment는 Route의 구성요소입니다. Route는 하나 이상의 RouteSegment로 이루어져 있습니다. RouteSegment의 Property는 아래와 같습니다.

Property Description
points segment를 구성하는 MapPoint 배열. 2개 이상의 point로 이루어집니다
styleIndex RouteSegment가 사용할 RouteStyleSet에 속한 RouteStyle 인덱스

routeSegment

위 스크린샷의 경우, 3개의 RouteSegment가 하나의 Route를 구성하며, 각 segment마다 다른 스타일을 적용하여 표시할 수 있습니다.

RouteSegment의 points구성 주의 사항

RouteSegment의 points를 입력할 때의 주의사항은 아래와 같습니다.

  1. points는 2개 이상의 MapPoint로 이루어져야 합니다.
    1.1. points의 개수가 2개인 경우, 두 포인트는 위치가 달라야 합니다.
    1.2. points가 2개 이상인 경우에도 위치가 동일한 포인트를 넣는 것은 권장하지 않습니다.
  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를 통해 표현할 수 있습니다.

routeProgress1 routeProgress2

//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)
    })
}