标记群集与谷歌地图SDK for iOS? [英] Marker clustering with google maps SDK for iOS?

查看:102
本文介绍了标记群集与谷歌地图SDK for iOS?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在iOS应用中使用Google Maps SDK,并且需要将彼此非常接近的标记分组 - 基本上需要使用标记聚类,如附加的url中所示。我能够在Android地图SDK中获得此功能,但是我没有为iOS Google地图SDK找到任何库。



您能否为任何库建议任何库这个? 建议一种方法来实现这个自定义库?



图片来源

这个双映射解决方案的基本概念,请看这个 WWDC 2011视频< /(> 22'30)。地图套件代码直接从这个视频中提取,除了我在几个注释中描述的几件事情。 Google Map SDK解决方案只是一个改编版本。


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



第二个主要思想将可见地图(加上边距)转换为方块,并将特定方块中的每个注解合并为一个注解。



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


$ b

   - (void)loadView {
[super的loadView];
self.mapView = [[GMSMapView alloc] initWithFrame:self.view.frame];
self.mapView.delegate = self;
self.allAnnotationMapView = [[GMSMapView alloc] initWithFrame:self.view.frame]; //不能为零或者你会得到结果(我不太清楚为什么)
self.view = self.mapView;
UIPinchGestureRecognizer * pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(didZoom :)];
[pinchRecognizer setDelegate:self];
[self.mapView addGestureRecognizer:pinchRecognizer];

$ b - (void)didZoom:(UIGestureRecognizer *)gestureRecognizer {
if(gestureRecognizer.state == UIGestureRecognizerStateEnded){
[self updateVisibleAnnotations];


$ b - (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));

$ b - (NSSet *)annotationsInRect:(CGRect)rect forMapView:(GMSMapView *)mapView {
GMSProjection * projection = self.mapView.projection; //总是使用self.mapView,因为它是放大屏幕上唯一的一个
CLLocationCoordinate2D southWestCoordinates = [投影坐标点: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];
(mapView.markers中的GMSMarker *标记){
if(marker.position.latitude< southWestCoordinates.latitude || marker.position.latitude> = northEastCoordinates.latitude){
继续;
}
if(marker.position.longitude< southWestCoordinates.longitude || marker.position.longitude> = northEastCoordinates.longitude){
continue;
}
[annotations addObject:marker.userData];
}
返回注释; (GMSMarker *)viewForAnnotation:(PointMapItem *)item forMapView:(GMSMapView *)mapView {
(mapView.markers中的GMSMarker *标记){


$ b - $ b 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;
}
返回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;
}
返回isPointMapItem&& !shouldBeMerged;
}];
for(PointMapItem * item in notMergedAnnotationsInBucket){
[self addAnnotation:item inMapView:self.mapView animated:NO];

$ b $ 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 * 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动画:^ {
annotation.coordinate = annotation.clusterAnnotation.coordinate;
}完成:^(BOOL finished){
annotation.coordinate = actualCoordinate;
[self removeAnnotation:注解inMapView:self.mapView];
}];
}
}
}
gridMapRect.origin.x + = bucketSize;
}
gridMapRect.origin.y + = bucketSize;


