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产品。

c# 基于GMap.NET实现电子围栏功能(WPF版)

围栏区域界面显示

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#,GMap.NET,电子围栏,wpf
0
投稿

猜你喜欢

  • SpringBoot整合Shiro实现登录认证的方法

    2022-03-23 01:12:19
  • 深入剖析Java中String类的concat方法

    2023-06-02 07:07:13
  • ShardingSphere jdbc集成多数据源的实现步骤

    2023-11-25 07:54:56
  • C#控制台模拟电梯工作原理

    2021-08-06 02:35:33
  • android显示TextView文字的倒影效果实现代码

    2022-11-13 00:05:40
  • Java并发编程之ConcurrentLinkedQueue源码详解

    2023-01-22 16:19:51
  • c# DataDirectory的用法

    2021-07-30 14:36:22
  • SpringMVC统一异常处理实例代码

    2021-05-31 02:29:26
  • C#实现为一张大尺寸图片创建缩略图的方法

    2021-08-03 21:07:15
  • spring boot集成smart-doc自动生成接口文档详解

    2023-11-28 23:08:02
  • Java数据结构BFS广搜法解决迷宫问题

    2023-12-03 16:04:33
  • maven assembly打包生成Java应用启动脚本bat和sh的方法

    2023-09-16 03:06:54
  • Java关键字synchronized原理与锁的状态详解

    2021-11-16 05:30:29
  • Java反射如何修改private final成员变量值

    2023-09-06 03:43:18
  • Spring Boot 详细分析Conditional自动化配置注解

    2021-11-25 21:56:14
  • C#使用List类实现动态变长数组的方法

    2022-11-30 03:44:35
  • Java设计模式之模板方法模式详解

    2021-08-04 04:32:51
  • 半小时实现Java手撸网络爬虫框架(附完整源码)

    2022-11-23 15:59:35
  • 详解Java使用JMH进行基准性能测试

    2021-08-01 12:22:32
  • Java中List与数组之间的相互转换

    2023-09-10 03:44:28
  • asp之家 软件编程 m.aspxhome.com