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


259

有一个点列表,如何找到它们是否按顺时针顺序排列?

例如:

point[0] = (5,0)
point[1] = (6,4)
point[2] = (4,5)
point[3] = (1,5)
point[4] = (1,0)

会说它是逆时针(对于某些人来说是逆时针)。


4
请注意:可接受的答案及其后的许多答案都需要大量加法和乘法运算(它们基于以负数或正数结尾的面积计算;例如“鞋类公式”)。在实现其中之一之前,请考虑lhf的答案,该答案基于简单多边形的Wiki方向更简单/更快速。
— ToolmakerSteve

我总是从两个相邻向量的叉积的角度来考虑它。如果我绕着多边形的周界走,我的头会指向平面之外。我将平面外向量与行走方向向量交叉,以获取坐标系中的第三个方向。如果该矢量指向,以使内部位于我的左侧,则为逆时针方向;如果内部在我的右边,则为顺时针方向。
— duffymo

Answers:


416

在非凸多边形(例如新月形)的情况下,某些建议的方法将失败。这是一个适用于非凸多边形的简单示例(它甚至可以与自相交的多边形(例如,八字形图形)一起使用,告诉您它是否大部分是顺时针方向)。

边的总和为(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

28
它的演算适用于一个简单的案例。(我没有技巧来发布图形。)线段下的面积等于其平均高度(y2 + y1)/ 2倍于其水平长度(x2-x1)。请注意x中的符号约定。尝试使用一些三角形,您将很快看到它的工作原理。
— Beta Beta

72
一个小警告:这个答案假设一个正常的笛卡尔坐标系。值得一提的原因是某些常见的上下文(例如HTML5画布)使用了反转的Y轴。然后必须翻转规则:如果面积为负,则曲线为顺时针方向。
— LarsH 2013年

8
@ Mr.Qbs:所以我的方法行得通,但是如果您跳过重要部分,那么它就行不通了。这不是新闻。
— Beta

11
@ 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)该公式仅适用于封闭的多边形。多边形没有虚边。
— Olivier Jacot-Descombes,2015年

4
此blog.element84.com/polygon-winding.html用简单的英语解释了此解决方案为何有效。
— David Zorychta '17

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)

所以标签的边缘连续为
edgeA从段point0到point1和
edgeB之间point1,以point2
...
edgeE之间point4和point0。

