高德地图点聚合算法实现与思考

说在前面


为了实现在地图上对大量的标注点进行聚合显示,使都有标注点根据中心点以及缩放比例的变化动态排列并完成点聚合的功能。现提供基于高德地图SDK的点聚合实现方式。

以下分别为两种放缩比例下标注点的聚合情况效果图:

整体效果

局部效果

思路


前期思路

服务端计算标注统计数据,在客户端加载绘制

在预研前期,我考虑了数据从后台获取的方式,在地图上添加overlay的方式,但是考虑到用户的操作会要求所有数据必须实时生成并展现,这意味着每次缩放比例和中心点的改变都要访问服务器以重载数据,实现起来难度较大,并且对服务器和客户端都造成了较大的压力。

一次性加载所有数据,客户端实现算法在需要时计算重载

在阅读高德开放平台提供的API时发现高德地图示例中心在2017年2月10日更新了点聚合效果示例

其应用场景为:当地图上需要展示的marker过多,可能会导致界面上marker压盖、性能变差。使用点聚合功能,则可以解决该问题。

原理与实现


架构

Controllers

  • <UIViewController>
    • BaseMapViewController 地图基类
      • AnnotationClusterViewController poi点聚合
    • PoiDetailViewController 显示poi详细信息列表

View

  • MAAnnotationView
    • ClusterAnnotationView 自定义的聚合annotationView

Models

  • Conform to <MAAnnotation>
    • ClusterAnnotation 记录annotation的信息,如其代表的poi数组、poi的个数、poi平均坐标,并提供两个annotation是否Equal的判断
  • CoordinateQuadTree 封装的四叉树类
  • QuadTree 四叉树基本算法

核心代码

点聚合算法高德官方代码(iOS)见iOS_3D_ClusterAnnotation于Github。

在项目中实现

引入AMap3DMap(当前使用版本为4.3.0)

1
pod 'AMap3DMap','~>4.3.0'

下载并添加MAAnnotationCluster

在地图上添加标注点

创建MAClusteringManagerMAClusterAnimator

1
2
@property(nonatomic,strong)MAClusteringManager * clusteringManager ;
@property(nonatomic,strong)MAClusterAnimator * animator;

初始化标注点数组

1
NSMutableArray *pointAnnotations = [NSMutableArray array];

将含有位置信息的点添加到数组中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (int i = 0; i < [receivedData count]; i ++) {
NSDictionary *annotationDict = receivedData[i];
CGFloat lat = [[annotationDict objectForKey:@"latitude"] floatValue];
CGFloat lon = [[annotationDict objectForKey:@"longitude"] floatValue];
NSNumber *pID = [annotationDict objectForKey:@"id"];
MAPointAnnotation *pointAnnotation = [[MAPointAnnotation alloc] init];
pointAnnotation.coordinate = CLLocationCoordinate2DMake(lat, lon);
pointAnnotation.title = [pID stringValue];
[pointAnnotations addObject:pointAnnotation];
}
self.animator = [[MAClusterAnimator alloc] init];
self.clusteringManager = [[MAClusteringManager alloc] initWithAnnotations:pointAnnotations];
self.clusteringManager.delegate = self;
[self mapView:_mapView regionDidChangeAnimated:YES];

其中 receivedData可以通过后台获取或本地生成,包含经度、纬度以及pID,即每一个标注点的唯一ID。mapView regionDidChangeAnimated:方法在mapView区域变化时自动调用,重算annotation,初始化时手动调用一次,使标注点以聚合的形式加载到地图上。

实现mapView regionDidChangeAnimated:方法体

1
2
3
4
5
6
7
double scale = mapView.bounds.size.width / mapView.visibleMapRect.size.width;
[[NSOperationQueue new] addOperationWithBlock:^{
// Get the main queue and do the UI stuff .
NSArray *annatations = [NSArray array];
annatations = [self.clusteringManager clusteredAnnotationsWithinMapRect:mapView.visibleMapRect withZoomScale:scale];
[self.clusteringManager displayAnnotations:annatations onMapView:mapView];
}];

注意:此处必须要使用多线程

根据MAClusteringManager计算的数量修改Annotation样式。

mapView viewForAnnotation:方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id<MAAnnotation>)annotation {
static NSString *const AnnotatioViewReuseID = @"AnnotatioViewReuseID";
static NSString *const ClusterAnnotationReuseID = @"ClusterAnnotationReuseID";
static NSString *const SingleClusterAnnotationRauseID = @"SingleClusterAnnotationRauseID";
if ([annotation isKindOfClass:[MAAnnotationCluster class]]) {
//聚合点
MAAnnotationCluster *cluster = (MAAnnotationCluster *)annotation;
cluster.title = [NSString stringWithFormat:@"%zd items",[cluster.annotations count]];
MAClusterAnnotationView *clusterView = (MAClusterAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:ClusterAnnotationReuseID];
if (!clusterView) {
clusterView = [[MAClusterAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ClusterAnnotationReuseID];
clusterView.canShowCallout = NO;
clusterView.clusterColor = ClusterColor;
}

clusterView.count = [((MAAnnotationCluster *)annotation).annotations count];
return clusterView;
}else {
if ([annotation isKindOfClass:[MAPointAnnotation class]]) {
MAAnnotationView *poiAnnotationView = (MAAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:AnnotatioViewReuseID];
if ([[annotation title] isEqualToString:(NSString*)RoutePlanningViewControllerStartTitle]) {
poiAnnotationView.image = [UIImage imageNamed:@""];
return poiAnnotationView;
}else if([[annotation title] isEqualToString:(NSString *)RoutePlanningViewControllerDestinationTitle]) {
poiAnnotationView.image = [UIImage imageNamed:@"ani"];
return poiAnnotationView;
}else {
MAClusterAnnotationView *clusterView = (MAClusterAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:ClusterAnnotationReuseID];
if (!clusterView) {
clusterView = [[MAClusterAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:SingleClusterAnnotationRauseID];
clusterView.canShowCallout = NO;
clusterView.clusterColor = ClusterColor;
}
clusterView.count = 1;
return clusterView;
}
}else {
return nil;
}
}
}

这样,就在四叉树算法的基础上完成了点聚合功能的实现。