web-dev-qa-db-de.com

Wie können Sie feststellen, ob eine Liste von Polygonpunkten im Uhrzeigersinn angeordnet ist?

Wie finde ich eine Liste mit Punkten, wenn sie im Uhrzeigersinn angeordnet sind?

Zum Beispiel:

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 (oder für manche Leute gegen den Uhrzeigersinn) ist.

225
Stécy

Einige der vorgeschlagenen Methoden schlagen im Fall eines nicht konvexen Polygons, beispielsweise eines Halbmondes, fehl. Hier ist eine einfache, die mit nicht-konvexen Polygonen funktioniert (es funktioniert sogar mit einem sich selbst überschneidenden Polygon wie einer Acht), das Ihnen sagt, ob dies der Fall ist meistens im Uhrzeigersinn).

Summe über die Kanten, (x2 - x1) (y2 + y1). Wenn das Ergebnis positiv ist, ist die Kurve im Uhrzeigersinn, wenn sie negativ ist, ist die Kurve gegen den Uhrzeigersinn. (Das Ergebnis ist das Doppelte der geschlossenen Fläche 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
368
Beta

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

Berechnen Sie also für jeden Scheitelpunkt (Punkt) des Polygons die Querproduktgröße der beiden angrenzenden 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 also die Kanten nacheinander als
edgeA ist das Segment von point0 bis point1 und
edgeB zwischen point1 bis point2
...
edgeE liegt zwischen point4 und point0.

Dann liegt Vertex A (point0) zwischen
edgeE [Von point4 bis point0]
edgeA [Von point0 bis `point1 '

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

Das Kreuzprodukt dieser beiden aneinandergrenzenden Kanten wird unter Verwendung der Determinante der folgenden Matrix berechnet, die durch Platzieren der Koordinaten der beiden Vektoren unter den Symbolen für die drei Koordinatenachsen (i, j, & k) erstellt wird. Die dritte (Null) -bewertete Koordinate ist vorhanden, weil das Kreuzproduktkonzept ein 3D-Konstrukt ist. Daher erweitern wir diese 2D-Vektoren zu 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 Komponente k (oder Z-Achse).
Die Formel zur Berechnung der Größe der Komponente k 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 also auf ein Maß für den Winkel 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 das nächste Segment nach dem Scheitelpunkt nach links oder rechts gebogen ist. Es ist nicht erforderlich, einen Arcussinus zu nehmen. Wir kümmern uns nur um die Größe und natürlich um das Vorzeichen (positiv oder negativ)!

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

Wenn die Endsumme positiv ist, sind Sie im Uhrzeigersinn, negativ und gegen den Uhrzeigersinn gefahren.

49
Charles Bretana

Ich denke, das ist eine ziemlich alte Frage, aber ich werde sowieso eine andere Lösung aussenden, weil sie unkompliziert und nicht mathematisch intensiv ist. Berechnen Sie den vorzeichenbehafteten Bereich des Polygons. Wenn es negativ ist, sind die Punkte im Uhrzeigersinn, wenn sie positiv sind, sind sie gegen den Uhrzeigersinn. (Dies ist der Lösung von Beta sehr ähnlich.)

Berechnen Sie den vorzeichenbehafteten Bereich: A = 1/2 * (x1* y2 - x2* y1 + x2* y3 - x3* y2 + ... + xn* y1 - x1* yn)

Oder im Pseudo-Code:

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

Wenn Sie nur die Bestellung überprüfen, müssen Sie sich nicht durch 2 teilen.

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

43
Sean the Bean

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

Nehmen wir an, wir haben einen Vector-Typ mit X- und Y-Eigenschaften vom 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]; // % is the modulo operator
        sum += (v2.X - v1.X) * (v2.Y + v1.Y);
    }
    return sum > 0.0;
}

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


Verweise:

17
lhf

Beginnen Sie an einem der Scheitelpunkte und berechnen Sie den Winkel, der auf jeder Seite angezeigt wird. 

Die erste und die letzte ist Null (überspringen Sie diese also); im übrigen wird der Sinus des Winkels durch das Kreuzprodukt der Normalisierungen zu Längeneinheiten von (Punkt [n] -Punkt [0]) und (Punkt [n-1] -Punkt [0]) gegeben.

Wenn die Summe der Werte positiv ist, wird Ihr Polygon im Gegenuhrzeigersinn gezeichnet.

6
Steve Gilham

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

Ziemlich sicher, dass das stimmt. Es scheint zu funktionieren :-)

Diese Polygone sehen so aus, wenn Sie sich fragen:

4
mpen

Für das, was es wert ist, habe ich diese Mischung verwendet, um die Reihenfolge der Wicklung für Google Maps API v3-Apps zu berechnen.

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

Verwendungsbeispiel:

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  [email protected]
 *
 *  @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;
  }
})();
4
Steve Jansen

Dies ist die implementierte Funktion für OpenLayers 2 . Die Bedingung für ein Polygon im Uhrzeigersinn ist area < 0, dies wird 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);
}
2
MSS

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

2
Frederick

Wie auch in diesem Wikipedia-Artikel erklärt Kurvenorientierung , gegeben 3 Punkte p, q und r in der Ebene (dh mit x- und y-Koordinaten) ) können Sie das Vorzeichen der folgenden Determinante berechnen

enter image description here

Wenn die Determinante negativ ist (d. H. Orient(p, q, r) < 0), ist das Polygon im Uhrzeigersinn ausgerichtet (CW). Wenn die Determinante positiv ist (d. H. Orient(p, q, r) > 0), ist das Polygon gegen den Uhrzeigersinn (CCW) ausgerichtet. Die Determinante ist Null (d. H. Orient(p, q, r) == 0), wenn die Punkte p, q und rkollinear sind.

In der obigen Formel stellen wir die vor den Koordinaten von p, q und r, da wir homogene Koordinaten verwenden.

1
Ian

Lösung für R, um die Richtung zu bestimmen und umzukehren, wenn im Uhrzeigersinn (dies ist für Objekte notwendig):

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
dez

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

0
daniel

Hier ist die Swift 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
Toby Evetts

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

Nimm alle Scheitelpunkte als ein 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
Ind

Dies ist meine Lösung mit den 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
0
Gianni Spear

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

punkt [0] = (5,0)

punkt [1] = (6,4)

punkt [2] = (4,5)

punkt [3] = (1,5)

punkt [4] = (1,0)

Wenn wir davon ausgehen, dass P2 der nördlichste Punkt ist, dann bestimmen der östliche Punkt entweder den vorherigen oder den nächsten Punkt im Uhrzeigersinn, CW oder CCW. Da sich der nördlichste Punkt auf der Nordseite befindet, ist die Richtung CW, wenn sich P1 (vorhergehend) zu P2 nach Osten bewegt. In diesem Fall bewegt es sich nach Westen, also ist die Richtung CCW, wie die akzeptierte Antwort sagt. Wenn der vorherige Punkt keine horizontale Bewegung hat, gilt dasselbe System für den nächsten Punkt, P3. Wenn sich P3 westlich von P2 befindet, ist die Bewegung CCW. Wenn die Bewegung P2 zu P3 nach Osten verläuft, ist es in diesem Fall nach Westen, die Bewegung ist CW. Angenommen, nte, P2 in Ihren Daten, ist der nördlichste, dann östliche 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 ost Westen, wo Westen weniger als Osten ist, und [1] ist vertikal.

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)
0
VectorVortec

Nach dem Test mehrerer unzuverlässiger Implementierungen war der Algorithmus, der zufriedenstellende Ergebnisse hinsichtlich der CW/CCW-Orientierung nach dem Auspacken lieferte, der von OP in this thread (shoelace_formula_3) veröffentlichte. 

Eine positive Zahl steht wie immer für eine CW-Orientierung, eine negative für CCW.

0
Marjan Moderc

C # -Code zum Implementieren von 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;
}
0
ToolmakerSteve

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

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

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

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

0
Venkata Goli

Meine C #/LINQ-Lösung basiert auf den produktübergreifenden Empfehlungen von @charlesbretana. Sie können einen Referenznormal für die Wicklung angeben. Es sollte funktionieren, solange sich die Kurve hauptsächlich 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
bradgonesurfing