c# 基于GMap.NET实现电子围栏功能(WPF版)
作者:源之缘 发布时间:2023-01-28 05:13:01
前言
GMap.NET是一个强大、免费、跨平台、开源的.NET控件。分为WPF和winform版。GMap.NET的基本知识不做过多介绍,本文主要介绍如何使用该控件实现电子围栏功能。
电子围栏主要有两个功能模块:界面展示围栏区域,判断人员出入围栏的逻辑。GMap.NET的WPF版本功能并不强大,实现一些复杂的功能就只能发掘WPF的潜力了。GMap.NET给我们提供了一个基本的平台,必须熟练掌握WPF才能开发出复杂gis产品。
围栏区域界面显示
1 认识 GMapMarker
GMapControl是地图的主容器;地图就是多个图片拼接而来,这个图片组成GMapControl的底图。底图之上点缀用户自定义的控件。用户自定义控件必须通过GMapMarker间接添加进来,看下面代码:
GMapMarker maker = new GMapMarker(ptLatLng);
//UserControlFence用户自定控件
_ctrlCurrentFence = new UserControlFence() { Marker = maker, MapCtrl = MainMap };
_ctrlCurrentFence.FenceInfo = CreateFenceInfoModel();
maker.Shape = _ctrlCurrentFence;
this.MainMap.Markers.Add(maker);
GMapMarker 的定义也不复杂:
public class GMapMarker : INotifyPropertyChanged
{
public object Tag;
public GMapMarker(PointLatLng pos);
public UIElement Shape { get; set; }
public PointLatLng Position { get; set; }
public GMapControl Map { get; }
public Point Offset { get; set; }
public int LocalPositionX { get; }
public int LocalPositionY { get; }
public int ZIndex { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public virtual void Clear();
protected void OnPropertyChanged(string name);
protected void OnPropertyChanged(PropertyChangedEventArgs name);
}
一个GMapMarker关联一个gps坐标,同时可以显示一个控件(Shape );为什么在Shape外面包含一个marker?maker主要功能就是将控件钉到GMapControl的一个点。当地图移动时,maker会做相应的移动,maker移动会带动shape移动。所以,我们只管把shape内部处理好就行了,不用管地图移动。maker的作用不大,并不能帮我们实现复杂的功能;Shape才是我们施展拳脚的地方。
2 用户控件实现画图
在控件中UserControlFence实现电子围栏的绘制,该控件会关联到maker的shape。UserControlFence控件以Grid(name为gridRoot)布局;WPF的Path可以实现任意图像的绘画,首先要将Path加入到Grid。我们的输入是多个gps点坐标,怎么能转换成Path上各个点坐标? 这需要经过多次转换;
Point ToCtrlPoint(PointLatLng gpsPoint)
{
//转换成GMap.NET控件坐标
GPoint ptOfMapCtrl = MapCtrl.FromLatLngToLocal(gpsPoint);
//GMap.NET控件坐标要转换成 控件相对于直接父面板的坐标
Point ptToMapCtrl2 = new Point(ptOfMapCtrl.X, ptOfMapCtrl.Y);
//转成屏幕坐标
Point ptOfScreen = MapCtrl.PointToScreen(ptToMapCtrl2);
//转换成相对于gridRoot的坐标
Point ptOfParentPanel = gridRoot.PointFromScreen(ptOfScreen);
return ptOfParentPanel;
}
转换过程就是:相对于Map控件坐标-->屏幕坐标-->相对于Grid的坐标。因为Path是Grid的Child,最后的坐标也是相对于Grid的坐标。用该坐标绘制Path,就是电子围栏的区域;
Path的Data是Geometry,生成Geometry函数如下:
private PathGeometry CreatPath()
{
if (_listPoints.Count <= 1)
{
PathRouteLine.Data = null;
return null;
}
List<Point> listPt = ListWndPoint;
PathFigure pathFigure = new PathFigure();
pathFigure.StartPoint = listPt[0]; //起始点
pathFigure.IsClosed = true;
for (int i = 1; i < listPt.Count; i++)
{
//加入线段
LineSegment line = new LineSegment() { Point = listPt[i] };
pathFigure.Segments.Add(line);
}
PathGeometry geometry = new PathGeometry();
geometry.Figures.Add(pathFigure);
return geometry;
}
这样就完成电子围栏的区域绘制。还有一点要注意:当地图缩放时,必须重新绘制。地图缩放比例不同,绘制区域大小也会改变(形状不会变)。只需要监视地图控件的事件 public event MapZoomChanged OnMapZoomChanged;就行。
出入电子围栏区域判断
该判断逻辑有多种实现方法,下面逐一介绍;
1 利用WPF的辅助函数 VisualTreeHelper.HitTest
通过判断gps点坐标是否在控件内来判断。gps坐标先要转成控件点坐标(转换函数见前文)。函数实现比较简单;
private bool IsInFence(PointLatLng gpsPoint)
{
if (_listPoints.Count <= 2)
return false;
Point ptWnd = ToCtrlPoint(gpsPoint);
HitTestResult result = VisualTreeHelper.HitTest(gridRoot, ptWnd);
if (result == null || result.VisualHit==null)
return false;
bool hit = result.VisualHit == PathRouteLineInner;
return hit;
}
2 通过GraphicsPath、Region实现
这是System.Drawing下的一组类,属于微软早期的类库;该类的点坐标还是float型,精度不高。对于gps坐标我先做了放大处理,如果不做处理误差会很大。
private bool IsInFence2(PointLatLng gpsPoint)
{
double rate = 100000; //由于float精度问题。对坐标放大处理,否则误差会很大。
System.Drawing.Drawing2D.GraphicsPath pointPath = new System.Drawing.Drawing2D.GraphicsPath();
System.Drawing.PointF[] points = _listPoints.Select(o => new System.Drawing.PointF((float)(o.Lng * rate), (float)(o.Lat * rate))).ToArray();
pointPath.AddLines(points);
pointPath.CloseFigure();
System.Drawing.Region region = new System.Drawing.Region(pointPath);
System.Drawing.PointF ptHit = new System.Drawing.PointF((float)(gpsPoint.Lng * rate), (float)(gpsPoint.Lat * rate));
bool visible = region.IsVisible(ptHit);
return visible;
}
3 直接根据点坐标计算
理论上这种方式效率是最高的,并且不依赖界面控件。但是这种方法不是微软提供的,准确性还需要验证。下面的函数是从网上找的,我对此计算结果做了验证,与前两种计算方法的结果一致的。
private bool IsInFence3(PointLatLng gpsPoint)
{
int count = _listPoints.Count;
if (count < 3)
{
return false;
}
bool result = false;
for (int i = 0, j = count-1; i < count; i++)
{
var p1 = _listPoints[i];
var p2 = _listPoints[j];
if (p1.Lat < gpsPoint.Lat && p2.Lat >= gpsPoint.Lat || p2.Lat < gpsPoint.Lat && p1.Lat >= gpsPoint.Lat)
{
if (p1.Lng + (gpsPoint.Lat - p1.Lat) / (p2.Lat - p1.Lat) * (p2.Lng - p1.Lng) < gpsPoint.Lng)
{
result = !result;
}
}
j = i;
}
return result;
}
后记
电子围栏区域绘制方法与轨迹回放、测距等处理有类似之处;GMap.Net为我们做的工作并不多,关键是要掌握处理这一类问题的精髓,做到举一反三,许多问题就会迎刃而解。
来源:https://www.cnblogs.com/yuanchenhui/p/gis_fence.html


猜你喜欢
- 本文实例为大家分享了C#汉字转换为拼音缩写的实现代码,供大家参考,具体内容如下using System;using System.Confi
- 背景今天我们来谈一下我们自定义的一组WPF控件Form和FormItem,然后看一下如何自定义一组完整地组合WPF控件,在我们很多界面显示的
- 一.枚举和静态常量区别讲到枚举我们首先思考,它和public static final String 修饰的常量有什么不同。我举枚举的两个优
- 背景随着公司业务越来越复杂,在同一个列表中需要展示各种类型的数据。总体结构ItemViewAdapter: 每种类型的卡片分别都是不同的It
- 本文实例为大家分享了java实现上传网络图片到微信临时素材的具体代码,供大家参考,具体内容如下package org.afuos.playc
- 本文实例为大家分享了Unity3D实现物体旋转缩放移动的具体代码,供大家参考,具体内容如下由于项目运行在安卓上,运用到了插件,比较麻烦。你们
- 先来看我们以前利用RestTemplate发起远程调用的代码:存在下面的问题:代码可读性差,编程体验不统一参数复杂URL难以维护1. Fei
- 前言最近看了内部类后,总结一下,首先内部类嵌套在其他内部的类,根据出现的位置和关键字,可以分为以下四种类:成员内部类,静态内部类,方法内部类
- SpringTask是Spring自带的功能。实现起来比较简单。使用SpringTask实现定时任务有两种方式:1.注解方式基于注解@Sch
- java中Path是什么?在计算机上安装Java后,需要设置PATH环境变量以便从任何目录方便地运行可执行文件(javac.exe,java
- 1、简介双重检查锁定(也叫做双重检查锁定优化)是一种软件设计模式。它的作用是减少延迟初始化在多线程环境下获取锁的次数,尤其是单例模式下比较突
- 最近在配置OpenCV的时候,由于使用的是VS2019,结果找不到Microsoft.Cpp.X64.user这个文件。导致每次新建项目都得
- 工厂方法模式(Factory Method):定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。工
- java实现读取、删除文件夹下的文件package test.com;import java.io.File;import java.io.
- 在程序中对文件进行压缩解压缩是很重要的功能,不仅能减小文件的体积,还能对文件起到保护作用。如果是生成用户可以下载的文件,还可以极大的减少网络
- 一、正则表达式去除代码行号作为开发人员,我们经常从网上复制一些代码,有些时候复制的代码前面是带有行号,如:MyEclipse本身自带有查找替
- 本文实例讲述了Java实现的双向匹配分词算法。分享给大家供大家参考,具体如下:目前比较流行的几大分词算法有:基于字符串匹配的分词方法、基于理
- 引言在开发中有时候经常需要一些Http请求,请求数据,下载内容,也有一些简单的分布式应用直接使用Http请求作为跨应用的交互协议。在Java
- Servlet 实现文件上传所谓文件上传就是将本地的文件发送到服务器中保存。例如我们向百度网盘中上传本地的资源或者我们将写好的博客上传到服务
- 通过自定义view实现仿iOS实现滑动两端的点选择时间的效果效果图自定义的view代码public class Ring_Slide2 ex