基于DotSpatial实现面积测量图层

时间:2022-05-17 20:32:00

前面时间写了一个距离测量图层的实现方法。今天写一个面积测量图层的实现方法。

面积测量的关键在于用多边形表示测量图形,坐标点都是地图坐标。我们可以手动添加要素,用一个或者多个表示多边形的要素表示测量区域。DotSpatial自带3种类型的图层:MapPointLayer、MapLineLayer与MapPolygonLayer,并在DotSpatial.Topology.Algorithm的命名空间中提供了相关算法类型 。MapPolygonLayer本是不标绘测量信息,因为我们需要继承MapPolygonLayer类型,取名为MeasureAreaLayer,代码如下:

using DotSpatial.Controls;
using DotSpatial.Data;
using DotSpatial.Symbology;
using DotSpatial.Topology;
using DotSpatial.Topology.Algorithm;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CustomLayer
{
class MeasureAreaLayer : MapPolygonLayer
{

public MeasureAreaLayer()
{
}
}
}

加入计算并标绘测量结果的方法,代码如下:

        private void DrawMeasureInformation(IFeatureList features, MapArgs args)
{
if (double.IsInfinity(args.Dx) || double.IsInfinity(args.Dy) || double.IsInfinity(args.MinX) || double.IsInfinity(args.MaxY))
return;
var font = new Font("微软雅黑", 14);
foreach (var feature in features)
{
var length = Math.Abs(CgAlgorithms.SignedArea(feature.Coordinates));
var unit = "平方米";
var tool = new CentroidArea();
tool.Add(feature.Coordinates.ToArray());
var coord = tool.Centroid;
if (double.IsNaN(coord.X) || double.IsInfinity(coord.X))
{
Console.WriteLine("已跳过一行。");
continue;
}
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 > 1000000)
{
length /= 1000000;
unit = "平方公里";
}
shift.Translate(pt.X, pt.Y);
args.Device.Transform = shift;
var draws=length.ToString("F2") + unit;
args.Device.DrawString(draws, font, Brushes.Red, new PointF(-args.Device.MeasureString(draws, font).Width/2, 0));
args.Device.Transform = original;
shift.Dispose();
}
}
重载绘图方法,调用面积测量方法,代码如下:

        public override void DrawRegions(MapArgs args, List<Extent> regions)
{
base.DrawRegions(args, regions);
DrawMeasureInformation(FeatureSet.Features, args);
}
DotSpatial有一个限制过快刷新地图的机制,我想到一个动态生成标绘图形的方法给绘图方法调用以解决这个问题,代码如下:

        public void SaveToBitmap(Image img, Rectangle rc, Extent ext, List<Coordinate> coordinates)
{
var g = Graphics.FromImage(img);
var a = new MapArgs(rc, ext, g);
var p = new MapPointLayer() { Projection = MapFrame.Projection };
var l = new MapLineLayer() { Projection = MapFrame.Projection };
var n = new MapPolygonLayer() { Projection = MapFrame.Projection };
var e = new List<Extent>() { ext };

n.Symbolizer = Symbolizer;
p.Symbolizer = new PointSymbolizer(Color.Blue, DotSpatial.Symbology.PointShape.Ellipse, 4);
l.Symbolizer = new LineSymbolizer(Color.Blue, 1);
if (coordinates.Count > 2)
n.FeatureSet.Features.Add(new Feature(FeatureType.Polygon, coordinates));
else if (coordinates.Count == 1)
p.FeatureSet.AddFeature(new Feature(FeatureType.Point, coordinates));
else if (coordinates.Count == 2)
l.FeatureSet.AddFeature(new Feature(FeatureType.Line, coordinates));
n.DrawRegions(a, e);
p.DrawRegions(a, e);
l.DrawRegions(a, e);
if (coordinates.Count > 2)
DrawMeasureInformation(n.FeatureSet.Features, a);
n.Dispose();
p.Dispose();
l.Dispose();
g.Dispose();
}

提供用于支持用户绘图操作的地图函数,类型定义代码如下:

using DotSpatial.Controls;
using DotSpatial.Data;
using DotSpatial.Topology;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CustomLayer
{
class MeasureAreaFunction : MapFunction
{
private MeasureAreaLayer measureAreaLayer;
private List<Coordinate> coordinates;
private Bitmap backBufferImage = null;
private bool bMouseDown = false;
private bool bDoubleClicked = false;
private bool bMeasuring = false;

internal MeasureAreaFunction(IMap mapCtrl, MeasureAreaLayer measureAreaLayer) :
base(mapCtrl)
{
this.measureAreaLayer = measureAreaLayer;
coordinates = new List<Coordinate>();
}
}
}

