这一节就是要完成一个很常见的动态测距工具,一边绘制测距线,一边给出每个节点的结果。顺便把基本的四个方向的移动、放大、缩小、自由平移等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三剑客就也可以,有兴趣可以自己做着玩儿。至此地图工具完毕,下一节开始,要开始进行查询了。