然后顶点A(point0)在
edgeE[从 point4到point0]
edgeA[从 point0到'point1'

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

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

并且使用下面的矩阵,这是通过将代表这三个符号下方的两个向量的坐标的坐标轴构成的行列式计算这两个邻接边缘的叉积(i,j,& k)。因为叉积概念是3-D构造,所以存在第三个(零)值坐标,因此我们将这些2-D向量扩展为3-D以便应用叉积:

 i    j    k 
-4    0    0
 1    4    0    

假定所有叉积都产生一个垂直于两个相乘的向量的向量,则上述矩阵的行列式仅具有k(,或z轴)分量。
用于计算k或Z轴分量的大小的公式为
a1*b2 - a2*b1 = -4* 4 - 0* 1 = -16

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

因此,要返回到仅一个角度的度量,您需要将该值(-16)除以两个矢量的大小的乘积。

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

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

这是对顶点之后的下一个段是否向左或向右弯曲以及弯曲多少的度量。无需采取反正弦曲线。我们只关心它的大小,当然还有它的符号(正或负)!

对闭合路径周围的其他4个点分别执行此操作,然后将每个顶点上此计算中的值相加。

如果最终总和为正,则顺时针为负,逆时针为负。


3
实际上,此解决方案与接受的解决方案是不同的解决方案。它们是否相等,这是我正在研究的问题,但我怀疑它们不是...可接受的答案是通过计算多边形顶部边缘下方的面积与下面的面积之间的差来计算多边形的面积多边形的底边。一个将是负数(一个从左到右遍历的位置),另一个将是负数。顺时针移动时,上边缘从左到右移动,并且较大,因此总和为正。
— 查尔斯·布雷塔纳

1
我的解决方案测量每个顶点的边角变化的正弦之和。顺时针运行时为正,逆时针运行时为负。
— 查尔斯·布雷塔纳

2
用这种方法看来,您
— 确实

2
您确实需要使用反正弦。在一堆随机的非凸多边形上进行尝试,如果不使用反正弦,将会发现某些多边形的测试会失败。
— 卢克·哈奇森

1
@CharlesBretana-虽然我没有参加Luke的考试,但我相信他是正确的。这就是求和与非线性标度(无反正弦与反正反正反)相结合的本质。考虑一下marsbear提出的建议,您已正确拒绝了。他建议您“算数”,并指出少数几个大值可能会超过许多小值。现在考虑每个值与否的反正弦关系。还是不是因为没有使用arcsin赋予每个值不正确的权重,因此具有相同的缺陷(尽管少得多)的情况?
— ToolmakerSteve

47

我想这是一个很老的问题,但是无论如何我都会抛出另一个解决方案,因为它很简单,而且数学上并不密集-它只使用基本代数。计算多边形的有符号区域。如果为负,则点按顺时针方向排列;如果为正,则点按逆时针方向排列。(这与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

请注意,如果仅检查顺序,则无需麻烦除以2。

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


这是您上面签名区域公式中的错字吗?它以“ xn * y1-x1 * yn”结尾;我认为应该是“ x_n y_ {n + 1}-y_n x_ {n-1}”(至少在LaTeX中)。另一方面,距离我上任何线性代数课程已经十年了。
— 迈克尔·埃里克·奥伯林

不。如果查看源代码,您会发现该公式实际上确实在最后一项(y1和x1)中再次引用了第一点。(对不起,我对LaTeX不太熟悉,但是我设置了下标的格式以使其更易读。)
— 肖恩·比恩

我使用了此解决方案,它非常适合我的使用。请注意,如果您可以预先计划数组中的多余向量和多余向量,则可以通过在数组尾部添加第一个向量来摆脱比较(或%)。这样,您就可以循环遍历所有元素,除了最后一个元素(长度2而不是长度1)。
— Eric Fortier

2
@EricFortier-FWIW,而不是调整可能较大的数组的大小,一种有效的替代方法是每次迭代将其点保存previousPoint为下一次迭代。在开始循环之前,将其设置previousPoint为数组的最后一点。需要权衡的是额外的局部变量副本,但较少的数组访问。最重要的是,不必触摸输入数组。
— ToolmakerSteve

2
@MichaelEricOberlin- 通过包括从最后一点到第一个点的线段来关闭多边形是必要的。(无论哪个点开始闭合的多边形,正确的计算都是相同的。)
— ToolmakerSteve

36

找到具有最小y的顶点(如果有联系,则找到最大的x)。令顶点为A,列表中B的上一个顶点为,列表中的下一个顶点为C。现在计算符号的叉积的AB和AC。


参考文献:


7
在en.wikipedia.org/wiki/Curve_orientation中也对此进行了说明。关键是找到的点必须在凸包上,并且仅需要局部查看凸包(及其紧邻的邻居)上的单个点即可确定整个多边形的方向。
— M Katz 2015年

1
震惊和敬畏,这还没有收到更多投票。对于简单多边形(在某些字段中是大多数多边形),此答案给出了一个O(1)解决方案。所有其他答案可得出多边形点数量的O(n)解n。要获得更深入的优化,请参阅Wikipedia出色的“ 曲线方向”文章的“ 实用注意事项”小节。
— Cecil Curry

8
澄清:O(1)仅当以下情况之一时,该解决方案:(A)此多边形是凸的(在这种情况下,任意顶点位于凸包上,因此就足够了)或 (B)您已经知道Y坐标最小的顶点。如果不是这种情况(即,该多边形是非凸的,并且您对此一无所知),O(n)则需要进行搜索。但是,由于不需要求和,因此它仍然比简单多边形的任何其他解决方案都快得多。
— Cecil Curry


1
@CecilCurry我想您的第二条评论解释了为什么没有收到更多赞誉。在某些情况下,它会给出错误的答案,而没有提及这些限制。
— LarsH

23

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

假设我们有一个Vector类型为X且Y属性为type的类型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;
}

%是执行求模运算的模或余数运算符(根据Wikipedia)在将一个数除以另一个后找到余数。


6

从其中一个顶点开始,然后计算每侧对向的角度。

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

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


看到叉积基本上可以归结为正比例因子乘以角度正弦后,最好做一个叉积。它将更快,更简单。
— ReaperUnreal

4

物有所值,我使用了这个mixin来计算Google Maps API v3应用程序的绕线顺序。

该代码利用了多边形区域的副作用:顶点的顺时针缠绕顺序产生一个正面积,而相同顶点的逆时针缠绕顺序产生的面积与负值相同。该代码还在Google Maps几何图形库中使用了一种私有API。使用它令我感到自在-使用后果自负。

用法示例:

var myPolygon = new google.maps.Polygon({/*options*/});
var isCW = myPolygon.isPathClockwise();

单元测试的完整示例@ http://jsfiddle.net/stevejansen/bq2ec/

/** Mixin to extend the behavior of the Google Maps JS API Polygon type
 *  to determine if a polygon path has clockwise of counter-clockwise winding order.
 *  
 *  Tested against v3.14 of the GMaps API.
 *
 *  @author  stevejansen_github@icloud.com
 *
 *  @license http://opensource.org/licenses/MIT
 *
 *  @version 1.0
 *
 *  @mixin
 *  
 *  @param {(number|Array|google.maps.MVCArray)} [path] - an optional polygon path; defaults to the first path of the polygon
 *  @returns {boolean} true if the path is clockwise; false if the path is counter-clockwise
 */
(function() {
  var category = 'google.maps.Polygon.isPathClockwise';
     // check that the GMaps API was already loaded
  if (null == google || null == google.maps || null == google.maps.Polygon) {
    console.error(category, 'Google Maps API not found');
    return;
  }
  if (typeof(google.maps.geometry.spherical.computeArea) !== 'function') {
    console.error(category, 'Google Maps geometry library not found');
    return;
  }

  if (typeof(google.maps.geometry.spherical.computeSignedArea) !== 'function') {
    console.error(category, 'Google Maps geometry library private function computeSignedArea() is missing; this may break this mixin');
  }

  function isPathClockwise(path) {
    var self = this,
        isCounterClockwise;

    if (null === path)
      throw new Error('Path is optional, but cannot be null');

    // default to the first path
    if (arguments.length === 0)
        path = self.getPath();

    // support for passing an index number to a path
    if (typeof(path) === 'number')
        path = self.getPaths().getAt(path);

    if (!path instanceof Array && !path instanceof google.maps.MVCArray)
      throw new Error('Path must be an Array or MVCArray');

    // negative polygon areas have counter-clockwise paths
    isCounterClockwise = (google.maps.geometry.spherical.computeSignedArea(path) < 0);

    return (!isCounterClockwise);
  }

  if (typeof(google.maps.Polygon.prototype.isPathClockwise) !== 'function') {
    google.maps.Polygon.prototype.isPathClockwise = isPathClockwise;
  }
})();

尝试此操作,我得到的结果恰好相反,按顺时针方向绘制的多边形产生负的面积,而按逆时针方向绘制的多边形产生正的面积。无论哪种情况,此代码段在5年后还是非常有用的,谢谢。
— 卡梅伦·罗伯茨

@CameronRoberts规范(尤其是geoJson,请参见IETF)应遵循“右手规则”。我猜谷歌在抱怨。在这种情况下,外圈必须逆时针旋转(产生正区域),而内圈(孔)顺时针缠绕(从主区域移除负区域)。
— allez l'OM

4

一个实现 肖恩的回答在JavaScript:

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

可以肯定,这是正确的。它似乎正在工作:-)

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


