문제

iPhone에 관해 지도 키트 클러스터 pinpoints :

지도에 표시하고 싶은 1000의 마크가 있지만 처리하기에는 너무 많아서 클러스터링하고 싶습니다.

사용 가능한 프레임 워크 또는 개념 증명이 있습니까? 이것이 가능하거나 이미 완료 되었습니까?

도움이 되었습니까?

해결책

당신이 사용할 수있는 RevClusterMap 클러스터

다른 팁

참고 : 이것은 제가 제휴 한 상용 제품이지만이 문제를 해결합니다.

이 문제를 소수의 앱에서 해결하고 재사용 가능한 프레임 워크로 추출하기로 결정했습니다. 라고 불린다 슈퍼 핀 또한 주석 저장에 내부적으로 쿼드 트리를 사용하고 그리드 기반 클러스터링을 수행하는 (상업용 라이센스 비용 $ 149) iOS 프레임 워크입니다. 알고리즘은 매우 빠르며, 포함 된 샘플 앱은 세계 공항 (30k 이상의 주석)을 보여주고 3G iPhone에서 매우 매끄럽게 작동합니다.

이것은 전기 톱을 사용하여 잔디를 깎는 것과 비슷하지만 여기에 발췌 한 내용이 있습니다. 간단한 알고리즘

KD-Tree 만들기 ...

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;
  }

Wikipedia의 KD-Trees

나는 여기에서 제안 된 다른 사람들을 시도했고 또한 ocmapview 최선을 다했습니다.

무료이며 주석을 쉽게 그룹화 할 수 있습니다. 이것이 제가 필요한 것입니다. 리볼버보다 조금 더 새롭고 더 업데이트되고 나에게 더 쉽게 구현하기가 더 쉽습니다.

개념 증명은 오프라인 맵 앱 "오프 맵"입니다.)

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

최근에 Mapkit으로 주석 클러스터링을 구현해야했습니다. 위에서 언급 한 솔루션은 사용 사례에 따라 좋습니다. 나는 무료이기 때문에 fbannotationClustering (objective-c)을 사용하게되었고, Github에 별이 많고 문제가 거의 없었습니다.

https://github.com/infinum/fbannotationclustering

내가 작업하고있는 앱은 매우 맵 중심적이므로 fbannotationclustering을 신속하게 번역하는 것이 합리적이었습니다. 다음은 Github의 샘플 프로젝트 링크가 포함 된 접근 방식에 대한 블로그 게시물입니다.

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

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

제 생각에는 Foto Brisko (iTunes 링크)이 작업을 수행합니다.
나는 그것에 대한 코코아 터치 프레임 워크가 있다고 생각하지 않습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top