简体   繁体   English

多边形计算面积不一致

[英]Area of polygon calculation inconsistent

I was using voronoi_finite_polygons_2d(vor, radius=None) function that I found elsewhere on StackOverflow . 我正在使用我在StackOverflow上的其他地方找到的 voronoi_finite_polygons_2d(vor, radius=None)函数。

在此输入图像描述 I want to modify it to show the centroid of each voronoi cell . 我想修改它以显示每个voronoi单元质心 Debugging why some centroids appear dramatically wrong (see green arror pointing out centroid way off in the weeds). 调试为什么有些质心显得非常错误(参见绿色恐怖指出杂草中的质心方式)。 First error I identified: some of the calculations weren't processing the vertices in proper all-clockwise or all-counterclockwise order. 我发现的第一个错误:一些计算没有以正确的顺时针或全逆时针顺序处理顶点。

Not sure why some points don't get sorted correctly, but before I investigate that, I found another anomaly. 不确定为什么有些点不能正确排序,但在我调查之前,我发现了另一个异常现象。

I should get the same area (with opposite sign) if I go clockwise or counterclockwise. 如果我顺时针或逆时针走,我应该得到相同的区域(符号相反)。 In simple examples, I do. 在简单的例子中,我这样做。 But in a random polygon I made, I get /slightly/ different results. 但是在我制作的随机多边形中,我得到/稍微/不同的结果。

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

The first example work as expect -- same area and centroid regardless of processing order (cw or ccw). 第一个示例按预期工作 - 无论处理顺序(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))

simple parallelogram output: 简单的平行四边形输出

4 
(-12.0, (2.5, 1.5)) 
(12.0, (2.5, 1.5))

But now look at this 4-sided polygon (that is almost a triangle) 但现在看看这个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))

Gives me weird output: 给我奇怪的输出:

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

The areas are different. 区域不同。 And if areas are different, then the centroids will be different. 如果区域不同,那么质心将会不同。 The discrepancies of area (1280 versus 1278) are so large that I doubt it's a floating point rounding thing. 面积的差异(1280对1278)是如此之大,我怀疑它是一个浮点圆角的东西。 But other than that, I've run out of hypotheses why this isn't working. 但除此之外,我已经没有假设为什么这不起作用。

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

I found the error.... my list-comprehension/indexing hack to enable y-1 and y+1 notation was broken (in a sinister way that half-worked). 我发现错误....我的列表 - 理解/索引黑客启用y-1和y + 1符号被打破(以一种险恶的方式,半工作)。 The correct routine is as follows: 正确的例程如下:

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

So now NaN's example works right: 所以现在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))

results: 结果:

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

Polygons have to be self closing so the first and last point are equal. 多边形必须自动关闭,因此第一点和最后一点是相等的。 This is pretty standard. 这是非常标准的。 You can use the Shoelace formula ( https://en.m.wikipedia.org/wiki/Shoelace_formula ) with the regular coordinates, but if I get a dataset missing the replicated last point, I just add it.. which makes the calculations easier. 您可以使用Shoelace公式( https://en.m.wikipedia.org/wiki/Shoelace_formula )和常规坐标,但是如果我得到一个数据集缺少复制的最后一个点,我只需添加它..这使得计算更轻松。 So consider a polygon without holes defined by the following coordinates (from the reference). 因此,考虑一个没有由以下坐标定义的孔的多边形(来自参考)。 Note the first and last point are the same...if they aren't you will get alignment errors for multipart polygons (polygons with holes for instance) 请注意,第一个和最后一个点是相同的...如果它们不是,您将获得多部分多边形的对齐错误(例如,带有孔的多边形)

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

Now your polygon was treated the same, by adding the first point back in as the last point to give closure to the polygon 现在,您的多边形被处理相同,通过添加第一个点作为最后一个点来为多边形提供闭包

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

The area result differs from yours but I confirmed it using a third method using einsum. 区域结果与您的不同,但我使用einsum的第三种方法确认了它。 A portion of the script is below 脚本的一部分在下面

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

But you can see that is largely based on the slicing/rolling approach. 但是你可以看到这主要是基于切片/滚动方法。 I would check to see if you can confirm results by including the last point which is normally missing from polygon lists but assumed. 我会检查你是否可以通过包括多边形列表中通常缺少的最后一个点来确认结果。

The reason for that is it's missing the last point. 原因是它错过了最后一点。 It can be pretty much the same as the first one, a polygon has to be a circuit. 它可以与第一个几乎相同,多边形必须是电路。

Databases usually omit it because it's standard to have it as the same as the first one. 数据库通常会省略它,因为它与第一个数据库一样是标准的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM