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里没有现成的,至少没有我需要的这个样子,先上效果:

Arcgis apis for flex项目实例—开发篇(4):测距工具

      这个效果看起来略诱人吧,很眼熟吧,就不说从哪抄袭的了,我们只是研究一下技术。首先拆分一下,我们需要哪些元素来组建一条测距线。答案不是一个、不是两个、三个,而是….五个!分别是测距线、节点、节点距离注记、终点距离注记、关闭按钮。这个测距是完全绘制在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):测距工具

上一篇:SharePoint Client Object Model API 介绍以及工作原理解析


下一篇:WinForm下编写分页控件,实现DataGridView的分页效果