LodPoi

LodPoi 개념 및 사용 방법.

LodPoi는 Poi의 한 종류로, 이동 및 회전의 인터페이스가 없는 Poi입니다. 일반 Poi는 화면의 갱신이 있을때마다 경쟁에 대한 연산을 수행하는데, 추가된 Poi가 매우 많을 경우 이 처리 시간이 길어져서 Poi가 느리게 표시될 수 있습니다. LodPoi는 대량의 LodPoi를 추가할 경우, 경쟁에 참여할 Poi를 사전에 미리 모두 계산해두므로 대량의 Poi를 빠르게 표시할 수 있습니다. 즐겨찾기 등 대량의 Poi를 한꺼번에 보여줄 때 적합합니다.

LodPoi type은 생성하는 방법, 이동/회전등의 애니메이션이 없다는 점을 제외하고는 일반 Poi와 똑같습니다.

LodLabelLayer


LodLabelLayer는 LabelLayer의 한 종류로, 별도의 경쟁처리를 하는 LodPoi를 생성하기 위한 특수 목적을 가진 Layer입니다. 따라서 LodPoi는 Poi와 다르게 LabelLayer가 아닌 LodLabelLayer에서 생성해야 합니다.

LodLabelLayer도 일반 LabelLayer와 마찬가지로 LabelManager를 통해서 생성할 수 있습니다. 이를 생성하는 LodLabelLayerOptions은 아래와 같습니다.

Property Description
layerID layer의 고유 ID. ID는 중복으로 사용할 수 없습니다.
competitionType 다른 Poi와 경쟁하는 방법을 결정합니다. 기본 룰은 타입 순서에 따라 순서가 빠를수록 우선순위가 높습니다.
- none: 경쟁하지 않고 겹쳐서 그려집니다.
- upper: 자신보다 우선순위가 높은 Poi와 경쟁합니다. 상위 Poi에게 우선권이 있으므로 상위 Poi와 겹쳐질경우 그려지지 않습니다.
- same: 자신과 같은 우선순위를 가진 Poi와 경쟁합니다. 이때 경쟁의 기준은 orderType에 따라 결정됩니다.
- lower: 자신보다 우선순위가 낮은 Poi와 경쟁합니다. 상위 Poi에 우선권이 있으므로 자신이 그려진 위치에 upper 속성을 가진 Poi는 그려지지 않습니다.
- background: 최적화를 위해 경쟁 없이 배경처럼 뒤쪽에 그려집니다.
competitionUnit 경쟁하는 단위를 결정합니다.
- poi: Symbol과 Text 모두 경쟁을 통과해야 그려집니다.
- symbolFirst: Symbol만 경쟁의 기준이 됩니다. 문자열은 경쟁에서 통과하지 못하는 경우 심볼만 그려집니다.
orderType competitionType이 same일 때 경쟁하는 기준이 됩니다.
- rank: 각 Poi별로 가지고 있는 rank 속성에 따라 rank값이 높을수록 높은 우선순위를 갖습니다.
- closerFromLeftBottom: 화면 왼쪽 하단과 거리가 가까울수록 높은 우선순위를 갖습니다.
zOrder 레이어의 렌더링 우선순위를 정의합니다. 이 때 렌더링 우선순위는 레이어의 내부가 아닌 여러개의 LabelLayer간의 렌더링 우선순위를 의미합니다.
즉, zOrder가 0인 LabelLayer의 Poi는 zOrder가 1인 LabelLayer에 속한 Poi보다 뒤에 그려집니다.
radius LOD 선처리 작업을 위해 반경(px)을 지정합니다. 이 반경을 기준으로 경쟁에 참여할 LodPoi를 사전에 걸러냅니다.

LodLabelLayer는 일반 LabelLayer와 다르게, 내부적으로 정적인 경챙처리를 통해서 대량의 Poi를 빠르게 표시할 수 있습니다. 단, 이러한 선처리 작업이 들어가므로 LodLabelLayer에 속한 LodPoi는 일반 Poi와는 다르게 이동, 회전, 애니메이션 등에 대한 인터페이스가 제한적입니다. 따라서 이동, 회전, 애니메이션이 없는 대량의 Poi를 표시하고자 할 때는 LodPoi 사용을 권장합니다. 그 외에 사용방법 및 기능은 Poi와 동일합니다.

