如何确定多边形点列表是否按顺时针顺序?

Sté*_*écy 242 math geometry polygon computational-geometry

有一个点列表,我如何找到顺时针顺序?

例如:

point[0] = (5,0)
point[1] = (6,4)
point[2] = (4,5)
point[3] = (1,5)
point[4] = (1,0)
Run Code Online (Sandbox Code Playgroud)

会说它是逆时针(或逆时针,对某些人来说).

Bet*_*eta 396

在非凸多边形(例如新月形)的情况下,一些建议的方法将失败.这是一个非常简单的非凸多边形(它甚至可以使用自相交的多边形,如图8,告诉你它是否主要是顺时针方向).

边上的和,(x 2 - x 1)(y 2 + y 1).如果结果为正,则曲线为顺时针,如果为负,则曲线为逆时针.(结果是封闭区域的两倍,带有+/-约定.)

point[0] = (5,0)   edge[0]: (6-5)(4+0) =   4
point[1] = (6,4)   edge[1]: (4-6)(5+4) = -18
point[2] = (4,5)   edge[2]: (1-4)(5+5) = -30
point[3] = (1,5)   edge[3]: (1-1)(0+5) =   0
point[4] = (1,0)   edge[4]: (5-1)(0+0) =   0
                                         ---
                                         -44  counter-clockwise
Run Code Online (Sandbox Code Playgroud)

  • 一个小警告:这个答案假设一个正常的笛卡尔坐标系.值得一提的是,一些常见的上下文,如HTML5画布,使用倒Y轴.然后必须翻转规则:如果区域为*负*,则曲线为顺时针. (65认同)
  • 它的微积分应用于一个简单的案例.(我没有发布图形的技巧.)线段下的区域等于其平均高度(y2 + y1)/ 2倍于其水平长度(x2-x1).注意x中的符号约定.尝试使用一些三角形,你很快就会看到它是如何工作的. (26认同)
  • @ Mr.Qbs:你总是要把最后一点与第一点联系起来.如果N点的编号从0到N-1,则必须计算:`Sum((x [(i + 1)mod N] - x [i])*(y [i] + y [(i + 1)mod N]))`对于i = 0到N-1.即,必须采用指数Modulo N(`N≡0`)该公式仅适用于**封闭的**多边形.多边形没有虚边. (9认同)
  • @ Mr.Qbs:所以我的方法有效,但是如果你*跳过一个重要的部分*,那么它就不起作用了.这不是新闻. (7认同)
  • 这个 http://blog.element84.com/polygon-winding.html 用简单的英语解释了为什么这个解决方案有效。 (6认同)
  • 大.但有人可以解释为什么它有效吗? (5认同)
  • 这个方法错了!我实施了它广告是98%的情况下工作.但不总是.[对我来说有用(在每种情况下都是这个链接)(http://en.wikipedia.org/wiki/Curve_orientation). (2认同)
  • @Mr.Qbs:请给我们你知道的最简单的反例。 (2认同)
  • @DanM。如果结果为零,则意味着正区域和负区域相互抵消,如数字 8。 (2认同)
  • @Macmee:您给出的链接已损坏,存档:https://web.archive.org/web/20171212175910/blog.element84.com/polygon-winding.html (2认同)

Cha*_*ana 49

所述叉积测量两个向量的垂直岬的程度.想象一下,多边形的每个边都是三维(3-D)xyz空间的xy平面中的向量.然后,两个连续边的叉积是z方向上的矢量,(如果第二段是顺时针,则为正z方向,如果是逆时针,则为z方向).该矢量的大小与两个原始边缘之间的角度的正弦成比例,因此当它们垂直时达到最大值,并且当边缘共线(平行)时逐渐减小以消失.

因此,对于多边形的每个顶点(点),计算两个相邻边的叉积大小:

Using your data:
point[0] = (5, 0)
point[1] = (6, 4)
point[2] = (4, 5)
point[3] = (1, 5)
point[4] = (1, 0)
Run Code Online (Sandbox Code Playgroud)

所以标签的边缘连续为
edgeA从段point0point1
edgeB之间point1,以point2
...
edgeE之间point4point0.

然后Vertex A(point0)介于
edgeE[From point4to point0]
edgeA[From point0to`point1'之间

这两个边本身就是向量,其x和y坐标可以通过减去它们的起点和终点的坐标来确定:

edgeE= point0- point4= (1, 0) - (5, 0)= (-4, 0)
edgeA= point1- point0= (6, 4) - (1, 0)= (5, 4)

并且这两个邻接边缘的横产物用下面的矩阵,这是通过将代表这三个符号下方的两个向量的坐标的坐标轴(构成的行列式计算i,j,及k).第三个(零)值坐标是因为交叉积概念是三维构造,因此我们将这些二维向量扩展为三维以应用交叉积:

 i    j    k 
-4    0    0
 1    4    0    
Run Code Online (Sandbox Code Playgroud)

假设所有交叉乘积产生垂直于两个矢量平面的矢量,则上述矩阵的行列式仅具有k(或z轴)分量.
计算k或z轴分量的大小的公式是
a1*b2 - a2*b1 = -4* 4 - 0* 1 = -16

该值(-16)的大小是2个原始矢量之间的角度的正弦的乘积,乘以2个矢量的大小的乘积.
实际上,其价值的另一个公式是
A X B (Cross Product) = |A| * |B| * sin(AB).

因此,要回到角度的度量,您需要将此值(-16)除以两个向量的大小的乘积.

|A| * |B| = 4 * Sqrt(17) =16.4924...

所以罪的量度(AB)= -16 / 16.4924=-.97014...

这是衡量顶点向左或向右弯曲后的下一个段的度量,以及是多少.没有必要采用正弦波.我们所关心的只是它的大小,当然还有它的标志(正面或负面)!

对闭合路径周围的其他4个点中的每个点执行此操作,并在每个顶点处将此计算中的值相加.

如果最终总和为正,则顺时针方向,负方向,逆时针方向.

  • 实际上,该解决方案与公认的解决方案不同.它们是否相同是我正在调查的问题,但我怀疑它们不是......接受的答案通过取多边形顶边下方的面积与下面的面积之差来计算多边形的面积.多边形的底边.一个是负面的(你从左到右穿过的那个),另一个是负面的.顺时针移动时,上边缘从左向右移动并且较大,因此总数为正. (3认同)
  • 看起来这种方法你需要采用arcsin,除非你假设凸性(在这种情况下你只需要检查一个顶点) (2认同)
  • 您需要采取反正弦。在一堆随机的非凸多边形上进行尝试,如果不使用反正弦,将会发现某些多边形的测试会失败。 (2认同)

Sea*_*ean 44

我想这是一个非常古老的问题,但无论如何我都会抛出另一个解决方案,因为它很简单,而且不是数学密集的 - 它只是使用基本代数.计算多边形的有符号区域.如果它是负数,则点是顺时针顺序,如果它是正数,则它们是逆时针.(这与Beta的解决方案非常相似.)

计算有符号面积:A = 1/2*(x 1*y 2 - x 2*y 1 + x 2*y 3 - x 3*y 2 + ... + x n*y 1 - x 1*y n)

或者在伪代码中:

signedArea = 0
for each point in points:
    x1 = point[0]
    y1 = point[1]
    if point is last point
        x2 = firstPoint[0]
        y2 = firstPoint[1]
    else
        x2 = nextPoint[0]
        y2 = nextPoint[1]
    end if

    signedArea += (x1 * y2 - x2 * y1)
end for
return signedArea / 2
Run Code Online (Sandbox Code Playgroud)

请注意,如果您只是检查订购,则无需费力除以2.

资料来源:http://mathworld.wolfram.com/PolygonArea.html

  • @EricFortier - FWIW,而不是调整一个可能很大的数组,一个有效的替代方案是每次迭代将其点保存为下一次迭代的"previousPoint".在开始循环之前,将`previousPoint`设置为数组的最后一个点.权衡是额外的局部变量复制,但更少的数组访问.最重要的是,不必触摸输入数组. (2认同)
  • @MichaelEricOberlin - 通过包括从最后一个点到第一个点的线段,它必须*关闭*多边形.(正确的计算将是相同的,无论哪个点开始闭合多边形.) (2认同)

lhf*_*lhf 31

找到y最小的顶点(如果有连接则找到最大的x).设顶点为A和列表中的前一个顶点,列表中B的下一个顶点为C.现在计算符号的叉积的ABAC.


参考文献:

  • 这也在http://en.wikipedia.org/wiki/Curve_orientation中进行了解释.关键在于找到的点必须在凸包上,并且只需要局部地观察凸包(和它的直接邻居)上的单个点来确定整个多边形的方向. (5认同)
  • **_澄清:_**这个解是'O(1)`只有**(A)**这个多边形是凸的(在这种情况下,任意顶点都在凸包上,因此就足够了)*或***(B)**你已经知道了Y坐标最小的顶点.如果这不是*的情况(即,这个多边形是非凸的并且你对它一无所知),则需要进行"O(n)"搜索.但是,由于不需要求和,这仍然比任何其他简单多边形解决方案快得多. (4认同)
  • 令人震惊和敬畏的是,这还没有得到更多的支持。对于简单的多边形(*这是某些领域中的大多数多边形*),这个答案产生一个“O(1)”解决方案。所有其他答案都会为“n”多边形点数生成“O(n)”解决方案。对于更深入的优化,请参阅维基百科精彩的[曲线方向](http://en.wikipedia.org/wiki)的[*实际注意事项*](https://en.wikipedia.org/wiki/Curve_orientation#Practical_considerations)小节/Curve_orientation)文章。 (2认同)
  • @CecilCurry 我认为你的第二条评论解释了为什么这没有得到更多的支持。在某些情况下它会产生错误的答案,而没有提及这些限制。 (2认同)

Oli*_*bes 21

这是基于此答案的算法的简单C#实现.

假设我们有一个Vector类型XY属性类型double.

public bool IsClockwise(IList<Vector> vertices)
{
    double sum = 0.0;
    for (int i = 0; i < vertices.Count; i++) {
        Vector v1 = vertices[i];
        Vector v2 = vertices[(i + 1) % vertices.Count];
        sum += (v2.X - v1.X) * (v2.Y + v1.Y);
    }
    return sum > 0.0;
}
Run Code Online (Sandbox Code Playgroud)

  • 您可以通过在循环开始之前设置“v1 = vertices[vertices.Count-1]”来避免昂贵的“%”并避免分支,然后在添加“sum”之后使用“v2 = vertices[i];”执行“v1 = v2”。 (2认同)

Ste*_*ham 6

从其中一个顶点开始,计算每一侧所对的角度.

第一个和最后一个将为零(所以跳过那些); 对于其余部分,角度的正弦将由归一化与单位长度(点[n] - 点[0])和(点[n-1] - 点[0])的叉积给出.

如果值的总和为正,则以逆时针方向绘制多边形.


mpe*_*pen 5

用 JavaScript实现Sean 的回答

function calcArea(poly) {
    if(!poly || poly.length < 3) return null;
    let end = poly.length - 1;
    let sum = poly[end][0]*poly[0][1] - poly[0][0]*poly[end][1];
    for(let i=0; i<end; ++i) {
        const n=i+1;
        sum += poly[i][0]*poly[n][1] - poly[n][0]*poly[i][1];
    }
    return sum;
}

function isClockwise(poly) {
    return calcArea(poly) > 0;
}

let poly = [[352,168],[305,208],[312,256],[366,287],[434,248],[416,186]];

console.log(isClockwise(poly));

let poly2 = [[618,186],[650,170],[701,179],[716,207],[708,247],[666,259],[637,246],[615,219]];

console.log(isClockwise(poly2));
Run Code Online (Sandbox Code Playgroud)

很确定这是正确的。它似乎工作:-)

那些多边形看起来像这样,如果你想知道: