Help us understand the problem. What is going on with this article?

Google Mapsで大量発生したマーカーをクラスター化できるようになった!- Marker Clustering -

More than 3 years have passed since last update.

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 iOSと、Google 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を作成

Mapに表示する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

参考

以下のドキュメント、ソースコードを参考にしました。

globis
グロービスは 1992 年の創業以来、社会人を対象とした MBA、人材育成の領域で Ed-Tech サービスを提供し、現在は日本 No.1 の実績があります。これらの資産と、さらに IT や AI を活用することで、アジア No.1 を目指しています。
http://www.globis.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした