计算一个多边形的重心点坐标(准确版)

2019-11-29 13:04:47

主要用途,就是在百度地图上画多边形,然后我们要得到这个多边形的重心。

后来我发现了,百度地图,map对象直接有这个接口。大家可以仔细看下api。

接下来,我们还是要看下这个算法问题。

参考地址 计算一个多边形的重心点坐标



背景介绍与问题分析

在之前的 《如何判断一个多边形是否合法》  一文中有提到,用无人机规划飞行路线前,往往需要框选一个多边形的区域。

而在地图控件上显示这个多边形区域时,往往会遇到这样一个需求:需要把所要测绘的多边形区域移动到地图中心。

实现这个需求的基本思路就是:获取到多边形区域的重心点坐标,然后利用地图控件的 setCenter方法,就可以把地图的显示中心移动到多边形区域重心了。那么问题来了,如何求出一个多边形的重心点坐标呢?

这里所说的重心,也常常叫几何中心

这里首先给出一个公式:

平面多边形 X 可以被剖分为 n个有限的简单图形 X_1,X_2,....X_n,这些简单图形的重心点为 C_1,面积为 A_1,那么这个平面多边形的重心点坐标为 (C_x,C_y)
C_x = \frac{\sum C_{i_x} A_i}{\sum A_i}, C_y = \frac{\sum C_{i_y} A_i}{\sum A_i}

公式参考: 维基百科

一般来说我们可以给多边形进行三角剖分,而 \sum{A_i} 即为多边形的总面积,那么这个公式可以理解为:

多边形重心横坐标 = 多边形剖分的每一个三角形重心的横坐标 * 该三角形的面积之和 / 多边形总面积

多边形重心纵坐标 = 多边形剖分的每一个三角形重心的纵坐标 * 该三角形的面积之和 / 多边形总面积

所以这里就把问题拆分成了三个小问题:

  • 求每个剖分出来的三角形的重心。

  • 求每个剖分出来的三角形的面积。

  • 求多边形的面积。

算法解析

1. 求三角形的重心


三角形重心


三角形的重心:三条中线的交点。其中重心到其中一个顶点的距离是重心到该顶点对边中点的距离的2倍。
即:GC = 2 * GP,也就是说重心坐标在 CP 线段上距离 AB 的中点 P 的 1/3 处。
假设 A,B,C 三点的坐标为:


那么通过简单坐标计算,可以得出其重心坐标为 (x,y)
x = \frac{(x_1+x_2+x_3)}{3} , y = \frac{(y_1+y_2+y_3)}{3}

2. 求三角形面积

计算三角形的面积,我们这里利用 向量积来计算,我们知道平面中的两个向量的叉乘的模等于以这两个向量为边的平行四边形的面积,那么以这个两个向量为边的三角形,则是这个平行四边形的面积的一半。

参考:向量叉积

如上图,已知平面上两点 A:(x_1,y_1),B(x_2,y_2) ,以 A,B和坐标原点 P(0,0) 构成的三角形的面积 S 为:
S=\frac{\vec{PB}\times\vec{PA}}{2} = \frac{x_2y_1 - x_1y_2 }{2}

这里给出运算草稿:


为什么这里我们会以原点作为第三个点构成三角形呢?其实是跟接下来求多边形面积是有关联的。

3. 求多边形的面积

我们在上面给出的求平面多边形重心的公式中有说到,一般我们会把多边形剖分为多个三角形。
那么这个剖分点 P 我们可以设在哪里呢?这里先给出结论:这个剖分点可以设置在多边形的内部,也可以设置到外部。

为什么这个剖分点可以设置到外部呢?我们可以通过简单的三角形情况来推广到多边形的情况。
对于三角形ABC,我们把剖分点设置在其外部 P 的一点上


如果大家还记得 《如何判断一个多边形是否合法》 一文中有讲过向量叉积是有正负之分的,并且根据上面所说的计算三角形面积,那么以 P 为剖分点,通过向量积可以得出这个三角形的面积 A 为:
A = \frac{1}{2}(\vec{PB} \times \vec{PC} + \vec{PC} \times \vec{PA} + \vec{PA} \times \vec{PB})

因为 向量PB 在 向量PA 的顺时针方向,所以 \vec{PA} \times \vec{PB} 的结果是负数的。那么上面的面积计算公式其实就可以理解为:

三角形ABC的面积 = 三角形PBC面积 + 三角形PCA面积 - 三角形PAB面积

假设这四个点的坐标为:P(x_0,y_0), A(x_1,y_1), B(x_2,y_2), C(x_3,y_3),通过上面的公式进行计算,具体的演算过程我就不给出了,这里直接给出计算结果:
A = x_1y_2-x_2y_1+x_2y_3-x_3y_2+x_3y_1-x_1y_3

我们可以发现,计算结果中没有 x_0,y_0 的项,因为它们在计算过程中给消去了,数学就是这么奇妙!所以我们可以得出一个结论,多边形的面积结果与这个剖分点的位置是无关的。那么为了计算方便,我们当然选择把这个 P 点设置到原点上啦。

那么只要我们知道多边形的每一个顶点,通过原点进行剖分成多个三角形,然后通过向量的叉乘求出每个三角的面积,最后相加,就可以求出多边形的面积了。

