给定(x,y)坐标计算多边形面积
我有一组点,想知道有没有什么函数可以计算这些点围成的区域面积,这样做可能更方便,也可能更快。
举个例子:
x = np.arange(0,1,0.001)
y = np.sqrt(1-x**2)
points = zip(x,y)
给定这些points
,计算出来的面积应该大约等于(pi-2)/4
。也许可以用scipy、matplotlib、numpy、shapely等库来实现这个功能?我的x和y坐标不会有负值,而且这些点会形成多边形,但没有特定的函数来描述它们。
补充说明:
这些点很可能不会按照特定的顺序排列(顺时针或逆时针),而且可能会比较复杂,因为它们是一组来自某个边界的shapefile的utm坐标。
13 个回答
在OpenCV中,cv2.contourArea()提供了一种计算面积的替代方法。
举个例子:
points = np.array([[0,0],[10,0],[10,10],[0,10]])
area = cv2.contourArea(points)
print(area) # 100.0
这里的参数(上面例子中的points)是一个numpy数组,数据类型是整数,表示一个多边形的顶点:[[x1,y1],[x2,y2], ...]
maxb的回答在性能上表现不错,但当坐标值或点的数量很大时,可能会导致精度损失。我们可以通过简单的坐标平移来解决这个问题:
def polygon_area(x,y):
# coordinate shift
x_ = x - x.mean()
y_ = y - y.mean()
# everything else is the same as maxb's code
correction = x_[-1] * y_[0] - y_[-1]* x_[0]
main_area = np.dot(x_[:-1], y_[1:]) - np.dot(y_[:-1], x_[1:])
return 0.5*np.abs(main_area + correction)
举个例子,一个常见的地理参考系统是UTM,它的(x,y)坐标可能是(488685.984, 7133035.984)
。这两个值相乘的结果是3485814708748.448
。你可以看到,这个乘积已经接近精度的极限(它的小数位数和输入值一样多)。如果再加上几个这样的乘积,更别提成千上万的了,就会导致精度损失。
一个简单的解决办法是把多边形的坐标从很大的正值平移到更接近(0,0)的地方,比如通过减去中心点坐标来实现,就像上面的代码那样。这有两个好处:
- 它消除了每个乘积中的一个因素
x.mean() * y.mean()
- 它在每个点积中产生了正值和负值的混合,这样大部分会相互抵消。
坐标平移并不会改变总面积,只是让计算在数值上更稳定。
通过分析Mahdi的回答,我发现大部分时间都花在了执行np.roll()
这个操作上。通过去掉这个操作,同时仍然使用numpy,我把每次循环的执行时间降低到了4-5微秒,而Mahdi的时间是41微秒(为了比较,Mahdi的函数在我的机器上平均耗时37微秒)。
def polygon_area(x,y):
correction = x[-1] * y[0] - y[-1]* x[0]
main_area = np.dot(x[:-1], y[1:]) - np.dot(y[:-1], x[1:])
return 0.5*np.abs(main_area + correction)
通过计算修正项,然后对数组进行切片,就不需要进行滚动操作或者创建新的数组了。
基准测试:
10000 iterations
PolyArea(x,y): 37.075µs per loop
polygon_area(x,y): 4.665µs per loop
计时是通过time
模块和time.clock()
来完成的。
最优化的解决方案是使用一些几何库,比如shapely、scikit-geometry或者pygeos。这些库的底层都是用C++写的几何包。第一个库通过pip安装非常简单:
pip install shapely
而且使用起来也很简单:
from shapely.geometry import Polygon
pgon = Polygon(zip(x, y)) # Assuming the OP's x,y coordinates
print(pgon.area)
如果你想从头开始构建或者了解底层算法是怎么工作的,可以看看鞋带公式:
# e.g. corners = [(2.0, 1.0), (4.0, 5.0), (7.0, 8.0)]
def Area(corners):
n = len(corners) # of corners
area = 0.0
for i in range(n):
j = (i + 1) % n
area += corners[i][0] * corners[j][1]
area -= corners[j][0] * corners[i][1]
area = abs(area) / 2.0
return area
这个公式适用于简单的多边形:
如果你有一个有孔的多边形:先计算外环的面积,再减去内环的面积。
如果你有自交的环:你需要把它们分解成简单的扇形。
可以用 鞋带公式 来计算面积,使用 Numpy
库来实现。假设我们有这些顶点:
import numpy as np
x = np.arange(0,1,0.001)
y = np.sqrt(1-x**2)
我们可以在 Numpy 中重新定义一个函数来计算面积:
def PolyArea(x,y):
return 0.5*np.abs(np.dot(x,np.roll(y,1))-np.dot(y,np.roll(x,1)))
然后我们就能得到结果:
print PolyArea(x,y)
# 0.26353377782163534
不使用 for
循环,这个函数的速度比 PolygonArea
快大约 50 倍:
%timeit PolyArea(x,y)
# 10000 loops, best of 3: 42 µs per loop
%timeit PolygonArea(zip(x,y))
# 100 loops, best of 3: 2.09 ms per loop.
时间测量是在 Jupyter notebook 中进行的。