标记群集与谷歌地图SDK的iOS?

我在iOS应用程序中使用Google Maps SDK,并且需要将彼此非常接近的标记进行分组 – 基本上需要使用标记集群,如附加的url中所示。 我能够在Android地图SDK中获得此function,但是我没有findiOS Google Maps SDK的任何库。

你能为此build议任何图书馆吗? build议一种方法来实现这个自定义库?

Marker_Clusterer_Full.png

(这张图片的来源 )

要了解这个双映射解决scheme的基本概念,请看这个WWDC 2011video (从22'30)。 地图套件代码直接从这个video中提取,除了我在几个笔记中描述的几件事情之外。 Google Map SDK解决scheme只是一个改编。

主要思想 :隐藏地图,并保存每一个注释,包括合并的(我的代码中的allAnnotationMapView )。 另一个是可见的,只显示集群的注释或注释,如果它是单一的(我的代码中的mapView)。

第二个主要思想 :将可见图(加上边距)分成正方形,每个特定正方形的注解合并为一个注释。

我使用Google Maps SDK的代码(请注意,我在GMSMapView类中使用markers属性的时候写了这个代码,现在已经不是了,但是你可以跟踪你添加到你自己数组中的所有标记,并且使用这个数组而不是调用mapView.markers):

 - (void)loadView { [super loadView]; self.mapView = [[GMSMapView alloc] initWithFrame:self.view.frame]; self.mapView.delegate = self; self.allAnnotationMapView = [[GMSMapView alloc] initWithFrame:self.view.frame]; // can't be zero or you'll have weard results (I don't remember exactly why) self.view = self.mapView; UIPinchGestureRecognizer* pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(didZoom:)]; [pinchRecognizer setDelegate:self]; [self.mapView addGestureRecognizer:pinchRecognizer]; } - (void)didZoom:(UIGestureRecognizer*)gestureRecognizer { if (gestureRecognizer.state == UIGestureRecognizerStateEnded){ [self updateVisibleAnnotations]; } } - (float)distanceFrom:(CGPoint)point1 to:(CGPoint)point2 { CGFloat xDist = (point2.x - point1.x); CGFloat yDist = (point2.y - point1.y); return sqrt((xDist * xDist) + (yDist * yDist)); } - (NSSet *)annotationsInRect:(CGRect)rect forMapView:(GMSMapView *)mapView { GMSProjection *projection = self.mapView.projection; //always take self.mapView because it is the only one zoomed on screen CLLocationCoordinate2D southWestCoordinates = [projection coordinateForPoint:CGPointMake(rect.origin.x, rect.origin.y + rect.size.height)]; CLLocationCoordinate2D northEastCoordinates = [projection coordinateForPoint:CGPointMake(rect.origin.x + rect.size.width, rect.origin.y)]; NSMutableSet *annotations = [NSMutableSet set]; for (GMSMarker *marker in mapView.markers) { if (marker.position.latitude < southWestCoordinates.latitude || marker.position.latitude >= northEastCoordinates.latitude) { continue; } if (marker.position.longitude < southWestCoordinates.longitude || marker.position.longitude >= northEastCoordinates.longitude) { continue; } [annotations addObject:marker.userData]; } return annotations; } - (GMSMarker *)viewForAnnotation:(PointMapItem *)item forMapView:(GMSMapView *)mapView{ for (GMSMarker *marker in mapView.markers) { if (marker.userData == item) { return marker; } } return nil; } - (void)updateVisibleAnnotations { static float marginFactor = 1.0f; static float bucketSize = 100.0f; CGRect visibleMapRect = self.view.frame; CGRect adjustedVisibleMapRect = CGRectInset(visibleMapRect, -marginFactor * visibleMapRect.size.width, -marginFactor * visibleMapRect.size.height); double startX = CGRectGetMinX(adjustedVisibleMapRect); double startY = CGRectGetMinY(adjustedVisibleMapRect); double endX = CGRectGetMaxX(adjustedVisibleMapRect); double endY = CGRectGetMaxY(adjustedVisibleMapRect); CGRect gridMapRect = CGRectMake(0, 0, bucketSize, bucketSize); gridMapRect.origin.y = startY; while(CGRectGetMinY(gridMapRect) <= endY) { gridMapRect.origin.x = startX; while (CGRectGetMinX(gridMapRect) <= endX) { NSSet *allAnnotationsInBucket = [self annotationsInRect:gridMapRect forMapView:self.allAnnotationMapView]; NSSet *visibleAnnotationsInBucket = [self annotationsInRect:gridMapRect forMapView:self.mapView]; NSMutableSet *filteredAnnotationsInBucket = [[allAnnotationsInBucket objectsPassingTest:^BOOL(id obj, BOOL *stop) { BOOL isPointMapItem = [obj isKindOfClass:[PointMapItem class]]; BOOL shouldBeMerged = NO; if (isPointMapItem) { PointMapItem *pointItem = (PointMapItem *)obj; shouldBeMerged = pointItem.shouldBeMerged; } return shouldBeMerged; }] mutableCopy]; NSSet *notMergedAnnotationsInBucket = [allAnnotationsInBucket objectsPassingTest:^BOOL(id obj, BOOL *stop) { BOOL isPointMapItem = [obj isKindOfClass:[PointMapItem class]]; BOOL shouldBeMerged = NO; if (isPointMapItem) { PointMapItem *pointItem = (PointMapItem *)obj; shouldBeMerged = pointItem.shouldBeMerged; } return isPointMapItem && !shouldBeMerged; }]; for (PointMapItem *item in notMergedAnnotationsInBucket) { [self addAnnotation:item inMapView:self.mapView animated:NO]; } if(filteredAnnotationsInBucket.count > 0) { PointMapItem *annotationForGrid = (PointMapItem *)[self annotationInGrid:gridMapRect usingAnnotations:filteredAnnotationsInBucket]; [filteredAnnotationsInBucket removeObject:annotationForGrid]; annotationForGrid.containedAnnotations = [filteredAnnotationsInBucket allObjects]; [self removeAnnotation:annotationForGrid inMapView:self.mapView]; [self addAnnotation:annotationForGrid inMapView:self.mapView animated:NO]; if (filteredAnnotationsInBucket.count > 0){ // [self.mapView deselectAnnotation:annotationForGrid animated:NO]; } for (PointMapItem *annotation in filteredAnnotationsInBucket) { // [self.mapView deselectAnnotation:annotation animated:NO]; annotation.clusterAnnotation = annotationForGrid; annotation.containedAnnotations = nil; if ([visibleAnnotationsInBucket containsObject:annotation]) { CLLocationCoordinate2D actualCoordinate = annotation.coordinate; [UIView animateWithDuration:0.3 animations:^{ annotation.coordinate = annotation.clusterAnnotation.coordinate; } completion:^(BOOL finished) { annotation.coordinate = actualCoordinate; [self removeAnnotation:annotation inMapView:self.mapView]; }]; } } } gridMapRect.origin.x += bucketSize; } gridMapRect.origin.y += bucketSize; } } - (PointMapItem *)annotationInGrid:(CGRect)gridMapRect usingAnnotations:(NSSet *)annotations { NSSet *visibleAnnotationsInBucket = [self annotationsInRect:gridMapRect forMapView:self.mapView]; NSSet *annotationsForGridSet = [annotations objectsPassingTest:^BOOL(id obj, BOOL *stop) { BOOL returnValue = ([visibleAnnotationsInBucket containsObject:obj]); if (returnValue) { *stop = YES; } return returnValue; }]; if (annotationsForGridSet.count != 0) { return [annotationsForGridSet anyObject]; } CGPoint centerMapPoint = CGPointMake(CGRectGetMidX(gridMapRect), CGRectGetMidY(gridMapRect)); NSArray *sortedAnnotations = [[annotations allObjects] sortedArrayUsingComparator:^(id obj1, id obj2) { CGPoint mapPoint1 = [self.mapView.projection pointForCoordinate:((PointMapItem *)obj1).coordinate]; CGPoint mapPoint2 = [self.mapView.projection pointForCoordinate:((PointMapItem *)obj2).coordinate]; CLLocationDistance distance1 = [self distanceFrom:mapPoint1 to:centerMapPoint]; CLLocationDistance distance2 = [self distanceFrom:mapPoint2 to:centerMapPoint]; if (distance1 < distance2) { return NSOrderedAscending; } else if (distance1 > distance2) { return NSOrderedDescending; } return NSOrderedSame; }]; return [sortedAnnotations objectAtIndex:0]; return nil; } - (void)addAnnotation:(PointMapItem *)item inMapView:(GMSMapView *)mapView { [self addAnnotation:item inMapView:mapView animated:YES]; } - (void)addAnnotation:(PointMapItem *)item inMapView:(GMSMapView *)mapView animated:(BOOL)animated { GMSMarker *marker = [[GMSMarker alloc] init]; GMSMarkerAnimation animation = kGMSMarkerAnimationNone; if (animated) { animation = kGMSMarkerAnimationPop; } marker.appearAnimation = animation; marker.title = item.title; marker.icon = [[AnnotationsViewUtils getInstance] imageForItem:item]; marker.position = item.coordinate; marker.map = mapView; marker.userData = item; // item.associatedMarker = marker; } - (void)addAnnotations:(NSArray *)items inMapView:(GMSMapView *)mapView { [self addAnnotations:items inMapView:mapView animated:YES]; } - (void)addAnnotations:(NSArray *)items inMapView:(GMSMapView *)mapView animated:(BOOL)animated { for (PointMapItem *item in items) { [self addAnnotation:item inMapView:mapView]; } } - (void)removeAnnotation:(PointMapItem *)item inMapView:(GMSMapView *)mapView { // Try to make that work because it avoid loopigng through all markers each time we just want to delete one... // Plus, your associatedMarker property should be weak to avoid memory cycle because userData hold strongly the item // GMSMarker *marker = item.associatedMarker; // marker.map = nil; for (GMSMarker *marker in mapView.markers) { if (marker.userData == item) { marker.map = nil; } } } - (void)removeAnnotations:(NSArray *)items inMapView:(GMSMapView *)mapView { for (PointMapItem *item in items) { [self removeAnnotation:item inMapView:mapView]; } } 

一些注意事项:

  • PointMapItem是我的注释数据类(如果我们正在使用Map工具包,则为id<MKAnnotation> )。
  • 这里我在shouldBeMerged上使用了一个shouldBeMerged属性,因为有一些我不想合并的注释。 如果您不需要,请删除正在使用它的部分,或将所有注释的shouldBeMerged设置为YES。 但是,如果你不想合并用户的位置,你应该保持课堂testing!
  • 当你想添加注释时,将它们添加到隐藏的allAnnotationMapView并调用updateVisibleAnnotationupdateVisibleAnnotation方法负责select要合并的注释和要显示的注释。 然后它会将注释添加到可见的mapView中。

对于Map Kit,我使用下面的代码:

 - (void)didZoom:(UIGestureRecognizer*)gestureRecognizer { if (gestureRecognizer.state == UIGestureRecognizerStateEnded){ [self updateVisibleAnnotations]; } } - (void)updateVisibleAnnotations { static float marginFactor = 2.0f; static float bucketSize = 50.0f; MKMapRect visibleMapRect = [self.mapView visibleMapRect]; MKMapRect adjustedVisibleMapRect = MKMapRectInset(visibleMapRect, -marginFactor * visibleMapRect.size.width, -marginFactor * visibleMapRect.size.height); CLLocationCoordinate2D leftCoordinate = [self.mapView convertPoint:CGPointZero toCoordinateFromView:self.view]; CLLocationCoordinate2D rightCoordinate = [self.mapView convertPoint:CGPointMake(bucketSize, 0) toCoordinateFromView:self.view]; double gridSize = MKMapPointForCoordinate(rightCoordinate).x - MKMapPointForCoordinate(leftCoordinate).x; MKMapRect gridMapRect = MKMapRectMake(0, 0, gridSize, gridSize); double startX = floor(MKMapRectGetMinX(adjustedVisibleMapRect) / gridSize) * gridSize; double startY = floor(MKMapRectGetMinY(adjustedVisibleMapRect) / gridSize) * gridSize; double endX = floor(MKMapRectGetMaxX(adjustedVisibleMapRect) / gridSize) * gridSize; double endY = floor(MKMapRectGetMaxY(adjustedVisibleMapRect) / gridSize) * gridSize; gridMapRect.origin.y = startY; while(MKMapRectGetMinY(gridMapRect) <= endY) { gridMapRect.origin.x = startX; while (MKMapRectGetMinX(gridMapRect) <= endX) { NSSet *allAnnotationsInBucket = [self.allAnnotationMapView annotationsInMapRect:gridMapRect]; NSSet *visibleAnnotationsInBucket = [self.mapView annotationsInMapRect:gridMapRect]; NSMutableSet *filteredAnnotationsInBucket = [[allAnnotationsInBucket objectsPassingTest:^BOOL(id obj, BOOL *stop) { BOOL isPointMapItem = [obj isKindOfClass:[PointMapItem class]]; BOOL shouldBeMerged = NO; if (isPointMapItem) { PointMapItem *pointItem = (PointMapItem *)obj; shouldBeMerged = pointItem.shouldBeMerged; } return shouldBeMerged; }] mutableCopy]; NSSet *notMergedAnnotationsInBucket = [allAnnotationsInBucket objectsPassingTest:^BOOL(id obj, BOOL *stop) { BOOL isPointMapItem = [obj isKindOfClass:[PointMapItem class]]; BOOL shouldBeMerged = NO; if (isPointMapItem) { PointMapItem *pointItem = (PointMapItem *)obj; shouldBeMerged = pointItem.shouldBeMerged; } return isPointMapItem && !shouldBeMerged; }]; for (PointMapItem *item in notMergedAnnotationsInBucket) { [self.mapView addAnnotation:item]; } if(filteredAnnotationsInBucket.count > 0) { PointMapItem *annotationForGrid = (PointMapItem *)[self annotationInGrid:gridMapRect usingAnnotations:filteredAnnotationsInBucket]; [filteredAnnotationsInBucket removeObject:annotationForGrid]; annotationForGrid.containedAnnotations = [filteredAnnotationsInBucket allObjects]; [self.mapView addAnnotation:annotationForGrid]; //force reload of the image because it's not done if annotationForGrid is already present in the bucket!! MKAnnotationView* annotationView = [self.mapView viewForAnnotation:annotationForGrid]; NSString *imageName = [AnnotationsViewUtils imageNameForItem:annotationForGrid selected:NO]; UILabel *countLabel = [[UILabel alloc] initWithFrame:CGRectMake(15, 2, 8, 8)]; [countLabel setFont:[UIFont fontWithName:POINT_FONT_NAME size:10]]; [countLabel setTextColor:[UIColor whiteColor]]; [annotationView addSubview:countLabel]; imageName = [AnnotationsViewUtils imageNameForItem:annotationForGrid selected:NO]; annotationView.image = [UIImage imageNamed:imageName]; if (filteredAnnotationsInBucket.count > 0){ [self.mapView deselectAnnotation:annotationForGrid animated:NO]; } for (PointMapItem *annotation in filteredAnnotationsInBucket) { [self.mapView deselectAnnotation:annotation animated:NO]; annotation.clusterAnnotation = annotationForGrid; annotation.containedAnnotations = nil; if ([visibleAnnotationsInBucket containsObject:annotation]) { CLLocationCoordinate2D actualCoordinate = annotation.coordinate; [UIView animateWithDuration:0.3 animations:^{ annotation.coordinate = annotation.clusterAnnotation.coordinate; } completion:^(BOOL finished) { annotation.coordinate = actualCoordinate; [self.mapView removeAnnotation:annotation]; }]; } } } gridMapRect.origin.x += gridSize; } gridMapRect.origin.y += gridSize; } } - (id<MKAnnotation>)annotationInGrid:(MKMapRect)gridMapRect usingAnnotations:(NSSet *)annotations { NSSet *visibleAnnotationsInBucket = [self.mapView annotationsInMapRect:gridMapRect]; NSSet *annotationsForGridSet = [annotations objectsPassingTest:^BOOL(id obj, BOOL *stop) { BOOL returnValue = ([visibleAnnotationsInBucket containsObject:obj]); if (returnValue) { *stop = YES; } return returnValue; }]; if (annotationsForGridSet.count != 0) { return [annotationsForGridSet anyObject]; } MKMapPoint centerMapPoint = MKMapPointMake(MKMapRectGetMinX(gridMapRect), MKMapRectGetMidY(gridMapRect)); NSArray *sortedAnnotations = [[annotations allObjects] sortedArrayUsingComparator:^(id obj1, id obj2) { MKMapPoint mapPoint1 = MKMapPointForCoordinate(((id<MKAnnotation>)obj1).coordinate); MKMapPoint mapPoint2 = MKMapPointForCoordinate(((id<MKAnnotation>)obj2).coordinate); CLLocationDistance distance1 = MKMetersBetweenMapPoints(mapPoint1, centerMapPoint); CLLocationDistance distance2 = MKMetersBetweenMapPoints(mapPoint2, centerMapPoint); if (distance1 < distance2) { return NSOrderedAscending; } else if (distance1 > distance2) { return NSOrderedDescending; } return NSOrderedSame; }]; return [sortedAnnotations objectAtIndex:0]; } 

两者都应该正常工作,但如果您有任何问题,随时问!

经过长时间的研究,我终于find了一个很棒的家伙。

非常感谢你DDRBoxman

检查他的github: https : //github.com/DDRBoxman/google-maps-ios-utils

他最近推了一些代码示例。

当我想要运行他的项目时,我遇到了一些问题。 我刚刚删除了Google地图SDK,并按照完整的Google教程来整合Google地图SDK。 然后,没有更多的问题,我能够运行应用程序。 不要忘了把你的API KEY放在AppDelegate.m中。

我将在随后的日子里使用这个库,如果我发现一些bug,我会告诉你。

编辑#1 :这些天我在群集上工作了很多。 我最后的方法是集成一个MKMapView,在MKMapView上创build集群(比在Google Maps SDK for iOS上更容易),并将Google Maps Places集成到我的iOS项目中。 这种方法的性能比以前的更好。

编辑#2 :我不知道你是否使用领域,或者如果你打算使用它,但他们提供了一个非常好的地图集群解决scheme: https : //realm.io/news/building-an-ios-clustered-map -view合目标c /

我有一个应用程序处理这个问题,下面是代码

  1. 循环所有标记(nsdictionary)在一个数组中

  2. 使用gmsmapview.projection来获得CGPoint,以找出标记是否应该组合在一起

3我用100分testing,反应时间相当满意。

4如果缩放级差超过0.5,则地图将重新绘制;

  -(float)distance :(CGPoint)pointA point:(CGPoint) pointB{ return sqrt( (pow((pointA.x - pointB.x),2) + pow((pointA.y-pointB.y),2))); } -(void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position{ float currentZoomLevel = mapView.camera.zoom; if (fabs(currentZoomLevel- lastZoomLevel_)>0.5){ lastZoomLevel_ = currentZoomLevel; markersGroupArray_ = [[NSMutableArray alloc] init]; for (NSDictionary *photo in photoArray_){ float coordx = [[photo objectForKey:@"coordx"]floatValue]; float coordy = [[photo objectForKey:@"coordy"] floatValue]; CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(coordx, coordy); CGPoint currentPoint = [mapView.projection pointForCoordinate:coord]; if ([markersGroupArray_ count] == 0){ NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:photo, nil]; [markersGroupArray_ addObject:array]; } else{ bool flag_groupadded = false; int counter= 0; for (NSMutableArray *array in markersGroupArray_){ for (NSDictionary *marker in array){ float mcoordx = [[marker objectForKey:@"coordx"]floatValue]; float mcoordy = [[marker objectForKey:@"coordy"]floatValue]; CLLocationCoordinate2D mcoord = CLLocationCoordinate2DMake(mcoordx, mcoordy); CGPoint mpt = [mapView.projection pointForCoordinate:mcoord]; if ([self distance:mpt point:currentPoint] <30){ flag_groupadded = YES; break; } } if (flag_groupadded){ break; } counter++; } if (flag_groupadded){ if ([markersGroupArray_ count]>counter){ NSMutableArray *groupArray = [markersGroupArray_ objectAtIndex:counter]; [groupArray insertObject:photo atIndex:0]; [markersGroupArray_ replaceObjectAtIndex:counter withObject:groupArray]; } } else if (!flag_groupadded){ NSMutableArray * array = [[NSMutableArray alloc]initWithObjects:photo, nil]; [markersGroupArray_ addObject:array]; } } } // for loop for photoArray // display group point [mapView clear]; photoMarkers_ = [[NSMutableArray alloc] init]; for (NSArray *array in markersGroupArray_){ NSLog(@"arry count %d",[array count]); NSDictionary *item = [array objectAtIndex:0]; float coordx = [[item objectForKey:@"coordx"]floatValue]; float coordy = [[item objectForKey:@"coordy"] floatValue]; CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(coordx, coordy); GMSMarker *marker = [[GMSMarker alloc] init]; marker.position = coord; marker.map = mapView; [photoMarkers_ addObject:marker]; marker = nil; } NSLog(@"markers %@",photoMarkers_); } // zoomlevel diffference thersold } 

现在通过Google Maps IOS Utils来解决这个问题。 https://developers.google.com/maps/documentation/ios-sdk/utility/marker-clustering