示例代码及解析

好了,说到这里,我们已经找到所有满足最开始的计算多边形重心点坐标的所有计算元素了。是时候上代码了,这里构建一个函数calculatePolygonGravityCenter(coordinates: [CLLocationCoordinate2D]),这个函数传入的参数是多边形在地图上的坐标点数组。

func calculatePolygonGravityCenter(coordinates: [CLLocationCoordinate2D]) -> CLLocationCoordinate2D {
    var area = 0.0 // 多边形面积
    var gravityLat = 0.0 // 重心点 latitude
    var gravityLng = 0.0 // 重心点 longitude
    for (index, coordinate) in coordinates.enumerated() {
        // 1
        let lat = coordinate.latitude        let lng = coordinate.longitude        let nextLat = coordinates[(index + 1) % coordinates.count].latitude        let nextLng = coordinates[(index + 1) % coordinates.count].longitude        // 2
        let tempArea = (nextLat * lng - nextLng * lat) / 2.0
        // 3
        area += tempArea        // 4
        gravityLat += tempArea * (lat + nextLat) / 3
        gravityLng += tempArea * (lng + nextLng) / 3
    }
    // 5
    gravityLat = gravityLat / area
    gravityLng = gravityLng / area    
    return CLLocationCoordinate2D(latitude: gravityLat, longitude: gravityLng)}

对应上面代码的注释:

  1. 拿到多边形上连续两个点的坐标,我们可以把 latitude 看做横坐标,longitude 是纵坐标。

  2. 利用向量叉乘计算这两个点与原点组成的三角形的面积。

  3. 所有面积之和得出多边形的面积,就是求公式 C_x = \frac{\sum C_{i_x} A_i}{\sum A_i} 中的 \sum A_i

  4. (lat + nextLat) / 3 是以这两个点和原点组成的三角形的重心横坐标,这样的累加gravityLat += tempArea * (lat + nextLat) / 3 其实是求公式 C_x = \frac{\sum C_{i_x} A_i}{\sum A_i} 中的 \sum C_{i_x} A_i 的值。

  5. 到这一步就简单了,直接套用公式 C_x = \frac{\sum C_{i_x} A_i}{\sum A_i}

参考资料

  1. 维基百科-Centroid

  2. 维基百科-叉积

  3. cnblogs-用向量积(叉积)计算三角形的面积和多边形面积

  4. 知乎-两个向量的叉乘为什么是面积

  5. 中国知网-任意多边形匀面重心的计算方法



  • 2020-12-16 23:06:05

    Rocket.Chat推送信息

    Rocket.Chat推送消息 Rocket.Chat是一个开源实时通讯平台, 支持Windows, Mac OS, Linux. 支持聊天, 文件上传, 视频通话, 语音通话功能. 向Rocket.Chat推送消息 以下示例可以转为别的语言的版本, 本示例使用Linux平台的curl测试, curl非常强大. 登陆 首先需要登陆Rocket.Chat服务器

  • 2020-12-17 09:01:23

    对BitTorrent Tracker源码分析

    tracker服务器是BT下载中必须的角色。一个BT client 在下载开始以及下载进行的过程中,要不停的与 tracker 服务器进行通信,以报告自己的信息,并获取其它下载client的信息。这种通信是通过 HTTP 协议进行的,又被称为 tracker HTTP 协议,它的过程是这样的: client 向 tracker 发一个HTTP 的GET请求,并把它自己的信息放在GET的参数中;这个请求的大致意思是:我是xxx(一个唯一的id),我想下载yyy文件,我的ip是aaa,我用的端口是bbb。。。

  • 2020-12-17 10:55:48

    html5 video p2p research

    节约带宽,减少缓冲时间,提升服务质量,处理峰值流量, 视频观看的人越多,播放越流畅。

  • 2020-12-17 10:57:34

    使用 MediaSource 搭建流式播放器

    Media Source Extensions(媒体源扩展)大大地扩展了浏览器的媒体播放功能,提供允许JavaScript 生成媒体流。这可以用于自适应流(adaptive streaming,也是我毕设的研究方向)及随时间变化的视频直播流(live streaming)等应用场景。

  • 2020-12-17 11:00:37

    H5流式播放(FMP4转封装与mediaSource)

    W3C上有明确关于mediaSource 扩展接口的文档。mediaSource 扩展文档中是这么定义的, 它允许JS脚本动态构建媒体流用于和,允许JS传送媒体块到H5媒体元素。这种接口的应用可以让h5播放器实现持续添加数据进行播放。做as的朋友都知道as中的appendBytes方法,一种添加二进制数据进行播放的方式。这两种接口在概念上是类似的。只是里面的定义和对媒体文件的要求有所不同。对于mediaSource扩展接口我只介绍我们主要应用的几个。

  • 2020-12-18 17:15:29

    coTurn stun服务器搭建,禁用turn

    https://github.com/coturn/coturn 在这里git clone 下来然后编译安装,一切默认即可。编译后,也可以不用安装。在编译目录下bin文件夹下有turnserver turnutils_stunclient turnutils_uclient 这三个等一下会用到的软件。