아래 예제는 3개의 LodLabelLayer를 생성하여 각각 레이어별로 다른 스타일의 대량의 LodPoi를 표시하는 예제입니다. 아래 예제에서는 레이어별로 1000개씩 총 3000개의 Poi를 추가합니다.

    func createLodLabelLayer() {
        let view = mapController?.getView("mapview") as! KakaoMap
        let manager = view.getLabelManager()
        // LodLabelLayer를 생성하기 위한 Option.
        // LodLayer에서는 효율적인 계산을 위해 POI의 중심에서 일정 반경(radius, 단위 : pixel)의 원으로 겹치는지를 확인한다.
        let seoul = LodLabelLayerOptions(layerID: "seoul", competitionType: .none, competitionUnit: .symbolFirst, orderType: .rank, zOrder: 0, radius: 20.0)
        let busan = LodLabelLayerOptions(layerID: "busan", competitionType: .none, competitionUnit: .symbolFirst, orderType: .rank, zOrder: 0, radius: 20.0)
        let korea = LodLabelLayerOptions(layerID: "korea", competitionType: .none, competitionUnit: .symbolFirst, orderType: .rank, zOrder: 0, radius: 20.0)

        let _ = manager.addLodLabelLayer(option: seoul)
        let _ = manager.addLodLabelLayer(option: busan)
        let _ = manager.addLodLabelLayer(option: korea)
    }
    
    func createPoiStyle() {
        let view = mapController?.getView("mapview") as! KakaoMap
        let manager = view.getLabelManager()
        
        let symbols = [
            UIImage(named: "pin_blue.png"),
            UIImage(named: "pin_org.png"),
            UIImage(named: "pin_red.png")
        ]
        let anchorPoint = KMNormalizedPoint(x: 0.0, y: 0.5)
        
        let textLineStyles = [
            PoiTextLineStyle(textStyle: TextStyle(fontSize: 15, fontColor: UIColor.white, strokeThickness: 2, strokeColor: UIColor(red: 0.1, green: 0.1, blue: 0.1, alpha: 1.0)), textLayout: .bottom),
            PoiTextLineStyle(textStyle: TextStyle(fontSize: 12, fontColor: UIColor(red: 0.8, green: 0.1, blue: 0.1, alpha: 1.0), strokeThickness: 1, strokeColor: UIColor(red: 0.9, green: 0.1, blue: 0.1, alpha: 1.0)), textLayout: .bottom)
        ]
        for index in 0 ... 2 {
            let iconStyle = PoiIconStyle(symbol: symbols[index], anchorPoint: anchorPoint)
            let textStyle = PoiTextStyle(textStyles: textLineStyles)
            let poiStyle = PoiStyle(styleID: "customStyle" + String(index), styles: [
                PerLevelPoiStyle(iconStyle: iconStyle, textStyle: textStyle, level: 0)
            ])
            manager.addPoiStyle(poiStyle)
        }
    }
    
    func createLodPois() {
        let view = mapController?.getView("mapview") as! KakaoMap
        let manager = view.getLabelManager()
        
        for index in 0 ... (_layerNames.count - 1) {
            let layer = manager.getLodLabelLayer(layerID: _layerNames[index])
            let datas = testLodDatas(layerIndex: index)
            let _ = layer?.addLodPois(options: datas.0, at: datas.1)    // 대량의 POI를 add할때는 개별로 add하기 보다는 addPois를 사용하는 것이 효율적이다.
            layer?.showAllLodPois()
        }
    }
    
    func testLodDatas(layerIndex: Int) -> ([PoiOptions], [MapPoint]) {
        var datas = [PoiOptions]()
        var positions = [MapPoint]()
        
        var coords = [MapPoint]()
        var boundary = [GeoCoordinate]()

        coords.append(MapPoint(longitude: 126.627459, latitude: 35.129776))
        coords.append(MapPoint(longitude: 126.875658, latitude: 37.492889))
        coords.append(MapPoint(longitude: 128.774832, latitude: 35.126031))
        
        boundary.append(GeoCoordinate(longitude: 2.694945, latitude: 3.590908))
        boundary.append(GeoCoordinate(longitude: 0.269494, latitude: 0.179662))
        boundary.append(GeoCoordinate(longitude: 0.359326, latitude: 0.628808))

        for index in 1 ... 1000 {
            let options = PoiOptions(styleID: "customStyle" + String(layerIndex))
            options.rank = UInt(index)
            let coord = coords[layerIndex].kakaoCoord
            
            options.transformType = .decal
            options.clickable = true
            options.addText(PoiText(text: _layerNames[layerIndex], styleIndex: 0))
            options.addText(PoiText(text: String(index), styleIndex: 1))

            datas.append(options)
            positions.append(MapPoint(longitude: coord.longitude + Double.random(in: 0...boundary[layerIndex].longitude),
                                      latitude: coord.latitude + Double.random(in: 0...boundary[layerIndex].latitude)))
        }
        
        return (datas, positions)
    }
    
    override func containerDidResized(_ size: CGSize) {
        let mapView: KakaoMap? = mapController?.getView("mapview") as? KakaoMap
        mapView?.viewRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)
    }
    
    let _layerNames: [String] = ["korea", "seoul", "busan"]

LodLabelLayer에서 LodPoi를 추가하는 함수는 아래와 같이 3가지 형태가 존재하는데, 아래와 같은 사용을 권장합니다.

  • addPoi(option:at:)-> LodPoi : 하나의 LodPoi만 생성할 때 사용합니다.
  • addPois(option:at:) -> [LodPoi] : 똑같은 option을 가지고 위치만 다른 여러개의 LodPoi를 생성할 때 사용합니다.
  • addPois(options:at:) -> [LodPoi] : 각각 다른 option을 가지는 여러개의 LodPoi를 한꺼번에 생성합니다. 이때 option 배열의 사이즈는 위치를 담은 배열의 사이즈와 동일해야 합니다.

LodPoi Properties


LodLabelLayer를 통해 생성된 LodPoi는 아래와 같은 종류의 Property를 가지고 있습니다.

Property Description
layerID LodPoi가 속한 LodLabelLayer의 ID
itemID LodPoi의 ID
rank LodPoi의 rank. 값을 새로 설정하면, rank값이 업데이트 됩니다.
clickable LodPoi의 클릭 가능 여부. 값을 새로 설정하면, clickable 값이 업데이트 되고 LodPoi의 클릭 가능 여부가 변경됩니다.
isShow LodPoi가 현재 뷰에 표시되고 있는지 여부
userObject LodPoi의 UserObject