ChatGPT解决这个技术问题 Extra ChatGPT

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

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

例如:

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

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

请注意:接受的答案以及之后的许多答案需要大量的加法和乘法(它们基于以负数或正数结尾的面积计算;例如“鞋带公式”)。在实施其中之一之前,请考虑基于 wiki - orientation of simple polygon 的更简单/更快的 lhf's answer
我总是根据两个相邻向量的叉积来考虑它。如果我绕着多边形的周边走,我的头会指向平面之外。我将平面外向量交叉到我的步行方向向量中,以在我的坐标系中获得第三方向。如果该向量指向内部在我的左侧,则为逆时针方向;如果内部在我的右边,它是顺时针的。

R
Roberto Bonvallet

在非凸多边形(例如新月形)的情况下,一些建议的方法将失败。这是一个适用于非凸多边形的简单多边形(它甚至适用于像八字形这样的自相交多边形,告诉你它是否主要是顺时针的)。

对边求和,(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

它是应用于一个简单案例的微积分。 (我没有发布图形的技能。)线段下的面积等于它的平均高度(y2+y1)/2 乘以它的水平长度(x2-x1)。注意 x 中的符号约定。用一些三角形试试这个,你很快就会明白它是如何工作的。
一个小警告:这个答案假设一个正常的笛卡尔坐标系。值得一提的原因是一些常见的上下文,如 HTML5 画布,使用了倒置的 Y 轴。然后必须翻转规则:如果面积为负,则曲线为顺时针。
@Mr.Qbs:所以我的方法有效,但是如果您跳过重要部分,则它不起作用。这不是新闻。
@Mr.Qbs:您始终必须将最后一个点链接到第一个点。如果您有从 0 到 N-1 编号的 N 个点,那么您必须计算: Sum( (x[(i+1) mod N] - x[i]) * (y[i] + y[(i+1) mod N]) ) for i = 0 to N-1。即,必须取索引模N (N ≡ 0) 该公式仅适用于闭合多边形。多边形没有假想的边。
blog.element84.com/polygon-winding.html 用简单的英语解释了此解决方案为何有效。
M
Michael

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

参考:

如何找到简单多边形的方向?在常见问题解答中:comp.graphics.algorithms。

维基百科的曲线方向。


en.wikipedia.org/wiki/Curve_orientation 中也对此进行了说明。关键是找到的点必须在凸包上,并且只需要局部查看凸包(及其直接邻居)上的单个点即可确定整个多边形的方向。
震惊和敬畏这没有得到更多的支持。对于简单的多边形(这是某些领域中的大多数多边形),此答案会产生 O(1) 解决方案。对于 n 个多边形点,所有其他答案都会产生 O(n) 个解。如需更深入的优化,请参阅 Wikipedia 精彩的 Curve orientation 文章的 Practical Considerations 小节。
澄清: 只有当 (A) 这个多边形是凸的(在这种情况下,任意顶点位于凸hull 因此就足够了) (B)你已经知道Y坐标最小的顶点。如果情况不是(即,这个多边形是非凸的,而您对此一无所知),则需要进行O(n) 搜索。然而,由于不需要求和,这仍然比简单多边形的任何其他解决方案快得多。
@CecilCurry 我认为您的第二条评论解释了为什么这没有得到更多的支持。它在某些情况下会产生错误的答案,而没有提及这些限制。
S
StayOnTarget

我将提出另一种解决方案,因为它简单明了且数学上不密集——它只使用基本代数。计算多边形的有符号面积。如果为负,则点按顺时针顺序排列,如果为正,则按逆时针顺序排列。 (这与 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


这是您上面的签名区域公式中的错字吗?它以“xn*y1 - x1*yn”结尾;当我认为它应该是“x_n y_{n+1} - y_n x_{n-1}”(至少在 LaTeX 中)时。另一方面,我已经十年没有上任何线性代数课了。
没有。如果您检查 source,您会看到该公式实际上确实在最后一项(y1 和 x1)中再次引用了第一个点。 (对不起,我对 LaTeX 不是很熟悉,但我对下标进行了格式化以使其更具可读性。)
我使用了这个解决方案,它非常适合我的使用。请注意,如果您可以提前计划并在数组中留出多余的两个向量,则可以通过在数组的尾部添加第一个向量来消除比较(或 %)。这样,您只需遍历所有元素,最后一个元素除外(长度为 2 而不是长度为 1)。
@EricFortier - FWIW,而不是调整可能很大的数组的大小,一种有效的替代方法是每次迭代将其点保存为 previousPoint 以进行下一次迭代。在开始循环之前,将 previousPoint 设置为数组的最后一点。权衡是额外的局部变量复制但更少的数组访问。最重要的是,不必触摸输入数组。
@MichaelEricOberlin - 有必要通过包括从最后一点到第一点的线段来关闭多边形。 (无论哪个点开始闭合多边形,正确的计算都是相同的。)
C
Charles Bretana

cross product 测量两个向量的垂直度。想象一下,多边形的每条边都是三维 (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 是从 point0point1 的段,
edgeBpoint1point2
...
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,&{ 3})。第三个(零)值坐标在那里,因为叉积概念是一个 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),是 2 个原始向量之间夹角的正弦值乘以 2 个向量大小的乘积。
实际上,它的值的另一个公式是< br> A X B (Cross Product) = |A| * |B| * sin(AB)

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

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

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

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

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

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


实际上,此解决方案与公认的解决方案是不同的解决方案。它们是否相等是我正在调查的一个问题,但我怀疑它们不是......接受的答案计算多边形的面积,通过取多边形顶部边缘下的面积和下面的面积之间的差异多边形的底边。一个是负数(你从左到右遍历的那个),另一个是负数。顺时针遍历时,上边缘从左到右遍历并且较大,所以总数为正。
我的解决方案测量每个顶点的边角变化的正弦之和。顺时针移动时为正,逆时针移动时为负。
似乎用这种方法你确实需要采用反正弦,除非你假设凸性(在这种情况下你只需要检查一个顶点)
你确实需要服用 arcsin。在一堆随机的非凸多边形上尝试它,你会发现如果你不采用 arcsin,某些多边形的测试将失败。
@CharlesBretana - 虽然我没有进行卢克的测试,但我相信他是正确的。这就是求和与非线性尺度相结合的本质[没有 arcsin 与使用 arcsin]。考虑一下 Marsbear 的建议,你正确地拒绝了。他建议您“只计算”,并且您指出少数大值可能会超过大量小值。现在考虑每个值的 arcsin 与否。不采用 arcsin 是否仍然会给每个值赋予不正确的权重,因此具有相同的缺陷(尽管这样的缺陷要少得多)?
O
Olivier Jacot-Descombes

这是基于 @Beta's answer 的算法的简单 C# 实现。

假设我们有一个具有 XY 类型的 double 属性的 Vector 类型。

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

% 是执行模运算的模或余数运算符, (according to Wikipedia) 求一个数除以另一个数后的余数。

根据@MichelRouzic 的评论优化版本:

double sum = 0.0;
Vector v1 = vertices[vertices.Count - 1]; // or vertices[^1] with
                                          // C# 8.0+ and .NET Core
for (int i = 0; i < vertices.Count; i++) {
    Vector v2 = vertices[i];
    sum += (v2.X - v1.X) * (v2.Y + v1.Y);
    v1 = v2;
}
return sum > 0.0;

这不仅节省了模运算 %,还节省了数组索引。


您可以通过在循环开始之前设置 v1 = vertices[vertices.Count-1] 来避免代价高昂的 % 并避免分支,使用 v2 = vertices[i]; 然后在添加到 sum 之后执行 v1 = v2
@MichelRouzic (i+1) % (count-1) 分支如何不只是下一个索引的数学运算?为什么这会涉及分支?
我花了一些时间才记住我的意思,它不是分支,我的意思是如果有人试图在没有 % 的情况下做类似 (i+1) % (count-1) 的事情,他们会选择像 i+1 < count ? i+1 : 0 这样的事情,例如 this answer ,当有一种方法可以避免模数和分支时。
m
mpen

JavaScript 中 Sean's answer 的实现:

函数 calcArea(poly) { if(!poly || poly.length < 3) 返回 null;让 end = poly.length - 1;让 sum = poly[end][0]*poly[0][1] - poly[0][0]*poly[end][1]; for(让 i=0; i 0; } 让 poly = [[352,168],[305,208],[312,256],[366,287],[434,248],[416,186]]; console.log(isClockwise(poly));让 poly2 = [[618,186],[650,170],[701,179],[716,207],[708,247],[666,259],[637,246],[615,219]]; console.log(isClockwise(poly2));

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

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

https://i.imgur.com/JvZmmMH.png


S
Steve Gilham

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

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

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


看到叉积基本上归结为正比例因子乘以角度的正弦,最好只做叉积。它会更快,更简单。
S
Steve Jansen

对于它的价值,我使用这个 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 )是遵循“右手法则”。我猜谷歌正在抱怨。在这种情况下,外环必须是逆时针的(产生正区域),内环(孔)是顺时针方向的(负区域要从主区域中移除)。
n
nbro

这是 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);
}

Openlayers 是基于 javascript 的地图管理库,例如 googlemaps,它是在 openlayers 2 中编写和使用的。
你能解释一下你的代码做了什么,为什么要这样做吗?
@nbro 此代码实现了 lhf answer。通过将 vertices 直接作为参数,很容易将非 OpenLayer 部分保留在纯 javascript 函数中。效果很好,可以适应multiPolygon的情况。
T
ToolmakerSteve

实现 lhf's answer 的 C# 代码:

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

这似乎适用于向下为正的 Y 坐标。为标准坐标翻转 CW/CCW。
n
nbro

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


n
nbro

正如这篇维基百科文章 Curve orientation 中所解释的,给定平面上的 3 个点 pqr(即具有 x 和 y 坐标),您可以计算以下行列式的符号

https://i.stack.imgur.com/6sxC6.png

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

在上面的公式中,我们在 pqr 的坐标前面添加了一个,因为我们使用的是 homogeneous coordinates


@tibetty 你能解释一下为什么如果多边形是凹的,这种方法在很多情况下都行不通吗?
请查看您帖子中 wiki 项目参考中的最后一个表格。我很容易举一个错误的例子,但很难证明它。
请查看您帖子中 wiki 项目参考中的最后一个表格。我很容易举一个错误的例子,但很难证明它。
@tibetty 是正确的。您不能简单地沿多边形取任意三个点;您可能位于该多边形的凸面或凹面区域。仔细阅读wiki,必须沿着包围多边形的凸包取三个点。从“实际考虑”:“不需要构造一个多边形的凸包来找到一个合适的顶点。一个常见的选择是X坐标最小的多边形的顶点。如果有几个,一个选择最小 Y 坐标。保证是多边形凸包的 [a] 个顶点。
因此 lhf's earlier answer 是相似的,并且引用了相同的 wiki 文章,但指定了这样一个点。 [显然,取最小或最大、x 或 y 并不重要,只要避免居中即可;实际上是从多边形周围边界框的一个边缘开始工作,以保证在凹面区域内。]
n
nbro

这是一个基于 this answer 的简单 Python 3 实现(它又基于 the solution proposed in the accepted answer

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

d
daniel

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


没错,但是您误解了多边形缠绕顺序(顺时针或逆时针)的概念。在一个完全凸的多边形中,所有点的角度都是顺时针的,或者都是逆时针的[就像你的第一句话一样]。在具有凹面区域的多边形中,“洞穴”将在相反的方向上,但整个多边形仍然具有明确定义的内部,并相应地被认为是顺时针或逆时针。请参阅en.wikipedia.org/wiki/…
b
bradgonesurfing

我的 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();



        } 
    }
}

n
nbro

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

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

您能否具体说明该答案基于哪些其他答案?
n
nbro

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

按顺序从原始多边形、点及其坐标中选择任何线段。添加一个已知的“内部”点,并形成一个三角形。使用这三点计算此处建议的 CW 或 CCW。


如果多边形完全是凸的,也许这可行。如果有任何凹入区域肯定是不可靠的 - 它很容易选择一个位于洞穴边缘之一“错误”一侧的点,然后将其连接到该边缘。会得到错误的答案。
即使多边形是凹的,它也可以工作。该点需要在该凹多边形内。但是我不确定复杂的多边形(没有测试。)
“即使多边形是凹的,它也能工作。” - 反例: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) 内选择一个内部点相反的绕组。它们都在原始多边形的内部,但具有相反的绕组。因此,其中一个给出了错误的答案。
通常,如果多边形有任何凹面区域,请在凹面区域中选取一段。因为它是凹的,所以您可以在该线的相对两侧找到两个“内部”点。因为它们位于该线的相对两侧,所以形成的三角形具有相反的绕组。证明结束。
n
nbro

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

与往常一样,正数表示顺时针方向,而负数表示逆时针方向。


T
Toby Evetts

这是基于上述答案的 swift 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

I
Ind

另一个解决方案;

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

d
dez

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

V
VectorVortec

虽然这些答案是正确的,但它们在数学上的强度超过了必要的程度。假设地图坐标,最北点是地图上的最高点。找到最北的点,如果 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 的西边,是的,那么运动是逆时针方向的。如果 P2 到 P3 的运动是东,在这种情况下是西,运动是 CW。假设您的数据中的 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's answer 中显示的基本数学会更安全 - 感谢您提及他。将其减少到象限的挑战在于,要证明您的公式在所有情况下都是正确的,需要大量的工作。您是否正确计算了“更西”?在一个凹多边形中,both [1] 和 [3] 都是 [2] 的“西和南”?在那种情况下,您是否正确处理了不同长度的 [1] 和 [3]?我不知道,而如果我直接计算该角度(或其行列式),我使用的是众所周知的公式。
@ToolmakerSteve 如果 3 个点是凸点,则 if 语句始终有效。 if 语句将返回,那么您将得到正确的答案。如果形状是凹的和极端的,if 语句将不会返回。那是你必须做数学的时候。大多数图像都有一个象限,因此这部分很容易。我 99% 以上的子例程调用都是由 if 语句处理的。
这并没有解决我的担忧。那个公式是什么?是lhf答案的wiki链接中给出的方向决定因素吗?如果是这样,那就这么说吧。说明您正在做的是处理大多数情况的快速检查,以避免标准数学。如果是这样,那么您现在的回答对我来说很有意义。 (小问题:如果您使用结构的 .x.y,而不是 [0][1],会更容易阅读。我不知道您的代码在说什么,我第一次看它.)
由于我对您的方法没有信心,我implemented lhf's approach;来自他的链接的公式。缓慢的部分是 finding 适当的顶点 - O(N) 搜索。一旦找到,行列式就是一个 O(1) 运算,使用 6 次乘法和 5 次加法。最后一部分是您优化的部分;但是您已经通过添加额外的 if 测试来做到这一点。我个人无法证明采用非标准方法是合理的——需要验证每个步骤是否正确——但感谢您对象限的有趣分析!
u
ufukgun

找到这些点的质心。

假设从这一点到你的点有线。

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

比为 line1 和 line2 做

...

...

如果这个角度比逆时针单调增加,

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

否则(它不是单调的)

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


“质心”我认为你的意思是“质心”?
如果多边形完全是凸的,则可能有效。但最好改为使用适用于非凸多边形的答案。