多边形面积计算

手写地理信息组件系列 第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;

      //向量a
      double va_x = ax - x0;
      double va_y = ay - y0;

      //向量b
      double 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读取中,那些容易被遗落的重要细节


在这里插入图片描述

  • 12
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值