Google Maps에서 대량 발생한 마커를 클러스터링할 수 있게 되었습니다! -Marker Clustering -

TL; DR



Google Maps SDK for iOS에 Marker Clustering이 새롭게 등장 했으므로 시도해 보았습니다!
Marker Clustering에서 확대하면 Marker가 클러스터와 분리되고 축소되면 Marker가 클러스터됩니다. 이렇게 하면 대량의 Marker로 지도가 채워져 보기 어려워지는 문제가 해결됩니다.



환경


  • Xcode Version 8.0 beta
  • iPhone 6s iOS 9.3.3
  • Swift 3.0
  • Cocoapods 1.0.1
  • Google Maps SDK 1.13.2

  • GMS를 Cocoapods로 설치



    Cocoapods를 통해 Google Maps SDK for iOSGoogle Maps SDK for iOS Utility Library을 설치합니다.

    Podfile
    platform :ios, '9.0'
    
    target プロジェクト名 do
       pod 'GoogleMaps'
       pod 'Google-Maps-iOS-Utils'
    end
    
    

    Swift 프로젝트에서 사용할 준비



    GMS는 Objective-C로 작성되었습니다.
    Swift 프로젝트에서는 Bridging-Header를 만들고 GMUMarkerClustering을 가져옵니다.

    GoogleMapsTrial-Bridging-Header.h
    #import <Google-Maps-iOS-Utils/GMUMarkerClustering.h>
    

    AppDelegate에서 Google Maps API Key 설정을 잊지 않도록 합니다.

    AppDelegate.swift
    import UIKit
    import GoogleMaps
    
    // Google Maps API Key
    let APIKey = "YOUR_API_KEY"
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        var window: UIWindow?
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    
            GMSServices.provideAPIKey(APIKey)
    
            let mapViewController = UINavigationController.init(rootViewController: MapViewController())
    
            self.window = UIWindow(frame: UIScreen.main().bounds)
            self.window?.rootViewController = mapViewController
            self.window?.makeKeyAndVisible()
    
            return true
        }
    }
    

    GMUClusterItem 프로토콜에 적합한 Item 만들기



    지도에 표시할 MarkerItem을 만듭니다.

    MarkerItem.swift
    class MarkerItem: NSObject, GMUClusterItem {
        var position: CLLocationCoordinate2D //必須
    
        init(position: CLLocationCoordinate2D) {
            self.position = position
        }
    }
    

    Map을 표시하는 ViewController 만들기

    MapViewController.swift
    
    import UIKit
    import GoogleMaps
    
    
    // 新宿フロントタワー
    let cameraLatitude = 35.695978
    let cameraLongitude = 139.689340
    
    class MapViewController: UIViewController, GMUClusterManagerDelegate, GMSMapViewDelegate {
        private let camera = GMSCameraPosition.camera(withLatitude: cameraLatitude,
                                                      longitude: cameraLongitude, zoom: 15)
        private let mapView = GMSMapView.init(frame: CGRect.zero)
    
        private lazy var clusterManager: GMUClusterManager = {
            let iconGenerator = GMUDefaultClusterIconGenerator()
            let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
            let renderer = GMUDefaultClusterRenderer(mapView: self.mapView, clusterIconGenerator: iconGenerator)
            return GMUClusterManager(map: self.mapView, algorithm: algorithm, renderer: renderer)
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            self.mapView.camera = camera
            self.view = self.mapView
    
            // Marker × 1000 ランダム座標を生成し、ClusterManagerにadd
            for _ in 0...1000 {
                let extent = 0.1
                let latitude = cameraLatitude + extent * randomScale()
                let longitude = cameraLongitude + extent * randomScale()
                clusterManager.add(MarkerItem.init(position: CLLocationCoordinate2DMake(latitude, longitude)))
            }
    
            // MarkerItemをClusteringし、地図にプロット
            clusterManager.cluster()
    
            // GMUClusterManagerDelegate + GMSMapViewDelegateを設定
            clusterManager.setDelegate(self, mapDelegate: self)
        }
    
        // MARK: - GMUMapViewDelegate
    
        // Marker or Cluster Markerがタップされた
        func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
            if marker.userData is MarkerItem {
                debugPrint("ClusterのMarkerItemがタップされた")
            } else {
                debugPrint("通常のMarkerがタップされた")
            }
            return false
        }
    
        // MARK: - GMUClusterManagerDelegate
    
        // Clusterがタップされたたら、Camera Positionを移動
        func clusterManager(_ clusterManager: GMUClusterManager, didTap cluster: GMUCluster) {
            let newCamera = GMSCameraPosition.camera(withTarget: cluster.position,
                                                     zoom: mapView.camera.zoom + 1)
            let update = GMSCameraUpdate.setCamera(newCamera)
            mapView.moveCamera(update)
        }
    
    
        private func randomScale() -> Double {
            return Double(arc4random()) / Double(UINT32_MAX) * 2.0 - 1.0
        }
    
    }
    
    

    사용 가능한 클러스터링 알고리즘



    아직 문서가 보이지 않기 때문에 Jump to Definition으로 날아갔는데, 2 종류의 클러스터링 알고리즘을 확인.
    다음 설명은 의역입니다. 덧붙여 서두의 gif는 GMUNonHierarchicalDistanceBasedAlgorithm 를 사용하고 있습니다.
    // マップをグリッド状に分割
    GMUGridBasedClusterAlgorithm
    
    /*
    非階層的クラスタリング
    1. 追加順にitemsをイテレート(クラスター候補)
    2. itemの中心点を持つクラスターを作成
    3. 特定の距離圏内に含まれるすべてのitemをクラスターに追加
    4. 別のクラスターに近ければ、既存クラスターから追加済みitemを移動させる
    5. それらをクラスター候補から削除
    各クラスターは最初のitemの中心点を持つ(複数itemの重心ではなく)
    */
    GMUNonHierarchicalDistanceBasedAlgorithm
    

    참고



    이하의 문서, 소스 코드를 참고로 했습니다.
  • googlemaps/google-maps-ios-utils
  • Marker Clustering | Google Maps SDK for iOS | Google Developers
  • 좋은 웹페이지 즐겨찾기