2010-01-12 4 views
23

выявляет Что касается iPhone Map Kit кластера выявляет:iPhone Map Kit кластер

У меня есть 1000-х марок, которые я хочу показать на карте, но это слишком много, чтобы справиться, поэтому я хочу, чтобы сгруппировать их.

Существуют ли рамки или доказательство концепций? Что это возможно или уже сделано?

+0

Вы видели землетрясения демо? (Я думаю) У них была специальная булавка на том, что имело круг, который был сгруппирован, и размер круга увеличивался в зависимости от количества контактов в кластере, а также их комбинированных шкал Рихтера. –

+1

это демо? http://www.switchonthecode.com/tutorials/building-an-earthquake-monitor-for-iphone-using-mapkit –

ответ

13

Вы можете использовать REVClusterMap кластера

+1

Работал для меня после того, как я интегрировал awakeFromNib: исправить из комментариев этого сообщения в блоге. Кластеризация может быть немного быстрее ИМО, но она работает. –

+0

Ссылка больше недействительна –

+0

... но здесь вы можете найти: https://github.com/RVLVR/REVClusterMap –

-1

Я думаю, Foto Brisko (iTunes link) делает это.
Я не думаю, что для этого есть каркас Cocoa Touch.

6

Это может быть немного, как с помощью цепной пилы, чтобы косить газон, но вот выдержка из Algorithms in a Nutshell

Создание KD-дерево ...

public class KDFactory { 
    // Known comparators for partitioning points along dimensional axes. 
    private static Comparator<IMultiPoint> comparators[ ] ; 
    // Recursively construct KDTree using median method on input points. 
    public static KDTree generate (IMultiPoint [ ] points) { 
    if (points. length == 0) { return null; } 
    // median will be the root. 
    int maxD = points[ 0] . dimensionality(); 
    KDTree tree = new KDTree(maxD) ; 
    // Make dimensional comparators that compare points by ith dimension 
    comparators = new Comparator[ maxD+1] ; 
    for (int i = 1; i <= maxD; i++) { 
     comparators[ i] = new DimensionalComparator(i) ; 
    } 
    tree. setRoot(generate (1, maxD, points, 0, points. length-1)) ; 
    return tree; 
    } 

    // generate the node for the d-th dimension (1 <= d <= maxD) 
    // for points[ left, right] 
    private static DimensionalNode generate (int d, int maxD, 
              IMultiPoint points[ ] , 
              int left, int right) { 
    // Handle the easy cases first 
    if (right < left) { return null; } 
    if (right == left) { return new DimensionalNode (d, points[ left]) ; } 
    // Order the array[ left, right] so the mth element will be the median 
    // and the elements prior to it will all be <=, though they won' t 
    // necessarily be sorted; similarly, the elements after will all be >= 
    int m = 1+(right-left) /2; 
    Selection. select(points, m, left, right, comparators[ d]) ; 
    // Median point on this dimension becomes the parent 
    DimensionalNode dm = new DimensionalNode (d, points[ left+m-1]) ; 
    // update to the next dimension, or reset back to 1 
    if (++d > maxD) { d = 1; } 
    // recursively compute left and right sub-trees, which translate 
    // into ' below' and ' above' for n-dimensions. 
    dm. setBelow(maxD, generate (d, maxD, points, left, left+m-2)) ; 
    dm. setAbove(maxD, generate (d, maxD, points, left+m, right)) ; 
    return dm; 
    } 
} 

Поиск ближайших соседей лучше всего: O (журнал N) наихудший O (N)

// method in KDTree 
public IMultiPoint nearest (IMultiPoint target) { 
    if (root == null) return null; 
    // find parent node to which target would have been inserted. This is our 
    // best shot at locating closest point; compute best distance guess so far 
    DimensionalNode parent = parent(target) ; 
    IMultiPoint result = parent. point; 
    double smallest = target. distance(result) ; 
    // now start back at the root, and check all rectangles that potentially 
    // overlap this smallest distance. If better one is found, return it. 
    double best[ ] = new double[ ] { smallest }; 
    double raw[ ] = target. raw(); 
    IMultiPoint betterOne = root. nearest (raw, best) ; 
    if (betterOne ! = null) { return betterOne; } 
    return result; 
} 

