iPhone MKMapView 注解聚类

Posted

技术标签:

【中文标题】iPhone MKMapView 注解聚类【英文标题】:iPhone MKMapView Annotation Clustering 【发布时间】:2011-12-09 16:53:04 【问题描述】:

我有很多图钉要放在我的地图上,所以我认为将这些注释聚集在一起是个好主意。我不太确定如何在 iPhone 上实现这一点,我可以使用谷歌地图和一些 javascript 示例来解决问题。但是 iPhone 使用它的 mkmapview,我不知道如何在其中聚集注释。

您有什么好的想法或框架吗?谢谢。

【问题讨论】:

github.com/stars?direction=desc&sort=created&q=cluster @brian.clear: 你查询的链接什么也没显示。 @AdilSoomro 好吧,它会显示搜索结果到您个人关注的 github 项目。就我而言,我很幸运能找到 github.com/choefele/CCHMapClusterController 和 github.com/thoughtbot/TBAnnotationClustering 【参考方案1】:

您不一定需要使用 3rd 方框架,因为从 ios 4.2 开始,MKMapView 有一个名为 - (NSSet *)annotationsInMapRect:(MKMapRect)mapRect 的方法,您可以使用它来进行集群。

观看 WWDC11 会议视频“Visualizing Information Geographically with MapKit”。大约一半的时候解释了如何去做。但我会为你总结一下这个概念:

使用两个地图(第二个地图永远不会添加到视图层次结构中) 第二张地图包含所有注释(同样,它从未绘制) 将地图区域划分为正方形网格 使用-annotationsInMapRect方法获取注解数据 隐形地图 可见地图根据这些来自不可见地图的数据构建其注释

【讨论】:

在哪里可以找到此 WWDC 视频中演示的代码 我实现了这个,没关系...其他聚类算法,如 quadtree 或 optics 可能显示效果更好,但实现起来会更复杂 演示代码可在developer.apple.com/library/ios/samplecode/PhotoMap/…获取 我从developer.apple.com/library/ios/samplecode/PhotoMap/… 下载了演示代码,但我发现当我随机旋转和缩放地图时,它卡住了。如果您有任何其他演示,请帮助我。 这里是旋转问题的解决方案:***.com/a/36388947/1672575【参考方案2】:

幸运的是,您不再需要 3rd 方框架。 iOS 11 具有原生集群支持。

你需要实现mapView:clusterAnnotationForMemberAnnotations:方法。

在 Apple 示例中获取更多详细信息:https://developer.apple.com/sample-code/wwdc/2017/MapKit-Sample.zip

【讨论】:

如何在运行时禁用它?你知道吗?【参考方案3】:

由于这是一个非常常见的问题,我需要一个解决方案,因此我编写了一个支持集群的 MKMapView 的自定义子类。然后我把它开源了!你可以在这里得到它:https://github.com/yinkou/OCMapView。

它管理注释的集群,您可以自己处理它们的视图。 您不需要做任何事情,只需将OCMapView 文件夹复制到您的项目中,在您的笔尖中创建一个MKMapView 并将其类设置为OCMapView。 (或者像普通的MKMapView一样在代码中创建和委托它)

【讨论】:

这个答案中提到的类已被OP放弃。最后一次更改是一年多前。 嗨,我是开发人员。嗯,它有点。我目前正在开发一个快速端口。 :)【参考方案4】:

通过使用 Apple 演示代码,可以很容易地在我们的代码中实现集群概念。 Reference link

我们可以简单地使用以下代码进行聚类

实现聚类的步骤

Step1 : 重要的是聚类我们使用两个mapviews(allAnnotationsMapView, ),一个用于参考(allAnnotationsMapView)。

@property (nonatomic, strong) MKMapView *allAnnotationsMapView;
@property (nonatomic, strong) IBOutlet MKMapView *mapView;

viewDidLoad

    _allAnnotationsMapView = [[MKMapView alloc] initWithFrame:CGRectZero];

第二步:将所有注解添加到_allAnnotationsMapView,在_photos下面是注解数组。

        [_allAnnotationsMapView addAnnotations:_photos];
        [self updateVisibleAnnotations];

Step3 : 添加以下方法进行聚类,在这个 PhotoAnnotation 中是自定义注解。 MapViewDelegate 方法

 - (void)mapView:(MKMapView *)aMapView regionDidChangeAnimated:(BOOL)animated 

    [self updateVisibleAnnotations];


- (void)mapView:(MKMapView *)aMapView didAddAnnotationViews:(NSArray *)views 

    for (MKAnnotationView *annotationView in views) 
        if (![annotationView.annotation isKindOfClass:[PhotoAnnotation class]]) 
            continue;
        

        PhotoAnnotation *annotation = (PhotoAnnotation *)annotationView.annotation;

        if (annotation.clusterAnnotation != nil) 
            // animate the annotation from it's old container's coordinate, to its actual coordinate
            CLLocationCoordinate2D actualCoordinate = annotation.coordinate;
            CLLocationCoordinate2D containerCoordinate = annotation.clusterAnnotation.coordinate;

            // since it's displayed on the map, it is no longer contained by another annotation,
            // (We couldn't reset this in -updateVisibleAnnotations because we needed the reference to it here
            // to get the containerCoordinate)
            annotation.clusterAnnotation = nil;

            annotation.coordinate = containerCoordinate;

            [UIView animateWithDuration:0.3 animations:^
                annotation.coordinate = actualCoordinate;
            ];
        
    

