c# 基于GMap.NET实现电子围栏功能(WPF版)
前言
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实现电子围栏的资料请关注其它相关文章!
上一篇: SQL中EXPLAIN命令的使用方法