使用场景
当地图上需要展示的marker过多,可能会导致界面上marker压盖、性能变差。使用点聚合功能,则可以解决该问题。效果示例请见右侧视频。
用到产品
核心类/接口
类接口说明版本
MAMapView(void) addAnnotations: (NSArray *) annotations向地图窗口添加一组标注,需要实现MAMapViewDelegate的-mapView:viewForAnnotation:函数来生成标注对应的ViewV4.3.0版本起
核心难点
1、调用ClusterAnnotation文件夹下的代码能够实现poi点聚合,使用步骤如下:
初始化coordinateQuadTree。
self.coordinateQuadTree = [[CoordinateQuadTree alloc] init];
var coordinateQuadTree = CoordinateQuadTree()
2、获得poi数组pois后,创建coordinateQuadTree。
· 项目Demo通过关键字搜索获得poi数组数据,具体见工程。此处从获得poi数组开始说明。
· 创建四叉树coordinateQuadTree来建立poi的四叉树索引。
· 创建过程较为费时,建议另开线程。创建四叉树完成后,计算当前mapView下需要显示的annotation。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/* 建立四叉树. */
[self.coordinateQuadTree buildTreeWithPOIs:respons.pois];
dispatch_async(dispatch_get_main_queue(), ^{
/* 计算当前mapView区域内需要显示的annotation. */
NSLog(@"First time calculate annotations.");
[self addAnnotationsToMapView:self.mapView];
});
});
DispatchQueue.global(qos: .default).async(execute: { [weak self] in
self?.coordinateQuadTree.build(withPOIs: response.pois)
self?.shouldRegionChangeReCalculate = true
self?.addAnnotations(toMapView: (self?.mapView)!)
})
3、根据CoordinateQuadTree四叉树索引,计算当前zoomLevel下,mapView区域内的annotation。
- (void)addAnnotationsToMapView:(MAMapView *)mapView
{
/* 判断是否已建树. */
if (self.coordinateQuadTree.root == nil)
{
return;
}
/* 根据当前zoomLevel和zoomScale 进行annotation聚合. */
double zoomScale = self.mapView.bounds.size.width / self.mapView.visibleMapRect.size.width;
/* 基于先前建立的四叉树索引,计算当前需要显示的annotations. */
NSArray *annotations = [self.coordinateQuadTree clusteredAnnotationsWithinMapRect:mapView.visibleMapRect
withZoomScale:zoomScale
andZoomLevel:mapView.zoomLevel];
/* 更新annotations. */
[self updateMapViewAnnotationsWithAnnotations:annotations];
}
func addAnnotations(toMapView mapView: MAMapView) {
synchronized(lock: self) { [weak self] in
guard (self?.coordinateQuadTree.root != nil) || self?.shouldRegionChangeReCalculate != false else {
NSLog("tree is not ready.")
return
}
guard let aMapView = self?.mapView else {
return
}
let visibleRect = aMapView.visibleMapRect
let zoomScale = Double(aMapView.bounds.size.width) / visibleRect.size.width
let zoomLevel = Double(aMapView.zoomLevel)
DispatchQueue.global(qos: .default).async(execute: { [weak self] in
let annotations = self?.coordinateQuadTree.clusteredAnnotations(within: visibleRect, withZoomScale: zoomScale, andZoomLevel: zoomLevel)
self?.updateMapViewAnnotations(annotations: annotations as! Array)
})
}
}
4、更新annotations。对比mapView里已有的annotations,吐故纳新。
/* 更新annotation. */
- (void)updateMapViewAnnotationsWithAnnotations:(NSArray *)annotations
{
/* 用户滑动时,保留仍然可用的标注,去除屏幕外标注,添加新增区域的标注 */
NSMutableSet *before = [NSMutableSet setWithArray:self.mapView.annotations];
[before removeObject:[self.mapView userLocation]];
NSSet *after = [NSSet setWithArray:annotations];
/* 保留仍然位于屏幕内的annotation. */
NSMutableSet *toKeep = [NSMutableSet setWithSet:before];
[toKeep intersectSet:after];
/* 需要添加的annotation. */
NSMutableSet *toAdd = [NSMutableSet setWithSet:after];
[toAdd minusSet:toKeep];
/* 删除位于屏幕外的annotation. */
NSMutableSet *toRemove = [NSMutableSet setWithSet:before];
[toRemove minusSet:after];
/* 更新. */
dispatch_async(dispatch_get_main_queue(), ^{
[self.mapView addAnnotations:[toAdd allObjects]];
[self.mapView removeAnnotations:[toRemove allObjects]];
});
}
func updateMapViewAnnotations(annotations: Array) {
/* 用户滑动时,保留仍然可用的标注,去除屏幕外标注,添加新增区域的标注 */
let before = NSMutableSet(array: mapView.annotations)
before.remove(mapView.userLocation)
let after: Set = NSSet(array: annotations) as Set
/* 保留仍然位于屏幕内的annotation. */
var toKeep: Set = NSMutableSet(set: before) as Set
toKeep = toKeep.intersection(after)
/* 需要添加的annotation. */
let toAdd = NSMutableSet(set: after)
toAdd.minus(toKeep)
/* 删除位于屏幕外的annotation. */
let toRemove = NSMutableSet(set: before)
toRemove.minus(after)
DispatchQueue.main.async(execute: { [weak self] () -> Void in
self?.mapView.addAnnotations(toAdd.allObjects)
self?.mapView.removeAnnotations(toRemove.allObjects)
})
}
架构
Controllers BaseMapViewController //地图基类 AnnotationClusterViewController //poi点聚合 PoiDetailViewController //显示poi详细信息列表
ViewMAAnnotationView
ClusterAnnotationView //自定义的聚合annotationView
ModelsConform to
ClusterAnnotation //记录annotation的信息,如其代表的poi数组、poi的个数、poi平均坐标,并提供两个annotation是否Equal的判断
CoordinateQuadTree //封装的四叉树类
QuadTree //四叉树基本算法