首页 文章

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

提问于
浏览
215

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

例如:

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

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

20 回答

  • 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
    
  • 6

    R确定方向的解决方案,如果是顺时针方向则反转(发现对于owin对象是必要的):

    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
    
  • 355

    找到这些点的质量中心 .

    假设从这一点到你的观点都有一条线 .

    找到line0 line1的两行之间的角度

    而不是line1和line2

    ...

    ...

    如果这个角度单调增加而不是逆时针,

    否则,如果单调减少它是顺时针

    别的(这不是单调的)

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

  • -4

    我的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

    在测试了几个不可靠的实现之后,提供了关于开箱即用的CW / CCW方向的令人满意的结果的算法是OP在this thread( shoelace_formula_3 )中发布的算法 .

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

  • 3

    我认为为了顺时针给出一些点,所有边缘都需要是正的而不仅仅是边的总和 . 如果一个边是负的,则逆时针给出至少3个点 .

  • 0

    这是OpenLayers 2的实现函数 . 具有顺时针多边形的条件是 area < 0 ,由this reference确认 .

    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);
    }
    
  • 49

    JavaScript中Sean's answer的实现:

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

    很确定这是对的 . 它似乎工作:-)

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

  • 20

    正如本维基百科文章Curve orientation中所解释的那样,在平面上给出3点 pqr (即使用x和y坐标),您可以计算以下行列式的符号

    enter image description here

    如果行列式是负的(即 Orient(p, q, r) < 0 ),则多边形顺时针(CW)定向 . 如果行列式是正的(即 Orient(p, q, r) > 0 ),则多边形逆时针(CCW)定向 . 如果点 pqrcollinear,则行列式为零(即 Orient(p, q, r) == 0 ) .

    在上面的公式中,我们在 pqr 的坐标前面加上那些,因为我们使用homogeneous coordinates .

  • 2

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

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

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

  • 2

    如果使用Matlab,如果多边形顶点按顺时针顺序,函数ispolycw将返回true .

  • 2

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

    计算有符号区域:A = 1/2 *(x1 * y2 - x2 * y1 x2 * y3 - x3 * y2 ... xn * y1 - x1 * yn)

    或者在伪代码中:

    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

  • 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

    在非凸多边形(例如新月形)的情况下,一些建议的方法将失败 . 这里's a simple one that will work with non-convex polygons (it' ll甚至可以使用像八字形一样的自相交多边形,告诉你它是否主要是顺时针方向) .

    边上的和,(x2 - x1)(y2 y1) . 如果结果为正,则曲线为顺时针,如果为负,则曲线为逆时针 . (结果是封闭区域的两倍,使用/ - 约定 . )

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

    cross product测量两个向量的垂直度 . 想象一下,多边形的每个边都是三维(3-D)xyz空间的x-y平面中的向量 . 然后,两个连续边的叉积是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 是从 point0point1 的段
    edgeB 介于 point1point2 之间
    ...
    edgeE 介于 point4point0 之间 .

    然后是顶点A( point0
    edgeE [从 point4point0 ]
    edgeA [从 point0 到'point1'

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

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

    并且使用以下矩阵的行列式计算这两个相邻边的叉积,该矩阵是通过将两个向量的坐标放在表示三个坐标轴( ij ,& k )的符号下面而构造的 . 第三个(零)值坐标是因为交叉积概念是三维构造,因此我们将这些二维向量扩展为三维以应用交叉积:

    i    j    k 
    -4    0    0
     1    4    0
    

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

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

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

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

    所以罪的衡量标准(AB)= -16 / 16.4924 = -.97014...

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

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

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

  • 0

    这是基于this answer的算法的简单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]; // % is the modulo operator
            sum += (v2.X - v1.X) * (v2.Y + v1.Y);
        }
        return sum > 0.0;
    }
    
  • 0

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


    参考文献:

    维基百科

  • 4

    为了它的 Value ,我使用这个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;
      }
    })();
    
  • 42

    如果您已经知道多边形内的一个点,那么计算方法更简单:

    • 从原始多边形,点和它们的坐标中按顺序选择任何线段 .

    • 添加已知的“内部”点,并形成三角形 .

    • 按照建议here计算CW或CCW这三点 .

相关问题