先放代码地址:https://github.com/xjf1990930/ArcGis-iOS-Cluster
在实现聚合前,数据展示界面是这样的:
这种大量数据堆积起来首先是不美观,其次是很消耗显示性能,于是就提出需要聚合显示的要求,经过思考,大致思路是这样的:首先将屏幕分割为N等份,其中一份对应的当前屏幕内的地图范围作为最小的聚合范围单元,然后根据这个聚合范围单元进行空间查询,查询的结果有多少个就说明聚合数字显示为多少,用AGSSymbol标记到AGSGraphicLayer上即可。
说写就写,于是撸了以下四个类,看官们拿来也能立即使用,代码侵入极少:
WHGIClusterManager:负责关联AGSMapView地图对象,并负责创建聚合单元对象
WHGIClusterModel:聚合单元对象,负责本单元内的空间查询任务
WHGIClusterTask:每一项查询任务(不同图层id要进行独立的AGSQueryTask)
WHGIBlockTask:一个队列管理类
WHGIClusterManager关键代码:
//聚合时监听地图缩放和拖拽变化
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mapViewDidEndZoomAction:) name:AGSMapViewDidEndZoomingNotification object:nil];//缩放触发重新聚合查询
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mapViewDidEndDragAction:) name:AGSMapViewDidEndPanningNotification object:nil];//拖拽触发继续聚合查询
//获取当前屏幕范围内的地图需要聚合查询的所有最小单元
- (NSMutableArray *)currentScreenClusterEnvlopes {
NSUInteger cluster_X = 3;//取当前屏幕三分之一宽为聚合最小单元的宽
NSUInteger cluster_Y = 4;//取当前屏幕四分之一高为聚合最小单元的高
AGSEnvelope *clipTemp = [self.mapView toMapEnvelope:CGRectMake(0, 0, self.mapView.frame.size.width/cluster_X, self.mapView.frame.size.height/cluster_Y)];
AGSEnvelope *currentScreenEnv = [self.mapView toMapEnvelope:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
AGSPoint *startPoint = [AGSPoint pointWithX:self.mapView.maxEnvelope.xmin y:self.mapView.maxEnvelope.ymin spatialReference:self.mapView.spatialReference];
//参照当前地图最大范围的最小x和y坐标作为原点,计算出当前屏幕地图范围所有的聚合单元
NSUInteger env_minX = floor((currentScreenEnv.xmin - startPoint.x)/clipTemp.width);
NSUInteger env_minY = floor((currentScreenEnv.ymin - startPoint.y)/clipTemp.height);
NSMutableArray *allEnvs = [NSMutableArray new];
AGSEnvelope *tempEnv = nil;
for (NSUInteger i = 0; i<=cluster_X; i++) {
for (NSUInteger j = 0; j<=cluster_Y; j++) {
tempEnv = [[AGSEnvelope alloc] initWithXmin:startPoint.x+(i+env_minX)*clipTemp.width ymin:startPoint.y+(j+env_minY)*clipTemp.height xmax:startPoint.x+(i+1+env_minX)*clipTemp.width ymax:startPoint.y+(j+1+env_minY)*clipTemp.height spatialReference:self.mapView.spatialReference];
BOOL contains = NO;
for (WHGIClusterModel *clustModel in self.clusterModels) {
if ([clustModel.referEnv isEqualToEnvelope:tempEnv]) {
contains = YES;
break;
}
}
if (!contains) {
[allEnvs addObject:tempEnv];
}
}
}
return allEnvs;
}
WHGIClusterModel关键代码:
//展示查询的结果,最小聚合单元内大于3个结果,显示聚合数字符号,否则显示原本的符号
- (void)displayClust {
if (self.clusterLayer == nil) {
return;
}
NSMutableArray *tempGraphics = [NSMutableArray new];
for (WHGIClusterTask *taskModel in self.tasks) {
if (!taskModel.marked && taskModel.clustSuccess) {//已经在底图上显示过了就不会再处理一次
[tempGraphics addObjectsFromArray:taskModel.allResults];
taskModel.marked = YES;
}
}
if (tempGraphics.count<=0) {
return;
}
NSArray<AGSGraphic *>* graphics = tempGraphics;
NSMutableArray<AGSGraphic *>* showGraphics = [NSMutableArray new];
if (graphics.count>0) {
if (graphics.count>3) {
[showGraphics addObject:[AGSGraphic graphicWithGeometry:graphics.firstObject.geometry symbol:[self textSymbol:[NSString stringWithFormat:@"%@",@(graphics.count)]] attributes:nil]];
}else{
//小于三个,则每个graphic单独显示
for (AGSGraphic *singleShowGraphic in graphics) {
for (WHGIClusterTask *taskModel in self.tasks) {
if ([taskModel.allResults containsObject:singleShowGraphic]) {
[showGraphics addObject:[AGSGraphic graphicWithGeometry:singleShowGraphic.geometry symbol:[self pictureSymbolWithLayerID:taskModel.layerID] attributes:nil]];
break;
}
}
}
}
[self.clusterLayer addGraphics:showGraphics];
}
}
WHGIClusterTask关键代码:
//根据最小聚合单元的范围进行空间查询
- (void)clustWithEnvlope:(AGSEnvelope *)env andCall:(void(^)(BOOL success,NSArray<AGSGraphic *> *allResults))callBack {
if (nil == self.currentOperation || self.currentOperation.finished) {
if (self.clustSuccess == NO) {
self.resultCall = callBack;
self.queryTask = [[AGSQueryTask alloc] initWithURL:[NSURL URLWithString:[[NSString stringWithFormat:@"%@/%@",pWHGIClustLayerService,self.layerID] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
AGSQuery *queryObject = [[AGSQuery alloc] init];
queryObject.geometry = env;
queryObject.whereClause = @"1=1";
queryObject.outFields = @[@"*"];
queryObject.returnGeometry = YES;
queryObject.outSpatialReference = env.spatialReference;
queryObject.spatialRelationship = AGSSpatialRelationshipContains;
self.queryTask.delegate = self;
self.currentOperation = [self.queryTask executeWithQuery:queryObject];
}else{
if (callBack) {
callBack(YES,self.allResults);
}
}
}else{
if (callBack) {
callBack(NO,nil);
}
}
}
WHGIBlockTask就是一个工具类,可以对多个异步block回调的任务进行队列执行。
还有一些细节的优化,比如当用户缩放比较快的时候需要结束上一次没有执行完的任务等等,具体就都在代码里了。
最后的效果如下: