多边形面积计算不一致

3

我使用了voronoi_finite_polygons_2d(vor, radius=None)函数,这个函数是我在StackOverflow上找到的。

enter image description here 我想修改它以显示每个voronoi单元格的质心。调试时发现一些质心明显错误(见绿色箭头指出的偏离中心),首先我确定的错误是:某些计算没有按照逆时针或顺时针的顺序处理顶点。

不确定为什么有些点没有正确排序,但在我调查之前,我发现另一个异常。

如果我按顺时针或逆时针方向走,应该得到相同面积(带有相反的符号)。在简单的例子中,我确实得到了相同的结果。但在我制作的随机多边形中,我得到了略微不同的结果。

import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Voronoi
import random
import math

def measure_polygon(vertices):
    xs = vertices[:,0]
    ys = vertices[:,1]
    xs = np.append(xs,xs[0])
    ys = np.append(ys,ys[0])

    #https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon
    area = sum(xs[i]*(ys[i+1]-ys[i-1]) for i in range(0, len(xs)-1))/2.0
    centroid_x =  sum((xs[i]+xs[i+1])*(xs[i]*ys[i+1] - xs[i+1]*ys[i]) for i in range(0, len(xs)-1))/(6.0*area)
    centroid_y =  sum((ys[i]+ys[i+1])*(xs[i]*ys[i+1] - xs[i+1]*ys[i]) for i in range(0, len(xs)-1))/(6.0*area)

    return (area, (centroid_x, centroid_y))

第一个示例按预期工作--无论处理顺序(顺时针或逆时针),面积和重心都相同。
d = [[0.0 ,  0.0], [1.0,3.0],[  5.0,3.0],[   4.0 ,  0.0] ] 
print len(d)

defects = [] 
defects.append([d[0], d[1], d[2], d[3]]) 
defects.append([d[3], d[2], d[1], d[0]])

for v in defects:
    print measure_polygon(np.array(v))

简单的平行四边形输出:
4 
(-12.0, (2.5, 1.5)) 
(12.0, (2.5, 1.5))

现在看一下这个几乎是三角形的四边形

#original list of vertices
d = [[-148.35290745 ,  -1.95467472], [-124.93580616 ,  -2.09420039],[  -0.58281373,    1.32530292],[   8.77020932 ,  22.79390931] ]
print len(d)

defects = []
#cw
defects.append([d[0], d[2], d[3], d[1]])
#ccw
defects.append([d[1], d[3], d[2], d[0]])

for v in defects:
    print measure_polygon(np.array(v))

给我奇怪的输出:
4
(1280.4882517358433, (-36.609159411740798, 7.5961622623413145))
(-1278.8546083623708, (-36.655924939495335, 7.6058658049196115))

这些区域是不同的。如果区域不同,那么质心也会不同。面积差异(1280与1278)非常大,我怀疑这不是浮点舍入问题。但除此之外,我已经没有假设为什么这不起作用了。

===============================

我发现错误了...我的列表解析/索引技巧,用于实现y-1和y+1表示法的功能出了问题(以一种令人不安的方式半奏效)。正确的例程如下:
def measure_polygon(vertices):
    xs = vertices[:,0]
    ys = vertices[:,1]

    #the first and last elements are for +1 -1 to work at end of range
    xs = vertices[-1:,0]
    xs = np.append(xs,vertices[:,0])
    xs = np.append(xs,vertices[:1,0])

    ys = vertices[-1:,1]
    ys = np.append(ys,vertices[:,1])
    ys = np.append(ys,vertices[:1,1])

    #for i in range(1, len(xs)-1):
    #    print ("digesting x, y+1, y-1 points: {0}/{1}/{2}".format(xs[i], ys[i+1], ys[i-1]))

    #https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon
    area = sum(xs[i]*(ys[i+1]-ys[i-1]) for i in range(1, len(xs)-1))/2.0
    centroid_x =  sum((xs[i]+xs[i+1])*(xs[i]*ys[i+1] - xs[i+1]*ys[i]) for i in range(1, len(xs)-1))/(6.0*area)
    centroid_y =  sum((ys[i]+ys[i+1])*(xs[i]*ys[i+1] - xs[i+1]*ys[i]) for i in range(1, len(xs)-1))/(6.0*area)

    return (area, (centroid_x, centroid_y))

现在NaN的例子就可以正常工作了:

#NaN Example
d = [[3.0 ,  4], [5.0,11],[  12.0,8],[   9.0 ,  5],[5,6] ]
print "number of vertices: {0}".format(len(d))

defects = []
defects.append([d[0], d[1], d[2], d[3], d[4] ])
defects.append([ d[4], d[3], d[2], d[1], d[0]])

