It's the 15th anniversary of Google Maps Platform - Check out the latest news and announcements

Marker Clustering

This page describes the marker clustering utility that's available in the utility library for the Maps SDK for iOS.

By clustering your markers, you can put a large number of markers on a map without making the map hard to read. The marker clustering utility helps you manage multiple markers at different zoom levels.

When a user views the map at a high zoom level, the individual markers show on the map. When the user zooms out, the markers gather together into clusters, to make viewing the map easier.

Quick start

The demo app provides a sample implementation of the marker clustering utility. Run the demo app shipped with the utility library by using one of these two methods:

Use files from GitHub

  1. Download the code sample archive from GitHub and unpack the archive.
  2. Open a terminal window, navigate to the directory where you expanded the sample files, and drill down into the appropriate samples directory for the language you want to use:
    • For Swift, run cd google-maps-ios-utils-master/samples/SwiftDemoApp
    • For Objective-C, run cd google-maps-ios-utils-master/samples/ObjCDemoApp
  3. Run the following command:
    pod install
  4. CocoaPods updates your spec repositories, then opens the demo in a temporary Xcode project named SwiftDemoApp.xcodeproj or ObjCDemoApp.xcodeproj.

Use CocoaPods v1.6.1

  1. Open a terminal window and install version 1.6.1:
    sudo gem install cocoapods -v1.6.1
  2. Fetch the Google Maps files using Cocoapods:
    pod try Google-Maps-iOS-Utils

    Choose either Swift or Objective-C when prompted. CocoaPods updates your spec repositories, then opens the demo in a temporary Xcode project named SwiftDemoApp.xcodeproj or ObjCDemoApp.xcodeproj.

For the full code sample, see the ObjCDemoApp and SwiftDemoApp on GitHub.

The following screenshot shows the default style of marker clusters:

A map with clustered markers in the default style

Below is an example of custom marker clusters:

A map with custom clustered markers

Prerequisites and notes

Maps SDK for iOS Utility Library

The marker clustering utility is part of the Maps SDK for iOS Utility Library. If you haven't yet set up the library, follow the setup guide before reading the rest of this page.

For best performance, the recommended maximum number of markers is 10,000.

Location permission

This example uses the device's GPS to locate the user and the map on their coordinates. To enable this, you must add a description to the NSLocationWhenInUseUsageDescription permission in the project's Info.plist file.

To add this, do the following:

  1. Click the Info.plist file in the Project Navigator in Xcode to open the Property List Editor.
  2. Click the '+' icon next to 'Information Property List' to add a new property.
  3. In the 'key' field, type 'NSLocationWhenInUseUsageDescription'. Xcode will automatically translate this to the long name 'Privacy - Location When In Use Usage Description'. For a complete list of possible location permission properties, see Requesting Authorization for Location Services in the Apple Developer documentation.
  4. Leave the 'Type' field set to 'String'.
  5. In the 'Value' field, type a description of the reason your app requires the use of the user's location. For example, "Locates the user to provide nearby business listings."

Add a simple marker clusterer

To represent the markers you want to display on the map, implement the GMUClusterItem protocol in your ViewController. The following code defines a point of interest, the POIItem class, representing the markers to be managed in clusters on the map:

Swift

/// Point of Interest Item which implements the GMUClusterItem protocol.
class POIItem: NSObject, GMUClusterItem {
  var position: CLLocationCoordinate2D
  var name: String!

  init(position: CLLocationCoordinate2D, name: String) {
    self.position = position
    self.name = name
  }
}

Objective-C

#import "ViewController.h"

@import GoogleMapsUtils;
#import <GoogleMaps/GoogleMaps.h>

// Point of Interest Item which implements the GMUClusterItem protocol.
@interface POIItem : NSObject<GMUClusterItem>

@property(nonatomic, readonly) CLLocationCoordinate2D position;
@property(nonatomic, readonly) NSString *name;

- (instancetype)initWithPosition:(CLLocationCoordinate2D)position name:(NSString *)name;

@end

@implementation POIItem

- (instancetype)initWithPosition:(CLLocationCoordinate2D)position name:(NSString *)name {
  if (self = [super init]) {
    _position = position;
    _name = [name copy];
  }
  return self;
}

@end

To use the cluster manager, instantiate a GMUClusterManager object and pass it a GMSMapView, a GMUClusterAlgorithm, and a GMUClusterRenderer. The library includes default implementations of the cluster algorithm and renderer. The following code creates a cluster manager using the GMUNonHierarchicalDistanceBasedAlgorithm and the GMUDefaultClusterRenderer that are included in the utility library:

Swift

class ViewController: UIViewController, GMUClusterManagerDelegate,
    GMSMapViewDelegate {

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

  override func viewDidLoad() {
    super.viewDidLoad()

    // Set up the cluster manager with the supplied icon generator and
    // renderer.
    let iconGenerator = GMUDefaultClusterIconGenerator()
    let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
    let renderer = GMUDefaultClusterRenderer(mapView: mapView,
                                clusterIconGenerator: iconGenerator)
    clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm,
                                                      renderer: renderer)

    // Generate and add random items to the cluster manager.
    generateClusterItems()

    // Call cluster() after items have been added to perform the clustering
    // and rendering on map.
    clusterManager.cluster()
  }
}

Objective-C

@implementation ViewController {
  GMSMapView *_mapView;
  GMUClusterManager *_clusterManager;
}

