iPhone mkmapviewアノテーションクラスタリング
-
22-10-2019 - |
質問
私は自分の地図上に置くための非常にたくさんのピンを持っているので、それらの注釈をクラスター化するのは素晴らしいアイデアだと思います。 iPhoneでこれを達成する方法が本当にわかりません。GoogleマップとJavaScriptの例で何かを解決することができました。しかし、iPhoneはmkmapviewを使用しており、そこに注釈をクラスター化する方法がわかりません。
あなたが知っていて、良いアイデアやフレームワークはありますか?ありがとう。
解決
これは非常に一般的な問題であり、クラスタリングをサポートするMKMAPViewのカスタムサブクラスを書いた解決策が必要だったので、私は解決策を書きました。その後、オープンソースを利用できるようにしました!ここで入手できます: https://github.com/yinkou/ocmapview.
注釈のクラスタリングを管理し、自分で見解を処理できます。あなたはコピーする以外に何もする必要はありません OCMapView
プロジェクトのフォルダー、作成します MKMapView
あなたのペン先に、そのクラスをに設定します OCMapView
. 。 (または、通常のようにコードで作成して委任する MKMapView
)
他のヒント
iOS 4.2以降、mkmapviewには呼ばれるメソッドがあるため、必ずしもサードパーティのフレームワークを使用する必要はありません。 - (NSSet *)annotationsInMapRect:(MKMapRect)mapRect
クラスタリングを行うために使用できます。
WWDC11セッションビデオをご覧くださいMapKitで地理的に情報を視覚化します'。それを通しての半分の方法は、それを行う方法を説明しています。しかし、私はあなたのために概念を要約します:
- 2つのマップを使用します(2番目のマップがビュー階層に追加されることはありません)
- 2番目のマップにはすべての注釈が含まれています(繰り返しますが、それは決して描かれていません)
- マップ領域を正方形のグリッドに分けます
- 使用する
-annotationsInMapRect
目に見えないマップから注釈データを取得する方法 - 可視マップは、目に見えないマップからこのデータからの注釈を構築します
幸いなことに、サードパーティのフレームワークはもう必要ありません。 iOS 11には、ネイティブクラスタリングサポートがあります。
実装する必要があります mapView:clusterAnnotationForMemberAnnotations:
方法。
Appleの例で詳細を確認してください。 https://developer.apple.com/sample-code/wwdc/2017/mapkit-sample.zip
Appleデモコードを使用すると、コードにクラスタリングの概念を簡単に実装できます。 参照リンク
単に、クラスタリングに次のコードを使用できます
クラスタリングを実装する手順
ステップ1 : 重要なことは、クラスタリングには2つのMapView(AllannotationsMapView)を使用し、1つは参照用(AllannotationsMapView)です。
@property (nonatomic, strong) MKMapView *allAnnotationsMapView;
@property (nonatomic, strong) IBOutlet MKMapView *mapView;
の viewdidload
_allAnnotationsMapView = [[MKMapView alloc] initWithFrame:CGRectZero];
ステップ2 : 下の_photosでは、すべての注釈を_allannotationsmapviewに追加します。注釈配列があります。
[_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;
}];
}
}
}
クラスタリング処理方法
- (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;
}
}
上記の手順に従うことで、MapViewでクラスタリングを実現できます。サードパーティのコードやフレームワークを使用する必要はありません。確認してください Appleサンプルコード ここ。これに疑問がある場合はお知らせください。
AdclusterMapViewを見ましたか? https://github.com/applidium/adclustermapview
まさにこれを行います。
ピンをクラスタリングしたかっただけで、その番号を表示したかっただけです。次のものhttps://www.cocoacontrols.com/controls/qtree-objc 私の期待に合っています。
私は最近、別の答えで言及されたAdclusterMapViewを分岐し、プロジェクトに関連する問題について多くの問題を解決しました。これはKD-Treeアルゴリズムであり、クラスタリングをアニメーション化します。
ここで利用可能なオープンソースです https://github.com/ashare80/tsclustermapview
このフレームワーク(xmapview.framework)をお試しください。現在、iOS 8をサポートしています。
このフレームワークは、現在のプロジェクト構造を変更する必要はなく、MKMAPViewに直接使用できます。 zipファイルがあります。一度に200ピンをクラスター化する例を示します。 iPodでテストした後、非常に滑らかであることがわかりました。
http://www.xuliu.info/xmapview.html
このライブラリは次のようにサポートしています。
- さまざまなカテゴリのクラスタリング
- すべてのカテゴリをクラスタリングします
- 独自のクラスター半径などをセットアップします
- 特定のカテゴリを非表示または表示します
- マップ内の各ピンを個別に処理および制御します
ここには、Objective-CとSwiftの両方にかなりクールで手入れの行き届いたライブラリがあります。 https://github.com/bigfish24/abfrealmmapview
それは非常にうまくクラスタリングを行い、また統合により大量のポイントを処理します 領域.