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其它相關文章!

推薦閱讀: