在“Hello World Map”里我们已经成功加载了地图,以前没接触过GIS的同学先得补一补功课,起码要了解空间对象和数据模型的基本概念。常见的地图服务,其背后都有一个地图文档(*.mxd或*.msd),文档中有很多页(图层Layer),每页上又有许多空间要素(点、线、面)可能对应了现实世界中的水井、道路、绿地等等,同时地图文档中还保存了可见性、符号化、比例尺等很多配置信息,用于控制地图的最终呈现效果。
地图组件MapView是地图最基本的容器,负责地图展示和用户交互,因此AGSMapView也是最重要的类,它提供了一系列接口帮助开发者轻松叠加不同的空间数据、漫游地图、显示信息等等。
“Hello World Map”示例中叠加了两个图层,分别是基础底图图层(切片地图服务)和人口分布地图图层(动态地图服务),添加的方法很简单,其中Name参数是Layer的唯一标示,不能重名:
//按+1顺序添加图层 [self.mapView addMapLayer:tiledLyrwithName:@"TiledLayer"]; //按指定顺序插入图层 [self.mapView insertMapLayer:tiledLyrwithName:@"TiledLayer0" atIndex: 0]; //按名称删除指定图层 [self.mapViewremoveMapLayerwithName:@"TiledLayer"];
看得出来,MapView中图层绘制的顺序是自下而上,第一个加载的图层意义比较特殊,常称为BaseMapLayer,它将初始化整个地图容器的空间参考(Spatial reference)、初始范围(Initial extent)和全图范围(Full extent),这很关键,因为如果接下来添加的图层空间参考不同,该图层就显示不出来(动态图层除外,可以重投影),那怎么知道是否加载成功?需要引用一个重要的协议:AGSMapLayerDelegate,图层消息委托,会在下一节中详细介绍。
目前支持的图层类型包括了:
· 切片地图服务图层(AGSTiledMapServiceLayer)
· 动态地图服务图层(AGSDynamicMapServiceLayer)
· 影像服务图层(AGSImageServiceLayer)
· 要素服务图层(AGSFeatureLayer)
· 微软Bing服务图层(AGSBingMapLayer)
· OSM服务图层(AGSOpenStreetMapLayer)
· WMS服务图层(AGSWMSLayer)
· 离线切片服务图层(AGSLocalTiledLayer)
· 自定义切片服务图层(OfflineTiledLayer)
· 图形图层(AGSGraphicLayer)
· 草图绘制图层(AGSSketchLayer)
这里先了解一下ArcGIS for iOS支持的图层类型,具体都干嘛使的,下一章再详细介绍;此外还有一种新型的智能地图类型:WebMap,也会单独介绍。
地图每添加一个图层,MapView容器里就对应添加一个子视图,如果你希望控制该图层的可见性、透明度等参数,可以自己显式声明一个UIView,让其引用AGSLayerView协议,然后控制这个UIView就行了,如下:
//获取图层视图引用 UIView<AGSLayerView> *LyrView =[self.mapViewaddMapLayer: LyrwithName:@"PopulationLayer"]; //设置图层透明度 LyrView.alpha = 0.3; //设置图层可见性 LyrView.hidden = NO; //操作地图的同时允许绘制数据,会增加CPU消耗,建议仅对基础地图图层使用 LyrView.drawDuringPanning = YES; LyrView.drawDuringZooming = YES;
地图显示范围,默认是BaseMapLayer的全图范围,预先可以构建一个AGSEnvelope对象,并直接放大到该范围:
//设定地图初始化显示范围为中国 AGSEnvelope *chinaEnv = [AGSEnvelopeenvelopeWithXmin:7800000.00 ymin:44000.00 xmax:15600000.00 ymax:7500000.00 spatialReference:self.mapView.spatialReference]; [self.mapViewzoomToEnvelope:chinaEnvanimated:YES];
*缩放到的几何区域其空间参考必须和地图一致
地图还可以按指定点进行居中显示:
//居中显示 AGSPoint *newPoint = [AGSPoint pointWithX:-93.032201 y:49.636213 spatialReference:self.mapView.spatialReference]; [self.mapView centerAtPoint:newPoint animated:NO];
另外属性visibleArea(取代了原来的envelope)返回结果是当前地图的外接矩形:
AGSPolygon* mapExtent = self.mapView.visibleArea;
而属性MaxEnvelope可以设定地图的最大显示范围,所有操作都无能超该范围:
self.mapView.maxEnvelope = mapExtent.envelope;
MapView默认会响应以下手势操作:
表3-2-1 地图支持的手势
手势 |
地图响应 |
轻划(Swipe) |
平移 |
撑开(Pinch-Out) |
放大 |
捏合(Pinch-In) |
缩小 |
点击(Tap) |
查看 |
常按(Long Press) |
启用放大镜 |
双击(Double Tap) |
放大 |
两点点击(Two finger Tap) |
缩小 |
两点转动(Two finger Twist)* |
旋转 |
*地图旋转前提是MapView的allowRotationByPinching属性为YES。
一图胜千言的佳话里肯定少不了图例(MapLegend),尤其当要素类型复杂时,通过图例能简明标示关键要素,图例包括一组符号图片和对应的文字标注。
图3-2-3-1 常见的地图图例样式
ArcGIS for iOS v1.8之后下列图层都支持获取图例信息:
· 切片地图服务图层
· 动态地图服务图层
· 要素服务图层
· 图形图层
5.1. 对于切片和动态地图服务图层:
ArcGIS Server v10 SP1以上版本发布的切片和动态地图服务提供了图例信息,通过AGSMapServiceInfo对象的retrieveLegendInfo方法异步获取到legendLabels和legendImages:
//AGSMapServiceInfoDelegate 方法 -(void) mapServiceInfo:(AGSMapServiceInfo *)mapServiceInfo operationDidRetrieveLegendInfo:(NSOperation *)op { //loop through all sub-layers NSArray* layerInfos = mapServiceInfo.layerInfos; for(int i=0;i<[layerInfos count];i++){ //access legend information of each sub-layer AGSMapServiceLayerInfo* layerInfo = [layerInfos objectAtIndex:i]; NSArray* legendLabels = layerInfo.legendLabels; NSArray* legendImages = layerInfo.legendImages; ... } }
5.2. 对于要素和图形图层:
成功加载了要素和图形服务,其符号和文件标注已经下载到终端,所以只需要导出简单符号或渲染色带就好:
//导出symbol图片 AGSSymbol* symbol = ...; UIImage* image = [symbol swatchForGeometryType:AGSGeometryTypePoint size:CGSizeMake(20,30)]; //导出渲染色带 AGSRenderer* renderer = ...; AGSGraphic* graphic = ...; UIImage* image = [renderer swatchForGraphic:graphic size:CGSizeMake(20,30)];
目前还没有封装成拿来即用的组件,需要开发者手动填充到View或UITableView里,稍显麻烦。
平面地图都是从经度-180展开到180,如果你要同时浏览东西经180的数据就很不方便,因此在v1.8中新增加了经向环绕“Wrap Around”的设置,使得地图数据在东西向平移时无缝环绕:
//地图组件开启环绕 self.mapView.wrapAround = YES;
经向环绕的原理类似与屏幕扩展:-180~180是当前屏幕0,-180再往西就是屏幕-1(-180~-540),而正180再往东就是屏幕1(180~-540)。在采集坐标时这会造成困扰,因此需要先对进行标准化(normalize),Geometry Engine里提供了该方法normalizeCentralMeridianOfGeometry。
经向环绕的使用条件:
· 最大范围覆盖全球
· 地图空间参考是WGS 84 (WKID=4326) 或Web Mercator (WKID=102113, 102100, or 3857)
· 支持WKT关键字的动态服务(ArcGIS Server10.0以上版本发布)
包含时间信息的地理数据在服务器端注册成“时态(Time-aware Layer)”数据后, 客户端就能展示时态,比如:24小时内飓风的推进过程、1周的气温变化过程等。支持时态的地图服务都是动态服务,包括:动态地图服务、影像服务和要素服务。
时间范围(TimeExtent)属性,定义了显示数据的时间区间,利用它能快速过滤时态数据,范围外的数据都不会显示:
NSDate* now = [NSDate date]; NSDate* yesterday = ... [NSDate dateWithTimeIntervalSinceNow: -(24 * 60 * 60)]; //过滤显示最近1天的数据 AGSTimeExtent* extent = [[AGSTimeExtent alloc] initWithStart:now end: yesterday]; map.timeExtent = extent; //只显示某一时间点的数据 AGSTimeExtent* extent = [[AGSTimeExtent alloc] initWithStart:now end: now]; map.timeExtent = extent;
*要表示截止某时间点前(后),起始(终止)时间参数可以输入nil。
如果输入时间的时区是本机的系统时区(如:中国上海-香港-乌鲁木齐时间),而请求的时态服务又是其他时区,则需要统一时间格式:
//定义时间格式 NSDateFormatter* inputFormatter = [[NSDateFormatter alloc] init]; [inputFormatter setDateFormat:@"M/d/yyyy h:mm a"]; //设定时区 [inputFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]]; //按格式构造NSDate NSDate* date = [inputFormatter dateFromString:@"1/1/2001 12:00 AM"]; AGSTimeExtent* extent = [[AGSTimeExtent alloc] initWithStart:date end:date];
*更多NSDate时间转换的内容请参考博文-http://blog.csdn.net/diyagoanyhacker/article/details/7096612。
如果不需要按时间过滤,设定图层的时间开关(useTime)为false即可:
AGSLayerTimeOptions* op = [[[AGSLayerTimeOptions alloc] init] autorelease];op.layerId = 1; //关闭指定图层的时态响应 op.useTime = FALSE;