Authored by Thomas Rasch

o Leaf clustering (closes #1)

@@ -28,6 +28,7 @@ typedef enum { @@ -28,6 +28,7 @@ typedef enum {
28 RMQuadTreeNodeType nodeType; 28 RMQuadTreeNodeType nodeType;
29 RMMapView *mapView; 29 RMMapView *mapView;
30 RMAnnotation *cachedClusterAnnotation; 30 RMAnnotation *cachedClusterAnnotation;
  31 + NSArray *cachedClusterEnclosedAnnotations;
31 } 32 }
32 33
33 @property (nonatomic, readonly) NSArray *annotations; 34 @property (nonatomic, readonly) NSArray *annotations;
@@ -45,7 +46,8 @@ typedef enum { @@ -45,7 +46,8 @@ typedef enum {
45 @property (nonatomic, readonly) RMQuadTreeNode *southWest; 46 @property (nonatomic, readonly) RMQuadTreeNode *southWest;
46 @property (nonatomic, readonly) RMQuadTreeNode *southEast; 47 @property (nonatomic, readonly) RMQuadTreeNode *southEast;
47 48
48 -@property (nonatomic, readonly) RMAnnotation *cachedClusterAnnotation; 49 +@property (nonatomic, readonly) RMAnnotation *clusterAnnotation;
  50 +@property (nonatomic, readonly) NSArray *clusteredAnnotations;
49 51
50 // Operations on this node and all subnodes 52 // Operations on this node and all subnodes
51 @property (nonatomic, readonly) NSArray *enclosedAnnotations; 53 @property (nonatomic, readonly) NSArray *enclosedAnnotations;
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 16
17 #define kMinimumQuadTreeElementWidth 200.0 // projected meters 17 #define kMinimumQuadTreeElementWidth 200.0 // projected meters
18 #define kMaxAnnotationsPerLeaf 4 18 #define kMaxAnnotationsPerLeaf 4
  19 +#define kMinPixelDistanceForLeafClustering 100.0
19 20
20 @interface RMQuadTreeNode () 21 @interface RMQuadTreeNode ()
21 22
@@ -34,7 +35,7 @@ @@ -34,7 +35,7 @@
34 35
35 @synthesize nodeType; 36 @synthesize nodeType;
36 @synthesize boundingBox, northWestBoundingBox, northEastBoundingBox, southWestBoundingBox, southEastBoundingBox; 37 @synthesize boundingBox, northWestBoundingBox, northEastBoundingBox, southWestBoundingBox, southEastBoundingBox;
37 -@synthesize parentNode, northWest, northEast, southWest, southEast, cachedClusterAnnotation; 38 +@synthesize parentNode, northWest, northEast, southWest, southEast;
38 39
39 - (id)initWithMapView:(RMMapView *)aMapView forParent:(RMQuadTreeNode *)aParentNode inBoundingBox:(RMProjectedRect)aBoundingBox 40 - (id)initWithMapView:(RMMapView *)aMapView forParent:(RMQuadTreeNode *)aParentNode inBoundingBox:(RMProjectedRect)aBoundingBox
40 { 41 {
@@ -49,6 +50,7 @@ @@ -49,6 +50,7 @@
49 annotations = [NSMutableArray new]; 50 annotations = [NSMutableArray new];
50 boundingBox = aBoundingBox; 51 boundingBox = aBoundingBox;
51 cachedClusterAnnotation = nil; 52 cachedClusterAnnotation = nil;
  53 + cachedClusterEnclosedAnnotations = nil;
52 54
53 double halfWidth = boundingBox.size.width / 2.0, halfHeight = boundingBox.size.height / 2.0; 55 double halfWidth = boundingBox.size.width / 2.0, halfHeight = boundingBox.size.height / 2.0;
54 northWestBoundingBox = RMProjectedRectMake(boundingBox.origin.x, boundingBox.origin.y + halfHeight, halfWidth, halfHeight); 56 northWestBoundingBox = RMProjectedRectMake(boundingBox.origin.x, boundingBox.origin.y + halfHeight, halfWidth, halfHeight);
@@ -65,6 +67,7 @@ @@ -65,6 +67,7 @@
65 { 67 {
66 mapView = nil; 68 mapView = nil;
67 [cachedClusterAnnotation release]; cachedClusterAnnotation = nil; 69 [cachedClusterAnnotation release]; cachedClusterAnnotation = nil;
  70 + [cachedClusterEnclosedAnnotations release]; cachedClusterEnclosedAnnotations = nil;
68 71
69 @synchronized (annotations) 72 @synchronized (annotations)
70 { 73 {
@@ -263,22 +266,78 @@ @@ -263,22 +266,78 @@
263 return unclusteredAnnotations; 266 return unclusteredAnnotations;
264 } 267 }
265 268
266 -- (RMAnnotation *)cachedClusterAnnotation 269 +- (RMAnnotation *)clusterAnnotation
267 { 270 {
268 return cachedClusterAnnotation; 271 return cachedClusterAnnotation;
269 } 272 }
270 273
  274 +- (NSArray *)clusteredAnnotations
  275 +{
  276 + return cachedClusterEnclosedAnnotations;
  277 +}
  278 +
271 - (void)addAnnotationsInBoundingBox:(RMProjectedRect)aBoundingBox toMutableArray:(NSMutableArray *)someArray createClusterAnnotations:(BOOL)createClusterAnnotations withClusterSize:(RMProjectedSize)clusterSize findGravityCenter:(BOOL)findGravityCenter 279 - (void)addAnnotationsInBoundingBox:(RMProjectedRect)aBoundingBox toMutableArray:(NSMutableArray *)someArray createClusterAnnotations:(BOOL)createClusterAnnotations withClusterSize:(RMProjectedSize)clusterSize findGravityCenter:(BOOL)findGravityCenter
272 { 280 {
273 if (createClusterAnnotations) 281 if (createClusterAnnotations)
274 { 282 {
275 double halfWidth = boundingBox.size.width / 2.0; 283 double halfWidth = boundingBox.size.width / 2.0;
  284 + BOOL forceClustering = (boundingBox.size.width >= clusterSize.width && halfWidth < clusterSize.width);
  285 + NSArray *enclosedAnnotations = nil;
  286 +
  287 + // Leaf clustering
  288 + if (!forceClustering && nodeType == nodeTypeLeaf && [annotations count] > 1)
  289 + {
  290 + NSMutableArray *annotationsToCheck = [NSMutableArray arrayWithArray:self.enclosedAnnotations];
  291 +
  292 + for (NSInteger i=[annotationsToCheck count]-1; i>0; --i)
  293 + {
  294 + BOOL mustBeClustered = NO;
  295 + RMAnnotation *currentAnnotation = [annotationsToCheck objectAtIndex:i];
  296 +
  297 + for (NSInteger j=i-1; j>=0; --j)
  298 + {
  299 + RMAnnotation *secondAnnotation = [annotationsToCheck objectAtIndex:j];
  300 +
  301 + // This is of course not very accurate but is good enough for this use case
  302 + double distance = RMEuclideanDistanceBetweenProjectedPoints(currentAnnotation.projectedLocation, secondAnnotation.projectedLocation) / mapView.metersPerPixel;
  303 + if (distance < kMinPixelDistanceForLeafClustering)
  304 + {
  305 + mustBeClustered = YES;
  306 + break;
  307 + }
  308 + }
276 309
277 - if (boundingBox.size.width >= clusterSize.width && halfWidth < clusterSize.width) 310 + if (!mustBeClustered)
278 { 311 {
  312 + [someArray addObject:currentAnnotation];
  313 + [annotationsToCheck removeObjectAtIndex:i];
  314 + }
  315 + }
  316 +
  317 + forceClustering = [annotationsToCheck count] > 0;
  318 +
  319 + if (forceClustering)
  320 + {
  321 + [cachedClusterAnnotation release]; cachedClusterAnnotation = nil;
  322 + [cachedClusterEnclosedAnnotations release]; cachedClusterEnclosedAnnotations = nil;
  323 +
  324 + enclosedAnnotations = [NSArray arrayWithArray:annotationsToCheck];
  325 + }
  326 + }
  327 +
  328 + if (forceClustering)
  329 + {
  330 + if (!enclosedAnnotations)
  331 + enclosedAnnotations = self.enclosedAnnotations;
  332 +
  333 + if (cachedClusterAnnotation && [enclosedAnnotations count] != [cachedClusterEnclosedAnnotations count])
  334 + {
  335 + [cachedClusterAnnotation release]; cachedClusterAnnotation = nil;
  336 + [cachedClusterEnclosedAnnotations release]; cachedClusterEnclosedAnnotations = nil;
  337 + }
  338 +
279 if (!cachedClusterAnnotation) 339 if (!cachedClusterAnnotation)
280 { 340 {
281 - NSArray *enclosedAnnotations = self.enclosedAnnotations;  
282 NSUInteger enclosedAnnotationsCount = [enclosedAnnotations count]; 341 NSUInteger enclosedAnnotationsCount = [enclosedAnnotations count];
283 342
284 if (enclosedAnnotationsCount < 2) 343 if (enclosedAnnotationsCount < 2)
@@ -328,6 +387,8 @@ @@ -328,6 +387,8 @@
328 andTitle:[NSString stringWithFormat:@"%d", enclosedAnnotationsCount]]; 387 andTitle:[NSString stringWithFormat:@"%d", enclosedAnnotationsCount]];
329 cachedClusterAnnotation.annotationType = kRMClusterAnnotationTypeName; 388 cachedClusterAnnotation.annotationType = kRMClusterAnnotationTypeName;
330 cachedClusterAnnotation.userInfo = self; 389 cachedClusterAnnotation.userInfo = self;
  390 +
  391 + cachedClusterEnclosedAnnotations = [[NSArray alloc] initWithArray:enclosedAnnotations];
331 } 392 }
332 393
333 [someArray addObject:cachedClusterAnnotation]; 394 [someArray addObject:cachedClusterAnnotation];
@@ -336,7 +397,6 @@ @@ -336,7 +397,6 @@
336 return; 397 return;
337 } 398 }
338 399
339 - // TODO: leaf clustering (necessary?)  
340 if (nodeType == nodeTypeLeaf) 400 if (nodeType == nodeTypeLeaf)
341 { 401 {
342 @synchronized (annotations) 402 @synchronized (annotations)
@@ -385,6 +445,7 @@ @@ -385,6 +445,7 @@
385 [parentNode removeUpwardsAllCachedClusterAnnotations]; 445 [parentNode removeUpwardsAllCachedClusterAnnotations];
386 446
387 [cachedClusterAnnotation release]; cachedClusterAnnotation = nil; 447 [cachedClusterAnnotation release]; cachedClusterAnnotation = nil;
  448 + [cachedClusterEnclosedAnnotations release]; cachedClusterEnclosedAnnotations = nil;
388 } 449 }
389 450
390 @end 451 @end