// method in DimensionalNode. min[ 0] contains best computed shortest distance. 
IMultiPoint nearest (double[ ] rawTarget, double min[ ]) { 
    // Update minimum if we are closer. 
    IMultiPoint result = null; 
    // If shorter, update minimum 
    double d = shorter(rawTarget, min[ 0]) ; 
    if (d >= 0 && d < min[ 0]) { 
     min[ 0] = d; 
     result = point; 
    } 
    // determine if we must dive into the subtrees by computing direct 
    // perpendicular distance to the axis along which node separates 
    // the plane. If d is smaller than the current smallest distance, 
    // we could "bleed" over the plane so we must check both. 
    double dp = Math. abs(coord - rawTarget[ dimension-1]) ; 
    IMultiPoint newResult = null; 
    if (dp < min[ 0]) { 
     // must dive into both. Return closest one. 
     if (above ! = null) { 
     newResult = above. nearest (rawTarget, min) ; 
     if (newResult ! = null) { result = newResult; } 
     } 
     if (below ! = null) { 
     newResult = below. nearest(rawTarget, min) ; 
     if (newResult ! = null) { result = newResult; } 
     } 
    } else { 
     // only need to go in one! Determine which one now. 
     if (rawTarget[ dimension-1] < coord) { 
     if (below ! = null) { 
      newResult = below. nearest (rawTarget, min) ; 
     } 
     } else { 
     if (above ! = null) { 
      newResult = above. nearest (rawTarget, min) ; 
     } 
     } 
     // Use smaller result, if found. 
     if (newResult ! = null) { return newResult; } 
    } 
    return result; 
    } 

Подробнее о KD-Trees at Wikipedia

8

Примечание: Это коммерческий продукт, я связан с, но она решает эту самую проблему.

Я решил эту проблему в нескольких моих приложениях и решил извлечь ее в многоразовую структуру. Это называется Superpin, и это (коммерческая лицензия стоит $ 149) iOS Framework, которая внутренне использует квадранты для хранения аннотаций и выполняет кластеризацию на основе сетки. Алгоритм довольно быстрый, приложение с образцом включает в себя аэропорты мира (более 30 тыс. Аннотаций), и он работает довольно гладко на iPhone 3G.

+1

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

+0

эй @esad, где вы получили этот удивительный список аэропортов мира? благодаря! – Andres

+0

@Andres http://www.ourairports.com/ предоставляет данные о всемирных аэропортах под общественным достоянием – esad

6

Я попробовал другие предложили здесь, и я также нашел OCMapView, который работал лучше всего.

Его бесплатный и позволяет легко группировать аннотации, которые мне нужны. Его немного новее & больше, чем Revolver, и мне проще реализовать.

0

Вдохновленный видео WWDC 2011, этот код очень хорошо работает для меня. Возможно, это не самый быстрый из всех предлагаемых здесь, но он бесплатный, и это, безусловно, самый простой.

В основном использовать 2 карты. Один скрыт и удерживает каждую отдельную аннотацию (allAnnotationMapView в моем коде). Один из них виден и отображает только кластеры или аннотации, если они одиночные (mapView в моем коде).

- (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]; 
} 
1

Недавно мне пришлось внедрить кластеризацию аннотаций с помощью MapKit. Вышеупомянутые решения хороши в зависимости от вашего варианта использования.Я в конечном итоге происходит с FBAnnotationClustering (Objective-C), так как он был свободен, и было много звезд и несколько вопросов, на GitHub:

https://github.com/infinum/FBAnnotationClustering

приложение я работал на очень карте ориентированных, так это имеет смысл перевести FBAnnotationClustering в Swift. Ниже приведен блог о подходе, который включает ссылку на образец проекта на github.

http://ribl.co/blog/2015/05/28/map-clustering-with-swift-how-we-implemented-it-into-the-ribl-ios-app/

+0

Я использую ваш порт для быстрой, не могли бы вы взглянуть на этот вопрос, спасибо: http: //stackoverflow.com/questions/37747381/mapkit-display-annotation-clusters-and-along-with-non-clustered-annotations – Eduardo