From bf1f2bb75b7ce52fbd444b473a7afcf7027b339a Mon Sep 17 00:00:00 2001 From: Adam Share Date: Sat, 24 Oct 2015 16:11:02 -0700 Subject: [PATCH 01/12] multi tree clustering --- .../ViewControllers/CDMapViewController.m | 16 +- Pod/Classes/ADMapCluster.h | 10 +- Pod/Classes/ADMapCluster.m | 84 +++++++- Pod/Classes/TSClusterMapView.h | 5 + Pod/Classes/TSClusterMapView.m | 181 +++++++++++++----- Pod/Classes/TSClusterOperation.m | 9 + 6 files changed, 243 insertions(+), 62 deletions(-) diff --git a/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m b/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m index f76ef78..7f93e22 100644 --- a/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m +++ b/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m @@ -176,14 +176,14 @@ - (IBAction)addAll:(id)sender { if (_tabBar.selectedItem == _bathroomTabBarItem) { NSLog(@"Adding All %@", CDToiletJsonFile); - [_mapView addClusteredAnnotations:_bathroomAnnotations]; + [_mapView addClusteredAnnotations:_bathroomAnnotations toTree:CDToiletJsonFile]; _bathroomAnnotationsAdded = [NSMutableArray arrayWithArray:_bathroomAnnotations]; _stepper.value = _bathroomAnnotationsAdded.count; } else if (_tabBar.selectedItem == _streetLightsTabBarItem) { NSLog(@"Adding All %@", CDStreetLightJsonFile); - [_mapView addClusteredAnnotations:_streetLightAnnotations]; + [_mapView addClusteredAnnotations:_streetLightAnnotations toTree:CDStreetLightJsonFile]; _streetLightAnnotationsAdded = [NSMutableArray arrayWithArray:_streetLightAnnotations]; _stepper.value = _streetLightAnnotationsAdded.count; } @@ -194,13 +194,13 @@ - (IBAction)addAll:(id)sender { - (IBAction)removeAll:(id)sender { if (_tabBar.selectedItem == _bathroomTabBarItem) { - [_mapView removeAnnotations:_bathroomAnnotationsAdded]; + [_mapView removeAnnotations:_bathroomAnnotationsAdded fromTree:CDToiletJsonFile]; [_bathroomAnnotationsAdded removeAllObjects]; NSLog(@"Removing All %@", CDToiletJsonFile); } else if (_tabBar.selectedItem == _streetLightsTabBarItem) { - [_mapView removeAnnotations:_streetLightAnnotationsAdded]; + [_mapView removeAnnotations:_streetLightAnnotationsAdded fromTree:CDStreetLightJsonFile]; [_streetLightAnnotationsAdded removeAllObjects]; NSLog(@"Removing All %@", CDStreetLightJsonFile); @@ -253,7 +253,7 @@ - (void)addNewBathroom { TSBathroomAnnotation *annotation = [_bathroomAnnotations objectAtIndex:_bathroomAnnotationsAdded.count]; [_bathroomAnnotationsAdded addObject:annotation]; - [_mapView addClusteredAnnotation:annotation]; + [_mapView addClusteredAnnotation:annotation toTree:CDToiletJsonFile]; } - (void)addNewStreetLight { @@ -267,7 +267,7 @@ - (void)addNewStreetLight { TSStreetLightAnnotation *annotation = [_streetLightAnnotations objectAtIndex:_streetLightAnnotationsAdded.count]; [_streetLightAnnotationsAdded addObject:annotation]; - [_mapView addClusteredAnnotation:annotation]; + [_mapView addClusteredAnnotation:annotation toTree:CDStreetLightJsonFile]; } - (void)removeLastBathroom { @@ -276,7 +276,7 @@ - (void)removeLastBathroom { TSBathroomAnnotation *annotation = [_bathroomAnnotationsAdded lastObject]; [_bathroomAnnotationsAdded removeObject:annotation]; - [_mapView removeAnnotation:annotation]; + [_mapView removeAnnotation:annotation fromTree:CDToiletJsonFile]; } - (void)removeLastStreetLight { @@ -285,7 +285,7 @@ - (void)removeLastStreetLight { TSStreetLightAnnotation *annotation = [_streetLightAnnotationsAdded lastObject]; [_streetLightAnnotationsAdded removeObject:annotation]; - [_mapView removeAnnotation:annotation]; + [_mapView removeAnnotation:annotation fromTree:CDStreetLightJsonFile]; } - (IBAction)segmentedControlValueChanged:(id)sender { diff --git a/Pod/Classes/ADMapCluster.h b/Pod/Classes/ADMapCluster.h index 4c10162..ffa8fcf 100755 --- a/Pod/Classes/ADMapCluster.h +++ b/Pod/Classes/ADMapCluster.h @@ -46,13 +46,19 @@ typedef void(^KdtreeCompletionBlock)(ADMapCluster *mapCluster); @property (readonly) NSSet *clustersWithAnnotations; +@property (strong, nonatomic) NSString *clusterRootTreeIdentifier; + +- (id)initWithRootClusters:(NSArray *)clusters; + +- (ADMapCluster *)rootClusterForID:(NSString *)treeID; + /*! * @discussion Creates a KD-tree of clusters http://en.wikipedia.org/wiki/K-d_tree * @param annotations Set of ADMapPointAnnotation objects * @param mapView The ADClusterMapView that will send the delegate callback * @param completion A new ADMapCluster object. */ -+ (void)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView completion:(KdtreeCompletionBlock)completion ; ++ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView completion:(KdtreeCompletionBlock)completion ; /*! @@ -63,7 +69,7 @@ typedef void(^KdtreeCompletionBlock)(ADMapCluster *mapCluster); * @param showSubtitle A Boolean to show subtitle from titles of children * @param completion A new ADMapCluster object. */ -+ (void)rootClusterForAnnotations:(NSSet *)annotations centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion ; ++ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion ; /*! * @discussion Adds a single map point annotation to an existing KD-tree map cluster root diff --git a/Pod/Classes/ADMapCluster.m b/Pod/Classes/ADMapCluster.m index 142f9ee..bd839c8 100755 --- a/Pod/Classes/ADMapCluster.m +++ b/Pod/Classes/ADMapCluster.m @@ -14,6 +14,8 @@ #define ADMapClusterDiscriminationPrecision 1E-4 +static NSString * const kTSClusterMapViewRootMultiClusterID = @"kTSClusterMapViewRootMultiClusterID-Private"; + @interface ADMapCluster () @property (nonatomic, strong) ADMapCluster *leftChild; @@ -30,18 +32,20 @@ @implementation ADMapCluster #pragma mark - Init -+ (void)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView completion:(KdtreeCompletionBlock)completion { ++ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView completion:(KdtreeCompletionBlock)completion { [mapView mapView:mapView willBeginBuildingClusterTreeForMapPoints:annotations]; - [ADMapCluster rootClusterForAnnotations:annotations centerWeight:mapView.clusterDiscrimination title:mapView.clusterTitle showSubtitle:mapView.clusterShouldShowSubtitle completion:^(ADMapCluster *mapCluster) { + return [ADMapCluster rootClusterForAnnotations:annotations centerWeight:mapView.clusterDiscrimination title:mapView.clusterTitle showSubtitle:mapView.clusterShouldShowSubtitle completion:^(ADMapCluster *mapCluster) { [mapView mapView:mapView didFinishBuildingClusterTreeForMapPoints:annotations]; - completion(mapCluster); + if (completion) { + completion(mapCluster); + } }]; } -+ (void)rootClusterForAnnotations:(NSSet *)annotations centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion { ++ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion { // KDTree //NSLog(@"Computing KD-tree for %lu annotations...", (unsigned long)annotations.count); @@ -69,7 +73,10 @@ + (void)rootClusterForAnnotations:(NSSet *)annotations //NSLog(@"Computation done !"); - completion(cluster); + if (completion) { + completion(cluster); + } + return cluster; } - (id)initWithAnnotations:(NSSet *)annotations atDepth:(NSInteger)depth inMapRect:(MKMapRect)mapRect gamma:(double)gamma clusterTitle:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle parentCluster:(ADMapCluster *)parentCluster rootCluster:(ADMapCluster *)rootCluster { @@ -132,6 +139,69 @@ - (id)initWithAnnotations:(NSSet *)annotations atDepth: return self; } + +#pragma mark - Multi Tree + +- (id)initWithRootClusters:(NSArray *)clusters { + self = [super init]; + if (self) { + self.clusterRootTreeIdentifier = kTSClusterMapViewRootMultiClusterID; +#warning handle 2 trees + ADMapCluster *cluster1 = clusters.firstObject; + ADMapCluster *cluster2 = clusters.lastObject; + + cluster1.parentCluster = self; + cluster2.parentCluster = self; + + _depth = 0; + _mapRect = MKMapRectUnion(cluster1.mapRect, cluster2.mapRect); + _clusterTitle = cluster1.title; + _showSubtitle = cluster1.showSubtitle; + _gamma = cluster1.gamma; + _parentCluster = nil; + _clusterCount = cluster1.clusterCount + cluster2.clusterCount; + _progress = 0; + + self.annotation = nil; + + NSMutableSet *annotations = [[NSMutableSet alloc] initWithCapacity:clusters.count]; + + for (ADMapCluster *cluster in clusters) { + ADClusterAnnotation *tempAnnotation = [[ADClusterAnnotation alloc] init]; + tempAnnotation.coordinate = cluster.clusterCoordinate; + ADMapPointAnnotation *mapPoint = [[ADMapPointAnnotation alloc] initWithAnnotation:tempAnnotation]; + [annotations addObject:mapPoint]; + } + + + MKMapPoint centerMapPoint = [self meanCoordinateForAnnotations:annotations gamma:_gamma]; + _clusterCoordinate = MKCoordinateForMapPoint(centerMapPoint); + + _leftChild = cluster1; + _rightChild = cluster2; + } + return self; +} + +- (ADMapCluster *)rootClusterForID:(NSString *)treeID { + + for (ADMapCluster *cluster in self.children) { + + if ([cluster.clusterRootTreeIdentifier isEqualToString:treeID]) { + return cluster; + } + + if ([cluster.clusterRootTreeIdentifier isEqualToString:kTSClusterMapViewRootMultiClusterID]) { + ADMapCluster *foundCluster = [cluster rootClusterForID:treeID]; + if (foundCluster) { + return foundCluster; + } + } + } + + return nil; +} + #pragma mark Tree Mapping - (NSArray *>*)splitAnnotations:(NSSet *)annotations centerPoint:(MKMapPoint)center { @@ -622,6 +692,10 @@ - (BOOL)overlapsClusterOnMap:(ADMapCluster *)cluster annotationViewMapRectSize:( return NO; } + if (cluster.clusterRootTreeIdentifier || self.clusterRootTreeIdentifier) { + return NO; + } + MKMapPoint thisPoint = MKMapPointForCoordinate(_clusterCoordinate); MKMapPoint clusterPoint = MKMapPointForCoordinate(cluster.clusterCoordinate); diff --git a/Pod/Classes/TSClusterMapView.h b/Pod/Classes/TSClusterMapView.h index 607fcfe..98bf84a 100755 --- a/Pod/Classes/TSClusterMapView.h +++ b/Pod/Classes/TSClusterMapView.h @@ -102,6 +102,11 @@ typedef NS_ENUM(NSInteger, ADClusterBufferSize) { @interface TSClusterMapView : MKMapView +- (void)addClusteredAnnotation:(id)annotation toTree:(NSString *)treeID; +- (void)addClusteredAnnotations:(NSArray > *)annotations toTree:(NSString *)treeID; +- (void)removeAnnotations:(NSArray > *)annotations fromTree:(NSString *)treeID; +- (void)removeAnnotation:(id)annotation fromTree:(NSString *)treeID; + /*! * @discussion Adds an annotation to the map and clusters if needed (threadsafe). Only rebuilds entire cluster tree if there are less than 1000 clustered annotations or the annotation coordinate is an outlier from current clustered data set. * @param annotation The annotation to be added to map diff --git a/Pod/Classes/TSClusterMapView.m b/Pod/Classes/TSClusterMapView.m index 7f91c65..d3faa92 100755 --- a/Pod/Classes/TSClusterMapView.m +++ b/Pod/Classes/TSClusterMapView.m @@ -17,11 +17,12 @@ #define DATA_REFRESH_MAX 1000 static NSString * const kTSClusterAnnotationViewID = @"kTSClusterAnnotationViewID-private"; +static NSString * const kTSClusterMapViewRootClusterID = @"kTSClusterMapViewRootClusterID-private"; NSString * const KDTreeClusteringProgress = @"KDTreeClusteringProgress"; @interface TSClusterMapView () - +@property (strong, nonatomic) NSMutableDictionary >*> *annotationsByTreeID; @end @@ -51,6 +52,8 @@ - (void)initHelpers { [self setDefaults]; + _annotationsByTreeID = [[NSMutableDictionary alloc] init]; + _clusterAnnotationsPool = [[NSMutableSet alloc] init]; _preClusterOperationQueue = [[NSOperationQueue alloc] init]; @@ -145,15 +148,25 @@ - (NSUInteger)numberOfClusters { } - (void)needsRefresh { - - [self createKDTreeAndCluster:_clusterableAnnotationsAdded]; + [self createKDTreesAndCluster:_annotationsByTreeID]; +// [self createKDTreeAndCluster:self.clusterableAnnotationsAdded]; } #pragma mark - Add/Remove Annotations +- (NSMutableSet> *)clusterableAnnotationsAdded { + + NSMutableSet *mutableSet = [[NSMutableSet alloc] init]; + + for (NSMutableSet *set in self.annotationsByTreeID.allValues) { + [mutableSet unionSet:set]; + } + + return mutableSet; +} - (void)addAnnotation:(id)annotation { - if (![_clusterableAnnotationsAdded containsObject:annotation]) { + if (![self.clusterableAnnotationsAdded containsObject:annotation]) { [super addAnnotation:annotation]; } } @@ -161,47 +174,57 @@ - (void)addAnnotation:(id)annotation { - (void)addAnnotations:(NSArray > *)annotations { NSMutableSet *annotationsToAdd = [NSMutableSet setWithArray:annotations]; - [annotationsToAdd minusSet:_clusterableAnnotationsAdded]; + [annotationsToAdd minusSet:self.clusterableAnnotationsAdded]; if (annotationsToAdd.count) { [super addAnnotations:annotationsToAdd.allObjects]; } } -- (void)addClusteredAnnotation:(id)annotation { +#pragma mark - Multi Tree + +- (void)addClusteredAnnotation:(id)annotation toTree:(NSString *)treeID { BOOL refresh = NO; - if (_clusterableAnnotationsAdded.count < DATA_REFRESH_MAX) { + NSMutableSet *annotationsForTree = self.annotationsByTreeID[treeID]; + + if (annotationsForTree.count < DATA_REFRESH_MAX) { refresh = YES; } - [self addClusteredAnnotation:annotation clusterTreeRefresh:refresh]; + [self addClusteredAnnotation:annotation toTree:(NSString *)treeID clusterTreeRefresh:refresh]; + } -- (void)addClusteredAnnotation:(id)annotation clusterTreeRefresh:(BOOL)refresh { +- (void)addClusteredAnnotation:annotation toTree:(NSString *)treeID clusterTreeRefresh:(BOOL)refresh { + + + NSMutableSet *annotationsForTree = self.annotationsByTreeID[treeID]; - if (!annotation || [_clusterableAnnotationsAdded containsObject:annotation]) { + if (!annotation || [annotationsForTree containsObject:annotation]) { return; } - if (_clusterableAnnotationsAdded) { - [_clusterableAnnotationsAdded addObject:annotation]; + if (annotationsForTree) { + [annotationsForTree addObject:annotation]; } else { - _clusterableAnnotationsAdded = [[NSMutableSet alloc] initWithObjects:annotation, nil]; + annotationsForTree = [[NSMutableSet alloc] initWithObjects:annotation, nil]; + self.annotationsByTreeID[treeID] = annotationsForTree; } - if (refresh || _treeOperationQueue.operationCount > 10) { + ADMapCluster *rootForID = [_rootMapCluster rootClusterForID:treeID]; + + if (!rootForID || refresh || _treeOperationQueue.operationCount > 10) { [self needsRefresh]; return; } - __weak TSClusterMapView *weakSelf = self; [_treeOperationQueue addOperationWithBlock:^{ //Attempt to insert in existing root cluster - will fail if small data set or an outlier - [_rootMapCluster mapView:self addAnnotation:[[ADMapPointAnnotation alloc] initWithAnnotation:annotation] completion:^(BOOL added) { + [rootForID mapView:self addAnnotation:[[ADMapPointAnnotation alloc] initWithAnnotation:annotation] completion:^(BOOL added) { TSClusterMapView *strongSelf = weakSelf; @@ -215,45 +238,53 @@ - (void)addClusteredAnnotation:(id)annotation clusterTreeRefresh:( }]; } -- (void)addClusteredAnnotations:(NSArray > *)annotations { +- (void)addClusteredAnnotations:(NSArray > *)annotations toTree:(NSString *)treeID { if (!annotations || !annotations.count) { return; } - NSInteger count = _clusterableAnnotationsAdded.count; + NSMutableSet *annotationsForTree = self.annotationsByTreeID[treeID]; + NSMutableSet *addSet = [NSMutableSet setWithArray:annotations]; - if (_clusterableAnnotationsAdded) { - [_clusterableAnnotationsAdded unionSet:[NSSet setWithArray:annotations]]; + NSInteger preCount = annotationsForTree.count; + + if (!annotationsForTree) { + annotationsForTree = addSet; + self.annotationsByTreeID[treeID] = annotationsForTree; } else { - _clusterableAnnotationsAdded = [[NSMutableSet alloc] initWithArray:annotations]; + [annotationsForTree unionSet:addSet]; } - if (count != _clusterableAnnotationsAdded.count) { + if (preCount != annotationsForTree.count) { [self needsRefresh]; } } -- (void)removeAnnotation:(id)annotation { +- (void)removeAnnotation:(id)annotation fromTree:(NSString *)treeID { if (!annotation) { return; } - if ([_clusterableAnnotationsAdded containsObject:annotation]) { - [_clusterableAnnotationsAdded removeObject:annotation]; + + NSMutableSet *annotationsForTree = self.annotationsByTreeID[treeID]; + + if ([annotationsForTree containsObject:annotation]) { + [annotationsForTree removeObject:annotation]; //Small data set just rebuild - if (_clusterableAnnotationsAdded.count < DATA_REFRESH_MAX || _treeOperationQueue.operationCount > 10) { + if (annotationsForTree.count < DATA_REFRESH_MAX || _treeOperationQueue.operationCount > 10 || annotationsForTree.count == 0) { [self needsRefresh]; } else { + ADMapCluster *rootForID = [_rootMapCluster rootClusterForID:treeID]; __weak TSClusterMapView *weakSelf = self; [_treeOperationQueue addOperationWithBlock:^{ - [weakSelf.rootMapCluster mapView:self removeAnnotation:annotation completion:^(BOOL removed) { + [rootForID mapView:self removeAnnotation:annotation completion:^(BOOL removed) { TSClusterMapView *strongSelf = weakSelf; @@ -271,23 +302,56 @@ - (void)removeAnnotation:(id)annotation { [super removeAnnotation:annotation]; } -- (void)removeAnnotations:(NSArray > *)annotations { +- (void)removeAnnotations:(NSArray > *)annotations fromTree:(NSString *)treeID { if (!annotations) { return; } - NSUInteger previousCount = _clusterableAnnotationsAdded.count; + NSMutableSet *annotationsForTree = self.annotationsByTreeID[treeID]; + + if (!annotationsForTree) { + return; + } + + NSUInteger previousCount = annotationsForTree.count; NSSet *set = [NSSet setWithArray:annotations]; - [_clusterableAnnotationsAdded minusSet:set]; + [annotationsForTree minusSet:set]; - if (_clusterableAnnotationsAdded.count != previousCount) { + if (annotationsForTree.count != previousCount) { [self needsRefresh]; } [super removeAnnotations:annotations]; } +/////// + +- (void)addClusteredAnnotation:(id)annotation { + + [self addClusteredAnnotation:annotation toTree:kTSClusterMapViewRootClusterID]; +} + +- (void)addClusteredAnnotation:(id)annotation clusterTreeRefresh:(BOOL)refresh { + + [self addClusteredAnnotation:annotation toTree:kTSClusterMapViewRootClusterID clusterTreeRefresh:refresh]; +} + +- (void)addClusteredAnnotations:(NSArray > *)annotations { + + [self addClusteredAnnotations:annotations toTree:kTSClusterMapViewRootClusterID]; +} + +- (void)removeAnnotation:(id)annotation { + + [self removeAnnotation:annotation fromTree:kTSClusterMapViewRootClusterID]; +} + +- (void)removeAnnotations:(NSArray > *)annotations { + + [self removeAnnotations:annotations fromTree:kTSClusterMapViewRootClusterID]; +} + #pragma mark - Annotations - (void)refreshClusterAnnotation:(ADClusterAnnotation *)annotation { @@ -312,7 +376,7 @@ - (void)refreshClusterAnnotation:(ADClusterAnnotation *)annotation { NSMutableSet *set = [NSMutableSet setWithArray:[super annotations]]; [set minusSet:self.clusterAnnotations]; - [set unionSet:_clusterableAnnotationsAdded]; + [set unionSet:self.clusterableAnnotationsAdded]; return set.allObjects; } @@ -447,34 +511,57 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation { #pragma mark - Clustering -- (void)createKDTreeAndCluster:(NSSet > *)annotations { +- (void)createKDTreesAndCluster:(NSMutableDictionary >*> *)annotationsForTrees { - if (!annotations) { + if (!annotationsForTrees.allKeys.count) { return; } - annotations = [annotations copy]; + annotationsForTrees = [annotationsForTrees copy]; [_treeOperationQueue cancelAllOperations]; __weak TSClusterMapView *weakSelf = self; [_treeOperationQueue addOperationWithBlock:^{ - // use wrapper annotations that expose a MKMapPoint property instead of a CLLocationCoordinate2D property - NSMutableSet * mapPointAnnotations = [[NSMutableSet alloc] initWithCapacity:annotations.count]; - for (id annotation in annotations) { - ADMapPointAnnotation * mapPointAnnotation = [[ADMapPointAnnotation alloc] initWithAnnotation:annotation]; - [mapPointAnnotations addObject:mapPointAnnotation]; - } + NSMutableArray *allRootClusters = [[NSMutableArray alloc] initWithCapacity:annotationsForTrees.allKeys.count]; - [ADMapCluster rootClusterForAnnotations:mapPointAnnotations mapView:self completion:^(ADMapCluster *mapCluster) { + for (NSString *key in annotationsForTrees.allKeys) { - TSClusterMapView *strongSelf = weakSelf; + NSMutableSet *annotations = annotationsForTrees[key]; - strongSelf.rootMapCluster = mapCluster; + if (annotations.count == 0) { + continue; + } + // use wrapper annotations that expose a MKMapPoint property instead of a CLLocationCoordinate2D property + NSMutableSet * mapPointAnnotations = [[NSMutableSet alloc] initWithCapacity:annotations.count]; - [strongSelf clusterVisibleMapRectForceRefresh:YES]; - }]; + for (id annotation in annotations) { + ADMapPointAnnotation * mapPointAnnotation = [[ADMapPointAnnotation alloc] initWithAnnotation:annotation]; + [mapPointAnnotations addObject:mapPointAnnotation]; + } + + ADMapCluster *rootCluster = [ADMapCluster rootClusterForAnnotations:mapPointAnnotations mapView:self completion:nil]; + rootCluster.clusterRootTreeIdentifier = key; + [allRootClusters addObject:rootCluster]; + } + + + + TSClusterMapView *strongSelf = weakSelf; + + if (allRootClusters.count <= 1) { + strongSelf.rootMapCluster = allRootClusters.firstObject; + + if (!strongSelf.rootMapCluster) { + strongSelf.rootMapCluster = [ADMapCluster rootClusterForAnnotations:nil mapView:self completion:nil]; + } + } + else { + strongSelf.rootMapCluster = [[ADMapCluster alloc] initWithRootClusters:allRootClusters]; + } + + [strongSelf clusterVisibleMapRectForceRefresh:YES]; }]; } @@ -706,7 +793,7 @@ - (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view did - (void)selectAnnotation:(id)annotation animated:(BOOL)animated { - if ([_clusterableAnnotationsAdded containsObject:annotation]) { + if ([self.clusterableAnnotationsAdded containsObject:annotation]) { for (ADClusterAnnotation *clusterAnnotation in self.visibleClusterAnnotations) { if ([clusterAnnotation.originalAnnotations containsObject:annotation]) { [super selectAnnotation:clusterAnnotation animated:animated]; diff --git a/Pod/Classes/TSClusterOperation.m b/Pod/Classes/TSClusterOperation.m index 2f3c63a..984d073 100644 --- a/Pod/Classes/TSClusterOperation.m +++ b/Pod/Classes/TSClusterOperation.m @@ -415,6 +415,15 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { } } completion:^(BOOL finished) { + for (ADClusterAnnotation * annotation in _annotationPool) { + if (annotation.cluster) { + annotation.coordinate = annotation.coordinatePostAnimation; + [annotation.annotationView animateView]; + annotation.annotationView.transform = CGAffineTransformIdentity; + annotation.popInAnimation = NO; + } + } + //Make sure selected if was previously offscreen if (annotationToSelect) { [_mapView selectAnnotation:annotationToSelect animated:YES]; From fff9433b71fb6b8af89df77ac453a6b8daaab294 Mon Sep 17 00:00:00 2001 From: Adam Share Date: Mon, 26 Oct 2015 02:46:49 -0700 Subject: [PATCH 02/12] multi tree cluster separation --- Pod/Classes/ADMapCluster.h | 10 ++-- Pod/Classes/ADMapCluster.m | 31 +++++------ Pod/Classes/CLLocation+Utilities.h | 4 ++ Pod/Classes/CLLocation+Utilities.m | 39 ++++++++++++++ Pod/Classes/TSClusterMapView.m | 9 ++-- Pod/Classes/TSClusterOperation.m | 86 +++++++++++++++++++++++++++--- 6 files changed, 146 insertions(+), 33 deletions(-) diff --git a/Pod/Classes/ADMapCluster.h b/Pod/Classes/ADMapCluster.h index ffa8fcf..387fb99 100755 --- a/Pod/Classes/ADMapCluster.h +++ b/Pod/Classes/ADMapCluster.h @@ -46,30 +46,34 @@ typedef void(^KdtreeCompletionBlock)(ADMapCluster *mapCluster); @property (readonly) NSSet *clustersWithAnnotations; -@property (strong, nonatomic) NSString *clusterRootTreeIdentifier; +@property (strong, nonatomic) NSString *treeID; - (id)initWithRootClusters:(NSArray *)clusters; - (ADMapCluster *)rootClusterForID:(NSString *)treeID; +- (BOOL)overlapsClusterOnMap:(ADMapCluster *)cluster annotationViewMapRectSize:(MKMapRect)annotationViewRect; + /*! * @discussion Creates a KD-tree of clusters http://en.wikipedia.org/wiki/K-d_tree * @param annotations Set of ADMapPointAnnotation objects * @param mapView The ADClusterMapView that will send the delegate callback + * @param treeID The key associated with the group of annotations * @param completion A new ADMapCluster object. */ -+ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView completion:(KdtreeCompletionBlock)completion ; ++ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView treeID:(NSString *)treeID completion:(KdtreeCompletionBlock)completion ; /*! * @discussion Creates a KD-tree of clusters http://en.wikipedia.org/wiki/K-d_tree * @param annotations Set of ADMapPointAnnotation objects + * @param treeID The key associated with the group of annotations * @param gamma Descrimination power * @param clusterTitle Title of cluster * @param showSubtitle A Boolean to show subtitle from titles of children * @param completion A new ADMapCluster object. */ -+ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion ; ++ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations treeID:(NSString *)treeID centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion ; /*! * @discussion Adds a single map point annotation to an existing KD-tree map cluster root diff --git a/Pod/Classes/ADMapCluster.m b/Pod/Classes/ADMapCluster.m index bd839c8..212d176 100755 --- a/Pod/Classes/ADMapCluster.m +++ b/Pod/Classes/ADMapCluster.m @@ -32,11 +32,11 @@ @implementation ADMapCluster #pragma mark - Init -+ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView completion:(KdtreeCompletionBlock)completion { ++ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView treeID:(NSString *)treeID completion:(KdtreeCompletionBlock)completion { [mapView mapView:mapView willBeginBuildingClusterTreeForMapPoints:annotations]; - return [ADMapCluster rootClusterForAnnotations:annotations centerWeight:mapView.clusterDiscrimination title:mapView.clusterTitle showSubtitle:mapView.clusterShouldShowSubtitle completion:^(ADMapCluster *mapCluster) { + return [ADMapCluster rootClusterForAnnotations:annotations treeID:treeID centerWeight:mapView.clusterDiscrimination title:mapView.clusterTitle showSubtitle:mapView.clusterShouldShowSubtitle completion:^(ADMapCluster *mapCluster) { [mapView mapView:mapView didFinishBuildingClusterTreeForMapPoints:annotations]; if (completion) { @@ -45,7 +45,7 @@ + (ADMapCluster *)rootClusterForAnnotations:(NSSet *)an }]; } -+ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion { ++ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations treeID:(NSString *)treeID centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion { // KDTree //NSLog(@"Computing KD-tree for %lu annotations...", (unsigned long)annotations.count); @@ -69,7 +69,7 @@ + (ADMapCluster *)rootClusterForAnnotations:(NSSet *)an } - ADMapCluster * cluster = [[ADMapCluster alloc] initWithAnnotations:annotations atDepth:0 inMapRect:boundaries gamma:gamma clusterTitle:clusterTitle showSubtitle:showSubtitle parentCluster:nil rootCluster:nil]; + ADMapCluster * cluster = [[ADMapCluster alloc] initWithAnnotations:annotations treeID:(NSString *)treeID atDepth:0 inMapRect:boundaries gamma:gamma clusterTitle:clusterTitle showSubtitle:showSubtitle parentCluster:nil rootCluster:nil]; //NSLog(@"Computation done !"); @@ -79,7 +79,7 @@ + (ADMapCluster *)rootClusterForAnnotations:(NSSet *)an return cluster; } -- (id)initWithAnnotations:(NSSet *)annotations atDepth:(NSInteger)depth inMapRect:(MKMapRect)mapRect gamma:(double)gamma clusterTitle:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle parentCluster:(ADMapCluster *)parentCluster rootCluster:(ADMapCluster *)rootCluster { +- (id)initWithAnnotations:(NSSet *)annotations treeID:(NSString *)treeID atDepth:(NSInteger)depth inMapRect:(MKMapRect)mapRect gamma:(double)gamma clusterTitle:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle parentCluster:(ADMapCluster *)parentCluster rootCluster:(ADMapCluster *)rootCluster { self = [super init]; if (self) { _depth = depth; @@ -90,6 +90,7 @@ - (id)initWithAnnotations:(NSSet *)annotations atDepth: _parentCluster = parentCluster; _clusterCount = annotations.count; _progress = 0; + _treeID = treeID; if (depth == 0) { rootCluster = self; @@ -130,10 +131,10 @@ - (id)initWithAnnotations:(NSSet *)annotations atDepth: MKMapRect rightMapRect = [ADMapCluster boundariesForAnnotations:splitAnnotations[1]]; if (splitAnnotations[0]) { - _leftChild = [[ADMapCluster alloc] initWithAnnotations:splitAnnotations[0] atDepth:depth+1 inMapRect:leftMapRect gamma:gamma clusterTitle:clusterTitle showSubtitle:showSubtitle parentCluster:self rootCluster:rootCluster]; + _leftChild = [[ADMapCluster alloc] initWithAnnotations:splitAnnotations[0] treeID:(NSString *)treeID atDepth:depth+1 inMapRect:leftMapRect gamma:gamma clusterTitle:clusterTitle showSubtitle:showSubtitle parentCluster:self rootCluster:rootCluster]; } - _rightChild = [[ADMapCluster alloc] initWithAnnotations:splitAnnotations[1] atDepth:depth+1 inMapRect:rightMapRect gamma:gamma clusterTitle:clusterTitle showSubtitle:showSubtitle parentCluster:self rootCluster:rootCluster]; + _rightChild = [[ADMapCluster alloc] initWithAnnotations:splitAnnotations[1] treeID:(NSString *)treeID atDepth:depth+1 inMapRect:rightMapRect gamma:gamma clusterTitle:clusterTitle showSubtitle:showSubtitle parentCluster:self rootCluster:rootCluster]; } } return self; @@ -145,7 +146,7 @@ - (id)initWithAnnotations:(NSSet *)annotations atDepth: - (id)initWithRootClusters:(NSArray *)clusters { self = [super init]; if (self) { - self.clusterRootTreeIdentifier = kTSClusterMapViewRootMultiClusterID; + self.treeID = kTSClusterMapViewRootMultiClusterID; #warning handle 2 trees ADMapCluster *cluster1 = clusters.firstObject; ADMapCluster *cluster2 = clusters.lastObject; @@ -187,11 +188,11 @@ - (ADMapCluster *)rootClusterForID:(NSString *)treeID { for (ADMapCluster *cluster in self.children) { - if ([cluster.clusterRootTreeIdentifier isEqualToString:treeID]) { + if ([cluster.treeID isEqualToString:treeID]) { return cluster; } - if ([cluster.clusterRootTreeIdentifier isEqualToString:kTSClusterMapViewRootMultiClusterID]) { + if ([cluster.treeID isEqualToString:kTSClusterMapViewRootMultiClusterID]) { ADMapCluster *foundCluster = [cluster rootClusterForID:treeID]; if (foundCluster) { return foundCluster; @@ -363,7 +364,7 @@ - (void)mapView:(TSClusterMapView *)mapView addAnnotation:(ADMapPointAnnotation closestCluster.clusterCount = 0; - [ADMapCluster rootClusterForAnnotations:annotationsToRecalculate mapView:mapView completion:^(ADMapCluster *mapCluster) { + [ADMapCluster rootClusterForAnnotations:annotationsToRecalculate mapView:mapView treeID:self.treeID completion:^(ADMapCluster *mapCluster) { if (closestCluster.parentCluster.rightChild == closestCluster) { closestCluster.parentCluster.rightChild = mapCluster; } @@ -398,7 +399,7 @@ - (void)mapView:(TSClusterMapView *)mapView removeAnnotation:(id)a clusterParent.clusterCount = 0; - [ADMapCluster rootClusterForAnnotations:annotationsToRecalculate mapView:mapView completion:^(ADMapCluster *mapCluster) { + [ADMapCluster rootClusterForAnnotations:annotationsToRecalculate mapView:mapView treeID:self.treeID completion:^(ADMapCluster *mapCluster) { if (clusterParent.parentCluster.rightChild == clusterParent) { clusterParent.parentCluster.rightChild = mapCluster; @@ -578,7 +579,7 @@ - (void)setClusterCount:(NSInteger)clusterCount { for (ADMapCluster * child in children) { if (!overlap && !MKMapRectIsEmpty(annotationSizeRect) && children.count == 2) { - if ([child overlapsClusterOnMap:[children lastObject] annotationViewMapRectSize:annotationSizeRect]) { + if (child.depth != 0 && [child overlapsClusterOnMap:[children lastObject] annotationViewMapRectSize:annotationSizeRect]) { [clusters addObject:cluster]; break; } @@ -692,10 +693,6 @@ - (BOOL)overlapsClusterOnMap:(ADMapCluster *)cluster annotationViewMapRectSize:( return NO; } - if (cluster.clusterRootTreeIdentifier || self.clusterRootTreeIdentifier) { - return NO; - } - MKMapPoint thisPoint = MKMapPointForCoordinate(_clusterCoordinate); MKMapPoint clusterPoint = MKMapPointForCoordinate(cluster.clusterCoordinate); diff --git a/Pod/Classes/CLLocation+Utilities.h b/Pod/Classes/CLLocation+Utilities.h index a25374e..ea71e70 100644 --- a/Pod/Classes/CLLocation+Utilities.h +++ b/Pod/Classes/CLLocation+Utilities.h @@ -13,10 +13,14 @@ BOOL CLLocationCoordinate2DIsApproxEqual(CLLocationCoordinate2D coord1, CLLocationCoordinate2D coord2, float epsilon); +double CLLocationCoordinate2DBearingRadians(CLLocationCoordinate2D coord1, CLLocationCoordinate2D coord2); + CLLocationCoordinate2D CLLocationCoordinate2DOffset(CLLocationCoordinate2D coord, double x, double y); CLLocationCoordinate2D CLLocationCoordinate2DRoundedLonLat(CLLocationCoordinate2D coord, int decimalPlace); +CLLocationCoordinate2D CLLocationCoordinate2DMidPoint(CLLocationCoordinate2D coord1, CLLocationCoordinate2D coord2); + BOOL MKMapRectSizeIsEqual(MKMapRect rect1, MKMapRect rect2); BOOL MKMapRectApproxEqual(MKMapRect rect1, MKMapRect rect2); diff --git a/Pod/Classes/CLLocation+Utilities.m b/Pod/Classes/CLLocation+Utilities.m index 6830cef..bce363f 100644 --- a/Pod/Classes/CLLocation+Utilities.m +++ b/Pod/Classes/CLLocation+Utilities.m @@ -15,6 +15,45 @@ BOOL CLLocationCoordinate2DIsApproxEqual(CLLocationCoordinate2D coord1, CLLocati fabs(coord1.longitude - coord2.longitude) < epsilon); } +double CLLocationCoordinate2DBearingRadians(CLLocationCoordinate2D coord1, CLLocationCoordinate2D coord2) { + + double lat1 = coord1.latitude; + double lon1 = coord1.longitude; + + double lat2 = coord2.latitude; + double lon2 = coord2.longitude; + + double y = sin(lon2-lon1)*cos(lat2); //SIN(lon2-lon1)*COS(lat2) + double x = cos(lat1)*sin(lat2)-sin(lat1)*cos(lat2)*cos(lon2-lon1); + double bearing = atan2(y, x); + + return bearing; +} + +CLLocationCoordinate2D CLLocationCoordinate2DMidPoint(CLLocationCoordinate2D coord1, CLLocationCoordinate2D coord2) { + + CLLocationCoordinate2D midPoint; + + double lon1 = coord1.longitude * M_PI / 180; + double lon2 = coord2.longitude * M_PI / 180; + + double lat1 = coord1.latitude * M_PI / 180; + double lat2 = coord2.latitude * M_PI / 180; + + double dLon = lon2 - lon1; + + double x = cos(lat2) * cos(dLon); + double y = cos(lat2) * sin(dLon); + + double lat3 = atan2( sin(lat1) + sin(lat2), sqrt((cos(lat1) + x) * (cos(lat1) + x) + y * y) ); + double lon3 = lon1 + atan2(y, cos(lat1) + x); + + midPoint.latitude = lat3 * 180 / M_PI; + midPoint.longitude = lon3 * 180 / M_PI; + + return midPoint; +} + CLLocationCoordinate2D CLLocationCoordinate2DOffset(CLLocationCoordinate2D coord, double x, double y) { return CLLocationCoordinate2DMake(coord.latitude + y, coord.longitude + x); } diff --git a/Pod/Classes/TSClusterMapView.m b/Pod/Classes/TSClusterMapView.m index d3faa92..021f2af 100755 --- a/Pod/Classes/TSClusterMapView.m +++ b/Pod/Classes/TSClusterMapView.m @@ -528,7 +528,7 @@ - (void)createKDTreesAndCluster:(NSMutableDictionary *)annotations { - NSDictionary *coordinateValuesToAnnotations = [TSClusterOperation groupClusterAnnotationsByLocationValue:annotations]; + NSDictionary *coordinateValuesToAnnotations = [TSClusterOperation groupLeafAnnotationsByLocationValue:annotations]; for (NSValue *coordinateValue in coordinateValuesToAnnotations.allKeys) { NSMutableArray *outletsAtLocation = coordinateValuesToAnnotations[coordinateValue]; @@ -690,18 +690,51 @@ - (void)mutateCoordinatesOfClashingAnnotations:(NSSet *) [self repositionAnnotations:outletsAtLocation toAvoidClashAtCoordinate:coordinate]; } } + + annotations = [annotations filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(ADClusterAnnotation * evaluatedObject, NSDictionary * _Nullable bindings) { + + return evaluatedObject.type == ADClusterAnnotationTypeCluster && evaluatedObject.cluster; + }]]; + + for (ADClusterAnnotation *annotation in annotations) { + for (ADClusterAnnotation *compareAnnotation in annotations) { + if (compareAnnotation == annotation || [compareAnnotation.cluster.treeID isEqualToString:annotation.cluster.treeID]) { + continue; + } + + if ([annotation.cluster overlapsClusterOnMap:compareAnnotation.cluster annotationViewMapRectSize:[self mapRectAnnotationViewSize]]) { + [self repositionAnnotations:@[annotation, compareAnnotation] toAvoidClashAtCoordinate:CLLocationCoordinate2DMidPoint(annotation.cluster.clusterCoordinate, compareAnnotation.cluster.clusterCoordinate)]; + } + } + } +} + ++ (NSDictionary *>*)groupLeafAnnotationsByLocationValue:(NSSet *)annotations { + + annotations = [annotations filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(ADClusterAnnotation * evaluatedObject, NSDictionary * _Nullable bindings) { + + return evaluatedObject.type == ADClusterAnnotationTypeLeaf && evaluatedObject.cluster; + }]]; + + return [self groupClusterByLocationValue:annotations]; } + (NSDictionary *>*)groupClusterAnnotationsByLocationValue:(NSSet *)annotations { + annotations = [annotations filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(ADClusterAnnotation * evaluatedObject, NSDictionary * _Nullable bindings) { + + return evaluatedObject.type == ADClusterAnnotationTypeCluster && evaluatedObject.cluster; + }]]; + + return [self groupClusterByLocationValue:annotations]; +} + ++ (NSDictionary *>*)groupClusterByLocationValue:(NSSet *)annotations { + NSMutableDictionary *result = [NSMutableDictionary dictionary]; for (ADClusterAnnotation *pin in annotations) { - if (!pin.cluster || pin.type == ADClusterAnnotationTypeCluster) { - continue; - } - CLLocationCoordinate2D coordinate = CLLocationCoordinate2DRoundedLonLat(pin.cluster.clusterCoordinate, 5); NSValue *coordinateValue = [NSValue valueWithBytes:&coordinate objCType:@encode(CLLocationCoordinate2D)]; @@ -713,8 +746,10 @@ - (void)mutateCoordinatesOfClashingAnnotations:(NSSet *) [annotationsAtLocation addObject:pin]; } + return result; } + + (NSDictionary > *>*)groupAnnotationsByLocationValue:(NSSet >*)annotations { @@ -745,15 +780,50 @@ - (void)repositionAnnotations:(NSArray *)annotations toAv } } - double distance = 3 * annotations.count / 2.0; + MKMapRect mapViewRect = _mapView.visibleMapRect; + + CLLocationDistance width = MKMetersBetweenMapPoints(mapViewRect.origin, MKMapPointMake(mapViewRect.origin.x + mapViewRect.size.width, mapViewRect.origin.y)); + CLLocationDistance height = MKMetersBetweenMapPoints(mapViewRect.origin, MKMapPointMake(mapViewRect.origin.x, mapViewRect.origin.y + mapViewRect.size.height)); + CLLocationDistance minHeightWidth = MIN(width, height); + + + MKMapRect annotationRect = [self mapRectAnnotationViewSize]; + + CLLocationDistance distance = minHeightWidth/8; + + if (!MKMapRectIsNull(annotationRect)) { + + MKMapPoint originPoint = annotationRect.origin; + MKMapPoint sizePoint = MKMapPointMake(annotationRect.origin.x + annotationRect.size.width, annotationRect.origin.y + annotationRect.size.height); + distance = MKMetersBetweenMapPoints(originPoint, sizePoint); + } + + CLLocationDistance maxRadius = minHeightWidth - (2 * distance); + double radiansBetweenAnnotations = (M_PI * 2) / annotations.count; + double radius = (distance * annotations.count)/(2 * M_PI); + + radius = MIN(maxRadius, radius); + + if (annotations.count == 2 && !CLLocationCoordinate2DIsApproxEqual(annotations.firstObject.cluster.clusterCoordinate, annotations.lastObject.cluster.clusterCoordinate, .00001) ) { + + + for (ADClusterAnnotation *annotation in annotations) { + double bearing = CLLocationCoordinate2DBearingRadians(coordinate, annotation.cluster.clusterCoordinate); + CLLocationCoordinate2D newCoordinate = [TSClusterOperation calculateCoordinateFrom:coordinate onBearing:bearing atDistance:radius]; + + annotation.coordinatePostAnimation = newCoordinate; + } + + return; + } int i = 0; for (ADClusterAnnotation *annotation in annotations) { - double heading = radiansBetweenAnnotations * i; - CLLocationCoordinate2D newCoordinate = [TSClusterOperation calculateCoordinateFrom:coordinate onBearing:heading atDistance:distance]; + double bearing = radiansBetweenAnnotations * i; + CLLocationCoordinate2D newCoordinate = [TSClusterOperation calculateCoordinateFrom:coordinate onBearing:bearing atDistance:radius]; annotation.coordinatePostAnimation = newCoordinate; From 8068c37880c51d57f22598efb2fc35ffaa9527db Mon Sep 17 00:00:00 2001 From: Adam Share Date: Tue, 27 Oct 2015 01:31:29 -0700 Subject: [PATCH 03/12] multi tree ui --- .../TSDemoClusteredAnnotationView.m | 36 +++++++++++++++--- .../BathroomAnnotationGreen@3x.png | Bin 0 -> 6177 bytes .../Contents.json | 21 ++++++++++ .../ClusterAnnotationGreen@3x.png | Bin 0 -> 4750 bytes .../Contents.json | 21 ++++++++++ .../ClusterAnnotationYellow@3x.png | Bin 0 -> 4936 bytes .../Contents.json | 21 ++++++++++ .../Contents.json | 21 ++++++++++ .../StreetLightAnnotationYellow@3x.png | Bin 0 -> 5924 bytes .../ViewControllers/CDMapViewController.h | 6 +++ .../ViewControllers/CDMapViewController.m | 4 -- 11 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/BathroomAnnotationGreen.imageset/BathroomAnnotationGreen@3x.png create mode 100644 Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/BathroomAnnotationGreen.imageset/Contents.json create mode 100644 Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/ClusterAnnotationGreen.imageset/ClusterAnnotationGreen@3x.png create mode 100644 Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/ClusterAnnotationGreen.imageset/Contents.json create mode 100644 Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/ClusterAnnotationYellow.imageset/ClusterAnnotationYellow@3x.png create mode 100644 Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/ClusterAnnotationYellow.imageset/Contents.json create mode 100644 Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/StreetLightAnnotationYellow.imageset/Contents.json create mode 100644 Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/StreetLightAnnotationYellow.imageset/StreetLightAnnotationYellow@3x.png diff --git a/Example/TSClusterMapView/ClusterDemo/AnnotationViews/TSDemoClusteredAnnotationView.m b/Example/TSClusterMapView/ClusterDemo/AnnotationViews/TSDemoClusteredAnnotationView.m index a0a8f52..e1b8574 100644 --- a/Example/TSClusterMapView/ClusterDemo/AnnotationViews/TSDemoClusteredAnnotationView.m +++ b/Example/TSClusterMapView/ClusterDemo/AnnotationViews/TSDemoClusteredAnnotationView.m @@ -12,22 +12,35 @@ blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] #import "TSDemoClusteredAnnotationView.h" +#import "CDMapViewController.h" @implementation TSDemoClusteredAnnotationView -- (id)initWithAnnotation:(id)annotation reuseIdentifier:(NSString *)reuseIdentifier { +- (id)initWithAnnotation:(ADClusterAnnotation *)annotation reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; if (self) { // Initialization code - self.image = [UIImage imageNamed:@"ClusterAnnotation"]; - self.frame = CGRectMake(0, 0, self.image.size.width, self.image.size.height); - self.label = [[UILabel alloc] initWithFrame:self.frame]; + + if ([annotation.cluster.treeID isEqualToString:CDStreetLightJsonFile]) { + self.image = [UIImage imageNamed:@"ClusterAnnotationYellow"]; + self.label.textColor = UIColorFromRGB(0xf6d262); + } + else if ([annotation.cluster.treeID isEqualToString:CDToiletJsonFile]) { + self.image = [UIImage imageNamed:@"ClusterAnnotationGreen"]; + self.label.textColor = UIColorFromRGB(0x6fc99d); + } + else { + self.image = [UIImage imageNamed:@"ClusterAnnotation"]; + self.label.textColor = UIColorFromRGB(0x009fd6); + } + + self.frame = CGRectMake(0, 0, self.image.size.width, self.image.size.height); + self.label.frame = self.frame; self.label.textAlignment = NSTextAlignmentCenter; self.label.font = [UIFont systemFontOfSize:10]; - self.label.textColor = UIColorFromRGB(0x009fd6); self.label.center = CGPointMake(self.image.size.width/2, self.image.size.height*.43); self.centerOffset = CGPointMake(0, -self.frame.size.height/2); @@ -46,6 +59,19 @@ - (void)clusteringAnimation { NSUInteger count = clusterAnnotation.clusterCount; self.label.text = [self numberLabelText:count]; + + if ([clusterAnnotation.cluster.treeID isEqualToString:CDStreetLightJsonFile]) { + self.image = [UIImage imageNamed:@"ClusterAnnotationYellow"]; + self.label.textColor = UIColorFromRGB(0xf6d262); + } + else if ([clusterAnnotation.cluster.treeID isEqualToString:CDToiletJsonFile]) { + self.image = [UIImage imageNamed:@"ClusterAnnotationGreen"]; + self.label.textColor = UIColorFromRGB(0x6fc99d); + } + else { + self.image = [UIImage imageNamed:@"ClusterAnnotation"]; + self.label.textColor = UIColorFromRGB(0x009fd6); + } } - (NSString *)numberLabelText:(float)count { diff --git a/Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/BathroomAnnotationGreen.imageset/BathroomAnnotationGreen@3x.png b/Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/BathroomAnnotationGreen.imageset/BathroomAnnotationGreen@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..95e1fad2ef236efa152c064dc7c4b685b06d3725 GIT binary patch literal 6177 zcmV++7~bcJP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 literal 0 HcmV?d00001 diff --git a/Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/BathroomAnnotationGreen.imageset/Contents.json b/Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/BathroomAnnotationGreen.imageset/Contents.json new file mode 100644 index 0000000..453f0d9 --- /dev/null +++ b/Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/BathroomAnnotationGreen.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "BathroomAnnotationGreen@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/ClusterAnnotationGreen.imageset/ClusterAnnotationGreen@3x.png b/Example/TSClusterMapView/ClusterDemo/Resources/Images.xcassets/ClusterAnnotationGreen.imageset/ClusterAnnotationGreen@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..09469879cd1c754a8e29dcfd91380ac70f2bd399 GIT binary patch literal 4750 zcmV;95^?Q`P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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%0AOTf%-r2KP%w>NQDWVF z1HP##|Lvlcn#jXkdv7f{GSIZKOz@oUDZ%vIwB|7(i_Zaa z?!CQ5i6VTy>062^BHbt<1b2Tiib%pKsQs)}Zs{pG&ma#CZfeh!_1S0M7_fJ(_d}t> z^#C|=_}7+(!1Gy&TxvMW`@dyU)rL=^#0!V~M=b(5=(g@sloXIeu@o>PA4Txe93ogW~b<&(;PVHKf7 z#@-!Dt(1<35|Ztp^0yUC@{Ttat^JOocQ7e`{X$GO>Gp3D4ttnYR=YHAUv*o0J|X@N zjROxfo5F*dQs18uH1sB#mw!d+e1aVN|Ealf0ziK zx^`mgo>IzklE`<;Bd!jkCJy3mk^lf5$6hfvO#RF(mT7{)RKw>2_C@Ce(&a^291A5h z^#^ZkDfJa{hdhlM}l6n9u6 zh;R#!>1;gM@R^9MX>HJ!oUjRHbN0q$=AGPSGWlN8>tbG}XMWX>G!PLTJ?b!Y_YIWv zmbO_8tHspwd)`@o0c;;uyRo?ifFy$k5~L?Wj)(+A2uO@H7(odVs;4W;hmn!7mhQd* zSNW7w_Q^+Iz4+3mChIfR5W;-G$jI2@?!JLzYSB~GY!{#X!n1C-@AB%4rTUndaIIBS z5dam)9kTfB7oPF?rvIgSVyQmH$6cp&eX4)A+H@Z%0XZr>G)&win-7txELD#^c2W(#qWr3;=+muU`CxrY7r8YRN+;YN-f-8Y=?;;LywK zKVrAr-!7k0d2l!;&RBN-FaM{OJk(qn002jR*Y`IqEzPR0^@zghboVSd_doa6S`PC< z82|uBes|&O=H>;1N?vS;%`)@nLwDc6)X}4kdLpVw4k*rfUw#e9FA`32Uv}mJjn*8EN?6y~-s?lA=KCT;QDh&1;_!dYw_NFz?*d zTv$%~NpmO^=`V*yA|X4D9UE88!YYM%Zy9Mg|8VKEX0ze*O3Ou~u|M0+cWw}$rZKcTNLx&O=Y6x%x5nZ-J}Wx!q1#ciU2stEF+DPS3dih=4M;o zek2eGJ<@XKPj9L #import "TSClusterMapView.h" +static NSString * const CDStreetLightJsonFile = @"CDStreetlights"; +static NSString * const kStreetLightAnnotationImage = @"StreetLightAnnotationYellow"; + +static NSString * const CDToiletJsonFile = @"CDToilets"; +static NSString * const kBathroomAnnotationImage = @"BathroomAnnotationGreen"; + @interface CDMapViewController : UIViewController @property (strong, nonatomic) IBOutlet TSClusterMapView * mapView; diff --git a/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m b/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m index 7f93e22..1b99fc9 100644 --- a/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m +++ b/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m @@ -12,11 +12,7 @@ #import "TSStreetLightAnnotation.h" #import "TSDemoClusteredAnnotationView.h" -static NSString * const CDStreetLightJsonFile = @"CDStreetlights"; -static NSString * const kStreetLightAnnotationImage = @"StreetLightAnnotation"; -static NSString * const CDToiletJsonFile = @"CDToilets"; -static NSString * const kBathroomAnnotationImage = @"BathroomAnnotation"; @interface CDMapViewController () From ecd030f4db6acb1e70fd473c5802c88983f4b62b Mon Sep 17 00:00:00 2001 From: Adam Share Date: Tue, 27 Oct 2015 02:13:01 -0700 Subject: [PATCH 04/12] remove two tree limit --- Pod/Classes/ADMapCluster.m | 73 +++++++++++++++++++++++++----- Pod/Classes/ADMapPointAnnotation.h | 2 +- Pod/Classes/ADMapPointAnnotation.m | 22 +++++++++ 3 files changed, 84 insertions(+), 13 deletions(-) diff --git a/Pod/Classes/ADMapCluster.m b/Pod/Classes/ADMapCluster.m index 212d176..b014926 100755 --- a/Pod/Classes/ADMapCluster.m +++ b/Pod/Classes/ADMapCluster.m @@ -147,24 +147,30 @@ - (id)initWithRootClusters:(NSArray *)clusters { self = [super init]; if (self) { self.treeID = kTSClusterMapViewRootMultiClusterID; -#warning handle 2 trees - ADMapCluster *cluster1 = clusters.firstObject; - ADMapCluster *cluster2 = clusters.lastObject; - cluster1.parentCluster = self; - cluster2.parentCluster = self; + _clusterCount = 0; + + for (ADMapCluster *cluster in clusters) { + cluster.parentCluster = self; + _clusterCount += cluster.clusterCount; + + if (MKMapRectIsEmpty(_mapRect)) { + _mapRect = cluster.mapRect; + continue; + } + _mapRect = MKMapRectUnion(_mapRect, cluster.mapRect); + } _depth = 0; - _mapRect = MKMapRectUnion(cluster1.mapRect, cluster2.mapRect); - _clusterTitle = cluster1.title; - _showSubtitle = cluster1.showSubtitle; - _gamma = cluster1.gamma; + _clusterTitle = clusters.firstObject.title; + _showSubtitle = clusters.firstObject.showSubtitle; + _gamma = clusters.firstObject.gamma; _parentCluster = nil; - _clusterCount = cluster1.clusterCount + cluster2.clusterCount; _progress = 0; self.annotation = nil; + NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init]; NSMutableSet *annotations = [[NSMutableSet alloc] initWithCapacity:clusters.count]; for (ADMapCluster *cluster in clusters) { @@ -172,14 +178,57 @@ - (id)initWithRootClusters:(NSArray *)clusters { tempAnnotation.coordinate = cluster.clusterCoordinate; ADMapPointAnnotation *mapPoint = [[ADMapPointAnnotation alloc] initWithAnnotation:tempAnnotation]; [annotations addObject:mapPoint]; + + [mutableDict setObject:cluster forKey:mapPoint]; } MKMapPoint centerMapPoint = [self meanCoordinateForAnnotations:annotations gamma:_gamma]; _clusterCoordinate = MKCoordinateForMapPoint(centerMapPoint); - _leftChild = cluster1; - _rightChild = cluster2; + NSArray *splitAnnotations = [self splitAnnotations:annotations centerPoint:centerMapPoint]; + NSArray *left = splitAnnotations.firstObject; + NSArray *right = splitAnnotations.lastObject; + + NSMutableArray *leftChildren = [[NSMutableArray alloc] init]; + for (ADMapPointAnnotation *mapPoint in left) { + ADMapCluster *cluster = [mutableDict objectForKey:mapPoint]; + if (cluster) { + [leftChildren addObject:cluster]; + } + else { + NSLog(@"Lost cluster"); + } + } + + NSMutableArray *rightChildren = [[NSMutableArray alloc] init]; + for (ADMapPointAnnotation *mapPoint in right) { + ADMapCluster *cluster = [mutableDict objectForKey:mapPoint]; + if (cluster) { + [rightChildren addObject:cluster]; + } + else { + NSLog(@"Lost cluster"); + } + } + + if (leftChildren.count) { + if (leftChildren.count == 1) { + _leftChild = leftChildren.firstObject; + } + else { + _leftChild = [[ADMapCluster alloc] initWithRootClusters:leftChildren]; + } + } + + if (rightChildren.count) { + if (rightChildren.count == 1) { + _rightChild = rightChildren.firstObject; + } + else { + _rightChild = [[ADMapCluster alloc] initWithRootClusters:rightChildren]; + } + } } return self; } diff --git a/Pod/Classes/ADMapPointAnnotation.h b/Pod/Classes/ADMapPointAnnotation.h index ac1ec17..323f60b 100755 --- a/Pod/Classes/ADMapPointAnnotation.h +++ b/Pod/Classes/ADMapPointAnnotation.h @@ -12,7 +12,7 @@ /** * Do not subclass. This is a wrapper to give annotations added to cluster a map point. */ -@interface ADMapPointAnnotation : NSObject +@interface ADMapPointAnnotation : NSObject @property (nonatomic, readonly) MKMapPoint mapPoint; diff --git a/Pod/Classes/ADMapPointAnnotation.m b/Pod/Classes/ADMapPointAnnotation.m index a1bd622..a7814d4 100755 --- a/Pod/Classes/ADMapPointAnnotation.m +++ b/Pod/Classes/ADMapPointAnnotation.m @@ -19,4 +19,26 @@ - (id)initWithAnnotation:(id)annotation { return self; } +- (id)copyWithZone:(NSZone *)zone { + + return [[[self class] alloc] initWithAnnotation:_annotation]; +} + +- (BOOL)isEqual:(ADMapPointAnnotation *)other +{ + if (other == self) { + return YES; + } else { + if ([other isKindOfClass:[self class]]) { + return [self.annotation isEqual:other.annotation]; + } + return NO; + } +} + +- (NSUInteger)hash +{ + return [@(self.mapPoint.x) hash] ^ [@(self.mapPoint.y) hash] ^ [self.annotation hash]; +} + @end From 09f41042a2b509aae6ffc218a00d84542120a8fb Mon Sep 17 00:00:00 2001 From: Adam Share Date: Tue, 27 Oct 2015 02:14:39 -0700 Subject: [PATCH 05/12] cleanup --- Pod/Classes/TSClusterMapView.m | 1 - 1 file changed, 1 deletion(-) diff --git a/Pod/Classes/TSClusterMapView.m b/Pod/Classes/TSClusterMapView.m index 021f2af..277d6b7 100755 --- a/Pod/Classes/TSClusterMapView.m +++ b/Pod/Classes/TSClusterMapView.m @@ -149,7 +149,6 @@ - (NSUInteger)numberOfClusters { - (void)needsRefresh { [self createKDTreesAndCluster:_annotationsByTreeID]; -// [self createKDTreeAndCluster:self.clusterableAnnotationsAdded]; } #pragma mark - Add/Remove Annotations From c8d97233eb675cbf299d0b51ef30455b0f62c460 Mon Sep 17 00:00:00 2001 From: Adam Share Date: Tue, 27 Oct 2015 02:28:53 -0700 Subject: [PATCH 06/12] rename --- .../TSDemoClusteredAnnotationView.m | 8 +-- .../ViewControllers/CDMapViewController.m | 16 +++--- Pod/Classes/ADMapCluster.h | 12 ++--- Pod/Classes/ADMapCluster.m | 30 +++++------ Pod/Classes/TSClusterMapView.h | 8 +-- Pod/Classes/TSClusterMapView.m | 54 +++++++++---------- Pod/Classes/TSClusterOperation.m | 2 +- 7 files changed, 65 insertions(+), 65 deletions(-) diff --git a/Example/TSClusterMapView/ClusterDemo/AnnotationViews/TSDemoClusteredAnnotationView.m b/Example/TSClusterMapView/ClusterDemo/AnnotationViews/TSDemoClusteredAnnotationView.m index e1b8574..f91c77e 100644 --- a/Example/TSClusterMapView/ClusterDemo/AnnotationViews/TSDemoClusteredAnnotationView.m +++ b/Example/TSClusterMapView/ClusterDemo/AnnotationViews/TSDemoClusteredAnnotationView.m @@ -24,11 +24,11 @@ - (id)initWithAnnotation:(ADClusterAnnotation *)annotation reuseIdentifier:(NSSt self.label = [[UILabel alloc] initWithFrame:self.frame]; - if ([annotation.cluster.treeID isEqualToString:CDStreetLightJsonFile]) { + if ([annotation.cluster.groupID isEqualToString:CDStreetLightJsonFile]) { self.image = [UIImage imageNamed:@"ClusterAnnotationYellow"]; self.label.textColor = UIColorFromRGB(0xf6d262); } - else if ([annotation.cluster.treeID isEqualToString:CDToiletJsonFile]) { + else if ([annotation.cluster.groupID isEqualToString:CDToiletJsonFile]) { self.image = [UIImage imageNamed:@"ClusterAnnotationGreen"]; self.label.textColor = UIColorFromRGB(0x6fc99d); } @@ -60,11 +60,11 @@ - (void)clusteringAnimation { NSUInteger count = clusterAnnotation.clusterCount; self.label.text = [self numberLabelText:count]; - if ([clusterAnnotation.cluster.treeID isEqualToString:CDStreetLightJsonFile]) { + if ([clusterAnnotation.cluster.groupID isEqualToString:CDStreetLightJsonFile]) { self.image = [UIImage imageNamed:@"ClusterAnnotationYellow"]; self.label.textColor = UIColorFromRGB(0xf6d262); } - else if ([clusterAnnotation.cluster.treeID isEqualToString:CDToiletJsonFile]) { + else if ([clusterAnnotation.cluster.groupID isEqualToString:CDToiletJsonFile]) { self.image = [UIImage imageNamed:@"ClusterAnnotationGreen"]; self.label.textColor = UIColorFromRGB(0x6fc99d); } diff --git a/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m b/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m index 1b99fc9..dd57701 100644 --- a/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m +++ b/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m @@ -172,14 +172,14 @@ - (IBAction)addAll:(id)sender { if (_tabBar.selectedItem == _bathroomTabBarItem) { NSLog(@"Adding All %@", CDToiletJsonFile); - [_mapView addClusteredAnnotations:_bathroomAnnotations toTree:CDToiletJsonFile]; + [_mapView addClusteredAnnotations:_bathroomAnnotations toGroup:CDToiletJsonFile]; _bathroomAnnotationsAdded = [NSMutableArray arrayWithArray:_bathroomAnnotations]; _stepper.value = _bathroomAnnotationsAdded.count; } else if (_tabBar.selectedItem == _streetLightsTabBarItem) { NSLog(@"Adding All %@", CDStreetLightJsonFile); - [_mapView addClusteredAnnotations:_streetLightAnnotations toTree:CDStreetLightJsonFile]; + [_mapView addClusteredAnnotations:_streetLightAnnotations toGroup:CDStreetLightJsonFile]; _streetLightAnnotationsAdded = [NSMutableArray arrayWithArray:_streetLightAnnotations]; _stepper.value = _streetLightAnnotationsAdded.count; } @@ -190,13 +190,13 @@ - (IBAction)addAll:(id)sender { - (IBAction)removeAll:(id)sender { if (_tabBar.selectedItem == _bathroomTabBarItem) { - [_mapView removeAnnotations:_bathroomAnnotationsAdded fromTree:CDToiletJsonFile]; + [_mapView removeAnnotations:_bathroomAnnotationsAdded fromGroup:CDToiletJsonFile]; [_bathroomAnnotationsAdded removeAllObjects]; NSLog(@"Removing All %@", CDToiletJsonFile); } else if (_tabBar.selectedItem == _streetLightsTabBarItem) { - [_mapView removeAnnotations:_streetLightAnnotationsAdded fromTree:CDStreetLightJsonFile]; + [_mapView removeAnnotations:_streetLightAnnotationsAdded fromGroup:CDStreetLightJsonFile]; [_streetLightAnnotationsAdded removeAllObjects]; NSLog(@"Removing All %@", CDStreetLightJsonFile); @@ -249,7 +249,7 @@ - (void)addNewBathroom { TSBathroomAnnotation *annotation = [_bathroomAnnotations objectAtIndex:_bathroomAnnotationsAdded.count]; [_bathroomAnnotationsAdded addObject:annotation]; - [_mapView addClusteredAnnotation:annotation toTree:CDToiletJsonFile]; + [_mapView addClusteredAnnotation:annotation toGroup:CDToiletJsonFile]; } - (void)addNewStreetLight { @@ -263,7 +263,7 @@ - (void)addNewStreetLight { TSStreetLightAnnotation *annotation = [_streetLightAnnotations objectAtIndex:_streetLightAnnotationsAdded.count]; [_streetLightAnnotationsAdded addObject:annotation]; - [_mapView addClusteredAnnotation:annotation toTree:CDStreetLightJsonFile]; + [_mapView addClusteredAnnotation:annotation toGroup:CDStreetLightJsonFile]; } - (void)removeLastBathroom { @@ -272,7 +272,7 @@ - (void)removeLastBathroom { TSBathroomAnnotation *annotation = [_bathroomAnnotationsAdded lastObject]; [_bathroomAnnotationsAdded removeObject:annotation]; - [_mapView removeAnnotation:annotation fromTree:CDToiletJsonFile]; + [_mapView removeAnnotation:annotation fromGroup:CDToiletJsonFile]; } - (void)removeLastStreetLight { @@ -281,7 +281,7 @@ - (void)removeLastStreetLight { TSStreetLightAnnotation *annotation = [_streetLightAnnotationsAdded lastObject]; [_streetLightAnnotationsAdded removeObject:annotation]; - [_mapView removeAnnotation:annotation fromTree:CDStreetLightJsonFile]; + [_mapView removeAnnotation:annotation fromGroup:CDStreetLightJsonFile]; } - (IBAction)segmentedControlValueChanged:(id)sender { diff --git a/Pod/Classes/ADMapCluster.h b/Pod/Classes/ADMapCluster.h index 387fb99..5031557 100755 --- a/Pod/Classes/ADMapCluster.h +++ b/Pod/Classes/ADMapCluster.h @@ -46,11 +46,11 @@ typedef void(^KdtreeCompletionBlock)(ADMapCluster *mapCluster); @property (readonly) NSSet *clustersWithAnnotations; -@property (strong, nonatomic) NSString *treeID; +@property (strong, nonatomic) NSString *groupID; - (id)initWithRootClusters:(NSArray *)clusters; -- (ADMapCluster *)rootClusterForID:(NSString *)treeID; +- (ADMapCluster *)rootClusterForID:(NSString *)groupID; - (BOOL)overlapsClusterOnMap:(ADMapCluster *)cluster annotationViewMapRectSize:(MKMapRect)annotationViewRect; @@ -58,22 +58,22 @@ typedef void(^KdtreeCompletionBlock)(ADMapCluster *mapCluster); * @discussion Creates a KD-tree of clusters http://en.wikipedia.org/wiki/K-d_tree * @param annotations Set of ADMapPointAnnotation objects * @param mapView The ADClusterMapView that will send the delegate callback - * @param treeID The key associated with the group of annotations + * @param groupID The key associated with the group of annotations * @param completion A new ADMapCluster object. */ -+ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView treeID:(NSString *)treeID completion:(KdtreeCompletionBlock)completion ; ++ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView groupID:(NSString *)groupID completion:(KdtreeCompletionBlock)completion ; /*! * @discussion Creates a KD-tree of clusters http://en.wikipedia.org/wiki/K-d_tree * @param annotations Set of ADMapPointAnnotation objects - * @param treeID The key associated with the group of annotations + * @param groupID The key associated with the group of annotations * @param gamma Descrimination power * @param clusterTitle Title of cluster * @param showSubtitle A Boolean to show subtitle from titles of children * @param completion A new ADMapCluster object. */ -+ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations treeID:(NSString *)treeID centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion ; ++ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations groupID:(NSString *)groupID centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion ; /*! * @discussion Adds a single map point annotation to an existing KD-tree map cluster root diff --git a/Pod/Classes/ADMapCluster.m b/Pod/Classes/ADMapCluster.m index b014926..41113d8 100755 --- a/Pod/Classes/ADMapCluster.m +++ b/Pod/Classes/ADMapCluster.m @@ -32,11 +32,11 @@ @implementation ADMapCluster #pragma mark - Init -+ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView treeID:(NSString *)treeID completion:(KdtreeCompletionBlock)completion { ++ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView groupID:(NSString *)groupID completion:(KdtreeCompletionBlock)completion { [mapView mapView:mapView willBeginBuildingClusterTreeForMapPoints:annotations]; - return [ADMapCluster rootClusterForAnnotations:annotations treeID:treeID centerWeight:mapView.clusterDiscrimination title:mapView.clusterTitle showSubtitle:mapView.clusterShouldShowSubtitle completion:^(ADMapCluster *mapCluster) { + return [ADMapCluster rootClusterForAnnotations:annotations groupID:groupID centerWeight:mapView.clusterDiscrimination title:mapView.clusterTitle showSubtitle:mapView.clusterShouldShowSubtitle completion:^(ADMapCluster *mapCluster) { [mapView mapView:mapView didFinishBuildingClusterTreeForMapPoints:annotations]; if (completion) { @@ -45,7 +45,7 @@ + (ADMapCluster *)rootClusterForAnnotations:(NSSet *)ann }]; } -+ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations treeID:(NSString *)treeID centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion { ++ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations groupID:(NSString *)groupID centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion { // KDTree //NSLog(@"Computing KD-tree for %lu annotations...", (unsigned long)annotations.count); @@ -69,7 +69,7 @@ + (ADMapCluster *)rootClusterForAnnotations:(NSSet *)ann } - ADMapCluster * cluster = [[ADMapCluster alloc] initWithAnnotations:annotations treeID:(NSString *)treeID atDepth:0 inMapRect:boundaries gamma:gamma clusterTitle:clusterTitle showSubtitle:showSubtitle parentCluster:nil rootCluster:nil]; + ADMapCluster * cluster = [[ADMapCluster alloc] initWithAnnotations:annotations groupID:(NSString *)groupID atDepth:0 inMapRect:boundaries gamma:gamma clusterTitle:clusterTitle showSubtitle:showSubtitle parentCluster:nil rootCluster:nil]; //NSLog(@"Computation done !"); @@ -79,7 +79,7 @@ + (ADMapCluster *)rootClusterForAnnotations:(NSSet *)ann return cluster; } -- (id)initWithAnnotations:(NSSet *)annotations treeID:(NSString *)treeID atDepth:(NSInteger)depth inMapRect:(MKMapRect)mapRect gamma:(double)gamma clusterTitle:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle parentCluster:(ADMapCluster *)parentCluster rootCluster:(ADMapCluster *)rootCluster { +- (id)initWithAnnotations:(NSSet *)annotations groupID:(NSString *)groupID atDepth:(NSInteger)depth inMapRect:(MKMapRect)mapRect gamma:(double)gamma clusterTitle:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle parentCluster:(ADMapCluster *)parentCluster rootCluster:(ADMapCluster *)rootCluster { self = [super init]; if (self) { _depth = depth; @@ -90,7 +90,7 @@ - (id)initWithAnnotations:(NSSet *)annotations treeID:( _parentCluster = parentCluster; _clusterCount = annotations.count; _progress = 0; - _treeID = treeID; + _groupID = groupID; if (depth == 0) { rootCluster = self; @@ -131,10 +131,10 @@ - (id)initWithAnnotations:(NSSet *)annotations treeID:( MKMapRect rightMapRect = [ADMapCluster boundariesForAnnotations:splitAnnotations[1]]; if (splitAnnotations[0]) { - _leftChild = [[ADMapCluster alloc] initWithAnnotations:splitAnnotations[0] treeID:(NSString *)treeID atDepth:depth+1 inMapRect:leftMapRect gamma:gamma clusterTitle:clusterTitle showSubtitle:showSubtitle parentCluster:self rootCluster:rootCluster]; + _leftChild = [[ADMapCluster alloc] initWithAnnotations:splitAnnotations[0] groupID:(NSString *)groupID atDepth:depth+1 inMapRect:leftMapRect gamma:gamma clusterTitle:clusterTitle showSubtitle:showSubtitle parentCluster:self rootCluster:rootCluster]; } - _rightChild = [[ADMapCluster alloc] initWithAnnotations:splitAnnotations[1] treeID:(NSString *)treeID atDepth:depth+1 inMapRect:rightMapRect gamma:gamma clusterTitle:clusterTitle showSubtitle:showSubtitle parentCluster:self rootCluster:rootCluster]; + _rightChild = [[ADMapCluster alloc] initWithAnnotations:splitAnnotations[1] groupID:(NSString *)groupID atDepth:depth+1 inMapRect:rightMapRect gamma:gamma clusterTitle:clusterTitle showSubtitle:showSubtitle parentCluster:self rootCluster:rootCluster]; } } return self; @@ -146,7 +146,7 @@ - (id)initWithAnnotations:(NSSet *)annotations treeID:( - (id)initWithRootClusters:(NSArray *)clusters { self = [super init]; if (self) { - self.treeID = kTSClusterMapViewRootMultiClusterID; + self.groupID = kTSClusterMapViewRootMultiClusterID; _clusterCount = 0; @@ -233,16 +233,16 @@ - (id)initWithRootClusters:(NSArray *)clusters { return self; } -- (ADMapCluster *)rootClusterForID:(NSString *)treeID { +- (ADMapCluster *)rootClusterForID:(NSString *)groupID { for (ADMapCluster *cluster in self.children) { - if ([cluster.treeID isEqualToString:treeID]) { + if ([cluster.groupID isEqualToString:groupID]) { return cluster; } - if ([cluster.treeID isEqualToString:kTSClusterMapViewRootMultiClusterID]) { - ADMapCluster *foundCluster = [cluster rootClusterForID:treeID]; + if ([cluster.groupID isEqualToString:kTSClusterMapViewRootMultiClusterID]) { + ADMapCluster *foundCluster = [cluster rootClusterForID:groupID]; if (foundCluster) { return foundCluster; } @@ -413,7 +413,7 @@ - (void)mapView:(TSClusterMapView *)mapView addAnnotation:(ADMapPointAnnotation closestCluster.clusterCount = 0; - [ADMapCluster rootClusterForAnnotations:annotationsToRecalculate mapView:mapView treeID:self.treeID completion:^(ADMapCluster *mapCluster) { + [ADMapCluster rootClusterForAnnotations:annotationsToRecalculate mapView:mapView groupID:self.groupID completion:^(ADMapCluster *mapCluster) { if (closestCluster.parentCluster.rightChild == closestCluster) { closestCluster.parentCluster.rightChild = mapCluster; } @@ -448,7 +448,7 @@ - (void)mapView:(TSClusterMapView *)mapView removeAnnotation:(id)a clusterParent.clusterCount = 0; - [ADMapCluster rootClusterForAnnotations:annotationsToRecalculate mapView:mapView treeID:self.treeID completion:^(ADMapCluster *mapCluster) { + [ADMapCluster rootClusterForAnnotations:annotationsToRecalculate mapView:mapView groupID:self.groupID completion:^(ADMapCluster *mapCluster) { if (clusterParent.parentCluster.rightChild == clusterParent) { clusterParent.parentCluster.rightChild = mapCluster; diff --git a/Pod/Classes/TSClusterMapView.h b/Pod/Classes/TSClusterMapView.h index 98bf84a..a8fbbe3 100755 --- a/Pod/Classes/TSClusterMapView.h +++ b/Pod/Classes/TSClusterMapView.h @@ -102,10 +102,10 @@ typedef NS_ENUM(NSInteger, ADClusterBufferSize) { @interface TSClusterMapView : MKMapView -- (void)addClusteredAnnotation:(id)annotation toTree:(NSString *)treeID; -- (void)addClusteredAnnotations:(NSArray > *)annotations toTree:(NSString *)treeID; -- (void)removeAnnotations:(NSArray > *)annotations fromTree:(NSString *)treeID; -- (void)removeAnnotation:(id)annotation fromTree:(NSString *)treeID; +- (void)addClusteredAnnotation:(id)annotation toGroup:(NSString *)groupID; +- (void)addClusteredAnnotations:(NSArray > *)annotations toGroup:(NSString *)groupID; +- (void)removeAnnotations:(NSArray > *)annotations fromGroup:(NSString *)groupID; +- (void)removeAnnotation:(id)annotation fromGroup:(NSString *)groupID; /*! * @discussion Adds an annotation to the map and clusters if needed (threadsafe). Only rebuilds entire cluster tree if there are less than 1000 clustered annotations or the annotation coordinate is an outlier from current clustered data set. diff --git a/Pod/Classes/TSClusterMapView.m b/Pod/Classes/TSClusterMapView.m index 277d6b7..e44c59b 100755 --- a/Pod/Classes/TSClusterMapView.m +++ b/Pod/Classes/TSClusterMapView.m @@ -22,7 +22,7 @@ @interface TSClusterMapView () -@property (strong, nonatomic) NSMutableDictionary >*> *annotationsByTreeID; +@property (strong, nonatomic) NSMutableDictionary >*> *annotationsBygroupID; @end @@ -52,7 +52,7 @@ - (void)initHelpers { [self setDefaults]; - _annotationsByTreeID = [[NSMutableDictionary alloc] init]; + _annotationsBygroupID = [[NSMutableDictionary alloc] init]; _clusterAnnotationsPool = [[NSMutableSet alloc] init]; @@ -148,7 +148,7 @@ - (NSUInteger)numberOfClusters { } - (void)needsRefresh { - [self createKDTreesAndCluster:_annotationsByTreeID]; + [self createKDTreesAndCluster:_annotationsBygroupID]; } #pragma mark - Add/Remove Annotations @@ -157,7 +157,7 @@ - (void)needsRefresh { NSMutableSet *mutableSet = [[NSMutableSet alloc] init]; - for (NSMutableSet *set in self.annotationsByTreeID.allValues) { + for (NSMutableSet *set in self.annotationsBygroupID.allValues) { [mutableSet unionSet:set]; } @@ -182,24 +182,24 @@ - (void)addAnnotations:(NSArray > *)annotations { #pragma mark - Multi Tree -- (void)addClusteredAnnotation:(id)annotation toTree:(NSString *)treeID { +- (void)addClusteredAnnotation:(id)annotation toGroup:(NSString *)groupID { BOOL refresh = NO; - NSMutableSet *annotationsForTree = self.annotationsByTreeID[treeID]; + NSMutableSet *annotationsForTree = self.annotationsBygroupID[groupID]; if (annotationsForTree.count < DATA_REFRESH_MAX) { refresh = YES; } - [self addClusteredAnnotation:annotation toTree:(NSString *)treeID clusterTreeRefresh:refresh]; + [self addClusteredAnnotation:annotation toGroup:(NSString *)groupID clusterTreeRefresh:refresh]; } -- (void)addClusteredAnnotation:annotation toTree:(NSString *)treeID clusterTreeRefresh:(BOOL)refresh { +- (void)addClusteredAnnotation:annotation toGroup:(NSString *)groupID clusterTreeRefresh:(BOOL)refresh { - NSMutableSet *annotationsForTree = self.annotationsByTreeID[treeID]; + NSMutableSet *annotationsForTree = self.annotationsBygroupID[groupID]; if (!annotation || [annotationsForTree containsObject:annotation]) { return; @@ -210,10 +210,10 @@ - (void)addClusteredAnnotation:annotation toTree:(NSString *)treeID clusterTreeR } else { annotationsForTree = [[NSMutableSet alloc] initWithObjects:annotation, nil]; - self.annotationsByTreeID[treeID] = annotationsForTree; + self.annotationsBygroupID[groupID] = annotationsForTree; } - ADMapCluster *rootForID = [_rootMapCluster rootClusterForID:treeID]; + ADMapCluster *rootForID = [_rootMapCluster rootClusterForID:groupID]; if (!rootForID || refresh || _treeOperationQueue.operationCount > 10) { [self needsRefresh]; @@ -237,20 +237,20 @@ - (void)addClusteredAnnotation:annotation toTree:(NSString *)treeID clusterTreeR }]; } -- (void)addClusteredAnnotations:(NSArray > *)annotations toTree:(NSString *)treeID { +- (void)addClusteredAnnotations:(NSArray > *)annotations toGroup:(NSString *)groupID { if (!annotations || !annotations.count) { return; } - NSMutableSet *annotationsForTree = self.annotationsByTreeID[treeID]; + NSMutableSet *annotationsForTree = self.annotationsBygroupID[groupID]; NSMutableSet *addSet = [NSMutableSet setWithArray:annotations]; NSInteger preCount = annotationsForTree.count; if (!annotationsForTree) { annotationsForTree = addSet; - self.annotationsByTreeID[treeID] = annotationsForTree; + self.annotationsBygroupID[groupID] = annotationsForTree; } else { [annotationsForTree unionSet:addSet]; @@ -261,14 +261,14 @@ - (void)addClusteredAnnotations:(NSArray > *)annotations toTree } } -- (void)removeAnnotation:(id)annotation fromTree:(NSString *)treeID { +- (void)removeAnnotation:(id)annotation fromGroup:(NSString *)groupID { if (!annotation) { return; } - NSMutableSet *annotationsForTree = self.annotationsByTreeID[treeID]; + NSMutableSet *annotationsForTree = self.annotationsBygroupID[groupID]; if ([annotationsForTree containsObject:annotation]) { [annotationsForTree removeObject:annotation]; @@ -279,7 +279,7 @@ - (void)removeAnnotation:(id)annotation fromTree:(NSString *)treeI } else { - ADMapCluster *rootForID = [_rootMapCluster rootClusterForID:treeID]; + ADMapCluster *rootForID = [_rootMapCluster rootClusterForID:groupID]; __weak TSClusterMapView *weakSelf = self; [_treeOperationQueue addOperationWithBlock:^{ @@ -301,13 +301,13 @@ - (void)removeAnnotation:(id)annotation fromTree:(NSString *)treeI [super removeAnnotation:annotation]; } -- (void)removeAnnotations:(NSArray > *)annotations fromTree:(NSString *)treeID { +- (void)removeAnnotations:(NSArray > *)annotations fromGroup:(NSString *)groupID { if (!annotations) { return; } - NSMutableSet *annotationsForTree = self.annotationsByTreeID[treeID]; + NSMutableSet *annotationsForTree = self.annotationsBygroupID[groupID]; if (!annotationsForTree) { return; @@ -328,27 +328,27 @@ - (void)removeAnnotations:(NSArray > *)annotations fromTree:(NS - (void)addClusteredAnnotation:(id)annotation { - [self addClusteredAnnotation:annotation toTree:kTSClusterMapViewRootClusterID]; + [self addClusteredAnnotation:annotation toGroup:kTSClusterMapViewRootClusterID]; } - (void)addClusteredAnnotation:(id)annotation clusterTreeRefresh:(BOOL)refresh { - [self addClusteredAnnotation:annotation toTree:kTSClusterMapViewRootClusterID clusterTreeRefresh:refresh]; + [self addClusteredAnnotation:annotation toGroup:kTSClusterMapViewRootClusterID clusterTreeRefresh:refresh]; } - (void)addClusteredAnnotations:(NSArray > *)annotations { - [self addClusteredAnnotations:annotations toTree:kTSClusterMapViewRootClusterID]; + [self addClusteredAnnotations:annotations toGroup:kTSClusterMapViewRootClusterID]; } - (void)removeAnnotation:(id)annotation { - [self removeAnnotation:annotation fromTree:kTSClusterMapViewRootClusterID]; + [self removeAnnotation:annotation fromGroup:kTSClusterMapViewRootClusterID]; } - (void)removeAnnotations:(NSArray > *)annotations { - [self removeAnnotations:annotations fromTree:kTSClusterMapViewRootClusterID]; + [self removeAnnotations:annotations fromGroup:kTSClusterMapViewRootClusterID]; } #pragma mark - Annotations @@ -540,8 +540,8 @@ - (void)createKDTreesAndCluster:(NSMutableDictionary *) for (ADClusterAnnotation *annotation in annotations) { for (ADClusterAnnotation *compareAnnotation in annotations) { - if (compareAnnotation == annotation || [compareAnnotation.cluster.treeID isEqualToString:annotation.cluster.treeID]) { + if (compareAnnotation == annotation || [compareAnnotation.cluster.groupID isEqualToString:annotation.cluster.groupID]) { continue; } From 6752dd0aa02fbe76327d8e369d7da86b1d3e5c05 Mon Sep 17 00:00:00 2001 From: Adam Share Date: Tue, 27 Oct 2015 10:03:28 -0700 Subject: [PATCH 07/12] cluster param --- .../ClusterDemo/ViewControllers/CDMapViewController.m | 2 +- Pod/Classes/TSClusterMapView.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m b/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m index dd57701..14b98e4 100644 --- a/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m +++ b/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m @@ -78,7 +78,7 @@ - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation { +- (MKAnnotationView *)mapView:(TSClusterMapView *)mapView viewForClusterAnnotation:(ADClusterAnnotation *)annotation { TSDemoClusteredAnnotationView * view = (TSDemoClusteredAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:NSStringFromClass([TSDemoClusteredAnnotationView class])]; if (!view) { diff --git a/Pod/Classes/TSClusterMapView.h b/Pod/Classes/TSClusterMapView.h index a8fbbe3..107538d 100755 --- a/Pod/Classes/TSClusterMapView.h +++ b/Pod/Classes/TSClusterMapView.h @@ -26,7 +26,7 @@ extern NSString * const KDTreeClusteringProgress; * @param annotation The object representing the annotation that is about to be displayed. * @return The annotation view to display for the specified annotation or nil if you want to display a standard annotation view. */ -- (MKAnnotationView *)mapView:(TSClusterMapView *)mapView viewForClusterAnnotation:(id )annotation; +- (MKAnnotationView *)mapView:(TSClusterMapView *)mapView viewForClusterAnnotation:(ADClusterAnnotation *)annotation; /*! * @discussion MapView will begin creating Kd-tree from new annotations. Use this delegate to alert the user of a refresh for large data sets with long build times. From b12d34818b092348425a7d5323ae71689a884c51 Mon Sep 17 00:00:00 2001 From: Adam Share Date: Thu, 29 Oct 2015 16:15:03 -0700 Subject: [PATCH 08/12] optimize --- .../ViewControllers/CDMapViewController.m | 9 +- Pod/Classes/ADClusterAnnotation.h | 2 +- Pod/Classes/ADClusterAnnotation.m | 8 +- Pod/Classes/ADMapCluster.h | 8 +- Pod/Classes/ADMapCluster.m | 83 ++++++++++++------- Pod/Classes/TSClusterMapView.m | 78 ++++++++++++++--- 6 files changed, 137 insertions(+), 51 deletions(-) diff --git a/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m b/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m index 14b98e4..e0f0c54 100644 --- a/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m +++ b/Example/TSClusterMapView/ClusterDemo/ViewControllers/CDMapViewController.m @@ -152,6 +152,11 @@ - (BOOL)mapView:(TSClusterMapView *)mapView shouldRepositionAnnotations:(NSArray - (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item { + [self resetsStepperValues]; +} + +- (void)resetsStepperValues { + if (_tabBar.selectedItem == _bathroomTabBarItem) { _stepper.value = _bathroomAnnotationsAdded.count; _stepper.minimumValue = 0; @@ -174,17 +179,17 @@ - (IBAction)addAll:(id)sender { [_mapView addClusteredAnnotations:_bathroomAnnotations toGroup:CDToiletJsonFile]; _bathroomAnnotationsAdded = [NSMutableArray arrayWithArray:_bathroomAnnotations]; - _stepper.value = _bathroomAnnotationsAdded.count; } else if (_tabBar.selectedItem == _streetLightsTabBarItem) { NSLog(@"Adding All %@", CDStreetLightJsonFile); [_mapView addClusteredAnnotations:_streetLightAnnotations toGroup:CDStreetLightJsonFile]; _streetLightAnnotationsAdded = [NSMutableArray arrayWithArray:_streetLightAnnotations]; - _stepper.value = _streetLightAnnotationsAdded.count; } [self refreshBadges]; + + [self resetsStepperValues]; } - (IBAction)removeAll:(id)sender { diff --git a/Pod/Classes/ADClusterAnnotation.h b/Pod/Classes/ADClusterAnnotation.h index 3a86208..76d3762 100755 --- a/Pod/Classes/ADClusterAnnotation.h +++ b/Pod/Classes/ADClusterAnnotation.h @@ -53,7 +53,7 @@ typedef NS_ENUM(NSUInteger, ADClusterAnnotationType) { /*! * @discussion This array contains the MKAnnotation objects represented by this annotation */ -@property (weak, nonatomic, readonly) NSArray > * originalAnnotations; +@property (weak, nonatomic, readonly) NSSet > * originalAnnotations; /*! * @discussion Number of annotations represented by the annotation diff --git a/Pod/Classes/ADClusterAnnotation.m b/Pod/Classes/ADClusterAnnotation.m index 097925e..c77bf19 100755 --- a/Pod/Classes/ADClusterAnnotation.m +++ b/Pod/Classes/ADClusterAnnotation.m @@ -62,12 +62,12 @@ - (void)shouldReset { - (CLLocationCoordinate2D)offscreenCoordinate { - CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(MAXFLOAT, MAXFLOAT); + CLLocationCoordinate2D coordinate;// = CLLocationCoordinate2DMake(MAXFLOAT, MAXFLOAT); - if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) { +// if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) { coordinate = CLLocationCoordinate2DMake(85.0, 179.0); // this coordinate puts the annotation on the top right corner of the map. We use this instead of kCLLocationCoordinate2DInvalid so that we don't mess with MapKit's KVO weird behaviour that removes from the map the annotations whose coordinate was set to kCLLocationCoordinate2DInvalid. - } +// } return coordinate; } @@ -77,7 +77,7 @@ - (BOOL)offscreen { return (self.coordinate.latitude == offscreen.latitude && self.coordinate.longitude == offscreen.longitude); } -- (NSArray > *)originalAnnotations { +- (NSSet > *)originalAnnotations { NSAssert(self.cluster != nil, @"This annotation should have a cluster assigned!"); return self.cluster.originalAnnotations; } diff --git a/Pod/Classes/ADMapCluster.h b/Pod/Classes/ADMapCluster.h index 5031557..58b5198 100755 --- a/Pod/Classes/ADMapCluster.h +++ b/Pod/Classes/ADMapCluster.h @@ -26,9 +26,9 @@ typedef void(^KdtreeCompletionBlock)(ADMapCluster *mapCluster); @property (nonatomic, readonly) NSInteger depth; -@property (readonly) NSMutableArray > *originalAnnotations; +@property (readonly) NSSet > *originalAnnotations; -@property (readonly) NSMutableArray *originalMapPointAnnotations; +@property (readonly, strong) NSSet *originalMapPointAnnotations; @property (readonly) NSString *title; @@ -48,10 +48,14 @@ typedef void(^KdtreeCompletionBlock)(ADMapCluster *mapCluster); @property (strong, nonatomic) NSString *groupID; +@property (strong, nonatomic) NSArray *rootClusters; + - (id)initWithRootClusters:(NSArray *)clusters; - (ADMapCluster *)rootClusterForID:(NSString *)groupID; +- (instancetype)rebuildWithAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView completion:(KdtreeCompletionBlock)completion; + - (BOOL)overlapsClusterOnMap:(ADMapCluster *)cluster annotationViewMapRectSize:(MKMapRect)annotationViewRect; /*! diff --git a/Pod/Classes/ADMapCluster.m b/Pod/Classes/ADMapCluster.m index 41113d8..cca37b4 100755 --- a/Pod/Classes/ADMapCluster.m +++ b/Pod/Classes/ADMapCluster.m @@ -32,7 +32,25 @@ @implementation ADMapCluster #pragma mark - Init -+ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView groupID:(NSString *)groupID completion:(KdtreeCompletionBlock)completion { +- (instancetype)rebuildWithAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView completion:(KdtreeCompletionBlock)completion { + + return [ADMapCluster rootClusterForAnnotations:annotations mapView:mapView groupID:self.groupID completion:^(ADMapCluster *mapCluster) { + + mapCluster.parentCluster = self.parentCluster; + + if (self.parentCluster.leftChild == self) { + self.parentCluster.leftChild = mapCluster; + } + else { + self.parentCluster.rightChild = mapCluster; + } + if (completion) { + completion(mapCluster); + } + }]; +} + ++ (instancetype)rootClusterForAnnotations:(NSSet *)annotations mapView:(TSClusterMapView *)mapView groupID:(NSString *)groupID completion:(KdtreeCompletionBlock)completion { [mapView mapView:mapView willBeginBuildingClusterTreeForMapPoints:annotations]; @@ -45,7 +63,7 @@ + (ADMapCluster *)rootClusterForAnnotations:(NSSet *)ann }]; } -+ (ADMapCluster *)rootClusterForAnnotations:(NSSet *)annotations groupID:(NSString *)groupID centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion { ++ (instancetype)rootClusterForAnnotations:(NSSet *)annotations groupID:(NSString *)groupID centerWeight:(double)gamma title:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle completion:(KdtreeCompletionBlock)completion { // KDTree //NSLog(@"Computing KD-tree for %lu annotations...", (unsigned long)annotations.count); @@ -82,6 +100,7 @@ + (ADMapCluster *)rootClusterForAnnotations:(NSSet *)ann - (id)initWithAnnotations:(NSSet *)annotations groupID:(NSString *)groupID atDepth:(NSInteger)depth inMapRect:(MKMapRect)mapRect gamma:(double)gamma clusterTitle:(NSString *)clusterTitle showSubtitle:(BOOL)showSubtitle parentCluster:(ADMapCluster *)parentCluster rootCluster:(ADMapCluster *)rootCluster { self = [super init]; if (self) { + _originalMapPointAnnotations = [annotations copy]; _depth = depth; _mapRect = mapRect; _clusterTitle = clusterTitle; @@ -147,6 +166,7 @@ - (id)initWithRootClusters:(NSArray *)clusters { self = [super init]; if (self) { self.groupID = kTSClusterMapViewRootMultiClusterID; + self.rootClusters = clusters; _clusterCount = 0; @@ -165,7 +185,6 @@ - (id)initWithRootClusters:(NSArray *)clusters { _clusterTitle = clusters.firstObject.title; _showSubtitle = clusters.firstObject.showSubtitle; _gamma = clusters.firstObject.gamma; - _parentCluster = nil; _progress = 0; self.annotation = nil; @@ -174,6 +193,7 @@ - (id)initWithRootClusters:(NSArray *)clusters { NSMutableSet *annotations = [[NSMutableSet alloc] initWithCapacity:clusters.count]; for (ADMapCluster *cluster in clusters) { + cluster.parentCluster = self; ADClusterAnnotation *tempAnnotation = [[ADClusterAnnotation alloc] init]; tempAnnotation.coordinate = cluster.clusterCoordinate; ADMapPointAnnotation *mapPoint = [[ADMapPointAnnotation alloc] initWithAnnotation:tempAnnotation]; @@ -235,6 +255,10 @@ - (id)initWithRootClusters:(NSArray *)clusters { - (ADMapCluster *)rootClusterForID:(NSString *)groupID { + if ([self.groupID isEqualToString:groupID]) { + return self; + } + for (ADMapCluster *cluster in self.children) { if ([cluster.groupID isEqualToString:groupID]) { @@ -408,7 +432,7 @@ - (void)mapView:(TSClusterMapView *)mapView addAnnotation:(ADMapPointAnnotation } } - NSMutableSet *annotationsToRecalculate = [[NSMutableSet alloc] initWithArray:closestCluster.originalMapPointAnnotations]; + NSMutableSet *annotationsToRecalculate = [closestCluster.originalMapPointAnnotations mutableCopy];//[[NSMutableSet alloc] initWithArray:closestCluster.originalMapPointAnnotations]; [annotationsToRecalculate addObject:mapPointAnnotation]; closestCluster.clusterCount = 0; @@ -443,7 +467,7 @@ - (void)mapView:(TSClusterMapView *)mapView removeAnnotation:(id)a //Go up two cluster to ensure a more complete result ADMapCluster *clusterParent = clusterToRemove.parentCluster.parentCluster; - NSMutableSet *annotationsToRecalculate = [[NSMutableSet alloc] initWithArray:clusterParent.originalMapPointAnnotations]; + NSMutableSet *annotationsToRecalculate = [clusterParent.originalMapPointAnnotations mutableCopy]; [annotationsToRecalculate removeObject:clusterToRemove.annotation]; clusterParent.clusterCount = 0; @@ -495,18 +519,15 @@ - (NSString *)subtitle { return nil; } -- (NSMutableArray > *)originalAnnotations { - NSMutableArray * originalAnnotations = [[NSMutableArray alloc] initWithCapacity:1]; - if (self.annotation) { - [originalAnnotations addObject:self.annotation.annotation]; - } else { - if (_leftChild) { - [originalAnnotations addObjectsFromArray:_leftChild.originalAnnotations]; - } - if (_rightChild) { - [originalAnnotations addObjectsFromArray:_rightChild.originalAnnotations]; - } +- (NSSet > *)originalAnnotations { + + NSSet *originalMapPoints = self.originalMapPointAnnotations; + NSMutableSet * originalAnnotations = [[NSMutableSet alloc] initWithCapacity:originalMapPoints.count]; + + for (ADMapPointAnnotation *mapPointAnn in originalMapPoints) { + [originalAnnotations addObject:mapPointAnn.annotation]; } + return originalAnnotations; } @@ -540,21 +561,21 @@ - (NSString *)subtitle { } -- (NSMutableArray *)originalMapPointAnnotations { - NSMutableArray * originalAnnotations = [[NSMutableArray alloc] initWithCapacity:1]; - - if (self.annotation) { - [originalAnnotations addObject:self.annotation]; - } - - if (_leftChild) { - [originalAnnotations addObjectsFromArray:_leftChild.originalMapPointAnnotations]; - } - if (_rightChild) { - [originalAnnotations addObjectsFromArray:_rightChild.originalMapPointAnnotations]; - } - return originalAnnotations; -} +//- (NSSet *)originalMapPointAnnotations { +// NSMutableSet * originalAnnotations = [[NSMutableSet alloc] initWithCapacity:1]; +// +// if (self.annotation) { +// [originalAnnotations addObject:self.annotation]; +// } +// +// if (_leftChild) { +// [originalAnnotations unionSet:_leftChild.originalMapPointAnnotations]; +// } +// if (_rightChild) { +// [originalAnnotations unionSet:_rightChild.originalMapPointAnnotations]; +// } +// return originalAnnotations; +//} - (NSMutableSet *)clustersWithAnnotations { diff --git a/Pod/Classes/TSClusterMapView.m b/Pod/Classes/TSClusterMapView.m index e44c59b..516f6e3 100755 --- a/Pod/Classes/TSClusterMapView.m +++ b/Pod/Classes/TSClusterMapView.m @@ -148,7 +148,7 @@ - (NSUInteger)numberOfClusters { } - (void)needsRefresh { - [self createKDTreesAndCluster:_annotationsBygroupID]; + [self buildKDTreeAndCluster]; } #pragma mark - Add/Remove Annotations @@ -216,7 +216,7 @@ - (void)addClusteredAnnotation:annotation toGroup:(NSString *)groupID clusterTre ADMapCluster *rootForID = [_rootMapCluster rootClusterForID:groupID]; if (!rootForID || refresh || _treeOperationQueue.operationCount > 10) { - [self needsRefresh]; + [self buildKDTreeAndClusterWithGroupID:groupID]; return; } @@ -231,7 +231,7 @@ - (void)addClusteredAnnotation:annotation toGroup:(NSString *)groupID clusterTre [strongSelf clusterVisibleMapRectForceRefresh:YES]; } else { - [strongSelf needsRefresh]; + [strongSelf buildKDTreeAndClusterWithGroupID:groupID]; } }]; }]; @@ -257,7 +257,7 @@ - (void)addClusteredAnnotations:(NSArray > *)annotations toGrou } if (preCount != annotationsForTree.count) { - [self needsRefresh]; + [self buildKDTreeAndClusterWithGroupID:groupID]; } } @@ -275,7 +275,7 @@ - (void)removeAnnotation:(id)annotation fromGroup:(NSString *)grou //Small data set just rebuild if (annotationsForTree.count < DATA_REFRESH_MAX || _treeOperationQueue.operationCount > 10 || annotationsForTree.count == 0) { - [self needsRefresh]; + [self buildKDTreeAndClusterWithGroupID:groupID]; } else { @@ -291,7 +291,7 @@ - (void)removeAnnotation:(id)annotation fromGroup:(NSString *)grou [strongSelf clusterVisibleMapRectForceRefresh:YES]; } else { - [strongSelf needsRefresh]; + [strongSelf buildKDTreeAndClusterWithGroupID:groupID]; } }]; }]; @@ -318,7 +318,7 @@ - (void)removeAnnotations:(NSArray > *)annotations fromGroup:(N [annotationsForTree minusSet:set]; if (annotationsForTree.count != previousCount) { - [self needsRefresh]; + [self buildKDTreeAndClusterWithGroupID:groupID]; } [super removeAnnotations:annotations]; @@ -510,7 +510,9 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation { #pragma mark - Clustering -- (void)createKDTreesAndCluster:(NSMutableDictionary >*> *)annotationsForTrees { +- (void)buildKDTreeAndClusterWithGroupID:(NSString *)groupID { + + NSMutableDictionary >*> *annotationsForTrees = [_annotationsBygroupID copy]; if (!annotationsForTrees.allKeys.count) { return; @@ -518,6 +520,61 @@ - (void)createKDTreesAndCluster:(NSMutableDictionary annotation in annotations) { + ADMapPointAnnotation * mapPointAnnotation = [[ADMapPointAnnotation alloc] initWithAnnotation:annotation]; + [mapPointAnnotations addObject:mapPointAnnotation]; + } + + if (!clusterToReplace) { + + NSArray *allRootClusters = strongSelf.rootMapCluster.rootClusters; + + if (!self.rootMapCluster.originalMapPointAnnotations.count && !allRootClusters.count) { + [strongSelf buildKDTreeAndCluster]; + return; + } + + if (!allRootClusters.count) { + allRootClusters = @[self.rootMapCluster]; + } + + ADMapCluster *newCluster = [ADMapCluster rootClusterForAnnotations:mapPointAnnotations mapView:self groupID:groupID completion:nil]; + strongSelf.rootMapCluster = [[ADMapCluster alloc] initWithRootClusters:[allRootClusters arrayByAddingObject:newCluster]]; + [strongSelf clusterVisibleMapRectForceRefresh:YES]; + return; + } + + [clusterToReplace rebuildWithAnnotations:mapPointAnnotations mapView:strongSelf completion:^(ADMapCluster *mapCluster) { + + if ([strongSelf.rootMapCluster.groupID isEqualToString:groupID]) { + strongSelf.rootMapCluster = mapCluster; + } + + [strongSelf clusterVisibleMapRectForceRefresh:YES]; + }]; + }]; +} + +- (void)buildKDTreeAndCluster { + + if (!_annotationsBygroupID.allKeys.count) { + return; + } + + NSMutableDictionary >*> *annotationsForTrees = [_annotationsBygroupID copy]; + [_treeOperationQueue cancelAllOperations]; __weak TSClusterMapView *weakSelf = self; @@ -541,7 +598,6 @@ - (void)createKDTreesAndCluster:(NSMutableDictionary )annot // only leaf clusters have annotations if (((ADClusterAnnotation *)annotation).type == ADClusterAnnotationTypeLeaf) { - annotation = [((ADClusterAnnotation *)annotation).originalAnnotations firstObject]; + annotation = [((ADClusterAnnotation *)annotation).originalAnnotations anyObject]; if ([_clusterDelegate respondsToSelector:@selector(mapView:viewForAnnotation:)]) { delegateAnnotationView = [_clusterDelegate mapView:self viewForAnnotation:annotation]; } From 728a66f1dd1e9e387967ff990c98aba4dcf42b1e Mon Sep 17 00:00:00 2001 From: Adam Share Date: Thu, 10 Dec 2015 17:41:13 -0800 Subject: [PATCH 09/12] remove single grouped coordinate condition --- Pod/Classes/TSClusterMapView.m | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Pod/Classes/TSClusterMapView.m b/Pod/Classes/TSClusterMapView.m index 516f6e3..285401a 100755 --- a/Pod/Classes/TSClusterMapView.m +++ b/Pod/Classes/TSClusterMapView.m @@ -670,13 +670,9 @@ - (void)splitClusterToOriginal:(ADClusterAnnotation *)clusterAnnotation { return; } - NSDictionary *groupedRoundedLatLonAnnotations = [TSClusterOperation groupAnnotationsByLocationValue:clusterAnnotation.cluster.originalAnnotations]; - - if (groupedRoundedLatLonAnnotations.allKeys.count == 1) { - if ([_clusterDelegate respondsToSelector:@selector(mapView:shouldForceSplitClusterAnnotation:)]) { - if (![_clusterDelegate mapView:self shouldForceSplitClusterAnnotation:clusterAnnotation]) { - return; - } + if ([_clusterDelegate respondsToSelector:@selector(mapView:shouldForceSplitClusterAnnotation:)]) { + if (![_clusterDelegate mapView:self shouldForceSplitClusterAnnotation:clusterAnnotation]) { + return; } } From 5c603a6c85ab140962a1c0ad6d73121f13e300c1 Mon Sep 17 00:00:00 2001 From: Adam Share Date: Thu, 10 Dec 2015 19:21:38 -0800 Subject: [PATCH 10/12] remove single grouped coordinate condition --- Pod/Classes/TSClusterMapView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pod/Classes/TSClusterMapView.m b/Pod/Classes/TSClusterMapView.m index 285401a..6c5ae98 100755 --- a/Pod/Classes/TSClusterMapView.m +++ b/Pod/Classes/TSClusterMapView.m @@ -953,7 +953,7 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { MKCoordinateRegion region = MKCoordinateRegionForMapRect(zoomTo); - if (zoomTo.size.width < 3000) { + if (zoomTo.size.width < 3000 || zoomTo.size.height < 3000) { float ratio = self.camera.altitude/self.visibleMapRect.size.width; From d0856334f88fa87fbd1cc4405fdf6798a70ef9b5 Mon Sep 17 00:00:00 2001 From: Adam Share Date: Tue, 22 Mar 2016 21:00:54 -0700 Subject: [PATCH 11/12] refactoring --- .../TSDemoClusteredAnnotationView.m | 2 + Pod/Classes/ADClusterAnnotation.h | 2 +- Pod/Classes/ADClusterAnnotation.m | 14 +- Pod/Classes/ADMapCluster.m | 10 +- Pod/Classes/TSClusterAnnotationView.m | 3 + Pod/Classes/TSClusterMapView.m | 6 +- Pod/Classes/TSClusterOperation.m | 254 +++++++++++------- Pod/Classes/TSRefreshedAnnotationView.m | 4 + 8 files changed, 183 insertions(+), 112 deletions(-) diff --git a/Example/TSClusterMapView/ClusterDemo/AnnotationViews/TSDemoClusteredAnnotationView.m b/Example/TSClusterMapView/ClusterDemo/AnnotationViews/TSDemoClusteredAnnotationView.m index f91c77e..b6f35bc 100644 --- a/Example/TSClusterMapView/ClusterDemo/AnnotationViews/TSDemoClusteredAnnotationView.m +++ b/Example/TSClusterMapView/ClusterDemo/AnnotationViews/TSDemoClusteredAnnotationView.m @@ -33,6 +33,7 @@ - (id)initWithAnnotation:(ADClusterAnnotation *)annotation reuseIdentifier:(NSSt self.label.textColor = UIColorFromRGB(0x6fc99d); } else { + NSLog(@"Error Grouping: %@, %@, %@", annotation, annotation.cluster, annotation.cluster.groupID); self.image = [UIImage imageNamed:@"ClusterAnnotation"]; self.label.textColor = UIColorFromRGB(0x009fd6); } @@ -69,6 +70,7 @@ - (void)clusteringAnimation { self.label.textColor = UIColorFromRGB(0x6fc99d); } else { + NSLog(@"Error Grouping: %@, %@, %@", clusterAnnotation, clusterAnnotation.cluster, clusterAnnotation.cluster.groupID); self.image = [UIImage imageNamed:@"ClusterAnnotation"]; self.label.textColor = UIColorFromRGB(0x009fd6); } diff --git a/Pod/Classes/ADClusterAnnotation.h b/Pod/Classes/ADClusterAnnotation.h index 76d3762..7b4d726 100755 --- a/Pod/Classes/ADClusterAnnotation.h +++ b/Pod/Classes/ADClusterAnnotation.h @@ -28,7 +28,7 @@ typedef NS_ENUM(NSUInteger, ADClusterAnnotationType) { @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) NSString *subtitle; -@property (readonly, nonatomic) BOOL offscreen; +@property (readonly, nonatomic) BOOL offMap; /*! * @discussion Type of annotation, cluster or single. diff --git a/Pod/Classes/ADClusterAnnotation.m b/Pod/Classes/ADClusterAnnotation.m index c77bf19..7129bf9 100755 --- a/Pod/Classes/ADClusterAnnotation.m +++ b/Pod/Classes/ADClusterAnnotation.m @@ -17,7 +17,7 @@ - (id)init { self = [super init]; if (self) { _cluster = nil; - self.coordinate = [self offscreenCoordinate]; + self.coordinate = [self offMapCoordinate]; _shouldBeRemovedAfterAnimation = NO; _title = @"Title"; } @@ -52,15 +52,15 @@ - (void)setCoordinate:(CLLocationCoordinate2D)coordinate { - (void)reset { self.cluster = nil; - self.coordinate = [self offscreenCoordinate]; + self.coordinate = [self offMapCoordinate]; } - (void)shouldReset { self.cluster = nil; - self.coordinatePreAnimation = [self offscreenCoordinate]; + self.coordinatePreAnimation = [self offMapCoordinate]; } -- (CLLocationCoordinate2D)offscreenCoordinate { +- (CLLocationCoordinate2D)offMapCoordinate { CLLocationCoordinate2D coordinate;// = CLLocationCoordinate2DMake(MAXFLOAT, MAXFLOAT); @@ -72,9 +72,9 @@ - (CLLocationCoordinate2D)offscreenCoordinate { return coordinate; } -- (BOOL)offscreen { - CLLocationCoordinate2D offscreen = [self offscreenCoordinate]; - return (self.coordinate.latitude == offscreen.latitude && self.coordinate.longitude == offscreen.longitude); +- (BOOL)offMap { + CLLocationCoordinate2D offMapCoordinate = [self offMapCoordinate]; + return (self.coordinate.latitude == offMapCoordinate.latitude && self.coordinate.longitude == offMapCoordinate.longitude); } - (NSSet > *)originalAnnotations { diff --git a/Pod/Classes/ADMapCluster.m b/Pod/Classes/ADMapCluster.m index cca37b4..02ddf3e 100755 --- a/Pod/Classes/ADMapCluster.m +++ b/Pod/Classes/ADMapCluster.m @@ -779,9 +779,17 @@ - (BOOL)overlapsClusterOnMap:(ADMapCluster *)cluster annotationViewMapRectSize:( #pragma mark Tree Relations - (BOOL)isAncestorOf:(ADMapCluster *)mapCluster { - return _depth < mapCluster.depth && (_leftChild == mapCluster || _rightChild == mapCluster || [_leftChild isAncestorOf:mapCluster] || [_rightChild isAncestorOf:mapCluster]); + + BOOL sameGroup = [self.groupID isEqualToString:kTSClusterMapViewRootMultiClusterID] || [mapCluster.groupID isEqualToString:self.groupID]; + + if (!sameGroup) { + return NO; + } + + return sameGroup && _depth < mapCluster.depth && (_leftChild == mapCluster || _rightChild == mapCluster || [_leftChild isAncestorOf:mapCluster] || [_rightChild isAncestorOf:mapCluster]); } + - (BOOL)isRootClusterForAnnotation:(id)annotation { return _annotation.annotation == annotation || [_leftChild isRootClusterForAnnotation:annotation] || [_rightChild isRootClusterForAnnotation:annotation]; } diff --git a/Pod/Classes/TSClusterAnnotationView.m b/Pod/Classes/TSClusterAnnotationView.m index d17cc79..a149761 100644 --- a/Pod/Classes/TSClusterAnnotationView.m +++ b/Pod/Classes/TSClusterAnnotationView.m @@ -77,6 +77,9 @@ - (void)setAnnotation:(id)annotation { - (void)animateView { + if ([NSOperationQueue mainQueue] != [NSOperationQueue currentQueue]) { + NSLog(@"NotMain"); + } if ([_addedView isKindOfClass:[TSRefreshedAnnotationView class]]) { [(TSRefreshedAnnotationView*)_addedView clusteringAnimation]; } diff --git a/Pod/Classes/TSClusterMapView.m b/Pod/Classes/TSClusterMapView.m index 6c5ae98..89f103e 100755 --- a/Pod/Classes/TSClusterMapView.m +++ b/Pod/Classes/TSClusterMapView.m @@ -363,7 +363,7 @@ - (void)refreshClusterAnnotation:(ADClusterAnnotation *)annotation { - (NSArray *)visibleClusterAnnotations { NSMutableArray * displayedAnnotations = [[NSMutableArray alloc] init]; for (ADClusterAnnotation * annotation in [_clusterAnnotationsPool copy]) { - if (!annotation.offscreen) { + if (!annotation.offMap) { [displayedAnnotations addObject:annotation]; } } @@ -658,6 +658,8 @@ - (void)initAnnotationPools:(NSUInteger)numberOfAnnotationsInPool { [super addAnnotations:toAdd]; }]; } + + NSLog(@"%i", _clusterAnnotationsPool.count); } - (BOOL)shouldNotAnimate { @@ -912,7 +914,7 @@ - (MKAnnotationView *)refreshAnnotationViewForAnnotation:(id)annot } //If dequeued it won't have an annotation set; - if (!delegateAnnotationView.annotation) { + if (delegateAnnotationView.annotation != annotation) { delegateAnnotationView.annotation = annotation; } diff --git a/Pod/Classes/TSClusterOperation.m b/Pod/Classes/TSClusterOperation.m index 4fdec0b..f6eb0db 100644 --- a/Pod/Classes/TSClusterOperation.m +++ b/Pod/Classes/TSClusterOperation.m @@ -18,6 +18,14 @@ #import "TSClusterMapView.h" #import "TSRefreshedAnnotationView.h" +@interface AwaitingMatch : NSObject +@property (nonatomic, strong) ADMapCluster *cluster; +@property (nonatomic, strong) ADClusterAnnotation *annotation; +@end + +@implementation AwaitingMatch +@end + @interface TSClusterOperation () @property (weak, nonatomic) TSClusterMapView *mapView; @@ -28,6 +36,12 @@ @interface TSClusterOperation () @property (nonatomic, strong) NSMutableSet *annotationPool; @property (nonatomic, strong) NSMutableSet *poolAnnotationRemoval; +@property (nonatomic, readonly) NSSet * unmatchedOffMapAnnotations; +@property (nonatomic, readonly) NSSet * unmatchedAnnotations; +@property (nonatomic, readonly) NSSet * matchedAnnotations; + +@property (nonatomic, strong) NSMutableSet *removeAfterAnimation; + @property (nonatomic, strong) ADMapCluster *splitCluster; @end @@ -92,16 +106,77 @@ - (instancetype)initWithMapView:(TSClusterMapView *)mapView splitCluster:(ADMapC return self; } +#pragma mark - Annotation Groups -#pragma mark - Full Cluster Operation +- (NSSet *)unmatchedOffMapAnnotations { + + NSMutableSet *offMapAnnotations = [[NSMutableSet alloc] initWithCapacity:_annotationPool.count]; + for (ADClusterAnnotation *annotation in _annotationPool) { + if (annotation.offMap && !annotation.cluster) { + [offMapAnnotations addObject:annotation]; + } + } + + return offMapAnnotations; +} -- (void)clusterInMapRect:(MKMapRect)clusteredMapRect { +- (NSSet *)unmatchedAnnotations { - if (!_rootMapCluster.clusterCount) { - [self resetAll]; - return; + NSMutableSet *unmatchedAnnotations = [[NSMutableSet alloc] initWithCapacity:_annotationPool.count]; + for (ADClusterAnnotation *annotation in _annotationPool) { + if (!annotation.cluster) { + [unmatchedAnnotations addObject:annotation]; + } + } + return unmatchedAnnotations; +} + +- (NSSet *)matchedAnnotations { + + NSMutableSet *matchedAnnotations = [[NSMutableSet alloc] initWithSet:_annotationPool]; + [matchedAnnotations minusSet:self.unmatchedAnnotations]; + + return matchedAnnotations; +} + +- (NSMutableSet *)matchChildren:(NSSet *)children annotation:(ADClusterAnnotation *)annotation { + + NSMutableSet *childrenToMatch = [children mutableCopy]; + NSMutableSet *stillNeedsMatch = [[NSMutableSet alloc] initWithCapacity:children.count]; + //Choose any child cluster that needs to be shown and assign it to the existing annotation so that it stays on the map and moves to the new location to represent the child cluster + ADMapCluster *cluster = [children anyObject]; + annotation.cluster = cluster; + annotation.coordinatePreAnimation = annotation.coordinate; + [childrenToMatch removeObject:cluster]; + + //There should be more than one child if it splits so we'll need to grab unused annotations. + //Clusterless offMap annotations will then start at the annotation on screen's point and split to the child coordinate. + + NSMutableSet *offMap = [self.unmatchedOffMapAnnotations mutableCopy]; + for (ADMapCluster *cluster in childrenToMatch) { + ADClusterAnnotation *clusterlessAnnotation = [offMap anyObject]; + if (clusterlessAnnotation) { + [offMap removeObject:clusterlessAnnotation]; + clusterlessAnnotation.cluster = cluster; + clusterlessAnnotation.coordinatePreAnimation = annotation.coordinate; + } + else { + //Ran out of annotations off screen we'll come back after more have been sorted and reassign one that is available + AwaitingMatch *unmatched = [[AwaitingMatch alloc] init]; + unmatched.cluster = cluster; + unmatched.annotation = annotation; + [stillNeedsMatch addObject:unmatched]; + } } + //Returns the children that couldn't be matched do to not enough available annotations at the time + return stillNeedsMatch; +} + +#pragma mark - Full Cluster Operation + +- (NSSet *)clustersToShowOnMap:(MKMapRect)clusteredMapRect { + NSUInteger maxNumberOfClusters = _numberOfClusters; MKMapRect annotationViewSize = [self mapRectAnnotationViewSize]; @@ -119,8 +194,22 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { clusteredMapRect = _mapView.visibleMapRect; } + return [_rootMapCluster find:maxNumberOfClusters childrenInMapRect:clusteredMapRect annotationViewSize:annotationViewSize allowOverlap:shouldOverlap]; +} + +- (void)clusterInMapRect:(MKMapRect)clusteredMapRect { + + //NSLog(@"1 %@", [NSOperationQueue currentQueue].name); + + if (!_rootMapCluster.clusterCount) { + [self resetAll]; + return; + } + //Clusters that need to be visible after the animation - NSSet *clustersToShowOnMap = [_rootMapCluster find:maxNumberOfClusters childrenInMapRect:clusteredMapRect annotationViewSize:annotationViewSize allowOverlap:shouldOverlap]; + NSSet *clustersToShowOnMap = [self clustersToShowOnMap:clusteredMapRect]; + + //NSLog(@"2 %@", [NSOperationQueue currentQueue].name); if (self.isCancelled) { if (_finishedBlock) { @@ -129,82 +218,33 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { return; } - //Sort out the current annotations to get an idea of what you're working with - NSMutableSet *offscreenAnnotations = [[NSMutableSet alloc] initWithCapacity:_annotationPool.count]; - for (ADClusterAnnotation *annotation in _annotationPool) { - if (annotation.offscreen) { - [offscreenAnnotations addObject:annotation]; - } - } - NSMutableSet *unmatchedAnnotations = [[NSMutableSet alloc] initWithCapacity:_annotationPool.count]; - for (ADClusterAnnotation *annotation in _annotationPool) { - if (!annotation.cluster) { - [unmatchedAnnotations addObject:annotation]; - } - } - - NSMutableSet *matchedAnnotations = [[NSMutableSet alloc] initWithSet:_annotationPool]; - [matchedAnnotations minusSet:unmatchedAnnotations]; - - - // + //Begin with all clusters to show on the map NSMutableSet *unMatchedClusters = [[NSMutableSet alloc] initWithSet:clustersToShowOnMap]; //There will be only one annotation after clustering in so we want to know if the parent cluster was already matched to an annotation NSMutableSet *parentClustersMatched = [[NSMutableSet alloc] initWithCapacity:_numberOfClusters]; + //These will be the annotations that converge to a point and will no longer be needed - NSMutableSet *removeAfterAnimation = [[NSMutableSet alloc] initWithCapacity:_numberOfClusters]; + _removeAfterAnimation = [[NSMutableSet alloc] initWithCapacity:_numberOfClusters]; //These will be leftovers that didn't have any annotations available to match at the time. //Some annotations should become free after further sorting and matching. //At the end any unmatched annotations will be used. - NSMutableSet *stillNeedsMatch = [[NSMutableSet alloc] initWithCapacity:10]; - - if (self.isCancelled) { - if (_finishedBlock) { - _finishedBlock(clusteredMapRect, NO, nil); - } - return; - } + NSMutableSet *stillNeedsMatch = [[NSMutableSet alloc] initWithCapacity:10]; //Go through annotations that already have clusters and try and match them to new clusters - for (ADClusterAnnotation *annotation in matchedAnnotations) { + for (ADClusterAnnotation *annotation in self.matchedAnnotations) { - NSMutableSet *children = [annotation.cluster findChildrenForClusterInSet:clustersToShowOnMap]; + NSSet *children = [annotation.cluster findChildrenForClusterInSet:clustersToShowOnMap]; //Found children //These will start at cluster and split to their respective cluster coordinates if (children.count) { - - ADMapCluster *cluster = [children anyObject]; - annotation.cluster = cluster; - annotation.coordinatePreAnimation = annotation.coordinate; - - [children removeObject:cluster]; - [unMatchedClusters removeObject:cluster]; - - //There should be more than one child if it splits so we'll need to grab unused annotations. - //Clusterless offscreen annotations will then start at the annotation on screen's point and split to the child coordinate. - for (ADMapCluster *cluster in children) { - ADClusterAnnotation *clusterlessAnnotation = [offscreenAnnotations anyObject]; - - if (clusterlessAnnotation) { - clusterlessAnnotation.cluster = cluster; - clusterlessAnnotation.coordinatePreAnimation = annotation.coordinate; - - [unmatchedAnnotations removeObject:clusterlessAnnotation]; - [offscreenAnnotations removeObject:clusterlessAnnotation]; - - [unMatchedClusters removeObject:cluster]; - } - else { - //Ran out of annotations off screen we'll come back after more have been sorted and reassign one that is available - [stillNeedsMatch addObject:@[cluster, annotation]]; - } - } - + NSSet *unmatchedChildren = [self matchChildren:children annotation:annotation]; + [stillNeedsMatch unionSet:unmatchedChildren]; + [unMatchedClusters minusSet:children]; continue; } @@ -220,7 +260,7 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { [unMatchedClusters removeObject:cluster]; if ([parentClustersMatched containsObject:cluster]) { - [removeAfterAnimation addObject:annotation]; + [_removeAfterAnimation addObject:annotation]; } [parentClustersMatched addObject:cluster]; @@ -231,10 +271,11 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { //No ancestor or child found //This will happen when the annotation is no longer in the visible map rect and //the section of the cluster tree does not include this annotation - [unmatchedAnnotations addObject:annotation]; [annotation shouldReset]; } + + //NSLog(@"3 %@", [NSOperationQueue currentQueue].name); //Find annotations for remaining unmatched clusters //If there are available nearby, set the available annotation to animate to cluster position and take over. //After a full tree refresh all annotations will be unmatched but coordinates still may match up or be close by. @@ -248,8 +289,8 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { //Don't want annotations flying across the map CLLocationDistance min = MKMetersBetweenMapPoints(eastMapPoint, westMapPoint)/2; - NSMutableSet *unmatchedOnScreen = [NSMutableSet setWithSet:unmatchedAnnotations]; - [unmatchedOnScreen minusSet:offscreenAnnotations]; + NSMutableSet *unmatchedOnScreen = [NSMutableSet setWithSet:self.unmatchedAnnotations]; + [unmatchedOnScreen minusSet:self.unmatchedOffMapAnnotations ]; for (ADClusterAnnotation *checkAnnotation in unmatchedOnScreen) { //Could be same @@ -272,46 +313,43 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { annotation.popInAnimation = NO; //already visible don't animate appearance } - else if (offscreenAnnotations.count) { - annotation = [offscreenAnnotations anyObject]; + else if (self.unmatchedOffMapAnnotations.count) { + annotation = [self.unmatchedOffMapAnnotations anyObject]; annotation.coordinatePreAnimation = cluster.clusterCoordinate; annotation.popInAnimation = YES; //Not visible animate appearance } else { - NSLog(@"Not enough annotations?!"); + //NSLog(@"Not enough annotations?!"); break; } annotation.cluster = cluster; - [unmatchedAnnotations removeObject:annotation]; - [offscreenAnnotations removeObject:annotation]; [unMatchedClusters removeObject:cluster]; } + //NSLog(@"4 %@", [NSOperationQueue currentQueue].name); //Still need unmatched for a split into multiple from cluster if (stillNeedsMatch.count) { - for (NSArray *array in stillNeedsMatch) { - ADClusterAnnotation *clusterlessAnnotation = [unmatchedAnnotations anyObject]; + for (AwaitingMatch *awaitingMatch in stillNeedsMatch) { + ADClusterAnnotation *clusterlessAnnotation = [self.unmatchedAnnotations anyObject]; if (clusterlessAnnotation) { - clusterlessAnnotation.cluster = array[0]; - clusterlessAnnotation.coordinatePreAnimation = ((ADClusterAnnotation *)array[1]).coordinate; - - [unmatchedAnnotations removeObject:clusterlessAnnotation]; - [offscreenAnnotations removeObject:clusterlessAnnotation]; - [unMatchedClusters removeObject:clusterlessAnnotation.cluster]; + clusterlessAnnotation.cluster = awaitingMatch.cluster; + clusterlessAnnotation.coordinatePreAnimation = awaitingMatch.annotation.coordinate; + } + else { + [unMatchedClusters addObject:awaitingMatch.cluster]; } } } - matchedAnnotations = [NSMutableSet setWithSet:_annotationPool]; - [matchedAnnotations minusSet:unmatchedAnnotations]; - if (unMatchedClusters.count) { - NSLog(@"Unmatched Clusters!?"); + //NSLog(@"Unmatched Clusters!?"); } + //NSLog(@"5 %@", [NSOperationQueue currentQueue].name); + for (ADClusterAnnotation * annotation in _annotationPool) { if (annotation.cluster) { annotation.coordinatePostAnimation = annotation.cluster.clusterCoordinate; @@ -319,15 +357,24 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { } //Create a circle around coordinate to display all single annotations that overlap - [self mutateCoordinatesOfClashingAnnotations:matchedAnnotations]; + [self mutateCoordinatesOfClashingAnnotations:self.matchedAnnotations]; + ADClusterAnnotation *annotationToSelect = [self annotationToSelect]; + //NSLog(@"6 %@", [NSOperationQueue currentQueue].name); + + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [self executeAnimationAndSelectAnnotation:annotationToSelect]; + }]; +} + +- (ADClusterAnnotation *)annotationToSelect { ADClusterAnnotation *selectedAnnotation = [_mapView.selectedAnnotations firstObject]; ADClusterAnnotation *annotationToSelect; if (selectedAnnotation && [selectedAnnotation isKindOfClass:[ADClusterAnnotation class]]) { - for (ADClusterAnnotation *annotation in matchedAnnotations) { + for (ADClusterAnnotation *annotation in self.matchedAnnotations) { if (annotation.cluster == selectedAnnotation.cluster || [annotation.cluster isAncestorOf:selectedAnnotation.cluster]) { annotationToSelect = annotation; break; @@ -335,7 +382,7 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { if ((annotation.type == ADClusterAnnotationTypeCluster && CLLocationCoordinate2DIsApproxEqual(annotation.coordinate, selectedAnnotation.coordinate, .000001)) || - ![removeAfterAnimation containsObject:annotation]) { + ![_removeAfterAnimation containsObject:annotation]) { annotationToSelect = annotation; } } @@ -351,21 +398,25 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { annotationToSelect = nil; } + return annotationToSelect; +} + +- (void)executeAnimationAndSelectAnnotation:(ADClusterAnnotation *)annotationToSelect { - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - - //Make sure they are in the offscreen position - for (ADClusterAnnotation *annotation in unmatchedAnnotations) { + NSArray *selectedAnnotations = _mapView.selectedAnnotations; + ADClusterAnnotation *selectedAnnotation = [selectedAnnotations firstObject]; + + //Make sure they are in the offMap position + for (ADClusterAnnotation *annotation in self.unmatchedAnnotations) { [annotation reset]; } //Make sure we close callout of cluster if needed - NSArray *selectedAnnotations = _mapView.selectedAnnotations; for (ADClusterAnnotation *annotation in selectedAnnotations) { if ([annotation isKindOfClass:[ADClusterAnnotation class]]) { if ((annotation.type == ADClusterAnnotationTypeCluster && !CLLocationCoordinate2DIsApproxEqual(annotation.coordinate, annotation.coordinatePreAnimation, .000001)) || - [removeAfterAnimation containsObject:annotation]) { + [_removeAfterAnimation containsObject:annotation]) { [_mapView deselectAnnotation:annotation animated:NO]; } } @@ -424,24 +475,23 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { } } - //Make sure selected if was previously offscreen + //Make sure selected if was previously offMap if (annotationToSelect) { [_mapView selectAnnotation:annotationToSelect animated:YES]; } //Need to be removed after clustering they are no longer needed - for (ADClusterAnnotation *annotation in removeAfterAnimation) { + for (ADClusterAnnotation *annotation in _removeAfterAnimation) { [annotation reset]; } //If the number of clusters wanted on screen was reduced we can adjust the annotation pool accordingly to speed things up - NSSet *toRemove = [self poolAnnotationsToRemove:_numberOfClusters freeAnnotations:[unmatchedAnnotations setByAddingObjectsFromSet:removeAfterAnimation]]; + NSSet *toRemove = [self poolAnnotationsToRemove:_numberOfClusters freeAnnotations:[self.unmatchedAnnotations setByAddingObjectsFromSet:_removeAfterAnimation]]; if (_finishedBlock) { - _finishedBlock(clusteredMapRect, YES, toRemove); + _finishedBlock(_clusteringRect, YES, toRemove); } }]; - }]; } - (void)resetAll { @@ -484,12 +534,12 @@ - (void)splitSingleCluster:(ADMapCluster *)cluster { } else { annotation = [unmatchedAnnotations anyObject]; + [unmatchedAnnotations removeObject:annotation]; } annotation.cluster = leafCluster; annotation.coordinatePreAnimation = cluster.clusterCoordinate; - [unmatchedAnnotations removeObject:annotation]; [matchedAnnotations addObject:annotation]; } @@ -520,7 +570,9 @@ - (void)splitSingleCluster:(ADMapCluster *)cluster { [UIView animateWithDuration:options.duration delay:0.0 usingSpringWithDamping:options.springDamping initialSpringVelocity:options.springVelocity options:options.viewAnimationOptions animations:^{ for (ADClusterAnnotation * annotation in matchedAnnotations) { annotation.coordinate = annotation.coordinatePostAnimation; - [annotation.annotationView animateView]; + if (annotation.cluster) { + [annotation.annotationView animateView]; + } if (annotation.popInAnimation && _mapView.clusterAppearanceAnimated) { annotation.annotationView.transform = CGAffineTransformIdentity; diff --git a/Pod/Classes/TSRefreshedAnnotationView.m b/Pod/Classes/TSRefreshedAnnotationView.m index f59cfc0..60a4ee1 100644 --- a/Pod/Classes/TSRefreshedAnnotationView.m +++ b/Pod/Classes/TSRefreshedAnnotationView.m @@ -25,6 +25,10 @@ - (id)initWithAnnotation:(id)annotation reuseIdentifier:(NSString - (void)clusteringAnimation { //Subclass and add your cluster view updates to be animated here + + if ([NSOperationQueue mainQueue] != [NSOperationQueue currentQueue]) { + NSLog(@"NotMain"); + } } From b44809aa9b631d9d90c81359cb1b522baf6490fb Mon Sep 17 00:00:00 2001 From: Adam Share Date: Wed, 23 Mar 2016 15:20:08 -0700 Subject: [PATCH 12/12] refactoring without optimization --- Pod/Classes/TSClusterOperation.m | 301 ++++++++++++++++--------------- 1 file changed, 160 insertions(+), 141 deletions(-) diff --git a/Pod/Classes/TSClusterOperation.m b/Pod/Classes/TSClusterOperation.m index f6eb0db..7a28516 100644 --- a/Pod/Classes/TSClusterOperation.m +++ b/Pod/Classes/TSClusterOperation.m @@ -39,6 +39,7 @@ @interface TSClusterOperation () @property (nonatomic, readonly) NSSet * unmatchedOffMapAnnotations; @property (nonatomic, readonly) NSSet * unmatchedAnnotations; @property (nonatomic, readonly) NSSet * matchedAnnotations; +@property (nonatomic, readonly) NSMutableSet * parentClustersMatched; @property (nonatomic, strong) NSMutableSet *removeAfterAnimation; @@ -106,7 +107,7 @@ - (instancetype)initWithMapView:(TSClusterMapView *)mapView splitCluster:(ADMapC return self; } -#pragma mark - Annotation Groups +#pragma mark - Annotation Groups - (NSSet *)unmatchedOffMapAnnotations { @@ -173,6 +174,81 @@ - (instancetype)initWithMapView:(TSClusterMapView *)mapView splitCluster:(ADMapC return stillNeedsMatch; } +- (ADMapCluster *)matchedClusterForAnnotation:(ADClusterAnnotation *)annotation inSet:(NSSet *)clustersToShowOnMap { + + ADMapCluster *cluster = [annotation.cluster findAncestorForClusterInSet:clustersToShowOnMap]; + + //Found an ancestor + //These will start as individual annotations and converge into a single annotation during animation + if (cluster) { + annotation.cluster = cluster; + annotation.coordinatePreAnimation = annotation.coordinate; + + if ([_parentClustersMatched containsObject:cluster]) { + [_removeAfterAnimation addObject:annotation]; + } + + [_parentClustersMatched addObject:cluster]; + return cluster; + } + + return cluster; +} + +/** + If there are available nearby, set the available annotation to animate to cluster position and take over. After a full tree refresh all annotations will be unmatched but coordinates still may match up or be close by. +*/ +- (void)matchLeftoverOnscreenAnnotations:(NSMutableSet *)unMatchedClusters { + + for (ADMapCluster *cluster in [unMatchedClusters copy]) { + ADClusterAnnotation *annotation; + + MKMapRect mRect = _mapView.visibleMapRect; + MKMapPoint eastMapPoint = MKMapPointMake(MKMapRectGetMinX(mRect), MKMapRectGetMidY(mRect)); + MKMapPoint westMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), MKMapRectGetMidY(mRect)); + //Don't want annotations flying across the map + CLLocationDistance min = MKMetersBetweenMapPoints(eastMapPoint, westMapPoint)/2; + + NSMutableSet *unmatchedOnScreen = [NSMutableSet setWithSet:self.unmatchedAnnotations]; + [unmatchedOnScreen minusSet:self.unmatchedOffMapAnnotations ]; + for (ADClusterAnnotation *checkAnnotation in unmatchedOnScreen) { + + //Could be same + if (CLLocationCoordinate2DIsApproxEqual(checkAnnotation.coordinate, cluster.clusterCoordinate, 0.00001)) { + annotation = checkAnnotation; + break; + } + + //Find closest + CLLocationDistance distance = MKMetersBetweenMapPoints(MKMapPointForCoordinate(checkAnnotation.coordinate), + MKMapPointForCoordinate(cluster.clusterCoordinate)); + if (distance < min) { + min = distance; + annotation = checkAnnotation; + } + } + + if (annotation) { + annotation.coordinatePreAnimation = annotation.coordinate; + annotation.popInAnimation = NO; + //already visible don't animate appearance + } + else if (self.unmatchedOffMapAnnotations.count) { + annotation = [self.unmatchedOffMapAnnotations anyObject]; + annotation.coordinatePreAnimation = cluster.clusterCoordinate; + annotation.popInAnimation = YES; + //Not visible animate appearance + } + else { + //NSLog(@"Not enough annotations?!"); + break; + } + + annotation.cluster = cluster; + [unMatchedClusters removeObject:cluster]; + } +} + #pragma mark - Full Cluster Operation - (NSSet *)clustersToShowOnMap:(MKMapRect)clusteredMapRect { @@ -223,7 +299,7 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { NSMutableSet *unMatchedClusters = [[NSMutableSet alloc] initWithSet:clustersToShowOnMap]; //There will be only one annotation after clustering in so we want to know if the parent cluster was already matched to an annotation - NSMutableSet *parentClustersMatched = [[NSMutableSet alloc] initWithCapacity:_numberOfClusters]; + _parentClustersMatched = [[NSMutableSet alloc] initWithCapacity:_numberOfClusters]; //These will be the annotations that converge to a point and will no longer be needed @@ -239,32 +315,24 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { NSSet *children = [annotation.cluster findChildrenForClusterInSet:clustersToShowOnMap]; - //Found children - //These will start at cluster and split to their respective cluster coordinates if (children.count) { + //Found children + //These will start at cluster and split to their respective cluster coordinates NSSet *unmatchedChildren = [self matchChildren:children annotation:annotation]; + + //Unmatched children will be paired later when more annotations become free [stillNeedsMatch unionSet:unmatchedChildren]; [unMatchedClusters minusSet:children]; continue; } - ADMapCluster *cluster = [annotation.cluster findAncestorForClusterInSet:clustersToShowOnMap]; + ADMapCluster *cluster = [self matchedClusterForAnnotation:annotation inSet:clustersToShowOnMap]; - //Found an ancestor - //These will start as individual annotations and converge into a single annotation during animation if (cluster) { - annotation.cluster = cluster; - annotation.coordinatePreAnimation = annotation.coordinate; - + //Found an ancestor + //These will start as individual annotations and converge into a single annotation during animation [unMatchedClusters removeObject:cluster]; - - if ([parentClustersMatched containsObject:cluster]) { - [_removeAfterAnimation addObject:annotation]; - } - - [parentClustersMatched addObject:cluster]; - continue; } @@ -277,56 +345,7 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { //NSLog(@"3 %@", [NSOperationQueue currentQueue].name); //Find annotations for remaining unmatched clusters - //If there are available nearby, set the available annotation to animate to cluster position and take over. - //After a full tree refresh all annotations will be unmatched but coordinates still may match up or be close by. - for (ADMapCluster *cluster in [unMatchedClusters copy]) { - - ADClusterAnnotation *annotation; - - MKMapRect mRect = _mapView.visibleMapRect; - MKMapPoint eastMapPoint = MKMapPointMake(MKMapRectGetMinX(mRect), MKMapRectGetMidY(mRect)); - MKMapPoint westMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), MKMapRectGetMidY(mRect)); - //Don't want annotations flying across the map - CLLocationDistance min = MKMetersBetweenMapPoints(eastMapPoint, westMapPoint)/2; - - NSMutableSet *unmatchedOnScreen = [NSMutableSet setWithSet:self.unmatchedAnnotations]; - [unmatchedOnScreen minusSet:self.unmatchedOffMapAnnotations ]; - for (ADClusterAnnotation *checkAnnotation in unmatchedOnScreen) { - - //Could be same - if (CLLocationCoordinate2DIsApproxEqual(checkAnnotation.coordinate, cluster.clusterCoordinate, 0.00001)) { - annotation = checkAnnotation; - break; - } - - //Find closest - CLLocationDistance distance = MKMetersBetweenMapPoints(MKMapPointForCoordinate(checkAnnotation.coordinate), - MKMapPointForCoordinate(cluster.clusterCoordinate)); - if (distance < min) { - min = distance; - annotation = checkAnnotation; - } - } - - if (annotation) { - annotation.coordinatePreAnimation = annotation.coordinate; - annotation.popInAnimation = NO; - //already visible don't animate appearance - } - else if (self.unmatchedOffMapAnnotations.count) { - annotation = [self.unmatchedOffMapAnnotations anyObject]; - annotation.coordinatePreAnimation = cluster.clusterCoordinate; - annotation.popInAnimation = YES; - //Not visible animate appearance - } - else { - //NSLog(@"Not enough annotations?!"); - break; - } - - annotation.cluster = cluster; - [unMatchedClusters removeObject:cluster]; - } + [self matchLeftoverOnscreenAnnotations:unMatchedClusters]; //NSLog(@"4 %@", [NSOperationQueue currentQueue].name); //Still need unmatched for a split into multiple from cluster @@ -345,7 +364,7 @@ - (void)clusterInMapRect:(MKMapRect)clusteredMapRect { } if (unMatchedClusters.count) { - //NSLog(@"Unmatched Clusters!?"); + NSLog(@"Unmatched Clusters!?"); } //NSLog(@"5 %@", [NSOperationQueue currentQueue].name); @@ -406,92 +425,92 @@ - (void)executeAnimationAndSelectAnnotation:(ADClusterAnnotation *)annotationToS NSArray *selectedAnnotations = _mapView.selectedAnnotations; ADClusterAnnotation *selectedAnnotation = [selectedAnnotations firstObject]; - //Make sure they are in the offMap position - for (ADClusterAnnotation *annotation in self.unmatchedAnnotations) { - [annotation reset]; - } - - //Make sure we close callout of cluster if needed - for (ADClusterAnnotation *annotation in selectedAnnotations) { - if ([annotation isKindOfClass:[ADClusterAnnotation class]]) { - if ((annotation.type == ADClusterAnnotationTypeCluster && - !CLLocationCoordinate2DIsApproxEqual(annotation.coordinate, annotation.coordinatePreAnimation, .000001)) || - [_removeAfterAnimation containsObject:annotation]) { - [_mapView deselectAnnotation:annotation animated:NO]; - } + //Make sure they are in the offMap position + for (ADClusterAnnotation *annotation in self.unmatchedAnnotations) { + [annotation reset]; + } + + //Make sure we close callout of cluster if needed + for (ADClusterAnnotation *annotation in selectedAnnotations) { + if ([annotation isKindOfClass:[ADClusterAnnotation class]]) { + if ((annotation.type == ADClusterAnnotationTypeCluster && + !CLLocationCoordinate2DIsApproxEqual(annotation.coordinate, annotation.coordinatePreAnimation, .000001)) || + [_removeAfterAnimation containsObject:annotation]) { + [_mapView deselectAnnotation:annotation animated:NO]; } } - - //Set pre animation position - for (ADClusterAnnotation *annotation in _annotationPool) { - if (CLLocationCoordinate2DIsValid(annotation.coordinatePreAnimation)) { - annotation.coordinate = annotation.coordinatePreAnimation; - } + } + + //Set pre animation position + for (ADClusterAnnotation *annotation in _annotationPool) { + if (CLLocationCoordinate2DIsValid(annotation.coordinatePreAnimation)) { + annotation.coordinate = annotation.coordinatePreAnimation; + } + } + + + for (ADClusterAnnotation * annotation in _annotationPool) { + //Get the new or cached view from delegate + if (annotation.cluster && annotation.needsRefresh) { + [_mapView refreshClusterAnnotation:annotation]; } - + //Pre animation setup for popInAnimation + if (annotation.popInAnimation && _mapView.clusterAppearanceAnimated) { + CGAffineTransform t = CGAffineTransformMakeScale(0.001, 0.001); + t = CGAffineTransformTranslate(t, 0, -annotation.annotationView.frame.size.height); + annotation.annotationView.transform = t; + } + } + + //Selected if needed + if (annotationToSelect) { + [_mapView selectAnnotation:annotationToSelect animated:YES]; + } + else if (selectedAnnotation) { + [_mapView deselectAnnotation:selectedAnnotation animated:NO]; + } + + TSClusterAnimationOptions *options = _mapView.clusterAnimationOptions; + [UIView animateWithDuration:options.duration delay:0.0 usingSpringWithDamping:options.springDamping initialSpringVelocity:options.springVelocity options:options.viewAnimationOptions animations:^{ for (ADClusterAnnotation * annotation in _annotationPool) { - //Get the new or cached view from delegate - if (annotation.cluster && annotation.needsRefresh) { - [_mapView refreshClusterAnnotation:annotation]; + if (annotation.cluster) { + annotation.coordinate = annotation.coordinatePostAnimation; + [annotation.annotationView animateView]; } - - //Pre animation setup for popInAnimation if (annotation.popInAnimation && _mapView.clusterAppearanceAnimated) { - CGAffineTransform t = CGAffineTransformMakeScale(0.001, 0.001); - t = CGAffineTransformTranslate(t, 0, -annotation.annotationView.frame.size.height); - annotation.annotationView.transform = t; + annotation.annotationView.transform = CGAffineTransformIdentity; + annotation.popInAnimation = NO; + } + } + } completion:^(BOOL finished) { + + for (ADClusterAnnotation * annotation in _annotationPool) { + if (annotation.cluster) { + annotation.coordinate = annotation.coordinatePostAnimation; + [annotation.annotationView animateView]; + annotation.annotationView.transform = CGAffineTransformIdentity; + annotation.popInAnimation = NO; } } - //Selected if needed + //Make sure selected if was previously offMap if (annotationToSelect) { [_mapView selectAnnotation:annotationToSelect animated:YES]; } - else if (selectedAnnotation) { - [_mapView deselectAnnotation:selectedAnnotation animated:NO]; + + //Need to be removed after clustering they are no longer needed + for (ADClusterAnnotation *annotation in _removeAfterAnimation) { + [annotation reset]; } - TSClusterAnimationOptions *options = _mapView.clusterAnimationOptions; - [UIView animateWithDuration:options.duration delay:0.0 usingSpringWithDamping:options.springDamping initialSpringVelocity:options.springVelocity options:options.viewAnimationOptions animations:^{ - for (ADClusterAnnotation * annotation in _annotationPool) { - if (annotation.cluster) { - annotation.coordinate = annotation.coordinatePostAnimation; - [annotation.annotationView animateView]; - } - if (annotation.popInAnimation && _mapView.clusterAppearanceAnimated) { - annotation.annotationView.transform = CGAffineTransformIdentity; - annotation.popInAnimation = NO; - } - } - } completion:^(BOOL finished) { - - for (ADClusterAnnotation * annotation in _annotationPool) { - if (annotation.cluster) { - annotation.coordinate = annotation.coordinatePostAnimation; - [annotation.annotationView animateView]; - annotation.annotationView.transform = CGAffineTransformIdentity; - annotation.popInAnimation = NO; - } - } - - //Make sure selected if was previously offMap - if (annotationToSelect) { - [_mapView selectAnnotation:annotationToSelect animated:YES]; - } - - //Need to be removed after clustering they are no longer needed - for (ADClusterAnnotation *annotation in _removeAfterAnimation) { - [annotation reset]; - } - - //If the number of clusters wanted on screen was reduced we can adjust the annotation pool accordingly to speed things up - NSSet *toRemove = [self poolAnnotationsToRemove:_numberOfClusters freeAnnotations:[self.unmatchedAnnotations setByAddingObjectsFromSet:_removeAfterAnimation]]; - - if (_finishedBlock) { - _finishedBlock(_clusteringRect, YES, toRemove); - } - }]; + //If the number of clusters wanted on screen was reduced we can adjust the annotation pool accordingly to speed things up + NSSet *toRemove = [self poolAnnotationsToRemove:_numberOfClusters freeAnnotations:[self.unmatchedAnnotations setByAddingObjectsFromSet:_removeAfterAnimation]]; + + if (_finishedBlock) { + _finishedBlock(_clusteringRect, YES, toRemove); + } + }]; } - (void)resetAll { @@ -616,7 +635,7 @@ - (MKMapRect)mapRectForRect:(CGRect)rect { //Get Hypotenuse then calculate xA*xA + xB*xB = xC*xC = distance CLLocationDistance distance = MKMetersBetweenMapPoints(MKMapPointForCoordinate(topLeft), MKMapPointForCoordinate(bottomRight)); double x = sqrt(distance*distance/(rect.size.width*rect.size.width + rect.size.height*rect.size.height)); - + CLLocationCoordinate2D translated = [self translateCoord:topLeft MetersLat:-x*rect.size.height MetersLong:x*rect.size.width]; MKMapPoint topLeftPoint = MKMapPointForCoordinate(topLeft); @@ -801,7 +820,7 @@ - (void)mutateCoordinatesOfClashingAnnotations:(NSSet *) return result; } - + + (NSDictionary > *>*)groupAnnotationsByLocationValue:(NSSet >*)annotations {