3

这是OpenLayers 2的已实现功能。area < 0由该参考文献确定了具有顺时针多边形的条件。

function IsClockwise(feature)
{
    if(feature.geometry == null)
        return -1;

    var vertices = feature.geometry.getVertices();
    var area = 0;

    for (var i = 0; i < (vertices.length); i++) {
        j = (i + 1) % vertices.length;

        area += vertices[i].x * vertices[j].y;
        area -= vertices[j].x * vertices[i].y;
        // console.log(area);
    }

    return (area < 0);
}

Openlayers是基于javascript的地图管理库,如googlemaps,它是在openlayers 2中编写和使用的。–
— MSS,

您能否解释一下代码的功能以及为什么这么做?
— nbro

@nbro此代码实现了lhf答案。通过将顶点直接作为参数,可以很容易地将非OpenLayer部分保留在纯JavaScript函数中。它运行良好,并且可以适应multiPolygon的情况。
— allez l'OM


1

正如在此Wikipedia文章“ 曲线方向”中给出的,给定3个点p,q并且r在平面上(即具有x和y坐标),您可以计算以下行列式的符号

在此处输入图片说明

如果行列式为负(即Orient(p, q, r) < 0),则多边形的方向为顺时针(CW)。如果行列式为正(即Orient(p, q, r) > 0),则多边形的方向为逆时针(CCW)。行列式为零(即Orient(p, q, r) == 0),如果点是p,q并且r是共线的。

