2
0

CCHMapClusterOperation.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. //
  2. // CCHMapClusterOperation.m
  3. // CCHMapClusterController
  4. //
  5. // Copyright (C) 2014 Claus Höfele
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in
  15. // all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. // THE SOFTWARE.
  24. //
  25. #import "CCHMapClusterOperation.h"
  26. #import "CCHMapTree.h"
  27. #import "CCHMapClusterAnnotation.h"
  28. #import "CCHMapClusterControllerUtils.h"
  29. #import "CCHMapClusterer.h"
  30. #import "CCHMapAnimator.h"
  31. #import "CCHMapClusterControllerDelegate.h"
  32. #define fequal(a, b) (fabs((a) - (b)) < __FLT_EPSILON__)
  33. @interface CCHMapClusterOperation()
  34. @property (nonatomic) MKMapView *mapView;
  35. @property (nonatomic) double cellMapSize;
  36. @property (nonatomic) double marginFactor;
  37. @property (nonatomic) MKMapRect mapViewVisibleMapRect;
  38. @property (nonatomic) MKCoordinateRegion mapViewRegion;
  39. @property (nonatomic) CGFloat mapViewWidth;
  40. @property (nonatomic, copy) NSArray *mapViewAnnotations;
  41. @property (nonatomic) BOOL reuseExistingClusterAnnotations;
  42. @property (nonatomic) double maxZoomLevelForClustering;
  43. @property (nonatomic) NSUInteger minUniqueLocationsForClustering;
  44. @property (nonatomic, getter = isExecuting) BOOL executing;
  45. @property (nonatomic, getter = isFinished) BOOL finished;
  46. @end
  47. @implementation CCHMapClusterOperation
  48. @synthesize executing = _executing;
  49. @synthesize finished = _finished;
  50. - (instancetype)initWithMapView:(MKMapView *)mapView cellSize:(double)cellSize marginFactor:(double)marginFactor reuseExistingClusterAnnotations:(BOOL)reuseExistingClusterAnnotation maxZoomLevelForClustering:(double)maxZoomLevelForClustering minUniqueLocationsForClustering:(NSUInteger)minUniqueLocationsForClustering
  51. {
  52. self = [super init];
  53. if (self) {
  54. _mapView = mapView;
  55. _cellMapSize = [self.class cellMapSizeForCellSize:cellSize withMapView:mapView];
  56. _marginFactor = marginFactor;
  57. _mapViewVisibleMapRect = mapView.visibleMapRect;
  58. _mapViewRegion = mapView.region;
  59. _mapViewWidth = mapView.bounds.size.width;
  60. _mapViewAnnotations = mapView.annotations;
  61. _reuseExistingClusterAnnotations = reuseExistingClusterAnnotation;
  62. _maxZoomLevelForClustering = maxZoomLevelForClustering;
  63. _minUniqueLocationsForClustering = minUniqueLocationsForClustering;
  64. _executing = NO;
  65. _finished = NO;
  66. }
  67. return self;
  68. }
  69. + (double)cellMapSizeForCellSize:(double)cellSize withMapView:(MKMapView *)mapView
  70. {
  71. // World size is multiple of cell size so that cells wrap around at the 180th meridian
  72. double cellMapSize = CCHMapClusterControllerMapLengthForLength(mapView, mapView.superview, cellSize);
  73. cellMapSize = CCHMapClusterControllerAlignMapLengthToWorldWidth(cellMapSize);
  74. return cellMapSize;
  75. }
  76. + (MKMapRect)gridMapRectForMapRect:(MKMapRect)mapRect withCellMapSize:(double)cellMapSize marginFactor:(double)marginFactor
  77. {
  78. // Expand map rect and align to cell size to avoid popping when panning
  79. MKMapRect gridMapRect = MKMapRectInset(mapRect, -marginFactor * mapRect.size.width, -marginFactor * mapRect.size.height);
  80. gridMapRect = CCHMapClusterControllerAlignMapRectToCellSize(gridMapRect, cellMapSize);
  81. return gridMapRect;
  82. }
  83. - (void)start
  84. {
  85. self.executing = YES;
  86. double zoomLevel = CCHMapClusterControllerZoomLevelForRegion(self.mapViewRegion.center.longitude, self.mapViewRegion.span.longitudeDelta, self.mapViewWidth);
  87. BOOL disableClustering = (zoomLevel > self.maxZoomLevelForClustering);
  88. BOOL respondsToSelector = [_clusterControllerDelegate respondsToSelector:@selector(mapClusterController:willReuseMapClusterAnnotation:)];
  89. // For each cell in the grid, pick one cluster annotation to show
  90. MKMapRect gridMapRect = [self.class gridMapRectForMapRect:self.mapViewVisibleMapRect withCellMapSize:self.cellMapSize marginFactor:self.marginFactor];
  91. NSMutableSet *clusters = [NSMutableSet set];
  92. CCHMapClusterControllerEnumerateCells(gridMapRect, _cellMapSize, ^(MKMapRect cellMapRect) {
  93. NSSet *allAnnotationsInCell = [_allAnnotationsMapTree annotationsInMapRect:cellMapRect];
  94. if (allAnnotationsInCell.count > 0) {
  95. BOOL annotationSetsAreUniqueLocations;
  96. NSArray *annotationSets;
  97. if (disableClustering) {
  98. // Create annotation for each unique location because clustering is disabled
  99. annotationSets = CCHMapClusterControllerAnnotationSetsByUniqueLocations(allAnnotationsInCell, NSUIntegerMax);
  100. annotationSetsAreUniqueLocations = YES;
  101. } else {
  102. NSUInteger max = _minUniqueLocationsForClustering > 1 ? _minUniqueLocationsForClustering - 1 : 1;
  103. annotationSets = CCHMapClusterControllerAnnotationSetsByUniqueLocations(allAnnotationsInCell, max);
  104. if (annotationSets) {
  105. // Create annotation for each unique location because there are too few locations for clustering
  106. annotationSetsAreUniqueLocations = YES;
  107. } else {
  108. // Create one annotation for entire cell
  109. annotationSets = @[allAnnotationsInCell];
  110. annotationSetsAreUniqueLocations = NO;
  111. }
  112. }
  113. NSMutableSet *visibleAnnotationsInCell = [NSMutableSet setWithSet:[_visibleAnnotationsMapTree annotationsInMapRect:cellMapRect]];
  114. for (NSSet *annotationSet in annotationSets) {
  115. CLLocationCoordinate2D coordinate;
  116. if (annotationSetsAreUniqueLocations) {
  117. coordinate = [annotationSet.anyObject coordinate];
  118. } else {
  119. coordinate = [_clusterer mapClusterController:_clusterController coordinateForAnnotations:annotationSet inMapRect:cellMapRect];
  120. }
  121. CCHMapClusterAnnotation *annotationForCell;
  122. if (_reuseExistingClusterAnnotations) {
  123. // Check if an existing cluster annotation can be reused
  124. annotationForCell = CCHMapClusterControllerFindVisibleAnnotation(annotationSet, visibleAnnotationsInCell);
  125. // For unique locations, coordinate has to match as well
  126. if (annotationForCell && annotationSetsAreUniqueLocations) {
  127. BOOL coordinateMatches = fequal(coordinate.latitude, annotationForCell.coordinate.latitude) && fequal(coordinate.longitude, annotationForCell.coordinate.longitude);
  128. annotationForCell = coordinateMatches ? annotationForCell : nil;
  129. }
  130. }
  131. if (annotationForCell == nil) {
  132. // Create new cluster annotation
  133. annotationForCell = [[CCHMapClusterAnnotation alloc] init];
  134. annotationForCell.mapClusterController = _clusterController;
  135. annotationForCell.delegate = _clusterControllerDelegate;
  136. annotationForCell.annotations = annotationSet;
  137. annotationForCell.coordinate = coordinate;
  138. } else {
  139. // For an existing cluster annotation, this will implicitly update its annotation view
  140. [visibleAnnotationsInCell removeObject:annotationForCell];
  141. annotationForCell.annotations = annotationSet;
  142. dispatch_async(dispatch_get_main_queue(), ^{
  143. if (annotationSetsAreUniqueLocations) {
  144. annotationForCell.coordinate = coordinate;
  145. }
  146. annotationForCell.title = nil;
  147. annotationForCell.subtitle = nil;
  148. if (respondsToSelector) {
  149. [_clusterControllerDelegate mapClusterController:_clusterController willReuseMapClusterAnnotation:annotationForCell];
  150. }
  151. });
  152. }
  153. // Collect cluster annotations
  154. [clusters addObject:annotationForCell];
  155. }
  156. }
  157. });
  158. // Figure out difference between new and old clusters
  159. NSSet *annotationsBeforeAsSet = CCHMapClusterControllerClusterAnnotationsForAnnotations(self.mapViewAnnotations, self.clusterController);
  160. NSMutableSet *annotationsToKeep = [NSMutableSet setWithSet:annotationsBeforeAsSet];
  161. [annotationsToKeep intersectSet:clusters];
  162. NSMutableSet *annotationsToAddAsSet = [NSMutableSet setWithSet:clusters];
  163. [annotationsToAddAsSet minusSet:annotationsToKeep];
  164. NSArray *annotationsToAdd = [annotationsToAddAsSet allObjects];
  165. NSMutableSet *annotationsToRemoveAsSet = [NSMutableSet setWithSet:annotationsBeforeAsSet];
  166. [annotationsToRemoveAsSet minusSet:clusters];
  167. NSArray *annotationsToRemove = [annotationsToRemoveAsSet allObjects];
  168. // Show cluster annotations on map
  169. [_visibleAnnotationsMapTree removeAnnotations:annotationsToRemove];
  170. [_visibleAnnotationsMapTree addAnnotations:annotationsToAdd];
  171. dispatch_async(dispatch_get_main_queue(), ^{
  172. [self.mapView addAnnotations:annotationsToAdd];
  173. [self.animator mapClusterController:self.clusterController willRemoveAnnotations:annotationsToRemove withCompletionHandler:^{
  174. [self.mapView removeAnnotations:annotationsToRemove];
  175. self.executing = NO;
  176. self.finished = YES;
  177. }];
  178. });
  179. }
  180. - (void)setExecuting:(BOOL)executing
  181. {
  182. [self willChangeValueForKey:@"isExecuting"];
  183. _executing = YES;
  184. [self didChangeValueForKey:@"isExecuting"];
  185. }
  186. - (void)setFinished:(BOOL)finished
  187. {
  188. [self willChangeValueForKey:@"isFinished"];
  189. _finished = YES;
  190. [self didChangeValueForKey:@"isFinished"];
  191. }
  192. @end