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實現電子圍欄的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- c# wpf使用GMap.NET類庫,實現地圖軌跡回放
- C# .Net實現灰度圖和HeatMap熱力圖winform(進階)
- C#異常執行重試的實現方法
- C#實現簡易灰度圖和酷炫HeatMap熱力圖winform(附DEMO)
- c# WinForm制作圖片編輯工具(圖像拖動、縮放、旋轉、摳圖)