Skip to content

High performance map annotation clustering

License

Notifications You must be signed in to change notification settings

klaas/ClusterMap

Repository files navigation

ClusterMap

Swift Platform Framework Package Manager GitHub

ClusterMap is an open-source library for high-performance map clustering. When you want to present many points over the map for better user experience and performance, you can make clusters from many points. A good starting point is using a native clustering mechanism. It's simple to implement, simple to use, and has nice animations with zero lines of code. The core problem of the native implementation is adding all points to the map in MainThread. You can't avoid this bottleneck, and it's a problem if you want to operate with thousand points. ClusterMap uses efficient QuadTree storage and performs all computations in the background thread.

Comparison with 20,000 annotations. For a detailed comparison, use Example-UIKit.

Demo Cluster Demo MKMapKit

Features

  • UIKit
  • SwiftUI
  • Swift concurrency
  • Adding/Removing Annotations
  • Clustering Annotations
  • Multiple Managers
  • Dynamic Cluster Disabling
  • Custom Cell Size
  • Custom Annotation Views

Demo

The Example is a great place to start. It demonstrates how to:

  • Integrate the library
  • Add/remove annotations
  • Reload annotations
  • Configure the annotation view
  • Configure the manager
  • Compare Apple's native implementation with to library

Installation

ClusterMap is available via Swift Package Manager.

Swift Package Manager

Add the following dependency to your Package.swift file:

.package(url: "https://github.com/vospennikov/ClusterMap.git", from: "1.1.0")

Cocoapods

ClusterMap is available through CocoaPods. To install it, add the following line to your Podfile:

pod 'ClusterMap', '1.1.0'

Usage

The Basics

The ClusterManager class generates, manages, and displays annotation clusters.

let clusterManager = ClusterManager()

Adding an Annotation

Create an object that conforms to the MKAnnotation protocol. Next, add the annotation object to an instance of ClusterManager with add(annotation:).

let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: 21.283921, longitude: -157.831661)
manager.add(annotation)

Configuring the Annotation View

Implement the map view's mapView(_:viewFor:) delegate method to configure the annotation view. Return an instance of MKAnnotationView to visually represent the annotations.

To display clusters, return an instance of ClusterAnnotationView.

extension ViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        if let annotation = annotation as? ClusterAnnotation {
            return CountClusterAnnotationView(annotation: annotation, reuseIdentifier: "cluster")
        } else {
            return MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
        }
    }
}

For performance reasons, you should generally reuse MKAnnotationView objects in your map views. See the Example to learn more.

Customizing the Appearance

The ClusterAnnotationView class exposes a countLabel property. You can subclass ClusterAnnotationView to provide custom behavior as needed. Here's an example of subclassing the ClusterAnnotationView and customizing the layer borderColor.

class CountClusterAnnotationView: ClusterAnnotationView {
    override func configure(_ annotation: ClusterAnnotation) {
        super.configure(annotation)
        layer.borderColor = UIColor.white.cgColor
        layer.borderWidth = 1.5
    }
}

See the AnnotationView to learn more.

Removing Annotations

To remove annotations, you can call remove(annotation:). However, the annotations will still display until you call reload().

manager.remove(annotation)

If shouldRemoveInvisibleAnnotations is set to false, annotations that have been removed may still appear on the map until calling reload() on the visible region.

Reloading Annotations

Implement the map view's mapView(_:regionDidChangeAnimated:) delegate method to reload the ClusterManager when the region changes.

func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    clusterManager.reload(mapView: mapView) { finished in
        // handle completion
    }
}

You should call reload() anytime you add or remove annotations.

Configuring the Manager

The ClusterManager class exposes several properties to configure clustering:

var zoomLevel: Double // The current zoom level of the visible map region.
var maxZoomLevel: Double // The maximum zoom level before disabling clustering.
var minCountForClustering: Int // The minimum number of annotations for a cluster. The default is `2`.
var shouldRemoveInvisibleAnnotations: Bool // Whether to remove invisible annotations. The default is `true`.
var shouldDistributeAnnotationsOnSameCoordinate: Bool // Whether to arrange annotations in a circle if they have the same coordinate. The default is `true`.
var distanceFromContestedLocation: Double // The distance in meters from contested location when the annotations have the same coordinate. The default is `3`.
var clusterPosition: ClusterAlignment // The position of the cluster annotation. The default is `.nearCenter`.

ClusterManagerDelegate

The ClusterManagerDelegate protocol provides many functions to manage clustering and configure cells.

// The size of each cell on the grid at a given zoom level.
func cellSize(for zoomLevel: Double) -> Double? { ... }

// Whether to cluster the given annotation.
func shouldClusterAnnotation(_ annotation: MKAnnotation) -> Bool { ... }

Documentation

The documentation for releases and main are available here:

Credits

This project is based on the work of Lasha Efremidze, who created the Cluster.

License

ClusterMap is available under the MIT license. See the LICENSE file for more info.

About

High performance map annotation clustering

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Swift 99.6%
  • Objective-C 0.4%