Wie kann festgestellt werden, ob eine Liste von Polygonpunkten im Uhrzeigersinn vorliegt?


259

Wie finde ich eine Liste mit Punkten im Uhrzeigersinn?

Beispielsweise:

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

würde sagen, dass es gegen den Uhrzeigersinn ist (oder gegen den Uhrzeigersinn für einige Leute).


5
BITTE BEACHTEN SIE: Die akzeptierte Antwort und viele Antworten danach erfordern viele Additionen und Multiplikationen (sie basieren auf Flächenberechnungen, die negativ oder positiv enden; z. B. "Schnürsenkelformel"). Bevor Sie eine dieser Optionen implementieren, sollten Sie die Antwort von lhf berücksichtigen , die einfacher / schneller ist - basierend auf dem Wiki - und die Ausrichtung eines einfachen Polygons .
ToolmakerSteve

Ich denke immer an das Kreuzprodukt zweier benachbarter Vektoren. Wenn ich um den Umfang des Polygons herum gehe, zeigt mein Kopf aus der Ebene. Ich kreuze den Vektor außerhalb der Ebene in meinen Laufrichtungsvektor, um die dritte Richtung in meinem Koordinatensystem zu erhalten. Wenn dieser Vektor so zeigt, dass sich der Innenraum zu meiner Linken befindet, ist er gegen den Uhrzeigersinn. Wenn sich der Innenraum zu meiner Rechten befindet, ist er im Uhrzeigersinn.
Duffymo

Antworten:


416

Einige der vorgeschlagenen Methoden schlagen bei einem nicht konvexen Polygon wie einem Halbmond fehl. Hier ist eine einfache Methode, die mit nicht konvexen Polygonen funktioniert (sie funktioniert sogar mit einem sich selbst überschneidenden Polygon wie einer Acht, das Ihnen sagt, ob es größtenteils im Uhrzeigersinn ist).

Summiere über die Kanten (x 2 - x 1 ) (y 2 + y 1 ). Wenn das Ergebnis positiv ist, ist die Kurve im Uhrzeigersinn, wenn es negativ ist, ist die Kurve gegen den Uhrzeigersinn. (Das Ergebnis ist doppelt so groß wie der umschlossene Bereich mit einer +/- Konvention.)

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
Es ist ein Kalkül, der auf einen einfachen Fall angewendet wird. (Ich kann keine Grafiken veröffentlichen.) Die Fläche unter einem Liniensegment entspricht der durchschnittlichen Höhe (y2 + y1) / dem 2-fachen der horizontalen Länge (x2-x1). Beachten Sie die Vorzeichenkonvention in x. Versuchen Sie dies mit einigen Dreiecken und Sie werden bald sehen, wie es funktioniert.
Beta

72
Eine kleine Einschränkung: Diese Antwort setzt ein normales kartesisches Koordinatensystem voraus. Der erwähnenswerte Grund ist, dass einige gängige Kontexte, wie z. B. HTML5-Canvas, eine invertierte Y-Achse verwenden. Dann muss die Regel umgedreht werden: Wenn der Bereich negativ ist , ist die Kurve im Uhrzeigersinn.
LarsH

8
@ Mr.Qbs: Meine Methode funktioniert also, aber wenn Sie einen wichtigen Teil überspringen , funktioniert es nicht. Das sind keine Neuigkeiten.
Beta

11
@ Mr.Qbs: Du musst immer den letzten Punkt mit dem ersten verknüpfen. Wenn Sie N Punkte haben, die von 0 bis N-1 nummeriert sind, müssen Sie berechnen: Sum( (x[(i+1) mod N] - x[i]) * (y[i] + y[(i+1) mod N]) )für i = 0 bis N-1. Dh muss den Index Modulo N ( N ≡ 0) annehmen. Die Formel funktioniert nur für geschlossene Polygone. Polygone haben keine imaginären Kanten.
Olivier Jacot-Descombes

4
In diesem blog.element84.com/polygon-winding.html wird in einfachem Englisch erklärt, warum diese Lösung funktioniert.
David Zorychta

49

