diff --git a/Sources/Cluster.swift b/Sources/Cluster.swift index ead3141..4660415 100755 --- a/Sources/Cluster.swift +++ b/Sources/Cluster.swift @@ -9,30 +9,13 @@ import CoreLocation import MapKit -open class ClusterManager { - - var tree = QuadTree(rect: MKMapRectWorld) - - /** - The size of each cell on the grid (The larger the size, the better the performance). - - If nil, automatically adjusts the cell size to zoom level. The default is nil. - */ - open var cellSize: Double? - - /** - The current zoom level of the visible map region. - - Min value is 0 (max zoom out), max is 20 (max zoom in). - */ - open internal(set) var zoomLevel: Double = 0 - +open class Configuration { /** The maximum zoom level before disabling clustering. Min value is 0 (max zoom out), max is 20 (max zoom in). The default is 20. */ - open var maxZoomLevel: Double = .maxZoomLevel + open var maxZoomLevel: Double = 20 /** The minimum number of annotations for a cluster. @@ -55,6 +38,8 @@ open class ClusterManager { */ open var shouldDistributeAnnotationsOnSameCoordinate: Bool = true + open var marginFactor: Double = -1 + /** The position of the cluster annotation. */ @@ -85,6 +70,33 @@ open class ClusterManager { */ open var clusterPosition: ClusterPosition = .nearCenter + /** + The size of each cell on the grid (The larger the size, the better the performance). + + If nil, automatically adjusts the cell size to zoom level. The default is nil. + */ + open var cellSize: (_ zoomScale: Double) -> CGSize = { CGSize(width: $0.cellSize, height: $0.cellSize) } +} + +public protocol ClusterManagerDelegate: class { + func clusterManager(_ manager: ClusterManager, cellSizeFor zoomScale: Double) -> CGSize +} + +open class ClusterManager { + + var tree = QuadTree(rect: MKMapRectWorld) + + open let configuration = Configuration() + + open weak var delegate: ClusterManagerDelegate? + + /** + The current zoom level of the visible map region. + + Min value is 0 (max zoom out), max is 20 (max zoom in). + */ + open internal(set) var zoomLevel: Double = 0 + /** The list of annotations associated. @@ -181,6 +193,7 @@ open class ClusterManager { let visibleMapRect = mapView.visibleMapRect let visibleMapRectWidth = visibleMapRect.size.width let zoomScale = Double(mapBounds.width) / visibleMapRectWidth + print(mapView.zoomLevel) queue.cancelAllOperations() queue.addBlockOperation { [weak self, weak mapView] operation in guard let `self` = self, let mapView = mapView else { return } @@ -199,15 +212,17 @@ open class ClusterManager { open func clusteredAnnotations(zoomScale: Double, visibleMapRect: MKMapRect, operation: Operation? = nil) -> (toAdd: [MKAnnotation], toRemove: [MKAnnotation]) { var isCancelled: Bool { return operation?.isCancelled ?? false } - guard !zoomScale.isInfinite, !zoomScale.isNaN else { return (toAdd: [], toRemove: []) } + guard !MKMapRectIsNull(visibleMapRect), !MKMapRectIsEmpty(visibleMapRect) else { return (toAdd: [], toRemove: []) } - zoomLevel = zoomScale.zoomLevel - let scaleFactor = zoomScale / (cellSize ?? zoomScale.cellSize) - - let minX = Int(floor(visibleMapRect.minX * scaleFactor)) - let maxX = Int(floor(visibleMapRect.maxX * scaleFactor)) - let minY = Int(floor(visibleMapRect.minY * scaleFactor)) - let maxY = Int(floor(visibleMapRect.maxY * scaleFactor)) +// zoomLevel = zoomScale.zoomLevel + zoomLevel = 0 + let scaleFactorX = zoomScale / Double(configuration.cellSize(zoomScale).width) + let scaleFactorY = zoomScale / Double(configuration.cellSize(zoomScale).height) + + let minX = Int(floor(visibleMapRect.minX * scaleFactorX)) + let maxX = Int(floor(visibleMapRect.maxX * scaleFactorX)) + let minY = Int(floor(visibleMapRect.minY * scaleFactorY)) + let maxY = Int(floor(visibleMapRect.maxY * scaleFactorY)) var allAnnotations = [MKAnnotation]() @@ -216,7 +231,7 @@ open class ClusterManager { for x in minX...maxX { for y in minY...maxY { - var mapRect = MKMapRect(x: Double(x) / scaleFactor, y: Double(y) / scaleFactor, width: 1 / scaleFactor, height: 1 / scaleFactor) + var mapRect = MKMapRect(x: Double(x) / scaleFactorX, y: Double(y) / scaleFactorY, width: 1 / scaleFactorX, height: 1 / scaleFactorY) if mapRect.origin.x > MKMapPointMax.x { mapRect.origin.x -= MKMapPointMax.x } @@ -237,7 +252,7 @@ open class ClusterManager { } // handle annotations on the same coordinate - for value in hash.values where shouldDistributeAnnotationsOnSameCoordinate && value.count > 1 { + for value in hash.values where configuration.shouldDistributeAnnotationsOnSameCoordinate && value.count > 1 { for (index, node) in value.enumerated() { let distanceFromContestedLocation = 3 * Double(value.count) / 2 let radiansBetweenAnnotations = (.pi * 2) / Double(value.count) @@ -248,9 +263,9 @@ open class ClusterManager { // handle clustering let count = annotations.count - if count >= minCountForClustering, zoomLevel <= maxZoomLevel { + if count >= configuration.minCountForClustering, zoomLevel <= configuration.maxZoomLevel { let cluster = ClusterAnnotation() - switch clusterPosition { + switch configuration.clusterPosition { case .center: cluster.coordinate = MKCoordinateForMapPoint(MKMapPoint(x: mapRect.midX, y: mapRect.midY)) case .nearCenter: @@ -285,7 +300,7 @@ open class ClusterManager { var toRemove = before.subtracted(after) let toAdd = after.subtracted(before) - if !shouldRemoveInvisibleAnnotations { + if !configuration.shouldRemoveInvisibleAnnotations { let nonRemoving = toRemove.filter { !visibleMapRect.contains($0.coordinate) } toRemove.subtract(nonRemoving) } diff --git a/Sources/ClusterMap.swift b/Sources/ClusterMap.swift new file mode 100644 index 0000000..fdec6c3 --- /dev/null +++ b/Sources/ClusterMap.swift @@ -0,0 +1,31 @@ +// +// ClusterMap.swift +// Cluster +// +// Created by Lasha Efremidze on 7/30/18. +// Copyright © 2018 efremidze. All rights reserved. +// + +import MapKit + +public struct Animation { + let annotations: [ClusterAnnotation] + var from: CLLocationCoordinate2D + var to: CLLocationCoordinate2D +} + +public protocol ClusterMap { + var manager: ClusterManager { get } + var visibleMapRect: MKMapRect { get } + var zoom: Double { get } + func selectCluster(annotation: MKAnnotation, animated: Bool) + func deselectCluster(annotation: MKAnnotation, animated: Bool) + func addClusters(annotations: [MKAnnotation]) + func removeClusters(annotations: [MKAnnotation]) + func performAnimations(_ animations: [Animation], completion: (Bool) -> Void) +} + +//extension MKMapView: ClusterMap { +// public var zoom: Double { +// } +//} diff --git a/Sources/Extensions.swift b/Sources/Extensions.swift index a3cc36a..91c551c 100644 --- a/Sources/Extensions.swift +++ b/Sources/Extensions.swift @@ -43,13 +43,13 @@ public func ==(lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude } -extension Double { - static let maxZoomLevel: Double = 20 +extension MKMapView { var zoomLevel: Double { - let maxZoomLevel = log2(MKMapSizeWorld.width / 256) // 20 - let zoomLevel = floor(log2(self) + 0.5) // negative - return max(0, maxZoomLevel + zoomLevel) // max - current + return floor(log2(360 * Double(frame.width / 256) / region.span.longitudeDelta)) } +} + +extension Double { var cellSize: Double { switch self { case 13...15: