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

Math 如何确定多边形点列表是否按顺时针顺序排列?,math,geometry,polygon,computational-geometry,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) 会说它是逆时针的(或者对某些人来说是逆时针的)。从其中一个顶点开始,计算每边所对的角度 第一个和最后一个将为零(因此跳过这些);对于其余部分,角度的正弦将由(点[n]-点[0])和(点[n-1]-点[0])单位长度的归一化的叉积给出 如果值之和为正,则多边形将按逆时针方

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

例如:

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

会说它是逆时针的(或者对某些人来说是逆时针的)。

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

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


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

找到这些点的质心

假设从这一点到你的点有几条线

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

而不是对第1行和第2行执行此操作

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

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

否则(它不是单调的)


您无法决定,因此这是不明智的

对于非凸多边形(如新月形),某些建议的方法将失败。这里有一个简单的方法可以处理非凸多边形(它甚至可以处理自相交多边形,如图8,告诉你它是否主要是顺时针的)

边上的和(x2− x1)(y2+y1)。如果结果为正,则曲线为顺时针方向,如果结果为负,则曲线为逆时针方向。(结果是封闭区域的两倍,具有+/-约定。)

测量两个向量的垂直度。假设多边形的每条边都是三维(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
是从
point0
point1

edgeB
介于
点1
点2


edgeE
位于
点4
点0
之间

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

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

edgeE
=
点0
-
点4
=
(1,0)-(5,0)
=
(-4,0)

edgeA
=
点1
-
点0
=
(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×B(叉积)=| A |*| B |*sin(AB)

所以,为了回到角度的度量,你需要将这个值(
-16
)除以两个向量大小的乘积

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

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

这是衡量顶点后的下一段是否向左或向右弯曲以及弯曲程度的指标。不需要取弧正弦。我们只关心它的大小,当然还有它的符号(正或负)

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


如果最终和为正,则按顺时针、负时针和逆时针方向进行。

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


参考资料:

  • 如何找到简单多边形的方向

  • 在维基百科


我将抛出另一个解决方案,因为它简单明了,不需要数学计算——它只使用基本代数。计算多边形的有符号面积。如果是负数,点按顺时针顺序排列,如果是正数,点按逆时针顺序排列。(这与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
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
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
var myPolygon = new google.maps.Polygon({/*options*/});
var isCW = myPolygon.isPathClockwise();
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);
}
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();



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