Das Kreuzprodukt misst den Grad der Rechtwinkligkeit zweier Vektoren. Stellen Sie sich vor, jede Kante Ihres Polygons ist ein Vektor in der xy-Ebene eines dreidimensionalen (3-D) xyz-Raums. Dann ist das Kreuzprodukt zweier aufeinanderfolgender Kanten ein Vektor in z-Richtung (positive z-Richtung, wenn das zweite Segment im Uhrzeigersinn ist, minus z-Richtung, wenn es gegen den Uhrzeigersinn ist). Die Größe dieses Vektors ist proportional zum Sinus des Winkels zwischen den beiden ursprünglichen Kanten, sodass er ein Maximum erreicht, wenn sie senkrecht stehen, und sich verjüngt, um zu verschwinden, wenn die Kanten kollinear (parallel) sind.

Berechnen Sie also für jeden Scheitelpunkt (Punkt) des Polygons die Kreuzproduktgröße der beiden benachbarten Kanten:

Using your data:
point[0] = (5, 0)
point[1] = (6, 4)
point[2] = (4, 5)
point[3] = (1, 5)
point[4] = (1, 0)

Beschriften Sie die Kanten nacheinander, so wie
edgeAdas Segment von point0bis point1und
edgeBzwischen point1bis point2
...
edgeEzwischen point4und ist point0.

