iOS ArcGIS 10.2.5 详细介绍

经常有人问我,ArcGIS怎么弄,怎么弄。

作为当年在厦门搞过商业级天地图项目的我来说,是时候展现一波技术了,就把干货告诉大家,让大家好好搞。不仅会介绍API,还会介绍思路,以及分析过程等等。

前公司项目:天地图·厦门,已发布在App Store,欢迎下载查看。

iOS ArcGIS 10.2.5 详细介绍_第1张图片

天地图·厦门 首页

[AGSRuntimeEnvironment setClientID:clientID error:&error];

用来设置认证,只有通过认证,地图才不会显示开发版。

self.mapView.locationDisplay.showsPing = NO;//取消闪烁的光圈

self.mapView.locationDisplay.showsAccuracy = NO;//取消闪烁的光圈

通过这两段代码可以取消ArcGIS闪烁的光圈。

self.mapView.locationDisplay.location.accuracy = 10;

设置定位精度,单位是米。

self.mapView.layerDelegate = self;

设置层代理,主要用到了mapViewDidLoad的代理方法,当地图加载完成时调用。

self.mapView.touchDelegate = self;

设置触碰代理,主要用到了didClickAtPoint的代理方法,当点击地图时调用。

self.mapView.minScale = 100000000;

self.mapView.maxScale = 1000;

设置地图的放大倍数和缩小倍数,ArcGIS不会自己缩小到很小,需要你来设置,通过设置这个参数,可以看到门牌号。

[self.mapView enableWrapAround];

设置允许地图环绕,如果不设置这个参数,左右滑动地图时会到底。比如向左滑动到美国时就停止了,设置了这个参数,滑动到美国,可以继续左滑到英国,再回到美国,形成循环。

AGSSpatialReference *sr = [AGSSpatialReference spatialReferenceWithWKID:4490];

AGSEnvelope *env = [AGSEnvelope envelopeWithXmin:117.85362348365

ymin:24.398242072050003

xmax:118.48276347535

ymax:24.93150561495

spatialReference:sr];

[self.mapView zoomToEnvelope:env animated:NO];

设置初始定位区域,我将它定位在厦门。

以上方法我放在viewDidLoad方法中执行。

[self.mapView.locationDisplay startDataSource];

在地图上显示设备位置。

[self.mapView.locationDisplay addObserver:self

forKeyPath:@"autoPanMode"

options:(NSKeyValueObservingOptionNew)

context:NULL];

监听地图的定位情况,用来控制当前是否处于地图的中心

[self.mapView addObserver:self

forKeyPath:@"mapScale"

options:(NSKeyValueObservingOptionNew)

context:NULL];

地图缩放系数改变时调用,我用来取消搜索框键盘。

[self.mapView addObserver:self

forKeyPath:@"visibleAreaEnvelope"

options:(NSKeyValueObservingOptionNew)

context:NULL];

地图可见区域监测,实时获取地图中心位置,纠错的时候可以用到。

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(mapViewDidEndZooming:)

name:AGSMapViewDidEndZoomingNotification

object:nil];

地图停止缩放时触发,用来判断是否需要移除全球地图,提高加载速度。

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(mapViewDidEndPanning:)

name:AGSMapViewDidEndPanningNotification

object:nil];

地图停止平移时触发。

UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self

action:@selector(handleMapPan:)];

[self.mapView addGestureRecognizer:panGestureRecognizer];

处理地图平移。

以上方法我放在mapViewDidLoad方法中执行。

[self.mapView zoomIn:YES];

[self.mapView zoomOut:YES];

地图的放大和缩小操作。

TDTTiledServiceLayer *tianDiTuLyr = [[TDTTiledServiceLayer alloc] initWithLayerType:type LocalServiceURL:nil error:&err];

添加全球地图操作,TDTTiledServiceLayer是我自己自定义的类,用来加载全球地图用的。遗憾的是,ArcGIS并没有使用一个类来处理加载这一类型全球底图的操作,所以要自己去实现,而这个实现过程过于复杂,我当初也是花了不少时间,篇幅的限制,就不在这里细细展开,因为细细展开的话,文章太长太长了。

[self.mapView insertMapLayer:tianDiTuLyr withName:name atIndex:0];

在某个层级插入地图。地图上放置一张底图,如果上面有什么信息的话,就往上面加一层,需要什么样的信息,就叠加什么样的层,层级处理的理论基础与iOS视图或者cocos 2D都是一样的。

id tiledLayer = [AGSLocalTiledLayer localTiledLayerWithPath:fileName];

加载本地图层,如果本地有tpk文件,可以直接加载。tpk文件和离线地图有紧密的关系,因为离线地图下载下来的就是tpk文件,tpk文件是离线地图数据格式,之后会说到离线地图。

id tiledLayer = [MDTiledMapServiceLayer tiledMapServiceLayerWithURL:url];

MDTiledMapServiceLayer是我自己定义的,继承于AGSTiledMapServiceLayer,根据需要,可能要重写urlForTileKey方法,并配置好column、row、level等信息,而level信息要与服务端约定好。

