Кластеризация аннотаций iPhone MKMapView - PullRequest
17 голосов
/ 18 октября 2011

У меня достаточно много булавок, чтобы поставить на мою карту, поэтому я думаю, что было бы неплохо объединить эти аннотации. Я не совсем уверен, как добиться этого на iPhone, я смог кое-что проработать с Google Maps и некоторыми примерами JavaScript. Но iPhone использует его mkmapview, и я не знаю, как кластеризовать там аннотации.

Какие идеи или рамки, которые вы знаете и которые хороши? Спасибо.

Ответы [ 9 ]

38 голосов
/ 15 марта 2013

Вам не обязательно использовать сторонние фреймворки, потому что начиная с iOS 4.2, в MKMapView есть метод под названием - (NSSet *)annotationsInMapRect:(MKMapRect)mapRect, который вы можете использовать для кластеризации.

Посмотрите видео сеанса WWDC11 ' Географическая визуализация информации с помощью MapKit '.Примерно на полпути это объясняет, как это сделать.Но я обобщу концепцию для вас:

  • Использовать две карты ( вторая карта никогда не добавляется в иерархию представлений )
  • Вторая карта содержит все аннотации( снова, он никогда не рисуется )
  • Разделите область карты на сетку квадратов
  • Используйте метод -annotationsInMapRect для получения данных аннотаций с невидимой карты
  • Видимая карта строит свои аннотации на основе этих данных с невидимой карты

enter image description here

13 голосов
/ 21 октября 2011

Поскольку это очень распространенная проблема, и мне нужно решение, я написал собственный подкласс MKMapView, который поддерживает кластеризацию. Тогда я сделал его доступным с открытым исходным кодом! Вы можете получить его здесь: https://github.com/yinkou/OCMapView.

Он управляет кластеризацией аннотаций, и вы можете самостоятельно обрабатывать их представления. Вам не нужно ничего делать, кроме как скопировать папку OCMapView в свой проект, создать MKMapView в своем кончике и установить для его класса значение OCMapView. (Или создайте и передайте его в коде как обычный MKMapView)

11 голосов
/ 25 августа 2017

К счастью, вам больше не нужны сторонние фреймворки. iOS 11 имеет встроенную поддержку кластеризации.

Вам необходимо реализовать метод mapView:clusterAnnotationForMemberAnnotations:.

Получить более подробную информацию в примере Apple: https://developer.apple.com/sample-code/wwdc/2017/MapKit-Sample.zip

7 голосов
/ 22 июня 2015

Используя демонстрационный код Apple, легко реализовать концепцию кластеризации в нашем коде. Ссылочная ссылка

Просто мы можем использовать следующий код для кластеризации

Шаги для реализации кластеризации

Шаг 1: Важная вещьдля кластеризации мы используем два вида карты (allAnnotationsMapView,), один для справки (allAnnotationsMapView).

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

In viewDidLoad

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

Step2: Добавьте все аннотации в _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;
    }
}

Следуя вышеприведенным шагам, мы можем добиться кластеризации в просмотре карты, в этом нет необходимостииспользовать любой сторонний код или структуру.Пожалуйста, проверьте образец кода Apple здесь.Пожалуйста, дайте мне знать, если у вас есть какие-либо сомнения по этому поводу.

2 голосов
/ 13 июня 2015

Я просто хотел кластеризовать контакты, просто показывая их номер.Следующий https://www.cocoacontrols.com/controls/qtree-objc соответствует моим ожиданиям.

2 голосов
/ 07 июня 2013

Вы смотрели на ADClusterMapView?https://github.com/applidium/ADClusterMapView

Именно так и поступает.

1 голос
/ 27 января 2015

Я недавно отключил ADClusterMapView, упомянутый в другом ответе, и решил многие, если не все, проблемы, связанные с проектом.Это алгоритм kd-дерева, который анимирует кластеризацию.

Здесь доступен открытый код https://github.com/ashare80/TSClusterMapView

0 голосов
/ 03 октября 2015

Здесь есть довольно классная и ухоженная библиотека для Objective-C и Swift: https://github.com/bigfish24/ABFRealmMapView

Она действительно хорошо работает с кластерами, а также обрабатывает большое количество точек благодаря своей интеграции с Realm .

0 голосов
/ 14 апреля 2015

Попробуйте эту платформу (XMapView.framework);теперь он поддерживает iOS 8.

Эта инфраструктура не требует от вас изменения текущей структуры проекта, и ее можно напрямую использовать в MKMapView.Есть почтовый файл.Это дает вам пример для кластеризации 200 контактов одновременно.После того, как я проверил его на iPod, я обнаружил, что он очень плавный.

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

Эта библиотека поддерживает:

  1. кластеризацию различных категорий
  2. кластеризация всех категорий
  3. настройка собственного радиуса кластера и т. д.
  4. скрытие или отображение определенных категорий
  5. индивидуальная обработка и управление каждым пином на карте
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...