在上面的公式中,我们在预先考虑的坐标的前的那些p,q 和r因为我们使用齐次坐标。


@tibetty您能解释一下,如果多边形是凹面的,为什么这种方法在许多情况下不起作用吗?
— nbro

1
请查看您帖子中Wiki项目参考中的最后一个表。我举一个错误的例子很容易,但是很难证明。
— tibetty

1
请查看您帖子中Wiki项目参考中的最后一个表。我举一个错误的例子很容易,但是很难证明。
— tibetty

1
@tibetty是正确的。您不能简单地沿多边形获取任何三个点。您可能位于该多边形的凸凹区域中。仔细阅读Wiki,必须沿着包围多边形的凸包取三点。从“实际考虑”中考虑:“无需构造多边形的凸包即可找到合适的顶点。常见的选择是X坐标最小的多边形的顶点。如果有多个顶点,则一个选取最小的Y坐标。它保证是多边形凸包的[a]顶点。”
— ToolmakerSteve

1
因此,lhf的较早答案(与之相似)引用了相同的Wiki文章,但指定了这一点。[显然,只要不避开中间位置,就可以取最小或最大的x或y。有效地是从包围多边形的边界框的一个边缘开始工作,以确保处于凹入区域。]
— ToolmakerSteve

0

我认为,为了使某些点沿顺时针方向给出,不仅所有边之和,所有边都必须为正。如果一个边为负,则至少有3个点是逆时针方向给出的。


是的,但是您会误解多边形的缠绕顺序(顺时针或逆时针)的概念。在完全凸的多边形中,所有点的角度均为顺时针或全部为逆时针(如您的第一句话)。在具有凹入区域的多边形中,“凹面”将处于相反的方向,但是作为一个整体,多边形仍具有定义明确的内部,因此被视为顺时针或逆时针方向。参见en.wikipedia.org/wiki/…–
— ToolmakerSteve

0

我的C#/ LINQ解决方案基于以下@charlesbretana的交叉产品建议。您可以为绕组指定参考法线。只要曲线大部分位于向上矢量所定义的平面内,它就应该起作用。

using System.Collections.Generic;
using System.Linq;
using System.Numerics;

namespace SolidworksAddinFramework.Geometry
{
    public static class PlanePolygon
    {
        /// <summary>
        /// Assumes that polygon is closed, ie first and last points are the same
        /// </summary>
       public static bool Orientation
           (this IEnumerable<Vector3> polygon, Vector3 up)
        {
            var sum = polygon
                .Buffer(2, 1) // from Interactive Extensions Nuget Pkg
                .Where(b => b.Count == 2)
                .Aggregate
                  ( Vector3.Zero
                  , (p, b) => p + Vector3.Cross(b[0], b[1])
                                  /b[0].Length()/b[1].Length());

            return Vector3.Dot(up, sum) > 0;

        } 

    }
}