- (void)loadView {
  GMSCameraPosition *camera =
      [GMSCameraPosition cameraWithLatitude:kCameraLatitude longitude:kCameraLongitude zoom:10];
  _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
  self.view = _mapView;
}

- (void)viewDidLoad {
  [super viewDidLoad];

  // Set up the cluster manager with a supplied icon generator and renderer.
  id<GMUClusterAlgorithm> algorithm =
      [[GMUNonHierarchicalDistanceBasedAlgorithm alloc] init];
  id<GMUClusterIconGenerator> iconGenerator =
      [[GMUDefaultClusterIconGenerator alloc] init];
  id<GMUClusterRenderer> renderer =
      [[GMUDefaultClusterRenderer alloc] initWithMapView:_mapView
                                    clusterIconGenerator:iconGenerator];
  _clusterManager =
      [[GMUClusterManager alloc] initWithMap:_mapView
                                   algorithm:algorithm
                                    renderer:renderer];

  // Generate and add random items to the cluster manager.
  [self generateClusterItems];

  // Call cluster() after items have been added
  // to perform the clustering and rendering on map.
  [_clusterManager cluster];
}

Feed your markers into the cluster as GMUClusterItem objects by calling clusterManager:addItem:. The following code randomly generates cluster items (POIs) within the scope of the map's camera, then feeds them to the cluster manager:

Swift

/// Randomly generates cluster items within some extent of the camera and
/// adds them to the cluster manager.
private func generateClusterItems() {
  let extent = 0.2
  for index in 1...kClusterItemCount {
    let lat = kCameraLatitude + extent * randomScale()
    let lng = kCameraLongitude + extent * randomScale()
    let name = "Item \(index)"
    let item =
        POIItem(position: CLLocationCoordinate2DMake(lat, lng), name: name)
    clusterManager.addItem(item)
  }
}

/// Returns a random value between -1.0 and 1.0.
private func randomScale() -> Double {
  return Double(arc4random()) / Double(UINT32_MAX) * 2.0 - 1.0
}

Objective-C

// Randomly generates cluster items within some extent of the camera and
// adds them to the cluster manager.
- (void)generateClusterItems {
  const NSUInteger kClusterItemCount = 100;
  const double kCameraLatitude = -33.8;
  const double kCameraLongitude = 151.2;
  const double extent = 0.2;
  for (int index = 1; index <= kClusterItemCount; ++index) {
    double lat = kCameraLatitude + extent * [self randomScale];
    double lng = kCameraLongitude + extent * [self randomScale];
    NSString *name = [NSString stringWithFormat:@"Item %d", index];
    id<GMUClusterItem> item =
        [[POIItem alloc] initWithPosition:CLLocationCoordinate2DMake(lat, lng)
                                     name:name];
    [_clusterManager addItem:item];
  }
}

// Returns a random value between -1.0 and 1.0.
- (double)randomScale {
  return (double)arc4random() / UINT32_MAX * 2.0 - 1.0;
}

Handle events on markers and clusters

Using the map delegate: In general when using the Maps SDK for iOS, to listen to events on the map you must implement the GMSMapViewDelegate protocol. By default when using a GMUClusterManager, your map delegate works as usual. You can listen to map events, but you can't listen to the type-safe cluster manager events. When the user taps a marker, an individual cluster item, or a cluster, the API triggers mapView:didTapMarker: and attaches the extra cluster data to the marker.userData property. You can use conformsToProtocol to check what the user tapped.

Using the cluster manager delegate: If you want to listen to the type-safe events on the cluster manager, you must implement the GMUClusterManagerDelegate as well as the GMSMapViewDelegate. Typically, you implement both these protocols in the view controller that displays the map. Then call clusterManager.setDelegate:mapDelegate: to set the cluster manager delegate as well as the map delegate, as shown in this code sample:

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

Objective-C

@interface ViewController ()<GMUClusterManagerDelegate, GMSMapViewDelegate>
@end

@implementation ViewController {
  GMSMapView *_mapView;
  GMUClusterManager *_clusterManager;
}

- (void)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];
}

#pragma mark GMUClusterManagerDelegate

- (void)clusterManager:(GMUClusterManager *)clusterManager didTapCluster:(id<GMUCluster>)cluster {
  GMSCameraPosition *newCamera =
      [GMSCameraPosition cameraWithTarget:cluster.position zoom:_mapView.camera.zoom + 1];
  GMSCameraUpdate *update = [GMSCameraUpdate setCamera:newCamera];
  [_mapView moveCamera:update];
}

#pragma mark GMSMapViewDelegate

- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker {
  POIItem *poiItem = marker.userData;
  if (poiItem != nil) {
    NSLog(@"Did tap marker for cluster item %@", poiItem.name);
  } else {
    NSLog(@"Did tap a normal marker");
  }
  return NO;
}

The cluster manager now intercepts any events that you've implemented on clusterManager. It forwards any remaining events to the map delegate, if provided. Note that events for standard markers (that is, markers not generated by the cluster renderer) are always forwarded to the map delegate.

You can listen for the following events when using the GMUClusterManagerDelegate:

  • clusterManager:didTapCluster: is called when the user taps a cluster of markers.
  • clusterManager:didTapClusterItem: is called when the user taps an individual cluster item (marker).

Customize the marker clusters

You can provide a custom implementation for the GMUClusterRenderer, GMUClusterIconGenerator, or GMUClusterAlgorithm. You can base your custom implementation on the sample implementation of these protocols included in the utility library, or you can code a fully custom implementation by fulfilling the protocols.