|
@@ -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
|