给DotSpatial实现中距离/长度测量图层

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();
        }


把MeasureLengthFunction注册到地图控件是MapFunction列表中, 激活此MapFunction后可以看到如下效果:




转载于:https://my.oschina.net/zhtqs/blog/1509823

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值