Google Maps iOS Utils でマーカーをまとめて表示する


Google Maps SDK を使用していてマップ上にマーカーを立てることはよくあると思います
ただ、マップ上にマーカーをたてすぎると、広域表示にした際にマーカーが密集しちゃって見辛くなってしまうってこともよくあると思います

そんな時マーカーを1つにまとめたいなーと思ったので、その実装方法を残しておきたいと追います

![GrWyuKwR.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53717/1adb1bac-7d9e-de26-b807-cc4172bfaedf.gif)



# 開発環境
Xcode：11.1（11A1027）
Swift 5
iOS：13.1
CocoaPods: 1.8.0

# 実装

## ライブラリのインストール
Google Maps iOS Utils をインストールするために CocoaPods を使用します
以下のように Podfile を編集し、 `pod install` を実行してください

```
target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
  use_frameworks!

  pod 'GoogleMaps'
  pod 'Google-Maps-iOS-Utils'
end
```

[こちらのセットアップガイド](https://developers.google.com/maps/documentation/ios-sdk/utility/setup)にも記載されていますが、
CocoaPods のバージョンが 1.8.1 以上はサポート対象外のようです

1.6.1 が推奨されているようですが、Xcode11 では 1.6.1 を使用した場合にビルドが通らなかったため、今回は 1.8.0 を使用しています

CocoaPods のバージョンを下げるには[こちら](https://mushikago.com/i/?p=7298)を参考にしてください

## マーカーをまとめて表示する用の class を作成

普段は `GMSMarker` class を使用してマーカーを表示していますが、
マーカーをまとめて表示するには `GMUClusterItem` protocol に準拠した class を作成し、その class を使用してマーカーを表示する必要があります

```swift
import GoogleMapsUtils

class POIItem: NSObject, GMUClusterItem {

    var position: CLLocationCoordinate2D

    init(position: CLLocationCoordinate2D) {
        self.position = position
    }
}
```

## Map 上にマーカーを配置

先ほど作成した独自 class `POIItem` を使用してマーカーを Google Map 上に表示していきます

```swift
import UIKit
import GoogleMaps
import GoogleMapsUtils

class ViewController: UIViewController {

  // デフォルトの位置情報（仮で東京駅付近にしています）
    let defaultPositionLat = 35.681223
    let defaultPositionLng = 139.767059
    private var mapView: GMSMapView!
    /// Map 上に表示するマーカーを管理するためのプロパティ
    private var clusterManager: GMUClusterManager!

    override func viewDidLoad() {
        super.viewDidLoad()

        // GoogleMapの初期位置
        let camera = GMSCameraPosition.camera(withLatitude: defaultPositionLat, longitude: defaultPositionLng, zoom: 17.0)
        mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
        view = mapView

        let iconGenerator = GMUDefaultClusterIconGenerator()
        let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
        let renderer = GMUDefaultClusterRenderer(mapView: mapView, clusterIconGenerator: iconGenerator)
        clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm, renderer: renderer)

        // マーカーをランダムに生成して Map 上に表示
        generateClusterItems()
    }

    private func generateClusterItems() {
        let extent = 0.01
        for _ in 1...100 {
            let lat = defaultPositionLat + extent * randomScale()
            let lng = defaultPositionLng + extent * randomScale()
            let item = POIItem(position: CLLocationCoordinate2DMake(lat, lng))
            clusterManager.add(item)
        }
        // Map にマーカーを描画
        clusterManager.cluster()
    }

    /// ランダムな位置にマーカーを表示するための乱数を生成
    private func randomScale() -> Double {
        return Double(arc4random()) / Double(UINT32_MAX) * 2.0 - 1.0
    }
}
```

上記のコードを実行すると、最初に載せた gif のような挙動になるかと思います

## Delegate とか

マーカーをタップした際のアクションを設定したい場合、以下のように `GMUClusterManagerDelegate` と `GMSMapViewDelegate` を設定してあげるとタップ時のアクションが設定可能なようです

```swift
class ViewController: UIViewController, GMUClusterManagerDelegate, GMSMapViewDelegate {

    private var mapView: GMSMapView!
    private var clusterManager: GMUClusterManager!

    override func viewDidLoad() {
        super.viewDidLoad()

        // ... Rest of code omitted for easy reading.

        // Register self to listen to both GMUClusterManagerDelegate and
        // GMSMapViewDelegate events.
        clusterManager.setDelegate(self, mapDelegate: self)
    }

    // MARK: - GMUClusterManagerDelegate
    func clusterManager(clusterManager: GMUClusterManager, didTapCluster cluster: GMUCluster) {
        let newCamera = GMSCameraPosition.cameraWithTarget(cluster.position,
      zoom: mapView.camera.zoom + 1)
        let update = GMSCameraUpdate.setCamera(newCamera)
        mapView.moveCamera(update)
    }

    // MARK: - GMUMapViewDelegate
    func mapView(mapView: GMSMapView, didTapMarker marker: GMSMarker) -> Bool {
        if let poiItem = marker.userData as? POIItem {
            NSLog("Did tap marker for cluster item \(poiItem.name)")
        } else {
            NSLog("Did tap a normal marker")
        }
        return false
    }
}

```

# 終わりに
Google Maps iOS Utils というライブラリはマーカーをまとめるだけでなく、
マーカーの画像をカスタマイズしたり KML や GeoJSON をレンダリングできたりと他にも色々な機能が使用できるようなので、機会があれば触ってみようかと思います

# 参考

- [Marker Clustering](https://developers.google.com/maps/documentation/ios-sdk/utility/marker-clustering)
- [Marker Clustering Setup and Demo](https://developers.google.com/maps/documentation/ios-sdk/utility/setup)
- [github:google-maps-ios-utils](https://github.com/googlemaps/google-maps-ios-utils)