进行单元测试

namespace SolidworksAddinFramework.Spec.Geometry
{
    public class PlanePolygonSpec
    {
        [Fact]
        public void OrientationShouldWork()
        {

            var points = Sequences.LinSpace(0, Math.PI*2, 100)
                .Select(t => new Vector3((float) Math.Cos(t), (float) Math.Sin(t), 0))
                .ToList();

            points.Orientation(Vector3.UnitZ).Should().BeTrue();
            points.Reverse();
            points.Orientation(Vector3.UnitZ).Should().BeFalse();



        } 
    }
}

0

这是我使用其他答案中的解释的解决方案:

def segments(poly):
    """A sequence of (x,y) numeric coordinates pairs """
    return zip(poly, poly[1:] + [poly[0]])

def check_clockwise(poly):
    clockwise = False
    if (sum(x0*y1 - x1*y0 for ((x0, y0), (x1, y1)) in segments(poly))) < 0:
        clockwise = not clockwise
    return clockwise

poly = [(2,2),(6,2),(6,6),(2,6)]
check_clockwise(poly)
False

poly = [(2, 6), (6, 6), (6, 2), (2, 2)]
check_clockwise(poly)
True

1
您可以指定该答案所基于的其他答案吗?
— nbro

0

如果您已经知道多边形内的一个点,则这是一种计算简单得多的方法:

  1. 从原始多边形,点及其坐标中依次选择任何线段。

  2. 添加一个已知的“内部”点,并形成一个三角形。

  3. 按照这三点计算此处建议的CW或CCW 。


如果多边形完全是凸的,则可能可行。如果有任何凹入区域,绝对是不可靠的-它很容易选择一个位于洞穴边缘之一的“错误”一侧的点,然后将其连接到该边缘。会得到错误的答案。
— ToolmakerSteve

即使多边形是凹面的,它也可以工作。该点必须在该凹多边形内。但是,我不确定复杂的多边形(未经测试。)
— Venkata Goli,

“即使多边形是凹面的,它也可以工作。” -反例:poly(0,0),(1,1),(0,2),(2,2),(2,0)。线段(1,1),(0、2)。如果您在(1,1),(0,2),(1,2)内选取一个内部点以形成三角形->(1,1),(0,2),(0.5,1.5)),则得到与在(0,0),(1,1),(1,0)>(1,1),(0,2),(0.5、0.5)内选取一个内部点相比,则选择相反的绕组。它们既是原始多边形的内部,又具有相反的绕组。因此,其中之一给出了错误的答案。
— ToolmakerSteve

通常,如果多边形具有任何凹入区域,请在凹入区域中选取一个线段。因为它是凹形的,所以您可以在该线的相对两侧找到两个“内部”点。由于它们位于该线的相对两侧,因此形成的三角形具有相反的绕组。证明结束。
— ToolmakerSteve

0

在测试了几种不可靠的实现之后,提供了关于CW / CCW定向的开箱即用的令人满意结果的算法是由OP在此线程(shoelace_formula_3)中发布的。

与往常一样,正数表示CW方向,而负数表示CCW。


0

这是基于上述答案的快速3.0解决方案:

    for (i, point) in allPoints.enumerated() {
        let nextPoint = i == allPoints.count - 1 ? allPoints[0] : allPoints[i+1]
        signedArea += (point.x * nextPoint.y - nextPoint.x * point.y)
    }

    let clockwise  = signedArea < 0

0

另一个解决方案;

const isClockwise = (vertices=[]) => {
    const len = vertices.length;
    const sum = vertices.map(({x, y}, index) => {
        let nextIndex = index + 1;
        if (nextIndex === len) nextIndex = 0;

        return {
            x1: x,
            x2: vertices[nextIndex].x,
            y1: x,
            y2: vertices[nextIndex].x
        }
    }).map(({ x1, x2, y1, y2}) => ((x2 - x1) * (y1 + y2))).reduce((a, b) => a + b);

    if (sum > -1) return true;
    if (sum < 0) return false;
}