$ b - (PointMapItem *)annotationInGrid:(CGRect)gridMapRect usingAnnotations:(NSSet *)annotations {
NSSet * visibleAnnotationsInBucket = [self annotationsInRect:gridMapRect forMapView:self.mapView];
NSSet * annotationsForGridSet = [注解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];
返回零;


$ b - (void)addAnnotation:(PointMapItem *)item inMapView:(GMSMapView *)mapView {
[self addAnnotation:item inMapView:mapView animated:是];

$ b - (void)addAnnotation:(PointMapItem *)item inMapView:(GMSMapView *)mapView animated:(BOOL)animated {
GMSMarker * marker = [[GMSMarker alloc ] 在里面];
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;

$ b - (void)addAnnotations:(NSArray *)itemsMapMap:(GMSMapView *)mapView {
[self addAnnotations:items inMapView:mapView animated:YES];

$ b - (void)addAnnotations:(NSArray *)itemsMapMap:(GMSMapView *)mapView animated:(BOOL)animated {
for(PointMapItem * item in items) {
[self addAnnotation:item inMapView:mapView];


$ b - (void)removeAnnotation:(PointMapItem *)item inMapView:(GMSMapView *)mapView {
//试着让它工作,因为它避免在每次我们只想删除一个标记时通过所有标记来循环...
//另外,您的associatedMarker属性应该很弱以避免内存循环,因为userData强烈支持该项目
// GMSMarker * marker = item.associatedMarker;
// marker.map = nil;
(mapView.markers中的GMSMarker *标记){
if(marker.userData == item){
marker.map = nil; (void)removeAnnotations:(NSArray *)inMapView中的项目:(GMSMapView *)mapView {
for(PointMapItem *)($)


- item in items){
[self removeAnnotation:item inMapView:mapView];


$ / code $ / pre
$ b $ p

几个注释:


  • PointMapItem 是我的注释数据类( id< MKAnnotation>
  • 这里我在 PointMapItem 上使用 shouldBeMerged / code>因为有一些我不想合并的注释。如果您不需要此操作,请删除正在使用它的零件或将 shouldBeMerged 设置为YES以显示所有注释。但是,如果您不想合并用户位置,您应该保留类测试。

  • 当您要添加注释时,将它们添加到隐藏的 allAnnotationMapView 并调用 updateVisibleAnnotation updateVisibleAnnotation 方法负责选择要合并哪些注释以及要显示哪些注释。然后它会将注释添加到 mapView 这是可见的。



对于映射我使用下面的代码:


$ b

   - (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;
}
返回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;
}
返回isPointMapItem&& !shouldBeMerged;
}];
for(PointMapItem * item in notMergedAnnotationsInBucket){
[self.mapView addAnnotation:item];

$ b $ if(filteredAnnotationsInBucket.count> 0){
PointMapItem * annotationForGrid =(PointMapItem *)[self annotationInGrid:gridMapRect usingAnnotations:filteredAnnotationsInBucket];
[filteredAnnotationsInBucket removeObject:annotationForGrid];
annotationForGrid.containedAnnotations = [filteredAnnotationsInBucket allObjects];
[self.mapView addAnnotation:annotationForGrid];
//强制重新加载图像,因为如果annotationForGrid已经存在于桶中,则不会完成!
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 * 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动画:^ {
annotation.coordinate = annotation.clusterAnnotation.coordinate;
}完成:^(BOOL finished){
annotation.coordinate = actualCoordinate;
[self.mapView removeAnnotation:annotation];
}];
}
}
}
gridMapRect.origin.x + = gridSize;
}
gridMapRect.origin.y + = gridSize;


$ b - (id< MKAnnotation>)annotationInGrid:(MKMapRect)gridMapRect usingAnnotations:(NSSet *)annotations {
NSSet * visibleAnnotationsInBucket = [self。 mapView annotationsInMapRect:gridMapRect];
NSSet * annotationsForGridSet = [注解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];

$ / code>

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

I am using Google Maps SDK in my iOS app, and I need to group markers which are very close to each other - basically need to use marker clustering like its shown in the attached url. I am able to get this functionality in the Android maps SDK, but I didn't find any library for the iOS Google Maps SDK.

Can you please suggest any library for this? Or suggest a way to implement a custom library for this?

(Source of this picture)

解决方案

To understand the underlying concept of this double map solution, please have a look at this WWDC 2011 video (from 22'30). The Map kit code is directly extracted from this video except a few things that I described in a few notes. The Google Map SDK solution is just an adaptation.

Main idea: a map is hidden and holds every single annotation, including the merged ones (allAnnotationMapView in my code). Another is visible and shows only the cluster's annotations or the annotation if it's single (mapView in my code).

Second main idea: I divide the visible map (plus a margin) into squares, and every annotation in a specific square are merged into one annotation.

The code I use for Google Maps SDK (Please note that I wrote this when markers property were available on GMSMapView class. It's not anymore but you can keep track of all the marker you add in your own array, and use this array instead of calling 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];
    }
}

A few notes:

  • PointMapItem is my annotation data class (id<MKAnnotation> if we were working with Map kit).
  • Here I use a shouldBeMerged property on PointMapItem because there are some annotations I don't want to merge. If you do not need this, remove the part that is using it or set shouldBeMerged to YES for all your annotations. Though, you should probably keep the class testing if you don't want to merge user location!
  • When you want to add annotations, add them to the hidden allAnnotationMapView and call updateVisibleAnnotation. updateVisibleAnnotation method is responsible for choosing which annotations to merge and which to show. It will then add the annotation to mapView which is visible.

For Map Kit I use the following code:

- (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];
}

Both should work fine, but if you have any question, feel free to ask!

这篇关于标记群集与谷歌地图SDK for iOS?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