DotSpatial的设计虽然十分强大,但是很多功能没有实现。比如地图比例尺、地图测量功能。几个星期前我已经给DotSpatial实现了比例尺功能,今天来实现一个距离测量图层吧。
首先新建一个解决方案,解决方案类型是VC#的简单winform就用程序。然后添加DotSpatial的程序集的引用。给窗口new一个地图控件,加载一个地图。这些细节不是这篇文章的重点,就不细说了。下面是关键,新建一个MeasureLength类型,继承自MapLineLayer类型,增加一个IsDrawMeasure属性,代码如下:
using DotSpatial.Controls;
using DotSpatial.Data;
using DotSpatial.Topology;
using DotSpatial.Topology.Algorithm;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CustomLayer
{
class MeasureLengthLayer : MapLineLayer
{
public bool IsDrawMeasure { get; set; }
public MeasureLengthLayer()
: base()
{
IsDrawMeasure = true;
}
}
}
IsDrawMeasure属性用来表示当前绘图是否在鼠标移动事件里。接下来增加一个方法,专用于标注测量结果,代码如下:
private void DrawMeasureInformation(MapArgs args)
{
if (double.IsInfinity(args.Dx) || double.IsInfinity(args.Dy) || double.IsInfinity(args.MinX) || double.IsInfinity(args.MaxY))
return;
if (DataSet.Vertex.Length < 3)
return;
foreach (var feature in DataSet.Features)
{
if (feature.Coordinates.Count < 2)
continue;
if (DataSet.Features.IndexOf(feature) != 0)
if (!IsDrawMeasure)
continue;
var length = CgAlgorithms.Length(feature.Coordinates);
var unit = "米";
var coord = feature.Coordinates.First();
var pt = new PointF((float)((coord.X - args.MinX) * args.Dx), (float)((args.MaxY - coord.Y) * args.Dy));
var original = args.Device.Transform;
var shift = original.Clone();
if (length > 1000)
{
length /= 1000;
unit = "公里";
}
shift.Translate(pt.X, pt.Y);
args.Device.Transform = shift;
args.Device.DrawString(length.ToString("F2") + unit, new Font("微软雅黑", 14), Brushes.Red, new PointF(0, 0));
args.Device.Transform = original;
shift.Dispose();
}
}
绘图的关键在于坐标转换。DotSpatial默认的绘图Graphics对象用了一个Matrix进行矩阵转换。如果不对要绘制的东西进行坐标转 换,而是直接用坐标的位置进行绘图的话,界面将看不到任何标绘的测量结果信息。下面重载MapLineLayer的绘图方法,以调用此方法,代码如下:
public override void DrawRegions(MapArgs args, List<Extent> regions)
{
base.DrawRegions(args, regions);
DrawMeasureInformation(args);
}
把这个图层加到地图控件的图层列表中。下面创建一个MeasureLengthMapFunction类型,继承DotSpatial的MapFunction类型,代码如下:
using DotSpatial.Controls;
using DotSpatial.Data;
using DotSpatial.Topology;
using DotSpatial.Topology.Algorithm;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CustomLayer
{
class MeasureLengthFunction : MapFunction
{
private MeasureLengthLayer measureLengthLayer;
private bool bIsMouseDown = false;
private bool bDoubleClicked = false;
private Bitmap bufferImage = null;
internal MeasureLengthFunction(MeasureLengthLayer customLayer, IMap mapCtrl)
: base(mapCtrl)
{
this.measureLengthLayer = customLayer;
}
}
}
声明的几个变量的作用:measureLengthLayer是刚才那个建立的长度测量图层的类的对象,用来操作测量图层。bIsMouseDown用来判断鼠标的按键状态,在鼠标移动事件中,如果鼠标处理按下状态 ,那么鼠标移动事件将被跳过不予处理。bDouleClicked用来判断当前是否鼠标双击了。我设计的测量思想是鼠标点击一个记录一个点,鼠标双击表示一次测量结束,开始新的一次测量。鼠标单击事件紧接着鼠标双击事件,因此事件容易反串,加此标识用来防止事件反串。bufferImage是内存画刷,用来避免用户界面闪烁、解决DotSpatial在鼠标移动时不处理地图事件等问题。
首先重载MapFunction激活事件,用来初始化内存画像:
protected override void OnActivate()
{
base.OnActivate();
measureLengthLayer.IsDrawMeasure = false;
if (bufferImage != null)
bufferImage.Dispose();
bufferImage = new Bitmap(Map.ClientRectangle.Width, Map.ClientRectangle.Height, PixelFormat.Format32bppArgb);
Map.MapFrame.SaveLayersToBitmap(new List<Extent>() { Map.ViewExtents }, bufferImage, measureLengthLayer);
}
然后重载鼠标单击事件,处理开始测量事件:
protected override void OnMouseDown(GeoMouseArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Right)
return;
bIsMouseDown = true;
}
重载鼠标双击事件,控制新测量与旧测量的用户逻辑:
protected override void OnMouseDoubleClick(GeoMouseArgs e)
{
base.OnMouseDoubleClick(e);
measureLengthLayer.IsDrawMeasure = true;
measureLengthLayer.Invalidate();
measureLengthLayer.DataSet.Features.Insert(0, new Feature(FeatureType.Line, new List<Coordinate>()));
bufferImage.Dispose();
bufferImage = new Bitmap(Map.ClientRectangle.Width, Map.ClientRectangle.Height, PixelFormat.Format32bppArgb);
Map.MapFrame.SaveLayersToBitmap(new List<Extent>() { Map.ViewExtents }, bufferImage, measureLengthLayer);
bDoubleClicked = true;
measureLengthLayer.IsDrawMeasure = false;
}
重载鼠标弹起事件,用来记录坐标点:
protected override void OnMouseUp(GeoMouseArgs e)
{
base.OnMouseUp(e);
if (e.Button == MouseButtons.Right)
return;
bIsMouseDown = false;
if (bDoubleClicked == false)
{
if (measureLengthLayer.DataSet.Features.Count == 0)
measureLengthLayer.DataSet.Features.Add(new Feature(FeatureType.Line, new List<Coordinate>()));
measureLengthLayer.DataSet.Features.First().Coordinates.Add(e.GeographicLocation);
if (measureLengthLayer.DataSet.Features.First().Coordinates.Count == 1)
measureLengthLayer.DataSet.Features.First().Coordinates.Add(e.GeographicLocation);
measureLengthLayer.DataSet.Features.First().UpdateEnvelope();
measureLengthLayer.DataSet.InitializeVertices();
}
bDoubleClicked = false;
}
重载鼠标移动事件,控制实时测量信息的显示:
protected override void OnMouseMove(GeoMouseArgs e)
{
if (bIsMouseDown)
return;
if (measureLengthLayer.DataSet.Features.Count < 1)
return;
var coords = measureLengthLayer.DataSet.Features.First().Coordinates;
if (coords.Count < 2)
return;
coords[coords.Count - 1] = e.GeographicLocation;
measureLengthLayer.DataSet.InitializeVertices();
var img2 = new Bitmap(Map.ClientRectangle.Width, Map.ClientRectangle.Height, PixelFormat.Format32bppArgb);
var img3 = new Bitmap(Map.ClientRectangle.Width, Map.ClientRectangle.Height, PixelFormat.Format32bppArgb);
var g2 = Graphics.FromImage(img2);
//g2.SmoothingMode = SmoothingMode.AntiAlias;
var arg = new MapArgs(Map.ClientRectangle, Map.ViewExtents, g2);
measureLengthLayer.DrawRegions(arg, new List<Extent>() { Map.ViewExtents });
var g3 = Graphics.FromImage(img3);
//g3.SmoothingMode = SmoothingMode.AntiAlias;
g3.DrawImage(bufferImage, 0, 0);
g3.DrawImage(img2, 0, 0);
var g1 = (Map as Map).CreateGraphics();
g1.DrawImage(img3, 0, 0);
//g1.SmoothingMode = SmoothingMode.AntiAlias;
g1.Dispose();
g2.Dispose();
g3.Dispose();
img2.Dispose();
img3.Dispose();
}
最的处理测量MapFuntion被关闭的事件:
protected override void OnDeactivate()
{
base.OnDeactivate();
measureLengthLayer.IsDrawMeasure = true;
if (bufferImage != null)
bufferImage.Dispose();
}