ポリゴンポイントのリストが時計回りであるかどうかを確認する方法は?


260

ポイントのリストがある場合、それらが時計回りの順序であるかどうかをどのようにして見つけますか?

例えば:

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

それは反時計回り(または一部の人にとっては反時計回り)であると言うでしょう。


5
注意:受け入れられた回答とその後の多くの回答は、多くの加算と乗算を必要とします(これらは、負または正で終了する面積計算に基づいています。たとえば、「靴ひも式」)。これらのいずれかを実装する前に、lhfの回答を検討してください。これはwikiに基づいた単純な/より速い、単純なポリゴンの方向です
ToolmakerSteve

私は常に、2つの隣接するベクトルの外積の観点から考えます。ポリゴンの周囲を歩くと、頭が平面の外を指しています。平面外ベクトルを歩行方向ベクトルに交差させて、座標系の3番目の方向を取得します。そのベクトルが、内部が私の左側になるように指している場合、それは反時計回りです。インテリアが私の右側にある場合、それは時計回りです。
duffymo

回答:


416

三日月形などの非凸多角形の場合、提案された方法のいくつかは失敗します。以下は、非凸ポリゴンで機能する単純なものです(8の字のような自己交差するポリゴンでも機能し、ほとんどが時計回りかどうかがわかります)。

辺の合計、(x 2 − x 1)(y 2 + y 1)。結果が正の場合、曲線は時計回りであり、負の場合、曲線は反時計回りです。(結果は、囲まれた領域の2倍になり、+ /-規則が使用されます。)

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の記号規則に注意してください。いくつかの三角形でこれを試してみれば、すぐにそれがどのように機能するかがわかります。
ベータ版

72
軽微な注意事項:この回答は、通常のデカルト座標系を想定しています。言及する価値がある理由は、HTML5キャンバスなどの一部の一般的なコンテキストでは、逆Y軸が使用されるためです。次に、ルールを反転する必要があります。面積が負の場合、曲線は時計回りです。
LarsH 2013年

8
@ Mr.Qbs:だから私の方法は機能しますが、重要な部分スキップすると機能しません。これはニュースではありません。
Beta

11
@ Mr.Qbs:常に最後のポイントを最初のポイントにリンクする必要があります。0からN-1までの番号が付けられたN個のポイントがある場合、次のように計算する必要がありますSum( (x[(i+1) mod N] - x[i]) * (y[i] + y[(i+1) mod N]) )。つまり、インデックスはModulo N(N ≡ 0)を取る必要があります。この数式は閉じたポリゴンに対してのみ機能します。ポリゴンには架空のエッジはありません。
Olivier Jacot-Descombes

4
このblog.element84.com/polygon-winding.htmlは、このソリューションが機能する理由を簡単な英語で説明しています。
デビッドゾリクタ2017年

49

外積は、 2つのベクトルの垂直らしさの程度を測定します。ポリゴンの各エッジが3次元(3-D)xyz空間のxy平面のベクトルであると想像してください。次に、2つの連続するエッジの外積は、z方向のベクトルです(2番目のセグメントが時計回りの場合は正のz方向、反時計回りの場合はz方向)。このベクトルの大きさは、2つの元のエッジ間の角度の正弦に比例するため、垂直なときに最大に達し、エッジが同一線上(平行)になると徐々に消えて消えます。

したがって、ポリゴンの各頂点(ポイント)について、隣接する2つのエッジの外積の大きさを計算します。

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の間にはpoint1には、point2
...
edgeEの間にあるpoint4point0

次に、頂点A(point0)は
edgeE[From point4 to point0]
edgeA[From point0to `point1 'の間にあります

これらの2つのエッジはそれ自体がベクトルであり、そのx座標とy座標は、始点と終点の座標を差し引くことで決定できます。

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

そして、これらの2つの隣接するエッジの外積は、次の行列の行列式を使用して計算されます。行列は、2つのベクトルの座標を3つの座標軸を表すシンボルの下に置くことで構築されます(ijk)。3番目の(ゼロ)値の座標が存在するのは、外積の概念が3次元構造なので、外積を適用するためにこれらの2次元ベクトルを3次元に拡張するためです。

 i    j    k 
-4    0    0
 1    4    0    

すべてのクロス積が乗算される2つのベクトルの平面に垂直なベクトルを生成すると仮定すると、上の行列の行列式は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)を2つのベクトルの大きさの積で除算する必要があります。

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

したがって、sin(AB)の測定= -16 / 16.4924 =-.97014...

これは、頂点の後の次のセグメントが左または右に曲がったかどうか、およびどれくらいかを測定します。逆正弦をとる必要はありません。私たちが気にするのはその大きさ、そしてもちろんその兆候(正または負)です!

閉じたパスの周りの他の4つのポイントそれぞれについてこれを行い、この計算からの値を各頂点で合計します。

最終合計が正の場合、時計回り、負、反時計回りに進みました。


3
実際、このソリューションは、承認されたソリューションとは異なるソリューションです。同等であるかどうかは調査中の問題ですが、そうではないようです...受け入れられた答えは、ポリゴンの上端の下の領域と下の領域の差をとることによって、ポリゴンの領域を計算しますポリゴンの下端。1つはネガティブ(左から右にトラバースするもの)で、もう1つはネガティブです。時計回りにトラバースする場合、上端は左から右にトラバースされて大きくなるため、合計は正になります。
Charles Bretana 2013

1
私のソリューションは、各頂点でのエッジ角度の変化の正弦の合計を測定します。これは、時計回りにトラバースするときに正になり、反時計回りにトラバースするときに負になります。
Charles Bretana、2013

2
このアプローチでは、凸性を仮定しない限り、
アークシンを

2
あなたはアークシンを取る必要があります。ランダムな非凸ポリゴンの束で試してみてください。アークシンを取らないと、一部のポリゴンでテストが失敗することがわかります。
ルークハッチソン

1
@CharlesBretana-私はルークのテストを実行していませんが、彼は正しいと思います。これは、非線形スケールと組み合わされた合計の性質です[arcsinを使用しない場合とarcsinを使用する場合]。marsbearが提案したことを検討してください。あなたは正しく拒否しました。彼はあなたが「数える」ことを提案しました、そしてあなたは少数の大きな値が多数の小さな値を上回る可能性があることを指摘しました。次に、各値のアークシンを考慮してください。それでも、arcsinの取得に失敗すると、各値に誤った重みが付けられ、したがって同じ欠陥があります(はるかに少なくなります)。
ToolmakerSteve

47

これはかなり古い質問だと思いますが、とにかく簡単で数学的に集中的ではないので、別の解決策を捨てます。基本的な代数を使用するだけです。ポリゴンの符号付き領域を計算します。負の場合、ポイントは時計回りであり、正の場合、ポイントは反時計回りです。(これはベータのソリューションに非常に似ています。)

符号付き面積を計算します: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では)であると私が思うとき。一方、線形代数のクラスを受講してから10年になります。
Michael Eric Oberlin、

いいえ。ソースを確認すると、式が実際には最後の項(y1とx1)の最初の点を再び参照していることがわかります。(申し訳ありませんが、私はLaTeXについてはあまり詳しくありませんが、添え字を読みやすくするためにフォーマットしました。)
Sean the Bean

私はこのソリューションを使用しましたが、私の使用には完全に機能しました。事前に計画し、配列内に2つの追加のベクトルを追加できる場合は、配列の末尾に最初のベクトルを追加することで、比較(または%)を取り除くことができます。そうすれば、最後の要素(長さ1ではなく長さ2)を除くすべての要素をループするだけです。
Eric Fortier

2
@EricFortier-FWIW、可能性としては大きい配列のサイズを変更するのではなく、効率的な代替策は、各反復がpreviousPoint次の反復と同様にそのポイントを保存することです。ループを開始する前に、previousPoint配列の最後のポイントに設定します。トレードオフは、余分なローカル変数のコピーですが、配列アクセスが少なくなります。そして最も重要なのは、入力配列に触れる必要がないことです。
ToolmakerSteve

2
@MichaelEricOberlin- 最後のポイントから最初のポイントまでのラインセグメントを含めることにより、ポリゴンを閉じる必要があります。(正しい計算は、どのポイントが閉じた多角形を開始するかに関係なく、同じになります。)
ToolmakerSteve

38

最小のy(タイがある場合は最大のx)の頂点を見つけます。頂点とするAと、リスト内の前の頂点があることBと、リスト内の次の頂点になりますC。次に、との外積の符号を計算します。ABAC


参照:


7
これはen.wikipedia.org/wiki/Curve_orientationでも説明されています。ポイントは、見つかったポイントが凸包上にある必要があることであり、ポリゴン全体の向きを決定するには、凸包(およびそのすぐ隣)上の単一の点をローカルに見れば十分です。
M Katz

1
ショックを受けて、これはこれ以上の賛成票を受け取っていません。単純なポリゴン(一部のフィールドではほとんどのポリゴンです)の場合、この回答でO(1)解決策が得られます。他のすべての答えは、ポリゴンポイントの数のO(n)ソリューションをもたらしますn。さらに詳細な最適化については、Wikipediaの素晴らしい曲線の方向付けに関する記事の実用的な考慮事項のサブセクションを参照してください。
セシルカレー

8
明確化:この解決策はO(1)(A)このポリゴンが凸型である場合(この場合、任意の頂点が凸包上にあり、それで十分です)または (B)最小のY座標を持つ頂点がすでにわかっている場合のみです。これが当てはまらない場合(つまり、このポリゴンが非凸であり、それについて何も知らない場合)、O(n)検索が必要です。ただし、加算は必要ないため、単純なポリゴンの他のソリューションよりもはるかに高速です。
セシルカレー


1
@CecilCurryあなたの2番目のコメントは、これがこれ以上賛成票を受け取っていない理由を説明していると思います。特定のシナリオでは、これらの制限に言及することなく、誤った答えを出します。
LarsH

24

これは、この回答に基づくアルゴリズムの簡単な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;
}

%Wikipediaによれば)ある数値を別の数値で除算した後の剰余を見つける剰余演算を実行する剰余演算子です。


6

頂点の1つから開始し、各辺によって定められる角度を計算します。

最初と最後はゼロになります(それらをスキップしてください)。残りの場合、角度のサインは、(point [n] -point [0])と(point [n-1] -point [0])の単位長への正規化の外積によって与えられます。

値の合計が正の場合、ポリゴンは反時計回りに描画されます。


クロス積が基本的に正のスケーリングファクターに角度のサインを掛けたものにどのように要約されるかを考えると、クロス積を実行する方がおそらく良いでしょう。処理速度が上がり、複雑さが軽減されます。
ReaperUnreal 2009

4

価値があるので、私はこのミックスインを使用して、Google Maps API v3アプリのワインディング順序を計算しました。

このコードは、ポリゴン領域の副作用を利用します。頂点の時計回りのワインディング順序は正の領域を生成し、同じ頂点の反時計回りのワインディング順序は負の値と同じ領域を生成します。このコードでは、Googleマップジオメトリライブラリの一種のプライベート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を参照)は「右手の法則」に従うことです。Googleは不満を持っていると思います。その場合、外側のリングは反時計回り(正の領域を生成する)である必要があり、内側のリング(穴)は時計回りに回転します(負の領域はメイン領域から削除されます)。
allez l'OM

4

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

これが正しいことを確認してください。うまくいっているようです:-)

疑問に思っている場合、これらのポリゴンは次のようになります。


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をはGoogleマップのようなJavaScriptのベースマップ管理ライブラリであり、それはの書いたとOpenLayersを2で使用される
MSS

あなたのコードが何をするのか、そしてなぜあなたがそれをしているのかを少し説明できますか?
nbro 2018

@nbroこのコードはlhf回答を実装します頂点をパラメーターとして直接持つことで、OpenLayer以外の部分を純粋なJavaScript関数に簡単に保持できます。これはうまく機能し、multiPolygonの場合に適合させることができます。
allez l'OM


1

このWikipediaの記事で説明したようにカーブ方向 3点与えられ、pqおよびr平面(すなわち、x座標とy座標を有する)に、次の行列式の符号を算出することができます

ここに画像の説明を入力してください

行列式が負の場合(つまりOrient(p, q, r) < 0)、多角形は時計回り(CW)に方向付けられます。行列式が正の場合(つまりOrient(p, q, r) > 0)、ポリゴンは反時計回り(CCW)に方向付けられます。決定基(すなわち、ゼロであるOrient(p, q, r) == 0点の場合)pq及びrある同一直線

上記式において、我々は、座標の前にものを付加しpq そしてr我々が使用しているため、同次座標を


@tibettyポリゴンが凹型である場合、この方法が多くの状況で機能しない理由を説明できますか?
nbro 2018

1
投稿のwikiアイテムリファレンスの最後の表をご覧ください。私が誤った例を示すのは簡単ですが、それを証明するのは難しいです。
tibetty 2018

1
投稿のwikiアイテムリファレンスの最後の表をご覧ください。私が誤った例を示すのは簡単ですが、それを証明するのは難しいです。
tibetty

1
@tibettyは正しいです。ポリゴンに沿って3点を取ることはできません。あなたはその多角形の凸または凹の領域にいるかもしれません。wikiを注意深く読んで、ポリゴンを囲む凸包に沿って 3つのポイントを取る必要があります。「実用的な考慮事項」から:「適切な頂点を見つけるために、ポリゴンの凸包を作成する必要はありません。一般的な選択は、最小のX座標を持つポリゴンの頂点です。それらが複数ある場合、1つはY座標が最小のものが選択されます。これは、[a]ポリゴンの凸包の頂点であることが保証されています。
ToolmakerSteve

1
したがって、lhfの以前の回答は類似しており、同じWiki記事を参照していますが、そのようなポイントを指定しています。[どうやら、真ん中にいるのを避けさえすれば、最小でも最大でも、xでもyでもかまいません。効果的には、ポリゴンの周囲の境界ボックスの1つのエッジから作業して、凹状領域を保証します。]
ToolmakerSteve

0

いくつかのポイントが時計回りに与えられるためには、エッジの合計だけでなくすべてのエッジが正である必要があると思います。1つのエッジが負の場合、少なくとも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 2018

0

ポリゴン内のポイントが既にわかっている場合は、計算がはるかに簡単な方法です

  1. 元のポリゴン、ポイント、それらの座標から任意のラインセグメントをこの順序で選択します。

  2. 既知の「内側」の点を追加し、三角形を形成します。

  3. これらの3つのポイントを使用して、ここで提案さているようにCWまたはCCWを計算します。


多分これは、ポリゴンが完全に凸型である場合に機能します。凹状の領域がある場合は確実に信頼できません-洞窟の端の「間違った」側にある点を選び、その端に接続するのは簡単です。間違った答えになります。
ToolmakerSteve

ポリゴンが凹面でも機能します。ポイントはその凹型ポリゴンの内側にある必要があります。ただし、複雑なポリゴンについて
Venkata Goli

「ポリゴンが凹面でも機能します。」-反例:ポリ(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)内のポイントを選択する場合とは逆の巻線。どちらも元のポリゴンの内部にありますが、反対の曲がりくねっています。したがって、そのうちの1つは間違った答えを出します。
ToolmakerSteve

一般に、ポリゴンに凹状領域がある場合は、凹状領域のセグメントを選択します。凹型であるため、その線の反対側にある2つの「内部」ポイントを見つけることができます。それらはその線の反対側にあるので、形成された三角形は反対の巻線を持っています。証明の終わり。
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が方向を決定し、時計回りの場合は逆にするためのソリューション(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

0

これらの答えは正しいですが、必要以上に数学的に強いです。最も北のポイントがマップ上の最も高いポイントであるマップ座標を想定します。最も北の点を見つけます。2つの点が同点の場合、それは最も北で最も東です(これはlhfが彼の回答で使用する点です)。あなたのポイントでは、

point [0] =(5,0)

point [1] =(6,4)

point [2] =(4,5)

point [3] =(1,5)

point [4] =(1,0)

P2が最も北であると仮定すると、東または前のポイントが時計回り、CW、またはCCWを決定します。最も北のポイントは北の面上にあるため、P1(前)からP2までが東に移動する場合、方向はCWです。この場合、西に移動するため、受け入れられた答えが示すように、方向はCCWです。前のポイントに水平移動がない場合、同じシステムが次のポイントP3に適用されます。P3がP2の西側にある場合は、その場合、移動はCCWです。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の回答に示されている基本的な数学に固執する方が安全です。彼に言及していただきありがとうございます。それを象限に削減する際の課題は、式がすべての場合に正しいことを証明するためにかなりの作業量を要することです。「もっと西」を正しく計算しましたか?[1]と[3]の両方が[2]の「西と南」である凹面の多角形で?その状況で、[1]と[3]の異なる長さを正しく処理しましたか?その角度(またはその行列式)を直接計算する場合は、よく知られた公式を使用しています。
ToolmakerSteve

@ToolmakerSteve ifステートメントは、3つの点が凸型の場合に常に機能します。ifステートメントが返されると、正しい答えが得られます。形状が凹型で極端な場合、ifステートメントは返されません。それはあなたが数学をしなければならないときです。ほとんどの画像には1つの象限があるため、その部分は簡単です。私のサブルーチン呼び出しの99%以上がifステートメントによって処理されます。
VectorVortec、

それは私の懸念には対応していません。その式は何ですか?lhfの回答のwikiリンクで示されている方向決定要因ですか?もしそうなら、そう言う。あなたがしていることは、標準的な計算を避けるために、ほとんどのケースを処理する簡単なチェックをしていることを説明します。もしそうなら、あなたの答えは今私にとって理にかなっています。(マイナーnit:.xand .yの代わりに[0]とstruct を使用すると読みやすくなり[1]ます。コードを何が言っているのかわかりませんでした。初めて見たときです。)
ToolmakerSteve

私はあなたのアプローチに自信がなかったので、lhfのアプローチ実装しました。彼のリンクからの式。遅い部分は適切な頂点を見つけることです -O(N)検索。検出されると、行列式はO(1)演算で、6つの乗算と5つの加算を使用します。最後の部分は、あなたが最適化したものです。しかし、ifテストを追加することでそうしました。私は非標準的なアプローチを取ることを個人的に正当化することはできません-各ステップが正しいことを確認する必要があります-しかし、象限の興味深い分析をありがとう!
ToolmakerSteve

0

lhfの答えを実装する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;
}

1
これは、ダウンが正のY座標の場合に表示されます。標準座標用にCW / CCWを反転します。
ワーウィックアリソン


-4

これらの点の重心を見つけます。

このポイントからあなたのポイントへのラインがあると仮定します。

line0 line1の2つの線の間の角度を見つける

line1とline2の場合よりも

...

...

この角度が反時計回りより単調に増加している場合、

それ以外の場合、単調に減少する場合は時計回りです

その他(単調ではありません)

あなたが決めることができないので、それは賢明ではありません


「重心」とは「重心」という意味ですか?
Vicky Chijwani

ポリゴンが完全に凸型の場合はおそらく機能します。しかし、代わりに非凸ポリゴンで機能する回答を使用することをお勧めします。
ToolmakerSteve
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.