以上内容是图层的初始化和叠加操作。

接下来介绍专题图的加载。

所谓的专题图,无非是各种功能或者数据显示图,比如在地图上显示自行车位置,显示水坝位置,显示公园位置,这些都是一个个图层,需要往地图上叠加。而加载这些图层用到的地图类也不尽相同。比如:

AGSDynamicMapServiceLayer layer = [AGSDynamicMapServiceLayer dynamicMapServiceLayerWithURL:url];

用来加载动态图层。

AGSGraphicsLayer *graphicsLayer = [AGSGraphicsLayer graphicsLayer];

用来加载图像层。

我目前用到这两个,当然还有其他很多图层,之前都有研究过,走过不少弯路。

AGSCredential *credential = [[AGSCredential alloc] initWithToken:data.appToken referer:data.appReferer];

地图凭证,保证系统的安全和对用户的控制。

AGSQueryTask *queryTask = [AGSQueryTask queryTaskWithURL:url credential:credential];

queryTask.delegate = self;

创建查询任务。

AGSQuery *query = [AGSQuery query];

query.whereClause = data.whereClause;

query.outFields = [NSArray arrayWithObject:@"*"];

query.returnGeometry = YES;

query.outSpatialReference = self.mapView.spatialReference;

[queryTask executeWithQuery:query];

创建查询参数。

查询任务和查询参数用于显示图层上已经标出的指定的点。有些需求可能需要对这些点进行抽稀,我在抽稀过程中使用了冒泡算法,加了点动态规划。

AGSIdentifyTask *identifyTask = [AGSIdentifyTask identifyTaskWithURL:url];

identifyTask.delegate = self;

创建一个Id任务,之前使用这个去加载,因为内容定制度较差,后来改用服务端进行。

但无论是查询任务或者Id任务,它们都需要设置代理,然后实现代理的方法,确保操作得到响应。

接下来介绍测距和测面。

AGSSketchGraphicsLayer *measureSketchLayer = [AGSSketchGraphicsLayer graphicsLayer];

需要使用到该图层,在这图层上进行绘制。

然后你需要理解图层上几个相关变量的含义,比如:

midVertexSymbol

allowHitTest

vertexSymbol

selectedVertexSymbol

mainSymbol

geometry

特别是geometry,几种几何图形不同的创建方式如下:

//创建线

measureSketchLayer.geometry = [[AGSMutablePolyline alloc] initWithSpatialReference:self.mapView.spatialReference];

//创建面

measureSketchLayer.geometry = [[AGSMutablePolygon alloc] initWithSpatialReference:self.mapView.spatialReference];

记得设置一下地图的触摸代理。

self.mapView.touchDelegate = measureSketchLayer;

下面的通知,AGSSketchGraphicsLayerGeometryDidChangeNotification,是用来监测几何体的形状变化。

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(respondToGeomChanged:)

name:AGSSketchGraphicsLayerGeometryDidChangeNotification

object:nil];

在这个方法里面处理距离变化或者面积变化,以及处理之后要介绍的标绘内容。

介绍一下距离和面积的计算方法:

AGSGeometry *sketchGeometry = measureSketchLayer.geometry;

AGSGeometryEngine *geometryEngine = [AGSGeometryEngine defaultGeometryEngine];

self.distance = [geometryEngine geodesicLengthOfGeometry:sketchGeometry inUnit:AGSSRUnitMeter];

距离计算,单位是米。

AGSGeometry *sketchGeometry = measureSketchLayer.geometry;

AGSGeometryEngine *geometryEngine = [AGSGeometryEngine defaultGeometryEngine];

self.area = [geometryEngine shapePreservingAreaOfGeometry:sketchGeometry inUnit:AGSAreaUnitsSquareMeters];

面积计算,单位是平方米。

接下来介绍标绘,包括点标绘、线标绘、面标绘、文字标绘。

创建方式:

//创建点

measureSketchLayer.geometry = [[AGSMutablePoint alloc] initWithX:NAN y:NAN spatialReference:self.mapView.spatialReference];

//创建线

measureSketchLayer.geometry = [[AGSMutablePolyline alloc] initWithSpatialReference:self.mapView.spatialReference];

//创建面

measureSketchLayer.geometry = [[AGSMutablePolygon alloc] initWithSpatialReference:self.mapView.spatialReference];

在AGSSketchGraphicsLayerGeometryDidChangeNotification的通知方法中,更新点的信息。

如果你希望在点上面增加自定义视图,可以这么写:

self.mapView.callout.customView = antherView;

其中antherView,是我自己定义的一个视图,比如在视图上放个删除按钮,放个label显示数据等等,都可以。

如果你想创建文字标绘,你需要这么写:

AGSTextSymbol *textSymbol = [[AGSTextSymbol alloc] initWithText:self.plottingData.title

color:[UIColor redColor]];

textSymbol.fontFamily = @"Heiti SC";

textSymbol.vAlignment = AGSTextSymbolVAlignmentMiddle;

textSymbol.hAlignment = AGSTextSymbolHAlignmentCenter;

textSymbol.fontSize = 13;

textSymbol.offset = CGPointMake(0, 0);

AGSCompositeSymbol *compositeSymbol = [AGSCompositeSymbol compositeSymbol];

[compositeSymbol addSymbol:textSymbol];

AGSPoint *point_ags = [[AGSPoint alloc] initWithX:point.x y:point.y spatialReference:self.mapView.spatialReference];

AGSGraphic *graphic = [AGSGraphic graphicWithGeometry:point_ags symbol:compositeSymbol attributes:nil];

其中各种参数的含义,你通过英文的字面意思或者实际操作,应该都能理解。

有一个业务需求提到需要保存标绘数据,研究发现AGSGeometry实现了AGSCoding协议,可以将AGSGeometry对象转成NSDictionary,这样就可以使用归档保存在本地,归档类NSKeyedArchiver。

接下来介绍纠错,路线规划,运动,搜索,离线地图。

纠错需要让地图标识符一直处于地图的中点,调用的函数为:

CGPoint point = self.view.center;

AGSPoint *mapPoint = [self.mapView toMapPoint:point];

配合之前说的visibleAreaEnvelope就可以实现了。

路线规划涉及到画路线轨迹,先创建一个图像层AGSGraphicsLayer,然后创建直线,或者在直线终点创建点。

//创建直线,添加路径

AGSMutablePolyline *polyline = [[AGSMutablePolyline alloc] initWithSpatialReference:self.mapView.spatialReference];

[polyline addPathToPolyline];

//创建点,添加点路径

AGSPoint *point = [AGSPoint pointWithX:x y:y spatialReference:self.mapView.spatialReference];

[polyline addPointToPath:point];

起点和终点的符号是由图片和文字组成,你需要这么写:

//创建一个图片符号

AGSPictureMarkerSymbol *marker = [AGSPictureMarkerSymbol pictureMarkerSymbolWithImage:image];

//创建一个文字符号

AGSTextSymbol *textSymbol = [[AGSTextSymbol alloc] initWithText:text

color:[UIColor whiteColor]];

//然后创建一个AGSCompositeSymbol来加入这两个符号,如:

AGSCompositeSymbol *compositeSymbol = [AGSCompositeSymbol compositeSymbol];

[compositeSymbol addSymbol:marker];

[compositeSymbol addSymbol:textSymbol];

路线规划的数据是通过请求服务端获取的,获取的数据有一定的格式,一般为分段的一系列点的集合,需要根据约定好的规范进行适配。

路线画出来了,那么如何跳到路线所在位置的中心,你可以这么写:

AGSMutableEnvelope *envelope = [routeGraphicsLayer.fullEnvelope mutableCopy];

[envelope expandByFactor:2.5];

[self.mapView zoomToEnvelope:envelope animated:YES];

说说运动:

运动中需要计步,计步采用加速度传感器实现,封装了一个StepManager来管理步数,具体的算法就不细细展开,非本篇所讲内容。

运动需要在后台长时间运行,需要申请后台权限,上架的时候在备注中讲清楚这个事,态度诚恳,必要的时候叫苹果几声爹,一般就没什么问题了。

double distance = [geometryEngine geodesicLengthOfGeometry:lineGraphic.geometry inUnit:AGSSRUnitMeter];

运动采用的也是距离函数,保存上一点和下一点,然后规定一下距离超过5米或者10米时才画直线。

接下来说搜索,

[self.mapView zoomToScale:self.mapView.mapScale withCenterPoint:(AGSPoint *)graphic.geometry animated:YES];

当点击搜索出来的红色点时,或者点击表格数据时,相应的红点要位于中心。

搜索时需要与路线规划对接,记住在程序设计的时候,多抽离模块代码,避免相同模块写了两份相同的代码。

离线地图比较简单,就没什么好说的了。需要注意的是这是个在后台发起的下载任务,而不是属于某个控制器的下载任务,判断好总大小,控制好节点数据,处理好暂停、继续,就没什么问题了。

总结:ArcGIS是定位最准确的地图,比高德和百度还准确。但是缺点也是很明显的,API封装臃肿,内存消耗大,线程处理不够优美,地图块状切割太大,用户体验不够好等等。比起高德和百度地图来说,还是差了一截。但是,处理地图的方式基本都是一致的,可以作为参考。

尾记:当年研究的热血涌上心头,不断地自我突破,它是我1个人花了1个多月的时间做出来,同样的Deadline,安卓是两个人做的,还不算上我调通接口的时间。而且,论体验和Bug率来说,都属上层,最终让地测院的人赞不绝口。

当然,当初的架构还没有使用到我文章列表的“iOS架构”,代码的结构也非常的不优美,但是注释还是都有的。之所以出现这样的问题,还是那句话:Deadline不是第一生产力,只是给了你把一堆Shit交上去的勇气。

看完了,不点个赞吗?哈哈哈,随便你,开心就好。

你可能感兴趣的:(iOS ArcGIS 10.2.5 详细介绍)