GPS圍欄兩個多邊形相交問題的奇葩解法

来源:http://www.cnblogs.com/JangoJing/archive/2016/06/17/5594087.html
-Advertisement-
Play Games

前言 GPS測量儀測量的產地面積,然後提交到系統中,系統需要校驗這塊產地和其他產地是否有重疊,重疊超過10%就要提出警告這塊產地已經被XXX登記入庫了。GPS測量儀測量出來的數據是連續的經緯度坐標數據。現在的問題就轉換成求一個一系列點圍成的區域和其他區域是否存在交集。拿到這個需求我想應該很簡單,網上 ...


前言

GPS測量儀測量的產地面積,然後提交到系統中,系統需要校驗這塊產地和其他產地是否有重疊,重疊超過10%就要提出警告這塊產地已經被XXX登記入庫了。GPS測量儀測量出來的數據是連續的經緯度坐標數據。現在的問題就轉換成求一個一系列點圍成的區域和其他區域是否存在交集。拿到這個需求我想應該很簡單,網上應該有現成的代碼吧。

先上成品

 

最初的想法

一開始想(XMin,YMin)應該是多邊形的左下角,(XMax,YMin)應該是右下角,對應的找到4個頂點轉換成矩形應該會好做一點吧。但是GPS測量出來的數據可不是都是垂直坐標軸的矩形,有的是斜著的,這樣就找不到XMin和YMin了,而且如果是不規則的多邊形怎麼辦,硬要轉成矩形的話那樣誤差很大啊。

 

正統的解法

根據網上搜索到的資料顯示,這個問題屬於計算幾何這門學科解決的問題。然後涉及了很多向量啊,向量的叉積啊,還有各種幾何知識,如何在2-3天之內補習這些知識然後寫出代碼來,看起來是個很艱苦的過程。

  1. 求點集的凹包,目的是減少多邊形的點,一個GPS測量數據包含了少則500,多則3000多個連續的點集,所以要通過凹包來減少點集。
  2. 求兩個凹包多邊形的交集Mix
  3. 求交集Mix多邊形的面積
  4. 比較Mix多邊形的面積和原始凹包多邊形面積的比值

以上步驟最複雜的應該是求凹包和求交集了,原諒我學到凸包多邊形就已經放棄了。

 

奇葩的解法

為何叫奇葩的解法, 其實是為了賺一波眼球而已,我最後選擇的解法是通過gdi32.dll提供的windowsAPI來完成的,下麵介紹下用到的幾個API函數

根據點集創建一個多邊形

 

IntPtr CreatePolygonRgn(Point[] lpPoint, int nCount, int nPolyFillMode);

  

合併兩個多邊形,可以是OR/AND/XOR,這裡用到的是AND

 

int CombineRgn(IntPtr dest, IntPtr src1, IntPtr src2, int flags);

  

獲取一個多邊形的詳細數據,拆解為若幹個矩形

 

int GetRegionData(HandleRef hRgn, int size, IntPtr lpRgnData);

  

所以最後我們的步驟就是

  1. 根據GPS點集創建一個多邊形
  2. 再創建第二個多邊形
  3. 通過CombineRgn函數合併兩個多邊形,返回成功就表示有交集,返回失敗就是無交集
  4. 有交集的情況下,再通過GetRegionData獲取交集和第一個多邊形的信息
  5. 計算GetRegionData里拆解出來的若幹矩形的面積,長*寬
  6. 比較交集的面積和第一個多邊形面積的比值

整個過程看起來很簡單,這裡我上一些代碼,把其中比較難的部分解釋一下。

 

創建一個多邊形

先引用dll

 

		[DllImport("gdi32")]
		private static extern IntPtr CreatePolygonRgn(Point[] lpPoint, int nCount, int nPolyFillMode);

  

代碼中Point是System.Drawing空間下的

X和Y*1000000是因為GPS信息是118.12334232這樣的數據,如果直接取int的話就都變成118了,精度不夠。

代碼中IntPtr是句柄,windowsAPI編程中都是提供句柄,類似記憶體指針的玩意。

 

Point[] poin = new Point[gpsList.Count()];

for (int i = 0; i < gpsList.Count(); i++)
{
    string[] xy = gpsList[i].Split(',');
    double x = ConvertHelp.obj2Double(xy[0], 0);
    double y = ConvertHelp.obj2Double(xy[1], 0);
    poin[i].X = (int)(x * 1000000);
    poin[i].Y = (int)(y * 1000000);
}

IntPtr orginRgn = IntPtr.Zero;
orginRgn = CreatePolygonRgn(poin, poin.Count(), 1);

 

 

比較兩個多邊形