measureAreaLayer对象是上面定义的图层类型的对象,coordinates用来记录单次测量操作中多边形的坐标列表。backBufferImage用于鼠标移动时动态更新测时结果。bMouseDown表示当前鼠标是否按下,bDoubleClicked表示当前是否刚刚鼠标双击过,防止错误的记录鼠标点。bMeasuring表示当前是否开始了新的测量操作。

重载地图函数激活方法,预处理测量之前的操作,代码如下:

        protected override void OnActivate()
{
base.OnActivate();
coordinates.Clear();
backBufferImage = new Bitmap(Map.ClientRectangle.Width, Map.ClientRectangle.Height, PixelFormat.Format32bppArgb);
Map.MapFrame.SaveLayersToBitmap(new List<Extent>() { Map.ViewExtents }, backBufferImage, null);
bMouseDown = false;
bDoubleClicked = false;
}
重载鼠标按下事件,代码如下:

        protected override void OnMouseDown(GeoMouseArgs e)
{
base.OnMouseDown(e);
bMouseDown = true;
bMeasuring = true;
}
重载鼠标双击事件,用来处理单次测量结束事件,生行多边形对象到测量图层中。代码如下:

        protected override void OnMouseDoubleClick(GeoMouseArgs e)
{
base.OnMouseDoubleClick(e);
if (coordinates.Count < 3)
return;
coordinates.Add(coordinates.First());
var coords = new List<Coordinate>();
coords.AddRange(coordinates);
var feature = new Feature(new Polygon(coords));
feature.UpdateEnvelope();
measureAreaLayer.FeatureSet.AddFeature(feature);
measureAreaLayer.FeatureSet.InitializeVertices();
measureAreaLayer.Invalidate();
backBufferImage.Dispose();
backBufferImage = new Bitmap(Map.ClientRectangle.Width, Map.ClientRectangle.Height, PixelFormat.Format32bppArgb);
Map.MapFrame.SaveLayersToBitmap(new List<Extent>() { Map.ViewExtents }, backBufferImage, null);
coordinates.Clear();
bDoubleClicked = true;
bMeasuring = false;
}
这里有一个坑, MapPolygonLayer并没有管理Coordinate对象列表,它管理的是Coordinate列表的引用,这里开始新的测量操作会删除就的坐标点列表,导致图层的坐标点列表消失。正确的方法是为此图层生成一个坐标点列表对象,把对象的引用传递图层。下面重载鼠标弹起事件,用来新增坐标,代码如下:
        protected override void OnMouseUp(GeoMouseArgs e)        {            base.OnMouseUp(e);            if (bDoubleClicked == false)                coordinates.Add(e.GeographicLocation);            bDoubleClicked = false;            bMouseDown = false;        }
重载鼠标移动事件,用来动态显示测量结果,代码如下:

        protected override void OnMouseMove(GeoMouseArgs e)
{
base.OnMouseMove(e);
if (bMouseDown)
return;
if (!bMeasuring)
return;
var cs = new List<Coordinate>();
var img1 = new Bitmap(Map.ClientRectangle.Width, Map.ClientRectangle.Height, PixelFormat.Format32bppArgb);
var img2 = new Bitmap(Map.ClientRectangle.Width, Map.ClientRectangle.Height, PixelFormat.Format32bppArgb);
cs.AddRange(coordinates);
cs.Add(e.GeographicLocation);
measureAreaLayer.SaveToBitmap(img1, Map.ClientRectangle, Map.ViewExtents, cs);
var g2 = Graphics.FromImage(img2);
g2.DrawImage(backBufferImage, 0, 0);
g2.DrawImage(img1, 0, 0);
var g = (Map as Map).CreateGraphics();
g.DrawImage(img2, 0, 0);
g2.Dispose();
g.Dispose();
img1.Dispose();
img2.Dispose();
}
重载地图失效事件,进行相关的清理工作,代码如下:

        protected override void OnDeactivate()
{
base.OnDeactivate();
if (backBufferImage != null)
backBufferImage.Dispose();
coordinates.Clear();
bMeasuring = false;
bMouseDown = false;
bDoubleClicked = false;
}
在地图控件中加入这个面积测量图层和面积测量地图函数,激活函数后可看到如下效果:

基于DotSpatial实现面积测量图层

说明:地图用的是在网上下载的地图,坐标系是北京54,兰博特投影,*经线105度,东向加常数50公里,Y轴北向。