【问题标题】:Area of polygon calculation inconsistent多边形计算面积不一致
【发布时间】:2016-11-05 15:11:22
【问题描述】:

我使用的是voronoi_finite_polygons_2d(vor, radius=None) 函数,而我是found elsewhere on StackOverflow

我想修改它以显示centroid of each voronoi cell。调试为什么某些质心出现严重错误(请参阅绿色箭头,指出杂草中的质心距离)。我发现的第一个错误:一些计算没有以正确的全顺时针或全逆时针顺序处理顶点。

不知道为什么有些点没有得到正确排序,但在我调查之前,我发现了另一个异常情况。

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

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

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

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

但现在看看这个 4 边多边形(几乎是一个三角形)

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

【问题讨论】:

    标签: python numpy voronoi


    【解决方案1】:

    多边形必须是自闭合的,所以第一个点和最后一个点相等。这是相当标准的。您可以使用鞋带公式(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
    

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

    【讨论】:

    • 谢谢,在我的索引/列表理解中发现错误。顺便说一句,当您处理我的示例并获得 +/-1619 的区域时,该处理顺序不是纯 CCW 或 CW 顺序。当我以正确的 CW/CCW 顺序执行此操作时,我得到 +/-1270.1387323316385。当我像你一样描述多边形时,我也得到 +/-1619.5827808873739
    • 在我的领域中使用几何对象时,多边形的第一个点和最后一个点总是相等的,包括环。这可以防止内圈和外圈之间的“渗漏”。这不是我们必须做的,它只是,所以当我必须做手动演示时,这是第二天性。这也可以防止混淆。四个点用于表示多边形或闭环折线。三个点只能代表一条折线。我已经扩展了 ein_area 供您使用嵌套环进行探索。外圈是顺时针,内圈(即形成孔)是逆时针
    • 顺便说一句...所有其他示例都去哪儿了...曾经有 4 个现在有 2 个!这正常吗?
    【解决方案2】:

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

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

    【讨论】:

    • 我将多边形定义为它们的顶点列表,并且面积/质心计算应该正确考虑需要有一个闭合电路。除了,我搞砸了我的索引。一旦我解决了这个问题,它就可以工作了。
    猜你喜欢
    • 2013-11-13
    • 2013-10-24
    • 2020-08-23
    • 2013-09-07
    • 1970-01-01
    • 1970-01-01
    • 2012-04-07
    • 2014-09-05
    • 2013-03-25
    相关资源
    最近更新 更多