欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-07-08 11:51:59
前言gmap.net是一个强大、免费、跨平台、开源的.net控件。分为wpf和winform版。gmap.net的基本知识不做过多介绍,本文主要介绍如何使用该控件实现电子围栏功能。电子围栏主要有两个功...

前言 

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

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