首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >用google对iOS进行标记聚类?

用google对iOS进行标记聚类?
EN

Stack Overflow用户
提问于 2013-11-24 14:20:58
回答 3查看 26K关注 0票数 40

我在我的iOS应用程序中使用Google,我需要对彼此非常接近的标记进行分组--基本上需要使用标记聚类,就像附在附件url中所显示的那样。我能够在Android中获得这个功能,但是我没有找到iOS Google的任何库。

建议一种实现自定义库的方法吗?

(这张图片的来源)

EN

回答 3

Stack Overflow用户

发布于 2013-11-28 17:04:06

要理解这个双映射解决方案的基本概念,请看一下这个WWDC 2011视频 ( 22'30)。Map代码是直接从这个视频中提取出来的,除了我在几个注释中描述的一些事情之外。Google解决方案只是一种适应。

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

第二个主要思想:我将可见地图(加上一个边距)划分为正方形,每个批注都合并到一个注释中。

我为Google使用的代码(请注意,当markers属性在GMSMapView类上可用时,我编写了这个代码。它不再是了,但是您可以跟踪添加到自己数组中的所有标记,并使用这个数组而不是调用mapView.markers):

代码语言:javascript
运行
复制
- (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>)。
  • 这里我在PointMapItem上使用了一个PointMapItem属性,因为有一些注释我不想合并。如果您不需要这样做,请删除正在使用它的部分,或者将所有注释的shouldBeMerged设置为YES。不过,如果不想合并用户位置,则应该保留类测试!
  • 当您想要添加注释时,将它们添加到隐藏的allAnnotationMapView中并调用updateVisibleAnnotationupdateVisibleAnnotation方法负责选择要合并的注释和要显示的注释。然后将注释添加到可见的mapView中。

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

代码语言:javascript
运行
复制
- (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];
}

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

票数 27
EN

Stack Overflow用户

发布于 2014-06-11 23:07:54

经过长时间的研究,我终于找到了一个很棒的人。

非常感谢DDRBoxman

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

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

当我想管理他的项目时,我遇到了一些问题。我刚刚删除了Google,并按照完整的Google教程集成了Google。然后,没有更多的问题,我能够运行应用程序。不要忘记将API密钥放在AppDelegate.m中。

接下来的几天,我将使用这个库,如果我发现了一些bug,我会通知您。

编辑#1:这些天我在集群上做了很多工作。我的最后一种方法是集成MKMapView,在MKMapView上创建集群(比在上创建集群要容易得多),并将Google集成到MKMapView项目中。与以前的方法相比,这种方法的性能更好。

编辑#2:我不知道您是使用领域还是计划使用它,但是它们为地图集群提供了一个非常好的解决方案:https://realm.io/news/building-an-ios-clustered-map-view-in-objective-c/

票数 20
EN

Stack Overflow用户

发布于 2014-05-29 11:11:14

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

  1. 循环数组中的所有标记(ns字典)
  2. 使用gmsmapview.projection获取CGPoint,以确定标记是否应该组合在一起

3我用100分进行测试,响应时间比较满意。

如果变焦电平差大于0.5,地图将重新绘制;

代码语言:javascript
运行
复制
  -(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


    }
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20175605

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档