多边形计算面积不一致

时间:2016-11-05 15:11:23

标签: python numpy 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)) 函数found elsewhere on StackOverflow

enter image description here 我想修改它以显示centroid of each voronoi cell。调试为什么有些质心显得非常错误(参见绿色恐怖指出杂草中的质心方式)。我发现的第一个错误:一些计算没有以正确的顺时针或全逆时针顺序处理顶点。

不确定为什么某些点无法正确排序,但在我调查之前,我发现了另一个异常现象。

如果我顺时针或逆时针走,我应该得到相同的区域(符号相反)。在简单的例子中,我这样做。但是在我制作的随机多边形中,我得到/稍微/不同的结果。

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))

第一个示例按预期工作 - 无论处理顺序如何(cw或ccw),都是相同的区域和质心。

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边多边形(几乎是一个三角形)

4
(1280.4882517358433, (-36.609159411740798, 7.5961622623413145))
(-1278.8546083623708, (-36.655924939495335, 7.6058658049196115))

给我奇怪的输出:

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))

区域不同。如果区域不同,那么质心将会不同。面积的差异(1280对1278)是如此之大,我怀疑它是一个浮点圆角的东西。但除此之外,我已经没有假设为什么这不起作用。

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

我发现了错误....我的list-comprehension / indexing hack启用y-1和y + 1符号被打破(以一种险恶的方式,半工作)。正确的例程如下:

#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))

所以现在NaN的例子正确:

number of vertices: 5
(-30.0, (7.166666666666667, 7.6111111111111107))
(30.0, (7.166666666666667, 7.6111111111111107))

结果:

{{1}}

2 个答案:

答案 0 :(得分: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))

产生

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

但是你可以看到这主要是基于切片/滚动方法。我会检查你是否可以通过包括多边形列表中通常缺少的最后一个点来确认结果,但是假设。

答案 1 :(得分:0)

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

数据库通常会省略它,因为它与第一个数据库的标准相同。