像这样将所有顶点作为一个数组;

const vertices = [{x: 5, y: 0}, {x: 6, y: 4}, {x: 4, y: 5}, {x: 1, y: 5}, {x: 1, y: 0}];
isClockwise(vertices);

0

R确定方向并逆时针旋转的解决方案(发现单向物体是必需的):

coords <- cbind(x = c(5,6,4,1,1),y = c(0,4,5,5,0))
a <- numeric()
for (i in 1:dim(coords)[1]){
  #print(i)
  q <- i + 1
  if (i == (dim(coords)[1])) q <- 1
  out <- ((coords[q,1]) - (coords[i,1])) * ((coords[q,2]) + (coords[i,2]))
  a[q] <- out
  rm(q,out)
} #end i loop

rm(i)

a <- sum(a) #-ve is anti-clockwise

b <- cbind(x = rev(coords[,1]), y = rev(coords[,2]))

if (a>0) coords <- b #reverses coords if polygon not traced in anti-clockwise direction

0

尽管这些答案是正确的,但它们在数学上比必要的更为激烈。假设地图坐标,其中最北点是地图上的最高点。找到最北的点,如果有2个点平局,那就是最北,然后是最东(这是lhf在他的答案中使用的点)。在您看来

点[0] =(5,0)

点[1] =(6,4)

点[2] =(4,5)

点[3] =(1,5)

点[4] =(1,0)

如果我们假定P2是最北的,则上一个点或下一个点的东点确定为顺时针,顺时针或逆时针。由于最北点在北面上,因此如果P1(先前的P2)向东移动,则方向为CW。在这种情况下,它向西移动,因此,如接受的答案所示,方向为CCW。如果前一点没有水平运动,则相同的系统适用于下一点P3。如果P3在P2的西边,则为CCW。如果P2到P3的运动是东方的,那么在这种情况下,它的运动就是西方的。假设数据中的nte P2是最北点,然后是东点,prv是前一个点,数据中是P1,nxt是下一个点,数据中是P3,[0]是水平或东/西,西小于东,[1]是垂直的。

if (nte[0] >= prv[0] && nxt[0] >= nte[0]) return(CW);
if (nte[0] <= prv[0] && nxt[0] <= nte[0]) return(CCW);
// Okay, it's not easy-peasy, so now, do the math
if (nte[0] * nxt[1] - nte[1] * nxt[0] - prv[0] * (nxt[1] - crt[1]) + prv[1] * (nxt[0] - nte[0]) >= 0) return(CCW); // For quadrant 3 return(CW)
return(CW) // For quadrant 3 return (CCW)

恕我直言,坚持lhf的答案中显示的基本数学运算会更安全-感谢您提及他。将其简化为象限所面临的挑战是,需要大量工作来证明您的公式在所有情况下都是正确的。您是否正确计算出“西偏西”?在凹面多边形中, [1]和[3]均为[2]的“西边和南边”?在这种情况下,您是否正确处理了[1]和[3]的不同长度?我不知道,但是如果我直接计算该角度(或它的行列式),则使用的是众所周知的公式。
— ToolmakerSteve

@ToolmakerSteve如果3点是凸的,则if语句始终有效。如果if语句将返回,那么您将获得正确的答案。如果形状是凹面和极端,则if语句将不会返回。那是您必须进行数学运算的时候。大多数图像只有一个象限,因此该部分很容易。我的子例程调用中有99%以上是由if语句处理的。
— VectorVortec

那没有解决我的担忧。那公式是什么?是lhf的答案中wiki链接中给出的方向决定因素吗?如果是这样,那就这样说。说明您正在做的是对大多数情况进行快速检查,以避免标准数学运算。如果是这样,那么您的回答现在对我有意义。(小尼特:会更容易阅读,如果你使用.x和.y一个结构的,而不是[0]和[1]我不知道你的代码是说,我第一次在它一眼。)
— ToolmakerSteve

