iPhone MKMapView кластеризация аннотаций
У меня есть довольно много булавок, чтобы поместить их на мою карту, поэтому я думаю, что было бы неплохо сгруппировать эти аннотации. Я не совсем уверен, как добиться этого на iPhone, я смог что-то придумать с google maps и некоторыми примерами javascript. Но iPhone использует свой mkmapview, и я понятия не имею, как кластер аннотаций там.
Какие-нибудь идеи или схемы, которые вы знаете и которые хороши? Спасибо.
9 ответов:
Поскольку это очень распространенная проблема, и мне нужно было решение, я написал пользовательский подкласс MKMapView, который поддерживает кластеризацию. Тогда я сделал его доступным с открытым исходным кодом! Вы можете получить его здесь: https://github.com/yinkou/OCMapView .
Он управляет кластеризацией аннотаций, и вы можете обрабатывать их представления самостоятельно. Вам ничего не нужно делать, кроме как скопировать папкуOCMapView
в свой проект, создатьMKMapView
в своем наконечнике и установить его класс вOCMapView
. (Или создать и делегируйте его в коде, как обычныйMKMapView
)
Вам не обязательно использовать стороннюю платформу, потому что начиная с iOS 4.2, MKMapView имеет метод под названием
- (NSSet *)annotationsInMapRect:(MKMapRect)mapRect
, который вы можете использовать для кластеризации.Посмотрите видео сессии WWDC11 " визуализация информации географически с помощью MapKit ". Примерно на полпути он объясняет, как это сделать. Но я резюмирую концепцию для вас:
- использовать две карты (вторая карта никогда не добавляется в иерархию представлений )
- вторая карта содержит все аннотации (опять же, это никогда не рисовалось)
- разделите область карты на сетку квадратов
- Используйте метод
-annotationsInMapRect
для получения данных аннотаций из невидимая карта- видимая карта строит свои аннотации на основе этих данных из невидимой карты
К счастью, вам больше не нужны сторонние фреймворки. iOS 11 имеет встроенную поддержку кластеризации.
Вам необходимо реализовать
mapView:clusterAnnotationForMemberAnnotations:
метод.Получить более подробную информацию в Apple пример: https://developer.apple.com/sample-code/wwdc/2017/MapKit-Sample.zip
С помощью демонстрационного кода Apple легко реализовать концепцию кластеризации в нашем коде. Ссылка
Просто мы можем использовать следующий код для кластеризации
Шаги по реализации кластеризации
Шаг 1: важно то, что для кластеризации мы используем два вида карт (allAnnotationsMapView,), один-для справки (allAnnotationsMapView).
@property (nonatomic, strong) MKMapView *allAnnotationsMapView; @property (nonatomic, strong) IBOutlet MKMapView *mapView;
In viewDidLoad
_allAnnotationsMapView = [[MKMapView alloc] initWithFrame:CGRectZero];
Шаг 2: добавьте все аннотации к _allAnnotationsMapView, ниже _photos-массив аннотаций.
[_allAnnotationsMapView addAnnotations:_photos]; [self updateVisibleAnnotations];
Шаг 3: добавьте ниже методы кластеризации, в этой Фотоаннотации есть пользовательская аннотация. методы 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; }]; } } }
Методы обработки кластеризации
Следуя вышеизложенным шагам, мы можем достичь кластеризации в mapview, не нужно использовать какой-либо сторонний код или фреймворк. Пожалуйста, проверьте пример кодаApple здесь. Пожалуйста, дайте мне знать. если у вас есть какие-то сомнения на этот счет.- (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; } }
Вы смотрели на ADClusterMapView ? https://github.com/applidium/ADClusterMapView
Он делает именно это.
Я просто хотел сгруппировать булавки, просто показав их номер. Следующий https://www.cocoacontrols.com/controls/qtree-objc соответствует моим ожиданиям.
Недавно я отклонился от ADClusterMapView, упомянутого в другом ответе, и решил многие, если не все, проблемы, связанные с проектом. Это алгоритм kd-дерева и анимирует кластеризацию.
Он доступен с открытым исходным кодом здесь https://github.com/ashare80/TSClusterMapView
Попробуйте этот фреймворк (XMapView.framework); теперь он поддерживает iOS 8.
Этот фреймворк не нуждается в изменении текущей структуры проекта и может быть непосредственно использован в вашем MKMapView. Существует почтовый файл. Это дает вам пример для кластера 200 контактов сразу. После того, как я протестировал его в iPod, я обнаружил, что он очень гладкий.
Http://www.xuliu.info/xMapView.html
Эта библиотека поддерживает:
- кластеризация различных категорий
- кластеризация все категории
- настройка собственного радиуса кластера и так далее
- скрыть или показать некоторые категории
- индивидуально обрабатывать и контролировать каждый контакт в карте
Здесь есть довольно классная и хорошо поддерживаемая библиотека для Objective-C и Swift: https://github.com/bigfish24/ABFRealmMapView
Он делает кластеризацию действительно хорошо, а также обрабатывает большое количество точек из-за его интеграции с областью.