for v in defects:
    print measure_polygon(np.array(v))

结果:
number of vertices: 5
(-30.0, (7.166666666666667, 7.6111111111111107))
(30.0, (7.166666666666667, 7.6111111111111107))
2个回答

1

多边形必须自我封闭,因此第一个和最后一个点相等。这是非常标准的。您可以使用Shoelace公式(https://en.m.wikipedia.org/wiki/Shoelace_formula)来处理常规坐标,但如果数据集缺少复制的最后一个点,我会添加它...这样计算就更容易了。因此,请考虑以下由参考文献定义的没有洞的多边形坐标。请注意,第一个和最后一个点是相同的...如果它们不相等,则会出现多部分多边形(例如带有洞的多边形)的对齐错误。

x = np.array([3,5,12,9,5,3]) # wikipedia
y= np.array([4,11,8,5,6,4])
a = np.array(list(zip(x,y)))
area1 = 0.5*np.abs(np.dot(x, np.roll(y, 1))-np.dot(y, np.roll(x, 1)))
area2 =0.5*np.abs(np.dot(x[1:], y[:-1])-np.dot(y[1:], x[:-1]))
print("\nroll area {}\nslice area{}".format(area1, area2))

yielding

roll area 30.0
slice area30.0

现在您的多边形被同等对待,通过将第一个点作为最后一个点添加回来,以给多边形提供闭合。
x = np.array([-148.35290745, -124.93580616, -0.58281373,  8.77029032, -148.35290745])
y = np.array([-1.95467472, -2.09420039,  1.32530292,  22.79390931, -1.95467472])
roll area  1619.5826480482792
slice area 1619.5826480482792

你的面积结果与我的不同,但我使用了第三种方法(使用einsum)进行了确认。以下是脚本的一部分。
def ein_area(a, b=None):
    """Area calculation, using einsum.
    :Requires:
    :--------
    :  a - either a 2D+ array of coordinates or an array of x values
    :  b - if a < 2D, then the y values need to be supplied
    :  Outer rings are ordered clockwise, inner holes are counter-clockwise
    :Notes:
    :  x => array([ 0.000,  0.000,  10.000,  10.000,  0.000])  .... OR ....
    :  t = x.reshape((1,) + x.shape)
    :      array([[ 0.000,  0.000,  10.000,  10.000,  0.000]]) .... OR .... 
    :  u = np.atleast_2d(x)
    :      array([[ 0.000,  0.000,  10.000,  10.000,  0.000]]) .... OR ....
    :  v = x[None, :]
    :      array([[ 0.000,  0.000,  10.000,  10.000,  0.000]])
    """
    a = np.array(a)  
    if b is None:
        xs = a[..., 0]
        ys = a[..., 1]
    else:
        xs, ys = a, b
    x0 = np.atleast_2d(xs[..., 1:])
    y0 = np.atleast_2d(ys[..., :-1])
    x1 = np.atleast_2d(xs[..., :-1])
    y1 = np.atleast_2d(ys[..., 1:])
    e0 = np.einsum('...ij,...ij->...i', x0, y0)
    e1 = np.einsum('...ij,...ij->...i', x1, y1)
    area = abs(np.sum((e0 - e1)*0.5))
    return area

但是你可以看到,这主要是基于切片/滚动的方法。我建议检查是否可以通过包含通常从多边形列表中省略但被假定的最后一个点来确认结果。


谢谢,我在我的索引/列表推导中发现了一个错误。顺便说一下,当您处理我的示例并获得+/-1619的面积时,该处理顺序不是按照纯CCW或CW顺序进行的。当我按照正确的CW/CCW顺序进行操作时,我得到+/-1270.1387323316385。当我像您描述的那样描述多边形时,我也得到+/-1619.5827808873739。 - user3556757
在我的领域中处理几何对象时,多边形始终具有相等的第一个和最后一个点,包括环。这可以防止内部和外部环之间的“渗透”。这不是我们必须做的事情,它就是这样,因此当我必须进行手动演示时,这是第二天性的。这也可以防止混淆。四个点用于表示多边形或闭合折线。三个点只能表示折线。我已经为您扩展了ein_area,以便您可以探索嵌套环。外环为CW,内环(即形成孔)为CCW。 - NaN
顺便问一下...其他的例子都去哪了...以前有4个,现在只剩2个了!这正常吗? - NaN

0

原因是缺少最后一个点。 它可以与第一个点几乎相同,多边形必须是一个电路。

数据库通常省略它,因为将其作为第一个点的标准相同。


我将多边形定义为仅由它们的顶点列表组成,并且面积/质心计算应该正确考虑到需要闭合电路的需求。但是,我的索引出了问题。一旦我修复了它,它就可以正常工作了。 - user3556757

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接