iPhone MKMapView标注群集化

18

我有很多标记需要放在我的地图上,所以我觉得将这些注释聚类会是一个不错的主意。我不太确定如何在 iPhone 上实现这一点,我能够使用 Google 地图和一些 JavaScript 示例解决了这个问题。但 iPhone 使用它的 mkmapview,我不知道如何在其中聚类注释。

你知道有什么好的想法或框架吗?谢谢。


3
@brian.clear:你查询的链接没有显示任何内容。 - Adil Soomro
1
@AdilSoomro 好的,它会显示你个人收藏的 Github 项目的搜索结果。在我的情况下,我很幸运地找到了 https://github.com/choefele/CCHMapClusterController 和 https://github.com/thoughtbot/TBAnnotationClustering。 - Klaas
9个回答

40

自iOS 4.2以来,MKMapView有一个名为- (NSSet *)annotationsInMapRect:(MKMapRect)mapRect的方法,您可以使用它进行聚类,因此您不一定需要使用第三方框架。

请查看WWDC11会话视频'使用MapKit在地理上可视化信息'。在视频的中间部分,它解释了如何做到这一点。但我将为您总结概念:

  • 使用两个地图(第二个地图从未添加到视图层次结构中
  • 第二个地图包含所有注释(同样,它从未绘制
  • 将地图区域划分为网格
  • 使用-annotationsInMapRect方法从不可见地图获取注释数据
  • 可见地图从不可见地图中的注释数据构建其注释

enter image description here


我在哪里可以找到这个WWDC视频中演示的代码示例? - Firdous
1
我已经实现了这个功能,它还不错...其他聚类算法,如四叉树或者OPTICS,可能会显示得更好,但是实现起来会更加复杂。 - Cameron Askew
演示代码可在 https://developer.apple.com/library/ios/samplecode/PhotoMap/Introduction/Intro.html 找到。 - theiOSDude
我从https://developer.apple.com/library/ios/samplecode/PhotoMap/Introduction/Intro.html下载了演示代码,但是我发现当我随机旋转和缩放地图时,它会卡住。请帮忙提供其他的演示代码。 - Vivek Shah
这是旋转问题的解决方案:http://stackoverflow.com/a/36388947/1672575 - Kaktusiarz

16

你知道怎么在运行时禁用它吗? - JBarros35

13

由于这是一个非常普遍的问题,并且我需要解决方案,所以我编写了一个自定义的MKMapView子类来支持聚类。然后我将其开源!你可以在这里获取它:https://github.com/yinkou/OCMapView

它管理标注的聚类,你可以自己处理它们的视图。 你只需要将 OCMapView文件夹复制到你的项目中,在nib文件中创建一个MKMapView并将其类设置为OCMapView即可。(或者像使用普通的MKMapView那样在代码中创建和委托它)


本回答提到的类已被提问者废弃。最后更新时间是一年前。 - Jasper
1
嗨,我是开发者。嗯,有点是吧。我目前正在进行 Swift 移植的工作。 :) - yinkou

8

通过使用苹果的演示代码,我们可以轻松地在代码中实现聚类概念。参考链接

简单来说,我们可以使用以下代码进行聚类:

实现聚类的步骤:

步骤1: 聚类需要使用两个地图视图(allAnnotationsMapView),其中一个是用于参考的(allAnnotationsMapView)。

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

viewDidLoad 方法中:
    _allAnnotationsMapView = [[MKMapView alloc] initWithFrame:CGRectZero];

步骤2 : 将所有注释添加到_allAnnotationsMapView中,下面的_photos是注释数组。

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

步骤3 : 添加以下用于聚类的方法,其中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;
    }
}

通过以上步骤,我们可以在地图视图上进行聚类,不需要使用任何第三方代码或框架。请在此处查看苹果示例代码。如果您对此有任何疑问,请告诉我。

2

2

6
这个项目让我非常失望。在对代码进行过多更改以支持它之前,请确保阅读所有未解决的GitHub问题。我没有这样做,但我希望我能够这样做... - Aurelien Porte

1
我最近从另一个答案中提到的ADClusterMapView分支出来,并解决了该项目所涉及的许多问题,如果不是全部。它是一个kd-tree算法,可以对聚类进行动画处理。
它在这里开源 https://github.com/ashare80/TSClusterMapView

1
虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。仅有链接的答案如果链接页面发生更改可能会变得无效。 - Kevin

0

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

这个框架不需要你改变当前的项目结构,可以直接用于你的MKMapView。有一个压缩文件。它提供了一个一次聚合200个标记的示例。在iPod上测试后,我发现它非常流畅。

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

该库支持以下功能:

  1. 对不同类别进行聚类
  2. 对所有类别进行聚类
  3. 设置自己的聚类半径等参数
  4. 隐藏或显示特定类别
  5. 单独处理和控制地图中的每个标记

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接