当地图上需要展示的marker过多,可能会导致界面上marker压盖、性能变差。使用点聚合功能,则可以解决该问题。效果示例请见右侧视频。
核心类/接口
类 | 接口 | 说明 | 版本 |
---|---|---|---|
MAMapView | (void) addAnnotations: (NSArray *) annotations | 向地图窗口添加一组标注,需要实现MAMapViewDelegate的-mapView:viewForAnnotation:函数来生成标注对应的View | V4.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<ClusterAnnotation>)
})
}
}
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<ClusterAnnotation>) {
/* 用户滑动时,保留仍然可用的标注,去除屏幕外标注,添加新增区域的标注 */
let before = NSMutableSet(array: mapView.annotations)
before.remove(mapView.userLocation)
let after: Set<NSObject> = NSSet(array: annotations) as Set<NSObject>
/* 保留仍然位于屏幕内的annotation. */
var toKeep: Set<NSObject> = NSMutableSet(set: before) as Set<NSObject>
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详细信息列表
View
MAAnnotationView
ClusterAnnotationView //自定义的聚合annotationView
Models
Conform to <MAAnnotation>
ClusterAnnotation //记录annotation的信息,如其代表的poi数组、poi的个数、poi平均坐标,并提供两个annotation是否Equal的判断
CoordinateQuadTree //封装的四叉树类
QuadTree //四叉树基本算法
当地图上需要展示的marker过多,可能会导致界面上marker压盖、性能变差。使用点聚合功能,则可以解决该问题。
在之前的开源工程版本基础上做了一些修改,提高了显示速度和内存优化使用,支持的聚合点更多,效果更好。主要修改包括:
1、将marker处理线程和聚合算法线程进行分离,提高效率。
2、聚合过程中减少不必要的运算,提高聚合显示速度。
3、适配新版SDK应用场景,对用到的图标,统一管理,减少内存使用和提高效。
4、加入了动画效果,聚合切换过程更为平滑。
核心类/接口
类 | 接口 | 说明 | 版本 |
---|---|---|---|
AMap | setOnCameraChangeListener(this); | 定义了当可视范围改变时回调的接口。 | V2.0.0版本起 |
AMap | setOnMarkerClickListener(this) | 定义了当marker 对象被点击时回调的接口 | V2.0.0版本起 |
AMap | getScalePerPixel() | 获取比例尺数据。当前缩放级别下,地图上1像素点对应的长度,单位米。 | V2.0.0版本起 |
AMap | getProjection() | 返回一个Projection对象。可以通过这个对象在屏幕坐标与经纬度坐标之间进行转换。Projection对象返回的是当前可视区域的坐标,当可视区域变换时,它不会自己更新。 | V2.0.0版本起 |
1、初始化聚合和加入元素
批量初始化,适合一次大量加入
List<ClusterItem> items = new ArrayList<ClusterItem>();
//随机10000个点
for (int i = 0; i < 10000; i++) {
double lat = Math.random() + 39.474923;
double lon = Math.random() + 116.027116;
LatLng latLng = new LatLng(lat, lon, false);
RegionItem regionItem = new RegionItem(latLng,
"test" + i);
items.add(regionItem);
}
mClusterOverlay = new ClusterOverlay(mAMap, items,
dp2px(getApplicationContext(), clusterRadius),
getApplicationContext());
初始化之后,有新的聚合点加入
double lat = Math.random() + 39.474923;
double lon = Math.random() + 116.027116;
LatLng latLng1 = new LatLng(lat, lon, false);
RegionItem regionItem = new RegionItem(latLng1,
"test");
mClusterOverlay.addClusterItem(regionItem);
2、设置渲染render和聚合点点击事件监听
mClusterOverlay.setClusterRenderer(MainActivity.this);
mClusterOverlay.setOnClusterClickListener(MainActivity.this);
3、自定义渲染
public Drawable getDrawAble(int clusterNum) {
int radius = dp2px(getApplicationContext(), 80);
if (clusterNum == 1) {
Drawable bitmapDrawable = mBackDrawAbles.get(1);
if (bitmapDrawable == null) {
bitmapDrawable =
getApplication().getResources().getDrawable(
R.drawable.icon_openmap_mark);
mBackDrawAbles.put(1, bitmapDrawable);
}
return bitmapDrawable;
} else if (clusterNum < 5) {
Drawable bitmapDrawable = mBackDrawAbles.get(2);
if (bitmapDrawable == null) {
bitmapDrawable = new BitmapDrawable(null, drawCircle(radius,
Color.argb(159, 210, 154, 6)));
mBackDrawAbles.put(2, bitmapDrawable);
}
return bitmapDrawable;
} else if (clusterNum < 10) {
Drawable bitmapDrawable = mBackDrawAbles.get(3);
if (bitmapDrawable == null) {
bitmapDrawable = new BitmapDrawable(null, drawCircle(radius,
Color.argb(199, 217, 114, 0)));
mBackDrawAbles.put(3, bitmapDrawable);
}
return bitmapDrawable;
} else {
Drawable bitmapDrawable = mBackDrawAbles.get(4);
if (bitmapDrawable == null) {
bitmapDrawable = new BitmapDrawable(null, drawCircle(radius,
Color.argb(235, 215, 66, 2)));
mBackDrawAbles.put(4, bitmapDrawable);
}
return bitmapDrawable;
}
}
4、聚合点击事件
public void onClick(Marker marker, List<ClusterItem> clusterItems) {
mClusterItems = clusterItems;
mCenterLat = marker.getPosition();
LatLngBounds.Builder builder = new LatLngBounds.Builder();
for (ClusterItem clusterItem : clusterItems) {
builder.include(clusterItem.getPosition());
}
LatLngBounds latLngBounds = builder.build();
mAMap.animateCamera(CameraUpdateFactory.newLatLngBounds(latLngBounds, 0)
);
}