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

2022-07-24,,,

前言 

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为我们做的工作并不多,关键是要掌握处理这一类问题的精髓,做到举一反三,许多问题就会迎刃而解。

以上就是c# 基于gmap.net实现电子围栏功能(wpf版)的详细内容,更多关于c# gmap.net实现电子围栏的资料请关注其它相关文章!

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

下载本文的Word格式文档,以方便收藏与打印。