多边形面积计算
手寫地理信息組件系列 第7篇
多邊形實體的面積計算
難度指數:★★★☆☆
“這個世界會好嗎?”
第一次聽到這一句,是在一個某已"行為不端"的民謠歌手的作品里聽到的。究起源頭,是出自于“中國最后一位大儒家”梁漱溟的父親,梁濟之口。
梁濟所處清末,朝野無能,江河日下。梁濟半生為國奔走呼號,奈何仍國之不國。一天,梁濟將自己25歲的兒子梁漱溟叫到身邊,問了兒子:
“這個世界會好嗎”? “我相信世界是一天一天往好里去的”。
“能好就好啊”。
幾日,在梁濟距自己生辰三日之時,為警醒世人,在北京的凈業湖投湖自盡。
一句世紀之問,今日尤聽,仍振聾發聵。
你說這個世界會好嗎?
——讀《梁漱溟傳》
前情回顧
我們已經掌握了Shapefile的線面實體讀取,了解線面shp是由文件頭+記錄頭+記錄內容組成的。其中記錄內容是由Parts首坐標索引和Points點坐標對構成。依次的讀取這些記錄,就構成了一個個的空間實體對象。
當然我們不能止于文件讀取和繪圖,今天來走出GIS分析功能的第一步——多邊形實體的面積計算。
多邊形的構成
相信關注過以往內容的讀者會知道,我們當初在定義點線面時,已經為線面對象預先定義了Vertex屬性。因為它是構成多邊形對象最重要的屬性(環狀線段也視為多邊形)。
而Vertex被稱為節點,是構成多邊形對象的骨架,每個Vertex帶有坐標,而面積就是從中計算而來。
值得注意的是,我們平時在紙上畫多邊形并不強調點的順序,但這里Vertex的順序關系一定是必要的,因為不同的節點順序,可能構成不同的多邊形,從而導致面積計算的不一致。說來可別不信,希望這張圖能給你留下印象。
圖中,AB兩點在多邊形節點中是兩個位置完全相同的點,而在多邊形環序中卻是兩個順序相反的點。所以說順序會決定形狀,而形狀可以決定面積。坐標點一定要按序給出。
一般來說,對于凹凸多邊形,Shapefile都是按照順時針順序存儲多邊形節點。而對于內部帶孔洞多邊形,節點存儲相對外層來說,順序是完全相反的。
向量與叉乘
回顧高中數學必修4當中,第一次出現了向量。據此“史料”記載:向量是一個既有大小又有方向的量。
記得當初,用這本數學書上學到的向量數量積知識,還解決了不少物理題上木塊移動的做功問題。。
OK,還是回到我們當前的問題。多邊形的面積計算用向量來解決,是一種非常科學高效的方法。但并非使用高中數學的數量積(點乘)方法,而是大學數學中與線性代數相關的叉乘。
叉乘,又稱叉積、向量積。在數值上等于以兩個向量的模長為鄰邊,構成的平行四邊形的面積。
向量點乘的結果是一個標量,而叉乘的結果仍然是一個向量。在三維空間中,其位置處在兩不共線向量構成平面的法線之上,并滿足右手定則。
結合圖中,簡單的說右手定則:手掌四指指向OA方向,并將四指彎曲至OB方向,此時大拇指的指向,即為OA和OB叉乘結果的方向。
觀察圖中結果向量OC,可知OA和OB的叉乘為負數,而OB和OA的叉乘為正數。兩者的絕對值相等,都為OC的長度。
多邊形面積計算
多邊形面積計算可由三角形計算引出:
依照向量叉乘定義,圖中平行四邊形的面積即為叉乘結果,因為向量叉乘順序為**a**×_b_,根據右手定則可知,結果為負數。三角形面積即為1/2平行四邊形面積。
而多邊形面積計算的思想,就是將多邊形分割為多個三角形,然后進行加和:
值得注意的是,凸多邊形(左)和凹多邊形(右)不會因其凹凸性而影響面積的計算。右圖凹進去的三角形(d、e為邊)看似會增大多邊形的面積,但實際上在加和中,會扣減這部分三角形的面積。你可以在上圖看出,左邊d×e為負數,右邊d×_e_為正數。
嗯是的,看起來還合理。但是,如果像這樣,遇到這種內部帶有孔洞的多邊形怎么辦呢?
多邊形存在“洞”時,需要知道一點。孔洞的環序與外環順序是相反的,因此形成的面積會相減。所以帶孔多邊形的面積計算,只需將洞的環構成的面積代入計算即可。這里也再次體現了節點順序的重要性。
以上,我們分析了利用叉積計算多邊形面積的幾種情況。可以發現,我們將多邊形劃分為多個三角形來計算面積的思想是可行的。但其中一個細節是,如何選擇第一個分割點開始分割呢?
我們本能的會在頭腦中想到多邊形左上角的那個節點,因為這來源于平時的繪圖習慣。而實際的答案是:
坐標系內的任意點
以任意點開始分割的推理,可以在文末點擊閱讀****原文。這里需要知道的是,以任意點作為起點分割多邊形,依此來計算面積是完全成立的。由于叉積的方向,在多邊形外面的部分會抵消,得到的仍是多邊形的面積。通常,我們將原點視為分割的第一點,這是出于計算方便的考慮。
到這里,我們利用這個原點,基本可以整理出利用叉積計算多邊形面積的等式:
其中:
既然公式有了,是不是馬上就可以開始用代碼算了呢?
且慢!這里存在一個問題!
這種計算方式,在數學上是完全成立的,但是理論需要結合實際。我們知道,計算機在計算小數的時候,是會有舍入誤差的。拿當前這種以原點作為切割第一點的情況來說,如果圖形距離原點非常遠,那這種誤差就會變得非常大。例如下面這種情況:
本初子午線和赤道的交點位于中非西部,它就是Web墨卡托投影坐標系的原點(0,0)。如果以這個點來計算中國境內的某個圖形面積,在數學上是可行的,但在計算機中就會出現誤差,而且還有可能發生溢出錯誤,所以不推薦以原點進行分割。而以圖形上的節點作為分割的第一點是很合適的。
這樣,向量的叉積就不會參照原點來計算,而是參照圖形自身節點來計算,這樣,無論圖形距離原點多遠,都不會造成誤差。
實現叉積算法
以上我們分析了多邊形面積計算的叉乘方法,現在結合代碼實現多邊形面積的計算。
在C#工程中單獨定義一個類,算法類Algorithm,用于提供統一的算法相關函數。在類中定義一個函數SignedArea(有符號面積計算),以節點列表為參數。
public class Algorithm{public static double SignedArea(List<Vertex> vertexes){double area = 0.0d;//第一點坐標(x0,y0)double x0 = vertexes[0].x;double y0 = vertexes[0].y;//從第二點開始遍歷節點for (int i = 1; i < vertexes.Count - 1; i++){double ax = vertexes[i].x;double ay = vertexes[i].y;double bx = vertexes[i + 1].x;double by = vertexes[i + 1].y;//向量adouble va_x = ax - x0;double va_y = ay - y0;//向量bdouble vb_x = bx - x0;double vb_y = by - y0;//叉乘area += va_x * vb_y - vb_x * va_y;}return area / 2;} }在面實體中新增面積屬性,面對象就可以計算自己的面積了。這里為了展示方便,在面繪制的方法中加入了面積數值的顯示:
//面實體public class Polygon : Geometry{private List<Vertex> vertexes =new List<Vertex>();public List<Vertex> Vertexes{get { return vertexes; }}public double Area{get{//返回面積的絕對值return Math.Abs(Algorithm.SignedArea(vertexes));}}public override void Draw(Graphics graphics, Map map){System.Drawing.Point[] points =new System.Drawing.Point[vertexes.Count];for (int i = 0; i < points.Length; i++){points[i] = map.ToScreenPoint(vertexes[i]);}graphics.FillPolygon(new SolidBrush(Color.LightSeaGreen), points);//在界面繪制面積數值graphics.DrawString(Area.ToString(),new Font("宋體", 15),new SolidBrush(Color.Navy),new PointF(points[0].X, points[0].Y));} }面積計算
在Web墨卡托投影的底圖上,我選定了天津北部的一塊區域,手繪一個面shp,觀察河岸的曲線可以看出這是一個凹多邊形:
結合之前的Shpfile讀取代碼,現在面積可以計算并顯示出來了:
面積:3085928.87246157
再來對比ArcMap的計算結果:
可以認為:我們的計算是準確的
但是需要注意的是,涉及到面積計算,一定要將數據轉換為投影坐標系下進行。
我們的面積計算方法,本身是基于平面向量的,所以一定要將經緯度這種球面坐標轉換為平面坐標后計算才有意義。同時,這也是ArcGIS在計算面積時,要求數據必須轉換為投影坐標系的原因。
附:
如果你對文中,以“以原點為分割第一點會造成誤差”的分析有更多興趣,后臺回復【誤差】,查看對比結果。
看好關注,下期見。
上一篇:GIS底層|線面shp讀取中,那些容易被遺落的重要細節
總結
- 上一篇: MinIO杂谈(bucket、对象Obj
- 下一篇: 11GR2 中的常见 RMAN 问题