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三剑客就也可以,有兴趣可以自己做着玩儿。至此地图工具完毕,下一节开始,要开始进行查询了。

时间: 2024-08-02 11:02:37

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

Arcgis apis for flex项目实例—开发篇(1):地图浏览

前文已经明确数据用天地图,这样的选择一来是免费,二来各种来源的数据都大同小异,天地图用的2000坐标系是常见的经纬度,方便专题数据叠加. 闲话少说,看一下arcgis api for flex是如何吃定天地图的吧.首先要明确的是我们需要用到四个天地图瓦片服务,分别是经纬度地图底图.经纬度地图中文注记.经纬度影像底图.经纬度影像中文注记,详情可以参阅http://www.tianditu.com/guide/index.html.各种服务形式上都差不多,我们来写一个天地图图层类. 首先新建一个类,

Arcgis apis for flex项目实例—开发篇(6):自己的infowindow

前文已经完成了从数据库查询旅游景点的工作,下面就是要把这些查询的结果显示到地图上,同时要实现查询列表和地图的联动.也就是点击列表中的某一项,地图能定位至该位置并弹出infowindow. 这个开发工作本身没有太大的难度,但是我认为的增加了一点障碍,因为我不太喜欢arcgis api自带的infowindow,我需要自己做一个,这一节内容就从这个DIY的infowindow开始.首先来设计一下这个窗体,为了表示不抄袭,这个infowindow改名PopupWindow. PopupWindow.m

Arcgis apis for flex项目实例—开发篇(2):鹰眼图

在底图调用完成后,我们要为地图添加一些工具,首先就是出场率很高的鹰眼图.我这里的鹰眼图是从ESRI的Flex Viewer中剥离出来的,申明一下,怕ESRI说我侵权.经常有人质疑我为什么不直接用Viewer而要费心思自己去写一些组件.我个人的经验是Viewer东西太多了,很臃肿,对于我们这样有整洁强迫症的人来说工程里存在大量用不着的代码非常难受,其实也不光是心理作用了,确实Viewer不经过大量优化会加载比较慢.但是,Viewer中有很多思想和很多组件是非常好用的,比如这个鹰眼,所以我们依然要借

Arcgis apis for flex项目实例—开发篇(5):查询

前文已完成了基本的地图工具,现在要开始一个GIS项目的核心内容——查询.按这个项目的设计,查询要分为政区查询和关键字查询两类,其实都大同小异,这里就只按关键字查询来写了. 查询在一个系统中一般有几个要素,一是要有条件输入,这里就是一个文本框和一个搜索按钮:二是要有结果列表,项目设计的左侧区域就承担了这个任务,计划用一个list来实现:三是要有地图展示,地图展示主要是把查询结果在地图上用符号标注出来,并且能提供一个infowindow来展示详细信息.本节内容不包括第三点,与地图的一些联动在下节单独

Arcgis apis for flex项目实例—开发篇(3):地图级别控制器

地图级别控制器俗称“鱼骨头”,这几乎是Web地图的标配了.ESRI的flex api自带了级别控制器叫做zoomSlider,我觉得是非常的难看,难看的和“鱼骨头”都不太沾边,给任何客户提供这样的一个组件都是非常不严肃的事情,我的选择是要么索性不提供这个工具,要么,就自己做一个.这是我做的组件的效果: 这个组件可以整合很多的地图工具,本节只讨论中间的那个滑块部分,就是从“+”到“-”之间的部分,其他的工具留给下一节. 先来看布局,一个Group把所有的部件框起来,放下两个按钮.两个Rect.两个

Arcgis apis for flex项目实例—美工篇(1):样式与布局

前文已完成基本的功能,该是进入美工阶段了.我也只是一个小前端程序员,没有能力把页面做的美如画,美工的原则只有两点:一是采用大众化的页面风格,配色和谐,符合大多数用户的操作习惯:二是尽可能采用自定义控件样式,不要出现flex自带的控件样式.这一节先简要介绍一下总体的样式和布局.改变一下习惯,先美工处理过后的效果图: 从最终的结果看主要由几点改变:首先是有了主题色——绿.蓝.橙为主的组合:其次是加入了一些页面元素,页面整体变得丰满:然后是很多控件样式发生了变化,如滚动条.地图切换按钮.搜索框.工具按

Arcgis apis for flex项目实例—谋局篇(2):界面设计

界面设计是谋局当中第二个任务,也是很重要的,这个和概念设计排名不分先后.合理的界面布局会给用户带来非常舒心的体验,在项目之处就能把界面风格和布局确定下来,更是可以让后面的开发事半功倍. First:基本布局.我们不是艺术家,不是激进派,四平八稳符合我们的风格,也符合快速开发的要求,布局就采用上天下地,左表右图的经典百度地图式布局,下面开始. 基本元素:三个border.一个是主窗口,也就是map,一个border是网站的头部,也就是展示个名字放点图片当好看的:一个border是左侧栏,用于查询输

南沙政府应急系统之GIS一张图(arcgis api for flex)讲解(一)GIS一张图的系统开发环境以及flexviewer框架

系统的GIS功能实现是基于arcgis api for flex,首先附上系统的主界面图,接下来的是对主界面的模块功能详细讲解: 一.GIS环境软件安装 (1)arcgis desktop的安装,要是不想对地图数据进行样式配置或者数据加工的话,这步可以跳过不用安装,详细的安装步骤见这里: (2)arcgis server的安装,这个是必须的,用来发布webgis的地图服务,比如地图基础服务,路径分析的网络服务,地理编码服务等等,详细的安装步骤见这里: (3)arcsde直连创建地理企业数据库,为

《ArcGIS Runtime SDK for Android开发笔记》——(9)、空间数据的容器-地图MapView

1.前言 在上一篇内容里介绍了 关于ArcGIS Android开发的未来(“Quartz”版Beta)相关内容,期间也提到了关于API接口的重构,开发思路的调整,根据2015UC资料也可以知道新版预计将在明年的时候推出.届时在开发思路上将会往新版迁移. 总的来说,虽然“Quartz”版的开发思路有所变化,但总体变化不大,这里我将继续以现有正式发布版本为主梳理ArcGIS Runtime SDK for Android 开发内容. 参考API版本号:version 10.2.7.后续内容若不做特