先引用dll

 

    /// <summary>
        /*
         * CombineRgn(
              p1: HRGN;     {合成後的區域}
              p2, p3: HRGN; {兩個原始區域}
              p4: Integer   {合併選項; 見下表}
            ): Integer;     {有四種可能的返回值}

            //合併選項:
            RGN_AND  = 1;
            RGN_OR   = 2;
            RGN_XOR  = 3;
            RGN_DIFF = 4;
            RGN_COPY = 5; {複製第一個區域}

            //返回值:
            ERROR         = 0; {錯誤}
            NULLREGION    = 1; {空區域}
            SIMPLEREGION  = 2; {單矩形區域}
            COMPLEXREGION = 3; {多矩形區域}
         */
        /// </summary>
        /// <param name="dest"></param>
        /// <param name="src1"></param>
        /// <param name="src2"></param>
        /// <param name="flags"></param>
        /// <returns></returns>
        [DllImport("gdi32.dll", CharSet = CharSet.Auto)]
        public static extern int CombineRgn(IntPtr dest, IntPtr src1, IntPtr src2, int flags);

 

使用起來就很簡單,提供3個參數,第一個參數是合併後返回的句柄,第2個參數是多邊形1號,第3個參數是多邊形2號,最後一個flag參數是合併選項,1是and,2是or根據情況選用,這裡選用And所以是1

返回結果0表示錯誤也就是沒有交集,1表示空區域即無交集,2和3都表示有交集存在。

 

int nMix = CombineRgn(nextRgn, orginRgn, nextRgn, 1);
if (nMix != 1 && nMix != 0)
{
    //有交集
}                    

 

 

獲取交集的數據

計算交集的面積,其實就是如何根據句柄讀取記憶體里的數據,因為網上大多數都是C++的寫法,很少能找到。Net的寫法,所以這個部分占用了我一下午時間,包括走了一些彎路 ,最後通過google才找到了正解。

先引用dll,根據API返回的數據結構建立對應的結構

 

        public struct RGNDATAHEADER
        {
            public int dwSize;
            public int iType;
            public int nCount;
            public int nRgnSize;
            public RECT rcBound;
        }

        public struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }
        /// <summary>
        /// 獲取數據參考:http://www.pinvoke.net/default.aspx/gdi32/GetRegionData.html
        /// 數據結構參考:http://www.cnblogs.com/del/archive/2008/05/20/1203446.html
        /// </summary>
        /// <param name="hRgn"></param>
        /// <param name="size"></param>
        /// <param name="lpRgnData"></param>
        /// <returns></returns>
        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
        public static extern int GetRegionData(HandleRef hRgn, int size, IntPtr lpRgnData);

 

 

下麵的代碼只講一下GetRegionData這個API的調用的特殊之處,首先他要調用2次才能正確獲取到數據。

第一次調用如下GetRegionData(hr, 0, IntPtr.Zero),傳遞一個空句柄,此時會返回一個int的值,告訴你需要準備一個多大的記憶體區域。

然後要申請一個記憶體區域準備去接值。IntPtr bytes = Marshal.AllocCoTaskMem(dataSize);就是準備一個特定大小的記憶體區域的句柄。

第二次調用GetRegionData(hr, dataSize, bytes)的時候就能把我們想要的數據填充到bytes這個句柄指向的記憶體區域了。

接下來的問題就是有了句柄如何取結構化的數據了,C#里支持指針操作,但是是unsafe的代碼。最關鍵一句話在下麵已經加了註釋 了。

 

        
	const int RDH_RECTANGLES = 1;

        /// <summary>
        /// 分割多邊形,獲取多邊形內所有的矩形
        /// </summary>
        /// <param name="hRgn"></param>
        /// <returns></returns>
        public unsafe static RECT[] RectsFromRegion(IntPtr hRgn)
        {
            RECT[] rects = null;
            var hr = new HandleRef(null, hRgn);
            // First we call GetRegionData() with a null buffer.
            // The return from this call should be the size of buffer
            // we need to allocate in order to receive the data. 
            int dataSize = GetRegionData(hr, 0, IntPtr.Zero);

            if (dataSize != 0)
            {
                IntPtr bytes = IntPtr.Zero;

                // Allocate as much space as the GetRegionData call 
                // said was needed
                bytes = Marshal.AllocCoTaskMem(dataSize);

                // Now, make the call again to actually get the data
                int retValue = GetRegionData(hr, dataSize, bytes);

                // From here on out, we have the data in a buffer, and we 
                // just need to convert it into a form that is more useful
                // Since pointers are used, this whole routine is 'unsafe'
                // It's a small sacrifice to make in order to get this to work.
                // [RBS] Added missing second pointer identifier
                RGNDATAHEADER* header = (RGNDATAHEADER*)bytes;

                if (header->iType == RDH_RECTANGLES)
                {
                    rects = new RECT[header->nCount];

                    // The rectangle data follows the header, so we offset the specified
                    // header size and start reading rectangles.
                    //獲取偏移
                    int rectOffset = header->dwSize;
                    for (int i = 0; i < header->nCount; i++)
                    {
                        // simple assignment from the buffer to our array of rectangles
                        // will give us what we want.
                        //首先把bytes轉換成指針,得到bytes的地址,然後加上偏移,再轉換為RECT類型的指針。
                        rects[i] = *((RECT*)((byte*)bytes + rectOffset + (Marshal.SizeOf(typeof(RECT)) * i)));
                    }
                }

            }

            // Return the rectangles
            return rects;

        }

 

