CCHMapClusterController
CCHMapClusterController copied to clipboard
Added ClusterMethodDistanceBased
This is a drop-in replacement for CCHMapClusterOperation that addresses issue #82, the limitations of the grid-based algorithm. Now, when the operation is created in the controller, you can specify: operation.clusterMethod = ClusterMethodDistanceBased; (defaults to grid based) The algorithm is mostly translated from Google Maps Utilities, that I use in an Android app that handles map clustering nicely. It appears to work correctly here in iOS with some amount of performance hit with larger 100+ datasets. I think it's worth it to not see the pins jump around illogically when making small incremental zoom motions. I must admit I don't really understand the "unique locations" part of the original method, and I may have gotten some of the "reusing" existing annotations wrong, so let me know.
When trying out the code I got an exception:
2015-04-16 09:14:48.261 Roundhere[29815:1984234] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSSetM addObject:]: object cannot be nil'
*** First throw call stack:
(
0 CoreFoundation 0x01a69746 __exceptionPreprocess + 182
1 libobjc.A.dylib 0x045d9a97 objc_exception_throw + 44
2 CoreFoundation 0x0197e01b -[__NSSetM addObject:] + 699
3 MapKit 0x02b056df __30-[MKQuadTrie _itemsInMapRect:]_block_invoke + 694
4 MapKit 0x02b04859 _Z17__depthFirstApplyP14MKQuadTrieNodeU13block_pointerF28MKQuadTrieTraversalDirectiveS0_E + 79
5 MapKit 0x02b053fc -[MKQuadTrie _itemsInMapRect:] + 438
6 MapKit 0x02b058d2 -[MKQuadTrie itemsInMapRect:] + 393
7 MapKit 0x02b060e9 -[MKQuadTrie allItems] + 88
8 MapKit 0x02af1d4d -[MKAnnotationManager annotations] + 80
9 MapKit 0x02a9b7b8 -[MKMapView annotations] + 42
10 Roundhere 0x0020875d -[CCHMapClusterOperation initWithMapView:cellSize:marginFactor:reuseExistingClusterAnnotations:maxZoomLevelForClustering:minUniqueLocationsForClustering:] + 973
11 Roundhere 0x00202007 -[CCHMapClusterController updateAnnotationsWithCompletionHandler:] + 471
12 Roundhere 0x0020400d -[CCHMapClusterController mapView:regionDidChangeAnimated:] + 589
13 CoreFoundation 0x0194a84d __invoking___ + 29
14 CoreFoundation 0x0194a6f8 -[NSInvocation invoke] + 360
15 CoreFoundation 0x019e332a -[NSInvocation invokeWithTarget:] + 74
16 Roundhere 0x0020fa64 -[CCHMapViewDelegateProxy forwardInvocation:] + 468
17 CoreFoundation 0x019b804e ___forwarding___ + 478
18 CoreFoundation 0x019b7e4e _CF_forwarding_prep_0 + 14
19 MapKit 0x02a8fd98 -[MKMapView _didChangeRegionMidstream:] + 791
20 MapKit 0x02a98e51 -[MKMapView mapLayer:didChangeRegionAnimated:] + 77
21 VectorKit 0x0f524879 -[VKMapView map:didChangeRegionAnimated:] + 105
22 VectorKit 0x0f52ca87 -[VKMapCanvas cameraController:didChangeRegionAnimated:] + 55
23 VectorKit 0x0f5776fa -[VKCameraController endRegionChange] + 90
24 VectorKit 0x0f580385 -[VKMapCameraController stopPinchingWithFocusPoint:] + 101
25 VectorKit 0x0f52ce2f -[VKMapCanvas stopPinchingWithFocusPoint:] + 63
26 VectorKit 0x0f525779 -[VKMapView stopPinchingWithFocusPoint:] + 73
27 MapKit 0x02ad539d -[MKMapGestureController handlePinch:] + 1550
28 UIKit 0x0364d677 _UIGestureRecognizerSendActions + 327
29 UIKit 0x0364bef4 -[UIGestureRecognizer _updateGestureWithEvent:buttonEvent:] + 561
30 UIKit 0x0364df3d -[UIGestureRecognizer _delayedUpdateGesture] + 60
31 UIKit 0x036518ba ___UIGestureRecognizerUpdate_block_invoke661 + 57
32 UIKit 0x0365177d _UIGestureRecognizerRemoveObjectsFromArrayAndApplyBlocks + 317
33 UIKit 0x03645686 _UIGestureRecognizerUpdate + 3720
34 UIKit 0x0326595b -[UIWindow _sendGesturesForEvent:] + 1356
35 UIKit 0x032667c0 -[UIWindow sendEvent:] + 770
36 UIKit 0x032246d1 -[UIApplication sendEvent:] + 242
37 UIKit 0x03234b08 _UIApplicationHandleEventFromQueueEvent + 21484
38 UIKit 0x03208337 _UIApplicationHandleEventQueue + 2300
39 CoreFoundation 0x0198b06f __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15
40 CoreFoundation 0x01980b7d __CFRunLoopDoSources0 + 253
41 CoreFoundation 0x019800d8 __CFRunLoopRun + 952
42 CoreFoundation 0x0197fa5b CFRunLoopRunSpecific + 443
43 CoreFoundation 0x0197f88b CFRunLoopRunInMode + 123
44 GraphicsServices 0x067a92c9 GSEventRunModal + 192
45 GraphicsServices 0x067a9106 GSEventRun + 104
46 UIKit 0x0320c106 UIApplicationMain + 1526
47 Roundhere 0x0008fe1a main + 138
48 libdyld.dylib 0x054e4ac9 start + 1
@amishjake I'd like to work on this algorithm, too. Do you have more resources to read about this algorithm in detail? Thx.
In general I am wondering if it makes sense to offer different cluster algorithms to chose from. Why would you want to use the grid based algorithm when it obviously comes with some issues? I guess the library should just provide the best clustering possible in terms of performance and UX.
Hey @amishjake I went through your code in more detail and I like the idea of the algorithm. As I've said before, I guess it doesn't make too much sense to ship the library with two algorithms (especially if one of them provides bad UX).
Based on your PR I've changed the existing algorithm. I've changed everything to make the test suit pass. It's still work in progress but it's already usable. The only thing the is still missing is considering the minUniqueLocationsForClustering property and some specs for testing the performance with the new algorithm.
As we have an urgent deadline in our project, I will first go with this solution and create an actual PR later on when the I have more time to add some performance tests and the missing support for minUniqueLocationsForClustering
You can have a look at the current state in my fork:
https://github.com/mikrobi/CCHMapClusterController/tree/distance_clustering