由于我对您的方法没有信心,因此我采用了lhf的方法;从他的链接公式。最慢的部分是找到合适的顶点-O(N)搜索。一旦找到,行列式就是一个O(1)运算,使用6乘以5加。最后一部分是您已优化的内容;但您可以通过添加其他if测试来做到这一点。我个人无法证明采用非标准方法-需要验证每个步骤都是正确的-但感谢您对象限的有趣分析!
— ToolmakerSteve

0

C#代码实现lhf的答案:

// https://en.wikipedia.org/wiki/Curve_orientation#Orientation_of_a_simple_polygon
public static WindingOrder DetermineWindingOrder(IList<Vector2> vertices)
{
    int nVerts = vertices.Count;
    // If vertices duplicates first as last to represent closed polygon,
    // skip last.
    Vector2 lastV = vertices[nVerts - 1];
    if (lastV.Equals(vertices[0]))
        nVerts -= 1;
    int iMinVertex = FindCornerVertex(vertices);
    // Orientation matrix:
    //     [ 1  xa  ya ]
    // O = | 1  xb  yb |
    //     [ 1  xc  yc ]
    Vector2 a = vertices[WrapAt(iMinVertex - 1, nVerts)];
    Vector2 b = vertices[iMinVertex];
    Vector2 c = vertices[WrapAt(iMinVertex + 1, nVerts)];
    // determinant(O) = (xb*yc + xa*yb + ya*xc) - (ya*xb + yb*xc + xa*yc)
    double detOrient = (b.X * c.Y + a.X * b.Y + a.Y * c.X) - (a.Y * b.X + b.Y * c.X + a.X * c.Y);

    // TBD: check for "==0", in which case is not defined?
    // Can that happen?  Do we need to check other vertices / eliminate duplicate vertices?
    WindingOrder result = detOrient > 0
            ? WindingOrder.Clockwise
            : WindingOrder.CounterClockwise;
    return result;
}

public enum WindingOrder
{
    Clockwise,
    CounterClockwise
}

// Find vertex along one edge of bounding box.
// In this case, we find smallest y; in case of tie also smallest x.
private static int FindCornerVertex(IList<Vector2> vertices)
{
    int iMinVertex = -1;
    float minY = float.MaxValue;
    float minXAtMinY = float.MaxValue;
    for (int i = 0; i < vertices.Count; i++)
    {
        Vector2 vert = vertices[i];
        float y = vert.Y;
        if (y > minY)
            continue;
        if (y == minY)
            if (vert.X >= minXAtMinY)
                continue;

        // Minimum so far.
        iMinVertex = i;
        minY = y;
        minXAtMinY = vert.X;
    }

    return iMinVertex;
}

// Return value in (0..n-1).
// Works for i in (-n..+infinity).
// If need to allow more negative values, need more complex formula.
private static int WrapAt(int i, int n)
{
    // "+n": Moves (-n..) up to (0..).
    return (i + n) % n;
}

1
这似乎是针对下正Y坐标。翻转CW / CCW以获取标准坐标。
— 沃里克·艾莉森

0

这是一个基于此答案的简单Python 3实现(相应地,该实现基于已接受答案中提出的解决方案)

def is_clockwise(points):
    # points is your list (or array) of 2d points.
    assert len(points) > 0
    s = 0.0
    for p1, p2 in zip(points, points[1:] + [points[0]]):
        s += (p2[0] - p1[0]) * (p2[1] + p1[1])
    return s > 0.0

-4

找到这些点的质心。

假设从此点到您的点有直线。

查找line0 line1的两条线之间的角度

比第1行和第2行

...

...

如果此角度单调递增,而不是逆时针递增,

否则,如果单调递减,则顺时针

其他(不是单调的)

你不能决定,所以这是不明智的


通过“质心”我想你是说“质心”?
— Vicky Chijwani,2012年

如果多边形完全是凸的,则可能有效。但是最好改为使用适用于非凸多边形的答案。
— ToolmakerSteve
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.