聚类处理方法

    - (id<MKAnnotation>)annotationInGrid:(MKMapRect)gridMapRect usingAnnotations:(NSSet *)annotations 

    // first, see if one of the annotations we were already showing is in this mapRect
    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];
    

    // otherwise, sort the annotations based on their distance from the center of the grid square,
    // then choose the one closest to the center to show
    MKMapPoint centerMapPoint = MKMapPointMake(MKMapRectGetMidX(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;
    ];

    PhotoAnnotation *photoAnn = sortedAnnotations[0];
    NSLog(@"lat long %f %f", photoAnn.coordinate.latitude, photoAnn.coordinate.longitude);

    return sortedAnnotations[0];


- (void)updateVisibleAnnotations 

    // This value to controls the number of off screen annotations are displayed.
    // A bigger number means more annotations, less chance of seeing annotation views pop in but decreased performance.
    // A smaller number means fewer annotations, more chance of seeing annotation views pop in but better performance.
    static float marginFactor = 2.0;

    // Adjust this roughly based on the dimensions of your annotations views.
    // Bigger numbers more aggressively coalesce annotations (fewer annotations displayed but better performance).
    // Numbers too small result in overlapping annotations views and too many annotations on screen.
    static float bucketSize = 60.0;

    // find all the annotations in the visible area + a wide margin to avoid popping annotation views in and out while panning the map.
    MKMapRect visibleMapRect = [self.mapView visibleMapRect];
    MKMapRect adjustedVisibleMapRect = MKMapRectInset(visibleMapRect, -marginFactor * visibleMapRect.size.width, -marginFactor * visibleMapRect.size.height);

    // determine how wide each bucket will be, as a MKMapRect square
    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);

    // condense annotations, with a padding of two squares, around the visibleMapRect
    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;

    // for each square in our grid, pick one annotation to show
    gridMapRect.origin.y = startY;
    while (MKMapRectGetMinY(gridMapRect) <= endY) 
        gridMapRect.origin.x = startX;

        while (MKMapRectGetMinX(gridMapRect) <= endX) 
            NSSet *allAnnotationsInBucket = [self.allAnnotationsMapView annotationsInMapRect:gridMapRect];
            NSSet *visibleAnnotationsInBucket = [self.mapView annotationsInMapRect:gridMapRect];

            // we only care about PhotoAnnotations
            NSMutableSet *filteredAnnotationsInBucket = [[allAnnotationsInBucket objectsPassingTest:^BOOL(id obj, BOOL *stop) 
                return ([obj isKindOfClass:[PhotoAnnotation class]]);
            ] mutableCopy];

            if (filteredAnnotationsInBucket.count > 0) 
                PhotoAnnotation *annotationForGrid = (PhotoAnnotation *)[self annotationInGrid:gridMapRect usingAnnotations:filteredAnnotationsInBucket];

                [filteredAnnotationsInBucket removeObject:annotationForGrid];

                // give the annotationForGrid a reference to all the annotations it will represent
                annotationForGrid.containedAnnotations = [filteredAnnotationsInBucket allObjects];

                [self.mapView addAnnotation:annotationForGrid];

                for (PhotoAnnotation *annotation in filteredAnnotationsInBucket) 
                    // give all the other annotations a reference to the one which is representing them
                    annotation.clusterAnnotation = annotationForGrid;
                    annotation.containedAnnotations = nil;

                    // remove annotations which we've decided to cluster
                    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;
    

通过以上步骤我们可以在mapview上实现聚类,不需要使用任何第三方代码或框架。请在此处查看Apple sample code。如果您对此有任何疑问,请告诉我。

【讨论】:

【参考方案5】:

你看过 ADClusterMapView 吗? https://github.com/applidium/ADClusterMapView

它正是这样做的。

【讨论】:

我对这个项目非常失望。在对代码进行太多更改以支持它之前,请务必阅读所有未解决的 GitHub 问题。我没有,但希望我有......【参考方案6】:

我只是想对引脚进行聚类,只是显示它的编号。下面一个 https://www.cocoacontrols.com/controls/qtree-objc 符合我的期望。

【讨论】:

【参考方案7】:

我最近分叉了另一个答案中提到的 ADClusterMapView,并解决了与该项目相关的许多(如果不是全部)问题。它是一种 kd-tree 算法,可对聚类进行动画处理。

这里是开源的https://github.com/ashare80/TSClusterMapView

【讨论】:

虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。【参考方案8】:

试试这个框架(XMapView.framework);它现在支持 iOS 8。

这个框架不需要你改变你当前的项目结构,它可以直接用于你的MKMapView。有一个 zip 文件。它为您提供了一次集群 200 个引脚的示例。我在 iPod 中测试后发现它非常流畅。

http://www.xuliu.info/xMapView.html

这个库支持:

    聚类不同的类别 聚类所有类别 设置自己的集群半径等 隐藏或显示某些类别 单独处理和控制地图中的每个引脚

【讨论】:

【参考方案9】:

这里有一个非常酷且维护良好的 Objective-C 和 Swift 库:https://github.com/bigfish24/ABFRealmMapView

由于它与Realm 的集成,它的聚类效果非常好,还可以处理大量的点。

【讨论】:

以上是关于iPhone MKMapView 注解聚类的主要内容,如果未能解决你的问题,请参考以下文章

带有静态注解的 MKMapView

iPhone - MKMapView 实例的问题

iPhone应用程序中的MKMapView内存泄漏

userLocation 上的中心 MKMapView(最初) - 仅适用于 iPhone 4?

在 MKMapView 中为不同的注解设置不同的动作

如何在iPhone中使用MKMapView导航应用程序?