iPhone Map Kit 集群定位

Posted

技术标签:

【中文标题】iPhone Map Kit 集群定位【英文标题】:iPhone Map Kit cluster pinpoints 【发布时间】:2011-01-04 02:20:32 【问题描述】:

关于 :

我想在地图上显示 1000 个标记,但数量太多而无法处理,因此我想将它们聚集在一起。

是否有可用的框架或概念证明?这是可能的还是已经完成了?

【问题讨论】:

你看过地震演示吗? (我认为)他们有一个定制的别针,它有一个聚集的圆圈,圆圈的大小根据集群内的别针数量以及它们的组合里氏标度而增加。 这是演示吗? switchonthecode.com/tutorials/… 【参考方案1】:

可以使用REVClusterMap进行集群

【讨论】:

在我集成 awakeFromNib 后为我工作:从该博客文章的 cmets 修复。集群可能会更快 IMO,但它可以工作。 链接不再有效【参考方案2】:

注意:这是我所属的商业产品,但它解决了这个问题。

我在几个应用程序中解决了这个问题,并决定将其提取到一个可重用的框架中。它被称为Superpin,它是一个(商业,许可费用为 149 美元)ios 框架,内部使用四叉树进行注释存储并执行基于网格的聚类。该算法非常快,包含的示例应用程序显示了世界各地的机场(超过 30k+ 注释),并且在 3G iPhone 上运行非常流畅。

【讨论】:

Superpin 绝对是通往 - 如果你能负担得起的话。简单,干净,良好的实施。在我们的一款包含大约 500 条注释的应用中对地图产生了巨大影响。 嘿@esad 你从哪里得到的世界机场列表?谢谢! @Andres ourairports.com 在公共领域提供有关世界机场的数据【参考方案3】:

这可能有点像使用电锯修剪草坪,但这里摘录自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(log 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

【讨论】:

【参考方案4】:

我尝试了这里建议的其他人,我还发现OCMapView 效果最好。

它是免费的,并且可以轻松地对注释进行分组,这正是我所需要的。它比 Revolver 更新更多,对我来说更容易实现。

【讨论】:

【参考方案5】:

离线地图应用“OffMaps”是一个概念证明;)

http://itunes.apple.com/us/app/offmaps/id313854422?mt=8

【讨论】:

【参考方案6】:

我最近不得不使用 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/

【讨论】:

我正在使用你的端口来 swift,你能看看这个问题吗,谢谢:***.com/questions/37747381/…【参考方案7】:

受 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];

【讨论】:

【参考方案8】:

我认为Foto Brisko(iTunes 链接)可以做到这一点。 我认为没有 Cocoa Touch 框架。

【讨论】:

以上是关于iPhone Map Kit 集群定位的主要内容,如果未能解决你的问题,请参考以下文章

Sprite Kit 和 Pathfinding,iPad 和 iPhone 的区别?

iPhone Store Kit 返回无效产品 ID 错误

iPhone In-App Purchase Store Kit 错误 -1003“无法连接到 iTunes Store”

如何处理多个引脚将在 iphone sdk 中的一个经度和纬度上掉线?

iPhone如何进行地理定位?

来自 IOS App 的 Watch Kit 通知