Dann liegt Vertex A ( point0) zwischen
edgeE[Von point4bis point0]
edgeA[Von point0bis `Punkt1 '

Diese beiden Kanten sind selbst Vektoren, deren x- und y-Koordinaten durch Subtrahieren der Koordinaten ihrer Start- und Endpunkte bestimmt werden können:

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

Und das Kreuzprodukt der beiden angrenzenden Kanten berechnet die Determinante der folgenden Matrix verwendet, die , indem sie die Koordinaten der beiden Vektoren unter den Symbolen aufgebaut ist , die die drei Koordinatenachsen ( i, j, & k). Die dritte (null) -bewertige Koordinate ist vorhanden, da das Kreuzproduktkonzept ein 3D-Konstrukt ist. Daher erweitern wir diese 2D-Vektoren in 3D, um das Kreuzprodukt anzuwenden:

 i    j    k 
-4    0    0
 1    4    0    

Da alle Kreuzprodukte einen Vektor senkrecht zur Ebene zweier zu multiplizierender Vektoren erzeugen, hat die Determinante der obigen Matrix nur eine k(oder z-Achsen-) Komponente.
Die Formel zur Berechnung der Größe der kKomponente oder der Z-Achse lautet
a1*b2 - a2*b1 = -4* 4 - 0* 1 = -16

Die Größe dieses Wertes ( -16) ist ein Maß für den Sinus des Winkels zwischen den beiden ursprünglichen Vektoren, multipliziert mit dem Produkt der Größen der beiden Vektoren.
Eigentlich ist eine andere Formel für seinen Wert
A X B (Cross Product) = |A| * |B| * sin(AB).

Um auf ein Maß des Winkels zurückzukommen, müssen Sie diesen Wert ( -16) durch das Produkt der Größen der beiden Vektoren dividieren .

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

Also das Maß der Sünde (AB) = -16 / 16.4924=-.97014...

Dies ist ein Maß dafür, ob und um wie viel sich das nächste Segment nach dem Scheitelpunkt nach links oder rechts gebogen hat. Es ist nicht erforderlich, Arc-Sinus zu nehmen. Wir werden uns nur um seine Größe und natürlich um sein Vorzeichen (positiv oder negativ) kümmern!

Führen Sie dies für jeden der anderen 4 Punkte um den geschlossenen Pfad aus und addieren Sie die Werte aus dieser Berechnung an jedem Scheitelpunkt.

Wenn die endgültige Summe positiv ist, sind Sie im Uhrzeigersinn, negativ und gegen den Uhrzeigersinn gegangen.


3
Tatsächlich ist diese Lösung eine andere Lösung als die akzeptierte Lösung. Ob sie gleichwertig sind oder nicht, ist eine Frage, die ich untersuche, aber ich vermute, dass dies nicht der Fall ist ... Die akzeptierte Antwort berechnet die Fläche des Polygons, indem die Differenz zwischen der Fläche unter der Oberkante des Polygons und der Fläche darunter berechnet wird die Unterkante des Polygons. Einer ist negativ (der eine, bei dem Sie von links nach rechts fahren), und der andere ist negativ. Beim Fahren im Uhrzeigersinn wird die Oberkante von links nach rechts durchlaufen und ist größer, sodass die Summe positiv ist.
Charles Bretana

1
Meine Lösung misst die Summe der Sinuswerte der Änderungen der Kantenwinkel an jedem Scheitelpunkt. Dies ist positiv, wenn Sie im Uhrzeigersinn fahren, und negativ, wenn Sie gegen den Uhrzeigersinn fahren.
Charles Bretana

2
Es scheint, dass Sie mit diesem Ansatz den Arcsin nehmen müssen, es sei denn, Sie nehmen Konvexität an (in diesem Fall müssen Sie nur einen Scheitelpunkt überprüfen)
Agentp

2
Sie müssen den Arcsin nehmen. Versuchen Sie es mit einer Reihe von zufälligen nicht konvexen Polygonen, und Sie werden feststellen, dass der Test für einige Polygone fehlschlägt, wenn Sie den Arcsin nicht nehmen.
Luke Hutchison

1
@ CharlesBretana - obwohl ich Lukes Test nicht durchgeführt habe, glaube ich, dass er richtig ist. Das ist die Art der Summierung in Kombination mit einer nichtlinearen Skala [ohne Arcsin vs. mit Arcsin]. Überlegen Sie, was Marsbear vorgeschlagen hat, dass Sie richtig abgelehnt haben. Er schlug vor, dass Sie "nur zählen", und Sie wiesen darauf hin, dass eine Handvoll großer Werte eine große Anzahl kleiner Werte überwiegen könnte. Betrachten Sie nun Arcsin jedes Wertes gegen nicht. Ist es nicht immer noch so, dass die Nichteinnahme von Arcsin jedem Wert ein falsches Gewicht verleiht und daher denselben Fehler aufweist (wenn auch viel weniger)?
ToolmakerSteve

47

Ich denke, das ist eine ziemlich alte Frage, aber ich werde trotzdem eine andere Lösung herauswerfen, weil sie einfach und nicht mathematisch intensiv ist - sie verwendet nur die grundlegende Algebra. Berechnen Sie die vorzeichenbehaftete Fläche des Polygons. Wenn es negativ ist, sind die Punkte im Uhrzeigersinn, wenn es positiv ist, sind sie gegen den Uhrzeigersinn. (Dies ist der Beta-Lösung sehr ähnlich.)

Berechnen Sie die vorzeichenbehaftete Fläche: 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 )

Oder im Pseudocode:

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

Beachten Sie, dass Sie sich nicht die Mühe machen müssen, durch 2 zu teilen, wenn Sie nur die Bestellung überprüfen.

Quellen: http://mathworld.wolfram.com/PolygonArea.html


War das ein Tippfehler in Ihrer oben genannten Formel für signierte Bereiche? Es endet mit "xn * y1 - x1 * yn"; wenn ich glaube, dass es "x_n y_ {n + 1} - y_n x_ {n-1}" sein sollte (zumindest in LaTeX). Andererseits ist es zehn Jahre her, dass ich an linearen Algebra-Kursen teilgenommen habe.
Michael Eric Oberlin

Nee. Wenn Sie die Quelle überprüfen , werden Sie feststellen, dass die Formel tatsächlich wieder auf den ersten Punkt im letzten Term (y1 und x1) verweist. (Entschuldigung, ich bin mit LaTeX nicht sehr vertraut, aber ich habe die Indizes formatiert, um sie besser lesbar zu machen.)
Sean the Bean

Ich habe diese Lösung verwendet und sie hat perfekt für meine Verwendung funktioniert. Beachten Sie, dass Sie den Vergleich (oder%) durch Hinzufügen des ersten Vektors am Ende des Arrays entfernen können, wenn Sie im Voraus planen und zwei Vektoren in Ihrem Array sparen können. Auf diese Weise durchlaufen Sie einfach alle Elemente mit Ausnahme des letzten (Länge 2 statt Länge 1).
Eric Fortier

2
@EricFortier - FWIW Anstatt die Größe eines möglicherweise großen Arrays zu ändern, besteht eine effiziente Alternative darin, dass jede Iteration ihren Punkt wie previousPointbei der nächsten Iteration speichert . previousPointStellen Sie vor dem Starten der Schleife den letzten Punkt des Arrays ein. Ein Kompromiss ist eine zusätzliche lokale Variablenkopie, aber weniger Array-Zugriffe. Und vor allem müssen Sie das Eingabearray nicht berühren.
ToolmakerSteve

2
@MichaelEricOberlin - Es ist erforderlich, das Polygon zu schließen , indem das Liniensegment vom letzten zum ersten Punkt eingeschlossen wird. (Eine korrekte Berechnung ist dieselbe, egal an welchem ​​Punkt das geschlossene Polygon beginnt.)
ToolmakerSteve

37

Finden Sie den Scheitelpunkt mit dem kleinsten y (und dem größten x, wenn es Bindungen gibt). Der Scheitelpunkt sei Aund der vorherige Scheitelpunkt in der Liste sei Bund der nächste Scheitelpunkt in der Liste sei C. Berechnen Sie nun das Vorzeichen des Kreuzprodukts von ABund AC.


Verweise:


7
Dies wird auch in en.wikipedia.org/wiki/Curve_orientation erklärt . Der Punkt ist, dass der gefundene Punkt auf der konvexen Hülle liegen muss und es nur notwendig ist, lokal auf einen einzelnen Punkt auf der konvexen Hülle (und ihren unmittelbaren Nachbarn) zu schauen, um die Ausrichtung des gesamten Polygons zu bestimmen.
M Katz

1
Schockiert und beeindruckt hat dies nicht mehr positive Stimmen erhalten. Für einfache Polygone ( die in einigen Bereichen die meisten Polygone sind ) ergibt diese Antwort eine O(1)Lösung. Alle anderen Antworten ergeben O(n)Lösungen für ndie Anzahl der Polygonpunkte. Weitere Informationen zu noch tieferen Optimierungen finden Sie im Unterabschnitt " Praktische Überlegungen" des fantastischen Wikipedia- Artikels zur Kurvenorientierung .
Cecil Curry

8
Klarstellung: Diese Lösung istO(1)nur verfügbar, wenn entweder (A) dieses Polygon konvex ist (in diesem Fall befindet sich ein beliebiger Scheitelpunkt auf der konvexen Hülle und reicht daher aus) oder (B) Sie den Scheitelpunkt mit der kleinsten Y-Koordinate bereits kennen. Wenn dies nicht der Fall ist (dh dieses Polygon ist nicht konvex und Sie wissen nichts darüber), ist eineO(n)Suche erforderlich. Da jedoch keine Summierung erforderlich ist, ist dies immer noch erheblich schneller als jede andere Lösung für einfache Polygone.
Cecil Curry


1
@ CecilCurry Ich denke, dein zweiter Kommentar erklärt, warum dies nicht mehr positive Stimmen erhalten hat. In bestimmten Szenarien werden falsche Antworten geliefert, ohne dass diese Einschränkungen erwähnt werden.
LarsH

24

Hier ist eine einfache C # -Implementierung des Algorithmus basierend auf dieser Antwort .

Nehmen wir an, wir haben einen VectorTyp mit Xund YEigenschaften von Typ 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;
}

%ist der Modulo- oder Restoperator, der die Modulo-Operation ausführt, die ( laut Wikipedia ) den Rest nach Division einer Zahl durch eine andere findet.


6

Beginnen Sie an einem der Eckpunkte und berechnen Sie den Winkel, den jede Seite bildet.

Der erste und der letzte sind Null (überspringen Sie diese also); im übrigen wird der Sinus des Winkels durch das Kreuzprodukt der Normalisierungen zur Längeneinheit von (Punkt [n] -Punkt [0]) und (Punkt [n-1] -Punkt [0]) gegeben.

Wenn die Summe der Werte positiv ist, wird Ihr Polygon gegen den Uhrzeigersinn gezeichnet.


Angesichts der Tatsache, dass das Kreuzprodukt im Grunde genommen auf einen positiven Skalierungsfaktor multipliziert mit dem Sinus des Winkels hinausläuft, ist es wahrscheinlich besser, nur ein Kreuzprodukt zu erstellen. Es wird schneller und weniger kompliziert sein.
ReaperUnreal

4

Für das, was es wert ist, habe ich dieses Mixin verwendet, um die Wicklungsreihenfolge für Google Maps API v3-Apps zu berechnen.

Der Code nutzt den Nebeneffekt von Polygonbereichen: Eine Wicklungsreihenfolge von Scheitelpunkten im Uhrzeigersinn ergibt eine positive Fläche, während eine Wicklungsreihenfolge derselben Scheitelpunkte gegen den Uhrzeigersinn dieselbe Fläche wie ein negativer Wert erzeugt. Der Code verwendet auch eine Art private API in der Google Maps-Geometriebibliothek. Ich habe mich wohl gefühlt - auf eigenes Risiko.

Beispielnutzung:

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

Vollständiges Beispiel mit Unit-Tests @ 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;
  }
})();

Wenn ich dies versuche, erhalte ich genau das gegenteilige Ergebnis: Ein im Uhrzeigersinn gezeichnetes Polygon ergibt einen negativen Bereich, während ein gegen den Uhrzeigersinn gezeichnetes Polygon einen positiven Bereich ergibt. In beiden Fällen ist dieses Snippet nach 5 Jahren immer noch sehr nützlich, danke.
Cameron Roberts

@CameronRoberts Die Norm (siehe IETF insbesondere für geoJson) besteht darin, die 'Rechtsregel' zu befolgen. Ich denke, dass Google sich beschwert. In diesem Fall muss der äußere Ring gegen den Uhrzeigersinn sein (was einen positiven Bereich ergibt), und die inneren Ringe (Löcher) müssen im Uhrzeigersinn gewickelt sein (negativer Bereich muss vom Hauptbereich entfernt werden).
Allez l'OM

4

Eine Implementierung von Seans Antwort in 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));

Ich bin mir ziemlich sicher, dass das richtig ist. Es scheint zu funktionieren :-)

Diese Polygone sehen so aus, wenn Sie sich fragen:


3

Dies ist die implementierte Funktion für OpenLayers 2 . Die Bedingung für ein Polygon im Uhrzeigersinn ist area < 0, wie durch diese Referenz bestätigt .

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 ist eine Javascript-basierte Kartenverwaltungsbibliothek wie Googlemaps und wurde in Openlayers 2 geschrieben und verwendet.
MSS

Können Sie ein wenig erklären, was Ihr Code tut und warum Sie es tun?
nbro

@nbro Dieser Code implementiert die lhf-Antwort . Es ist einfach, den Nicht-OpenLayer-Teil in einer reinen Javascript-Funktion zu halten, indem Scheitelpunkte direkt als Parameter verwendet werden. Es funktioniert gut und könnte an den Fall von MultiPolygon angepasst werden .
Allez l'OM

2

Wenn Sie Matlab verwenden, gibt die Funktion ispolycwtrue zurück, wenn die Polygonscheitelpunkte im Uhrzeigersinn sind.


1

Wie erläutert auch in diesem Artikel Wikipedia Orientierung Curve , 3 Punkte gegeben p, qund rauf der Ebene (dh mit x und y - Koordinaten), kann das Vorzeichen der folgenden Determinante berechnen

Geben Sie hier die Bildbeschreibung ein

Wenn die Determinante negativ ist (dh Orient(p, q, r) < 0), ist das Polygon im Uhrzeigersinn ausgerichtet (CW). Wenn die Determinante positiv ist (dh Orient(p, q, r) > 0), ist das Polygon gegen den Uhrzeigersinn (CCW) ausgerichtet. Die Determinante Null ist (dh Orient(p, q, r) == 0) , wenn Punkte p, qund rsind kollinear .

In der obigen Formel prepend wir die , die vor den Koordinaten p, q und rda wir verwenden homogene Koordinaten .


@tibetty Kannst du erklären, warum diese Methode in vielen Situationen nicht funktioniert, wenn das Polygon konkav ist?
nbro

1
Bitte schauen Sie sich die letzte Tabelle in der Wiki-Artikelreferenz in Ihrem Beitrag an. Es fällt mir leicht, ein falsches Beispiel zu geben, aber es ist schwer, es zu beweisen.
Tibetty

1
Bitte schauen Sie sich die letzte Tabelle in der Wiki-Artikelreferenz in Ihrem Beitrag an. Es fällt mir leicht, ein falsches Beispiel zu geben, aber es ist schwer, es zu beweisen.
Tibetty

1
@ Tibetty ist richtig. Sie können nicht einfach drei Punkte entlang des Polygons nehmen. Sie befinden sich möglicherweise entweder in einem konvexen oder einem konkaven Bereich dieses Polygons. Wenn man das Wiki sorgfältig liest, muss man drei Punkte entlang der konvexen Hülle nehmen, die das Polygon umschließt . Aus "praktischen Überlegungen": "Man muss nicht die konvexe Hülle eines Polygons konstruieren, um einen geeigneten Scheitelpunkt zu finden. Eine übliche Wahl ist der Scheitelpunkt des Polygons mit der kleinsten X-Koordinate. Wenn es mehrere davon gibt, die eine mit der kleinsten Y-Koordinate wird ausgewählt. Es ist garantiert [ein] Scheitelpunkt der konvexen Hülle des Polygons. "
ToolmakerSteve

1
Daher ist die frühere Antwort von lhf , die ähnlich ist und auf denselben Wiki-Artikel verweist, aber einen solchen Punkt spezifiziert. [Anscheinend spielt es keine Rolle, ob man das kleinste oder das größte nimmt, x oder y, solange man es vermeidet, in der Mitte zu sein; effektiv arbeitet man von einer Kante des Begrenzungsrahmens um das Polygon, um in einem konkaven Bereich zu gewährleisten.]
ToolmakerSteve

0

Ich denke, damit einige Punkte im Uhrzeigersinn vergeben werden, müssen alle Kanten positiv sein, nicht nur die Summe der Kanten. Wenn eine Flanke negativ ist, werden mindestens 3 Punkte gegen den Uhrzeigersinn vergeben.


Stimmt, aber Sie verstehen das Konzept der Wicklungsreihenfolge eines Polygons (im oder gegen den Uhrzeigersinn) falsch. In einem vollständig konvexen Polygon ist der Winkel an allen Punkten im Uhrzeigersinn oder alle gegen den Uhrzeigersinn [wie in Ihrem ersten Satz]. In einem Polygon mit konkaven Bereichen befinden sich die "Höhlen" in der entgegengesetzten Richtung, aber das Polygon als Ganzes hat immer noch ein genau definiertes Inneres und wird entsprechend im oder gegen den Uhrzeigersinn betrachtet. Siehe en.wikipedia.org/wiki/…
ToolmakerSteve

0

Meine C # / LINQ-Lösung basiert auf den produktübergreifenden Empfehlungen von @charlesbretana. Sie können eine Referenznormale für die Wicklung angeben. Es sollte funktionieren, solange sich die Kurve größtenteils in der durch den Aufwärtsvektor definierten Ebene befindet.

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;

        } 

    }
}

mit einem Unit-Test

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

Dies ist meine Lösung unter Verwendung der Erklärungen in den anderen Antworten:

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
Können Sie angeben, auf welchen anderen Antworten genau diese Antwort basiert?
nbro

0

Eine viel rechnerisch einfachere Methode, wenn Sie bereits einen Punkt innerhalb des Polygons kennen :

  1. Wählen Sie ein beliebiges Liniensegment aus dem ursprünglichen Polygon, den Punkten und deren Koordinaten in dieser Reihenfolge.

  2. Fügen Sie einen bekannten "inneren" Punkt hinzu und bilden Sie ein Dreieck.

  3. Berechnen Sie CW oder CCW wie hier vorgeschlagen mit diesen drei Punkten.


Möglicherweise funktioniert dies, wenn das Polygon vollständig konvex ist. Es ist definitiv nicht zuverlässig, wenn es konkave Bereiche gibt - es ist einfach, einen Punkt auszuwählen, der sich auf der "falschen" Seite eines der Ränder der Höhle befindet, und ihn dann mit diesem Rand zu verbinden. Wird eine falsche Antwort bekommen.
ToolmakerSteve

Es funktioniert auch, wenn das Polygon konkav ist. Der Punkt muss innerhalb dieses konkaven Polygons liegen. Ich bin mir jedoch nicht sicher über komplexes Polygon (nicht getestet)
Venkata Goli

"Es funktioniert auch, wenn das Polygon konkav ist." - Gegenbeispiel: Poly (0,0), (1,1), (0,2), (2,2), (2,0). Liniensegment (1,1), (0, 2). Wenn Sie einen inneren Punkt innerhalb von (1,1), (0,2), (1,2) auswählen, um ein Dreieck zu bilden -> (1,1), (0,2), (0,5,1,5)), erhalten Sie entgegengesetzte Wicklung, als wenn Sie einen inneren Punkt innerhalb von (0,0), (1,1), (1,0)> (1,1), (0,2), (0,5,0,5) auswählen. Diese befinden sich beide im Inneren des ursprünglichen Polygons, haben jedoch entgegengesetzte Wicklungen. Daher gibt einer von ihnen die falsche Antwort.
ToolmakerSteve

Wenn ein Polygon einen konkaven Bereich hat, wählen Sie im Allgemeinen ein Segment im konkaven Bereich aus. Da es konkav ist, finden Sie zwei "innere" Punkte, die sich auf gegenüberliegenden Seiten dieser Linie befinden. Da sie sich auf gegenüberliegenden Seiten dieser Linie befinden, haben die gebildeten Dreiecke entgegengesetzte Wicklungen. Ende des Beweises.
ToolmakerSteve

0

Nach dem Testen mehrerer unzuverlässiger Implementierungen war der Algorithmus, der sofort zufriedenstellende Ergebnisse hinsichtlich der CW / CCW-Ausrichtung lieferte, derjenige, der von OP in diesem Thread veröffentlicht wurde (shoelace_formula_3 ) veröffentlicht wurde.

Wie immer steht eine positive Zahl für eine CW-Ausrichtung, während eine negative Zahl CCW darstellt.


0

Hier ist eine schnelle 3.0-Lösung basierend auf den obigen Antworten:

    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

Eine andere Lösung dafür;

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

Nehmen Sie alle Eckpunkte als Array wie dieses;

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

Lösung für R, um die Richtung zu bestimmen und im Uhrzeigersinn umzukehren (für Owin-Objekte erforderlich):

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

Diese Antworten sind zwar richtig, aber mathematisch intensiver als nötig. Nehmen Sie Kartenkoordinaten an, wobei der nördlichste Punkt der höchste Punkt auf der Karte ist. Finden Sie den nördlichsten Punkt, und wenn 2 Punkte gleich sind, ist es der nördlichste und dann der östlichste (dies ist der Punkt, den lhf in seiner Antwort verwendet). In Ihren Punkten,

Punkt [0] = (5,0)

Punkt [1] = (6,4)

Punkt [2] = (4,5)

Punkt [3] = (1,5)

Punkt [4] = (1,0)

Wenn wir annehmen, dass P2 der nördlichste und der östlichste Punkt ist, bestimmen entweder der vorherige oder der nächste Punkt im Uhrzeigersinn, CW oder CCW. Da sich der nördlichste Punkt auf der Nordseite befindet, ist die Richtung CW, wenn sich P1 (vorher) zu P2 nach Osten bewegt. In diesem Fall bewegt es sich nach Westen, sodass die Richtung CCW ist, wie in der akzeptierten Antwort angegeben. Wenn der vorherige Punkt keine horizontale Bewegung hat, gilt dasselbe System für den nächsten Punkt, P3. Wenn P3 westlich von P2 liegt, ist die Bewegung gegen den Uhrzeigersinn. Wenn die Bewegung von P2 nach P3 nach Osten, in diesem Fall nach Westen, ist die Bewegung CW. Angenommen, nte, P2 in Ihren Daten, ist der nördlichste und östlichste Punkt und prv ist der vorherige Punkt, P1 in Ihren Daten und nxt ist der nächste Punkt, P3 in Ihren Daten und [0] ist horizontal oder östlich / West, wo West weniger als Ost ist und [1] vertikal ist.

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)

IMHO, es wäre sicherer, sich an die grundlegende Mathematik zu halten, die in der Antwort von lhf gezeigt wird - danke, dass Sie ihn erwähnt haben. Die Herausforderung bei der Reduzierung auf Quadranten besteht darin, dass es eine Menge Arbeit ist, um zu beweisen, dass Ihre Formel in allen Fällen korrekt ist. Haben Sie "mehr Westen" richtig berechnet? In einem konkaven Polygon, in dem sowohl [1] als auch [3] "westlich und südlich" von [2] liegen? Haben Sie in dieser Situation unterschiedliche Längen von [1] und [3] richtig behandelt? Ich habe keine Ahnung, während ich, wenn ich diesen Winkel (oder seine Determinante) direkt berechne, bekannte Formeln verwende.
ToolmakerSteve

@ToolmakerSteve die if-Anweisungen funktionieren immer, wenn die 3 Punkte konvex sind. Wenn die if-Anweisungen zurückgegeben werden, erhalten Sie die richtige Antwort. Die if-Anweisungen werden nicht zurückgegeben, wenn die Form konkav und extrem ist. Dann musst du rechnen. Die meisten Bilder haben einen Quadranten, so dass dieser Teil einfach ist. Mehr als 99% meiner Unterprogrammaufrufe werden von den if-Anweisungen verarbeitet.
VectorVortec

Das spricht mein Anliegen nicht an. Was ist das für eine Formel? Ist es die Orientierungsdeterminante, wie sie im Wiki-Link aus der Antwort von lhf angegeben ist? Wenn ja, dann sag es. Erklären Sie, dass Sie in den meisten Fällen schnelle Überprüfungen durchführen, um die Standardmathematik zu vermeiden. Wenn dem so ist, dann macht Ihre Antwort jetzt für mich Sinn. (Minor nit: wäre einfacher zu lesen , wenn Sie verwenden .xund .yeine Struktur, statt [0]und [1]ich wusste nicht , was Ihr Code sagt, erstes Mal , dass ich es sah..)
ToolmakerSteve

Da ich Ihrem Ansatz nicht vertraute, habe ich den Ansatz von lhf umgesetzt . Formel aus seinem Link. Der langsame Teil findet die Suche nach einem geeigneten Scheitelpunkt - O (N). Einmal gefunden, ist die Determinante eine O (1) -Operation unter Verwendung von 6 Multiplikationen mit 5 Additionen. Diesen letzten Teil haben Sie optimiert. Sie haben dies jedoch getan, indem Sie zusätzliche if-Tests hinzugefügt haben. Ich kann es nicht rechtfertigen, einen nicht standardmäßigen Ansatz zu wählen - ich müsste überprüfen, ob jeder Schritt korrekt ist - aber ich danke Ihnen für eine interessante Analyse der Quadranten!
ToolmakerSteve

0

C # -Code zur Implementierung der Antwort von 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
Dies scheint für positive Y-Koordinaten zu gelten. Flip CW / CCW für Standardkoordinaten.
Warwick Allison


-4

Finden Sie den Schwerpunkt dieser Punkte.

Angenommen, es gibt Linien von diesem Punkt zu Ihren Punkten.

Finden Sie den Winkel zwischen zwei Linien für Linie0, Linie1

als für line1 und line2

...

...

Wenn dieser Winkel monoton größer ist als gegen den Uhrzeigersinn,

sonst, wenn es monoton abnimmt, ist es im Uhrzeigersinn

sonst (es ist nicht monoton)

Sie können sich nicht entscheiden, also ist es nicht klug


Mit "Schwerpunkt" meine ich "Schwerpunkt"?
Vicky Chijwani

Funktioniert wahrscheinlich, wenn das Polygon vollständig konvex ist. Verwenden Sie stattdessen eine Antwort, die für nicht konvexe Polygone funktioniert.
ToolmakerSteve
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.