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為我們做的工作並不多,關鍵是要掌握處理這一類問題的精髓,做到舉一反三,許多問題就會迎刃而解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM