首页 > 代码库 > Arcgis apis for flex项目实例—开发篇(4):测距工具
Arcgis apis for flex项目实例—开发篇(4):测距工具
这一节就是要完成一个很常见的动态测距工具,一边绘制测距线,一边给出每个节点的结果。顺便把基本的四个方向的移动、放大、缩小、自由平移等map工具都补全。
四个方向的箭头很简单,我直接贴代码了。
<commonitem:NavButton id="btn_up" top="10" horizontalCenter="0" icon="image/up_arrow.png" click="map.panUp()"> </commonitem:NavButton> <commonitem:NavButton id="btn_left" top="30" horizontalCenter="-20" icon="image/left_arrow.png" click="map.panLeft()"> </commonitem:NavButton> <commonitem:NavButton id="btn_right" top="30" horizontalCenter="20" icon="image/right_arrow.png" click="map.panRight()"> </commonitem:NavButton> <commonitem:NavButton id="btn_down" top="50" horizontalCenter="0" icon="image/down_arrow.png" click="map.panDown()"> </commonitem:NavButton>
放大、缩小、平移可以利用API提供的NavigationTool来实现,也很简单,代码我都不上了,就是NavigationTool自带的activate(NavigationTool.ZOOM_IN)、activate(NavigationTool.ZOOM_OUT),至于平移的话,就是把navigationTool直接deactivate()就行了。
我们重点看测距,因为这个功能API里没有现成的,至少没有我需要的这个样子,先上效果:
这个效果看起来略诱人吧,很眼熟吧,就不说从哪抄袭的了,我们只是研究一下技术。首先拆分一下,我们需要哪些元素来组建一条测距线。答案不是一个、不是两个、三个,而是….五个!分别是测距线、节点、节点距离注记、终点距离注记、关闭按钮。这个测距是完全绘制在map上的,所以这五个元素都必须是map支持的某种东西,那最好都是symbol,具体来说是一个LinsSymbol作为测距线、一个MarkerSymbol作为节点、两个TextSymbol作为标注、一个CompositeSymbol作为关闭按钮。因为测距功能小有点复杂,我把他们写在了一个叫Measure的类里,先看一下symbol们的定义,具体实现等会在初始化里放出。
public class Measure { private var lineSymbol:SimpleLineSymbol; private var markerSymbol:SimpleMarkerSymbol; private var normalLabel:TextSymbol; private var endLabel:TextSymbol; private var deleteLabel:CompositeSymbol; }
主要构成元素弄明白以后,下面先把准备工作做好。测距是在map上,map要传进来的,这个可以利用类的构造函数来完成,在map上绘制东西自然是DrawTool最合适,DrawTool就还需要一个GraphicsLayer,这些我们都在初始化里全部做好。为了方便对measure的控制,我还设计了一个IsActive属性,下面看前期的代码:
private var drawLayer:GraphicsLayer; private var drawTool:DrawTool; private var isActive:Boolean; public function set IsActive(value:Boolean):void { isActive = value; if(isActive) { InitDraw(); } else { StopDraw(); } } public function get IsActive():Boolean { return isActive; } public function Measure(_map:Map) { map = _map; } private function InitDraw():void { //对于这样的类,我很喜欢使用一个函数来完成GraphicsLayer的初始化和添加 drawLayer = AddGraphicLayer("measure"); //红色细实线作为测距线 lineSymbol = new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID,0xff0000,1,2); //小红圈作为节点 markerSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_CIRCLE,8,0xffffff,1,0,0,0,lineSymbol); //起点和中继点的注记符号,把用于显示的文字绑定于TEXT上 normalLabel = new TextSymbol(); normalLabel.background = true; normalLabel.backgroundColor = 0xffffff; normalLabel.border = true; normalLabel.borderColor = 0x666666; normalLabel.color = 0x666666; normalLabel.placement = TextSymbol.PLACEMENT_START; normalLabel.xoffset = 10; normalLabel.textAttribute = "TEXT"; //终点的注记符号,同样绑定在Text上 endLabel = new TextSymbol(); endLabel.background = true; endLabel.backgroundColor = 0xffffff; endLabel.border = true; endLabel.borderColor = 0xff0000; endLabel.color = 0x000000; endLabel.yoffset = 20; endLabel.textAttribute = "TEXT"; //删除按钮的符号,用一个组合符号把按钮图标放在一个正方形的框里 deleteLabel = new CompositeSymbol(); var picSymbol:PictureMarkerSymbol = new PictureMarkerSymbol("image/edit_cancel.png",16,16,15,-15); var borderSymbol:SimpleMarkerSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_SQUARE,18,0xffffff,1,15,-15,0, new SimpleLineSymbol("solid",0xff0000,1,1)); deleteLabel.symbols=[borderSymbol,picSymbol]; //drawTool初始化只需注意一下我们额外需要一个DRAW_START事件 drawTool = new DrawTool(map); drawTool.showDrawTips = false; drawTool.graphicsLayer = drawLayer; drawTool.lineSymbol = lineSymbol; drawTool.addEventListener(DrawEvent.DRAW_END,drawEnd); drawTool.addEventListener(DrawEvent.DRAW_START,drawStart); } //停止绘制时把绘图图层移除 private function StopDraw():void { if(map.getLayer("measure")) map.removeLayer(map.getLayer("measure")); } //这是我非常喜欢的添加图层函数,初始化、添加、赋值全部打包 //如果map里有这个ID的图层,就直接放回这个图层 //如果map里还没有,就new一个,放进去再返回这个图层 private function AddGraphicLayer(layerid:String):GraphicsLayer { var glayer:GraphicsLayer; if(map.getLayer(layerid)!=null) { glayer = map.getLayer(layerid) as GraphicsLayer; } else { glayer = new GraphicsLayer(); glayer.id = layerid; map.addLayer(glayer); } return glayer; }
以上就完成了准备工作。下面来看一下这个实现思路。我这个测距是实时进行的,鼠标点下第一个点,绘制一个起点,然后不断的绘制延伸这个测距线;每点击一次鼠标,就会生成一个中继节点,并在节点上计算从起点到当前节点的距离;直至双击结束绘图时,放置终点,计算总距离,并放置一个删除按钮;点击删除按钮,与这根测距线相关的所有线、节点、注记都要删除。这个实现过程有几个要点:
(1)drawTool本身会控制地图上的点击,但是这还不够,我们仍然需要控制mapClick事件来进行节点的添加;
(2)实时计算距离,就不能利用server的geometryService,何况我根本就没有server可用,对于天地图的2000坐标系4490可以使用API提供的GeometryUtil.geodesicLengths(),若是平面坐标系,就自己计算吧;
(3)测距线中各种元素都是graphic,graphic的attributes是个好东西,我们可以用它来传递很多值出来,特别提出的是,我们这个设计是可以一条接一条的绘制多条线在地图上,点击删除按钮的时候只能删除它所在的那一条,这需要在各个元素的attributes里放下一个sierialID,同一条测距线里的元素这个玩意儿相同,来标识一下。
(4)mapClick和drawTool注定不是一个东西,当drawTool向前发展时,遇到一个大红圈会干扰他对于地图的操作,我设计了timer延时器,让drawTool点下去以后,再把大红圈画上去,这样从视觉上看好像计算机有一个计算距离并显示的过程,还对体验提升有所帮助。
废话有点多了,后面只有代码:
//测距需要的几个全局变量 //节点数组,这是实际用于计算距离的值 private var polyArray:Array; //用这个来标识是否应放下终点注记 private var isDraw:Boolean; //当前节点,每次mapClick刷新 private var currentPoint:MapPoint; //每条测距线的标识码 private var sierialId:int = 0; //鼠标点击measure按钮,就执行这个函数开始测距 public function MeasureDistance():void { drawTool.activate(DrawTool.POLYLINE); polyArray = new Array(); isDraw = true; map.panEnabled = false; map.addEventListener(MapMouseEvent.MAP_CLICK,mapClicked); } //开始绘图时自增一下当前的测距线标识码 private function drawStart(event:DrawEvent):void { sierialId+=1; } //结束绘图时的操作,AppEvent是一个事件,目的在于通知页面测距完成了 private function drawEnd(event:DrawEvent):void { event.graphic.attributes = {id:sierialId}; drawTool.deactivate(); map.removeEventListener(MapMouseEvent.MAP_CLICK,mapClicked); map.panEnabled = true; isDraw = false; AppEvent.dispatch(Measure.MEASUREEND); } //绘图过程中点击地图时记录节点,注意每次记录节点的过程用timer来控制 private function mapClicked(event:MapMouseEvent):void { currentPoint = event.mapPoint; //0.2秒的延迟,放置节点红圈干扰drawTool var timer:Timer = new Timer(200); timer.addEventListener(TimerEvent.TIMER,timerEnd); timer.start(); } //timer里实际进行的就是距离计算和放置注记等复杂的工作 private function timerEnd(e:TimerEvent):void { (e.currentTarget as Timer).stop(); //拿到节点,一定要new啊,一定要new一个,否则会很悲剧 var mp:MapPoint = new MapPoint(currentPoint.x,currentPoint.y,currentPoint.spatialReference); //往用于计算的逻辑数组里塞入这个节点 polyArray.push(mp); //如果还在绘图当中的话,那就是放妖放下中继点或起点 if(isDraw) { //这样是起点,记得用TEXT来传递标注文字,用id来传递标识码 if(polyArray.length == 1) { drawLayer.add(new Graphic(mp,new CompositeSymbol([markerSymbol,normalLabel]),{TEXT:"起点",id:sierialId})); } //这样就是中继点,这是要计算距离的,注意计算时,我把polyArray实时转换成了Polygon else { var lenghthStr:String = LenghthCaculator(new Polyline([polyArray],map.spatialReference)); drawLayer.add(new Graphic(mp,new CompositeSymbol([markerSymbol,normalLabel]),{TEXT:lenghthStr,id:sierialId})); } } //结束绘图的话,就放终点 else { var totallenghthStr:String = LenghthCaculator(new Polyline([polyArray],map.spatialReference)); drawLayer.add(new Graphic(mp,new CompositeSymbol([markerSymbol,endLabel]),{TEXT:"总长:"+totallenghthStr,id:sierialId})); //这是要放删除按钮,就是一个graphic啦,但是需要绑定一下Click事件。 var deleteGr:Graphic = new Graphic(mp,deleteLabel,{id:sierialId}); deleteGr.toolTip = "删除"; deleteGr.buttonMode = true; deleteGr.addEventListener(MouseEvent.CLICK,deleteHandler); drawLayer.add(deleteGr); } } //删除的时候根据标识码来删除对应测距线中的元素 private function deleteHandler(event:MouseEvent):void { var id:int = (event.currentTarget as Graphic).attributes.id; var deletArray:Array = new Array(); for each(var line:Graphic in drawLayer.graphicProvider) { if(line.attributes.id == id) { deletArray.push(line); } } for each(var graphic:Graphic in deletArray) { drawLayer.remove(graphic); } //如果删除的是最后一条测距线,就把这个测距图层移除,并且让测距功能关闭,减少资源占用 if((drawLayer.graphicProvider as ArrayCollection).length == 0) this.IsActive = false; } //距离计算,注意使用的函数,另外距离比较短的时候单位可以变为米 private function LenghthCaculator(polyline:Polyline):String { var lenghth:Number = GeometryUtil.geodesicLengths([polyline],Units.METERS)[0]; if(lenghth>1000) return (lenghth/1000).toFixed(2)+"公里"; else return lenghth.toFixed(2)+"米"; }
万事大吉,最后看一下页面里的调用,注意我前面已经以measureTool = new Measure(map)的形式初始化过了这个measureTool。
if(!measureTool.IsActive) measureTool.IsActive = true; measureTool.MeasureDistance();
限于篇幅,这一节我没有给出完整的代码,不过这个类的使用是很基本的东西啦应该不要紧。这样的测距线兼顾了美观与用户体验,还有很多其他的办法可以实现,比如使用常见的鼠标down、move、up三剑客就也可以,有兴趣可以自己做着玩儿。至此地图工具完毕,下一节开始,要开始进行查询了。
Arcgis apis for flex项目实例—开发篇(4):测距工具