首页 > 代码库 > 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):测距工具