計算面積

因為上面獲取到的數據其實是一個RECT的數組,而RECT裡面包含的是上下左右四個點的坐標信息,那麼很顯然我們得到的是一個矩形的數組,每次分解大概有2000多個矩形,不用管多少了,直接拿來用就是了

 

        /// <summary>
        /// 計算多邊形的面積
        /// </summary>
        /// <param name="rgn"></param>
        /// <returns></returns>
        public static int CalculateAreas(IntPtr rgn)
        {
            RECT[] rectData = RectsFromRegion(rgn);
            int ret = 0;
            foreach (var rect in rectData)
            {
                int areas = (rect.Bottom - rect.Top) * (rect.Right - rect.Left);
                if (areas < 0)
                    areas = areas * -1;
                ret += areas;
                //Console.WriteLine("{0},{1},{2},{3},{4}", rect.Top, rect.Left, rect.Right, rect.Bottom, areas);
            }
            return ret;
        }

 

 

總結

這次這個需求一開始以為不複雜,網上應該有現成的代碼,實際上搜索後發現涉及的計算幾何的知識對幾何和演算法的要求特別高,無奈幾何知識都還給老師了,補習的話短短2-3天應該是來不及了。百度的能力畢竟有限,有的時候google能提供更大的幫助。通過另闢蹊徑借用windowsAPI解決了這個問題,同時瞭解了C#在指針操作上的知識。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、數值與數制轉換 (一)、數制 電腦中採用的是二進位,因為二進位具有運算簡單,易實現且可靠,為邏輯設計提供了有利的途徑、節省設備等優點。一般計數都採用進位計數: 逢N進一,N是每種進位計數製表示一位數所需要的符號數目為基數。 二進位:逢二進一,借一當二 八進位:逢八進一,借一當八 十六進位:逢十 ...
  • 這篇文章介紹了c#動態載入卸載DLL的方法,有需要的朋友可以參考一下 c#中通過反射可以方便的動態載入dll程式集,但是如果你需要對dll進行更新,卻發現.net類庫沒有提供卸載dll程式集的方法。在.net 中,加入了應用程式域的概念,應用程式域是可以卸載的。也就是說,如果需要對動態載入的dll程 ...
  • 前言 前前後後接觸Solr有一個多月了,想趁著學習Solr順便把java拾起來。我分別用4.X和5.X版本在windows環境下用jetty的方式、tomcat部署的方式自己搭建了一把。其中從4.x到5.x和6.x的變化還是有的。搭建起來Solr後我分別用調用http介面的方式、SolrNet實現了 ...
  • 一、mysql environment When we create an new database,first We need draw er diagram for somebody to show your idea,but our company have no good authorise ...
  • QQ群里,偶爾不時的會有人問,原來的分散式網站負載工具怎麼下載不了啦?或者不能用啦?之類的問題。 我只能說,鑒於互聯網精神,我之前把它關了。其實主要是我那個VPS商,前端時間,估計也是資金問題,泥瑪尼的打不開1個月都沒人理,最後還告訴我硬碟數據全沒了。 好在我夠善良,不然真想拿刀砍他。趁著感冒,敲敲... ...
  • 在開發ASP.NET MVC時,我們會遇上這樣的情形,需要一次性傳送多個Model從控制器Controller至視圖View。 實現很簡單,只是創建一個集合類即可。Ok,下麵先在資料庫準備一些數據,如: CREATE TABLE [dbo].[TableA] ( [A] NVARCHAR(30) N ...
  • 前言 如果你還不知道ZKEACMS,不妨先瞭解一下。 ASP.NET MVC 開源建站系統 ZKEACMS 推薦,從此網站“拼”起來 官方地址:http://www.zkea.net/zkeacms 下載地址:https://github.com/SeriaWei/ASP.NET-MVC-CMS/r ...
  • 原文: "Working with SQL Server LocalDB" 作者: "Rick Anderson" 翻譯: "魏美娟(初見)" 校對: "孟帥洋(書緣)" 、 "張碩(Apple)" 、 "許登洋(Seay)" 類負責連接資料庫並將 對象和數據記錄進行映射。 Startup.cs 文 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...