Skip to content

Commit

Permalink
2.4.4 (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
efremidze authored Feb 12, 2019
1 parent 03660e5 commit 0926f8e
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 73 deletions.
19 changes: 15 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
language: objective-c
osx_image: xcode10
os: osx
osx_image: xcode10.1
env:
global:
- PROJECT=Cluster.xcodeproj
- SCHEME="Cluster"
- EXAMPLE_SCHEME="Example"
matrix:
- DESTINATION="OS=12.1,name=iPhone XS" SCHEME="$SCHEME"
- DESTINATION="OS=11.4,name=iPhone X" SCHEME="$SCHEME"
- DESTINATION="OS=10.3.1,name=iPhone 7 Plus" SCHEME="$SCHEME"
script:
- xcodebuild clean -project Cluster.xcodeproj -scheme Cluster -destination "platform=iOS Simulator,name=iPhone X,OS=12.0" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO test | xcpretty
- xcodebuild clean -project Cluster.xcodeproj -scheme Example -destination "platform=iOS Simulator,name=iPhone X,OS=12.0" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO test | xcpretty
- set -o pipefail

- xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty;
- xcodebuild -project "$PROJECT" -scheme "$EXAMPLE_SCHEME" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty;
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change log

## [Version 2.4.4](https://github.com/efremidze/Cluster/releases/tag/2.4.4)
Released on 2019-02-11

- Refactored Clustering

## [Version 2.4.3](https://github.com/efremidze/Cluster/releases/tag/2.4.3)
Released on 2019-01-28

Expand Down
2 changes: 1 addition & 1 deletion Cluster.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'Cluster'
s.version = '2.4.3'
s.version = '2.4.4'
s.summary = 'Map Clustering Library'
s.homepage = 'https://github.com/efremidze/Cluster'
s.license = { :type => 'MIT', :file => 'LICENSE' }
Expand Down
2 changes: 0 additions & 2 deletions Sources/Annotation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ open class ClusterAnnotation: Annotation {
The view associated with your cluster annotations.
*/
open class ClusterAnnotationView: MKAnnotationView {

open lazy var countLabel: UILabel = {
let label = UILabel()
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
Expand All @@ -70,7 +69,6 @@ open class ClusterAnnotationView: MKAnnotationView {
let count = annotation.annotations.count
countLabel.text = "\(count)"
}

}

/**
Expand Down
132 changes: 67 additions & 65 deletions Sources/Cluster.swift
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,6 @@ open class ClusterManager {
let (toAdd, toRemove) = self.clusteredAnnotations(zoomScale: zoomScale, visibleMapRect: visibleMapRect, operation: operation)
DispatchQueue.main.async { [weak self, weak mapView] in
guard let self = self, let mapView = mapView else { return completion(false) }
if toAdd.isEmpty, toRemove.isEmpty { return completion(false) }
self.display(mapView: mapView, toAdd: toAdd, toRemove: toRemove)
completion(true)
}
Expand All @@ -264,68 +263,8 @@ open class ClusterManager {

guard !isCancelled else { return (toAdd: [], toRemove: []) }

var allAnnotations = [MKAnnotation]()

dispatchQueue.sync {
for mapRect in mapRects {
var totalLatitude: Double = 0
var totalLongitude: Double = 0
var annotations = [MKAnnotation]()
var hash = [CLLocationCoordinate2D: [MKAnnotation]]()

// add annotations
for node in tree.annotations(in: mapRect) {
if delegate?.shouldClusterAnnotation(node) ?? true {
totalLatitude += node.coordinate.latitude
totalLongitude += node.coordinate.longitude
annotations.append(node)
hash[node.coordinate, default: [MKAnnotation]()] += [node]
} else {
allAnnotations.append(node)
}
}

// handle annotations on the same coordinate
if shouldDistributeAnnotationsOnSameCoordinate {
for value in hash.values where value.count > 1 {
for (index, node) in value.enumerated() {
let distanceFromContestedLocation = 3 * Double(value.count) / 2
let radiansBetweenAnnotations = (.pi * 2) / Double(value.count)
let bearing = radiansBetweenAnnotations * Double(index)
(node as? Annotation)?.coordinate = node.coordinate.coordinate(onBearingInRadians: bearing, atDistanceInMeters: distanceFromContestedLocation)
}
}
}

// handle clustering
let count = annotations.count
if count >= minCountForClustering, zoomLevel <= maxZoomLevel {
let cluster = ClusterAnnotation()
switch clusterPosition {
case .center:
cluster.coordinate = MKMapPoint(x: mapRect.midX, y: mapRect.midY).coordinate
case .nearCenter:
let coordinate = MKMapPoint(x: mapRect.midX, y: mapRect.midY).coordinate
if let annotation = annotations.min(by: { coordinate.distance(from: $0.coordinate) < coordinate.distance(from: $1.coordinate) }) {
cluster.coordinate = annotation.coordinate
}
case .average:
cluster.coordinate = CLLocationCoordinate2D(
latitude: CLLocationDegrees(totalLatitude) / CLLocationDegrees(count),
longitude: CLLocationDegrees(totalLongitude) / CLLocationDegrees(count)
)
case .first:
if let annotation = annotations.first {
cluster.coordinate = annotation.coordinate
}
}
cluster.annotations = annotations
cluster.style = (annotations.first as? Annotation)?.style
allAnnotations += [cluster]
} else {
allAnnotations += annotations
}
}
let allAnnotations = dispatchQueue.sync {
clusteredAnnotations(tree: tree, mapRects: mapRects, zoomLevel: zoomLevel)
}

guard !isCancelled else { return (toAdd: [], toRemove: []) }
Expand All @@ -337,8 +276,8 @@ open class ClusterManager {
let toAdd = after.subtracted(before)

if !shouldRemoveInvisibleAnnotations {
let nonRemoving = toRemove.filter { !visibleMapRect.contains($0.coordinate) }
toRemove.subtract(nonRemoving)
let toKeep = toRemove.filter { !visibleMapRect.contains($0.coordinate) }
toRemove.subtract(toKeep)
}

dispatchQueue.async(flags: .barrier) {
Expand All @@ -349,6 +288,69 @@ open class ClusterManager {
return (toAdd: toAdd, toRemove: toRemove)
}

func clusteredAnnotations(tree: QuadTree, mapRects: [MKMapRect], zoomLevel: Double) -> [MKAnnotation] {
var allAnnotations = [MKAnnotation]()
for mapRect in mapRects {
var annotations = [MKAnnotation]()

// add annotations
for node in tree.annotations(in: mapRect) {
if delegate?.shouldClusterAnnotation(node) ?? true {
annotations.append(node)
} else {
allAnnotations.append(node)
}
}

// handle annotations on the same coordinate
if shouldDistributeAnnotationsOnSameCoordinate {
distributeAnnotations(annotations: annotations) // buggy (modifying coordinate)
}

// handle clustering
let count = annotations.count
if count >= minCountForClustering, zoomLevel <= maxZoomLevel {
let cluster = ClusterAnnotation()
cluster.coordinate = coordinate(annotations: annotations, position: clusterPosition, mapRect: mapRect)
cluster.annotations = annotations
cluster.style = (annotations.first as? Annotation)?.style
allAnnotations += [cluster]
} else {
allAnnotations += annotations
}
}
return allAnnotations
}

func distributeAnnotations(annotations: [MKAnnotation]) {
let hash = Dictionary(grouping: annotations) { $0.coordinate }
for value in hash.values where value.count > 1 {
for (index, annotation) in value.enumerated() {
let distanceFromContestedLocation = 3 * Double(value.count) / 2
let radiansBetweenAnnotations = (.pi * 2) / Double(value.count)
let bearing = radiansBetweenAnnotations * Double(index)
(annotation as? MKPointAnnotation)?.coordinate = annotation.coordinate.coordinate(onBearingInRadians: bearing, atDistanceInMeters: distanceFromContestedLocation)
}
}
}

func coordinate(annotations: [MKAnnotation], position: ClusterPosition, mapRect: MKMapRect) -> CLLocationCoordinate2D {
switch position {
case .center:
return MKMapPoint(x: mapRect.midX, y: mapRect.midY).coordinate
case .nearCenter:
let coordinate = MKMapPoint(x: mapRect.midX, y: mapRect.midY).coordinate
let annotation = annotations.min { coordinate.distance(from: $0.coordinate) < coordinate.distance(from: $1.coordinate) }
return annotation!.coordinate
case .average:
let coordinates = annotations.map { $0.coordinate }
let totals = coordinates.reduce((latitude: 0.0, longitude: 0.0)) { ($0.latitude + $1.latitude, $0.longitude + $1.longitude) }
return CLLocationCoordinate2D(latitude: totals.latitude / Double(coordinates.count), longitude: totals.longitude / Double(coordinates.count))
case .first:
return annotations.first!.coordinate
}
}

func mapRects(zoomScale: Double, visibleMapRect: MKMapRect) -> [MKMapRect] {
guard !zoomScale.isInfinite, !zoomScale.isNaN else { return [] }

Expand Down
2 changes: 1 addition & 1 deletion Sources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.4.3</string>
<string>2.4.4</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
Expand Down

0 comments on commit 0926f8e

Please sign in to comment.