Come determinare se un elenco di punti poligonali sono in senso orario?


260

Avendo un elenco di punti, come trovo se sono in senso orario?

Per esempio:

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

direi che è antiorario (o antiorario, per alcune persone).


5
NOTA BENE: la risposta accettata e molte risposte successive richiedono molte aggiunte e moltiplicazioni (si basano su calcoli di area che terminano in modo negativo o positivo, ad esempio "formula del laccio"). Prima di implementare uno di questi, considera la risposta di lhf , che è più semplice / più veloce - basato sul wiki - orientamento del semplice poligono .
ToolmakerSteve

Ci penso sempre in termini di prodotto incrociato di due vettori adiacenti. Se cammino intorno al perimetro del poligono, la mia testa punta fuori dall'aereo. Incrocio il vettore fuori dal piano nel vettore della direzione di marcia per ottenere la terza direzione nel mio sistema di coordinate. Se quel vettore punta in modo che l'interno sia alla mia sinistra, è in senso antiorario; se l'interno è alla mia destra è in senso orario.
duffymo,

Risposte:


416

Alcuni dei metodi suggeriti falliranno nel caso di un poligono non convesso, come una mezzaluna. Eccone uno semplice che funzionerà con poligoni non convessi (funzionerà anche con un poligono autointersecante come una figura otto, che ti dice se è principalmente in senso orario).

Somma sopra i bordi, (x 2 - x 1 ) (y 2 + y 1 ). Se il risultato è positivo, la curva è in senso orario, se è negativa la curva è in senso antiorario. (Il risultato è il doppio dell'area racchiusa, con una convenzione +/-.)

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
È il calcolo applicato a un caso semplice. (Non ho la capacità di pubblicare elementi grafici.) L'area sotto un segmento di linea è uguale alla sua altezza media (y2 + y1) / 2 volte la sua lunghezza orizzontale (x2-x1). Notare la convenzione dei segni in x. Prova questo con alcuni triangoli e vedrai presto come funziona.
Beta,

72
Un avvertimento minore: questa risposta assume un normale sistema di coordinate cartesiane. Il motivo che vale la pena menzionare è che alcuni contesti comuni, come la tela HTML5, usano un asse Y invertito. Quindi la regola deve essere invertita: se l'area è negativa , la curva è in senso orario.
LarsH,

8
@ Mr.Qbs: Quindi il mio metodo funziona, ma se salti una parte vitale , allora non funziona. Questa non è una novità.
Beta

11
@ Mr.Qbs: devi sempre collegare l'ultimo punto al primo. Se hai N punti numerati da 0 a N-1, devi calcolare: Sum( (x[(i+1) mod N] - x[i]) * (y[i] + y[(i+1) mod N]) )per i = da 0 a N-1. Vale a dire, deve prendere l'indice Modulo N ( N ≡ 0) La formula funziona solo per poligoni chiusi . I poligoni non hanno bordi immaginari.
Olivier Jacot-Descombes,

4
Questo blog.element84.com/polygon-winding.html spiega in inglese semplice perché questa soluzione funziona.
David Zorychta,

49

Il prodotto incrociato misura il grado di perpendicolarità di due vettori. Immagina che ogni bordo del tuo poligono sia un vettore nel piano xy di uno spazio xyz tridimensionale (3-D). Quindi il prodotto incrociato di due bordi successivi è un vettore nella direzione z (direzione z positiva se il secondo segmento è in senso orario, meno direzione z se è in senso antiorario). La grandezza di questo vettore è proporzionale al seno dell'angolo tra i due bordi originali, quindi raggiunge il massimo quando sono perpendicolari e si assottiglia per scomparire quando i bordi sono collineari (paralleli).

Quindi, per ciascun vertice (punto) del poligono, calcola l'entità del prodotto incrociato dei due bordi adiacenti:

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

Quindi etichettare i bordi consecutivamente così come
edgeAil segmento da point0a point1e
edgeBtra point1a point2
...
edgeEè tra point4e point0.

Quindi il vertice A ( point0) è compreso tra
edgeE[Da point4a point0]
edgeA[Da point0a `punto1 '

Questi due bordi sono essi stessi vettori, le cui coordinate xey possono essere determinate sottraendo le coordinate dei loro punti iniziale e finale:

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

E il prodotto vettoriale di questi due bordi contigui è calcolato utilizzando il determinante della matrice seguente, che è costruito inserendo le coordinate dei due vettori sotto i simboli che rappresentano i tre assi coordinati ( i, j, e k). La terza coordinata valutata (zero) è lì perché il concetto di prodotto incrociato è un costrutto 3D, quindi estendiamo questi vettori 2D in 3D per applicare il prodotto incrociato:

 i    j    k 
-4    0    0
 1    4    0    

Dato che tutti i prodotti incrociati producono un vettore perpendicolare al piano di due vettori che vengono moltiplicati, il determinante della matrice sopra ha solo un componente k, (o asse z).
La formula per calcolare la grandezza del kcomponente dell'asse z è
a1*b2 - a2*b1 = -4* 4 - 0* 1 = -16

La grandezza di questo valore ( -16), è una misura del seno dell'angolo tra i 2 vettori originali, moltiplicato per il prodotto delle magnitudini dei 2 vettori.
In realtà, un'altra formula per il suo valore è
A X B (Cross Product) = |A| * |B| * sin(AB).

Quindi, per tornare a solo una misura dell'angolo è necessario dividere questo valore, ( -16), per il prodotto delle magnitudini dei due vettori.

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

Quindi la misura del peccato (AB) = -16 / 16.4924=-.97014...

Questa è una misura per stabilire se il segmento successivo dopo il vertice si è piegato a sinistra o a destra, e di quanto. Non è necessario assumere arc-seno. Tutto ciò che ci interesserà è la sua grandezza, e ovviamente il suo segno (positivo o negativo)!

Fallo per ciascuno degli altri 4 punti attorno al percorso chiuso e aggiungi i valori di questo calcolo in ciascun vertice.

Se la somma finale è positiva, sei andato in senso orario, negativo, in senso antiorario.


3
In realtà, questa soluzione è una soluzione diversa rispetto alla soluzione accettata. Se sono equivalenti o meno è una domanda che sto indagando, ma sospetto che non lo siano ... La risposta accettata calcola l'area del poligono, prendendo la differenza tra l'area sotto il bordo superiore del poligono e l'area sotto il bordo inferiore del poligono. Uno sarà negativo (quello in cui si sta attraversando da sinistra a destra) e l'altro sarà negativo. Quando si sposta in senso orario, il bordo superiore viene attraversato da sinistra a destra ed è più grande, quindi il totale è positivo.
Charles Bretana,

1
La mia soluzione misura la somma dei seni delle variazioni degli angoli del bordo di ciascun vertice. Ciò sarà positivo quando si attraversa in senso orario e negativo quando si attraversa in senso antiorario.
Charles Bretana,

2
Sembra con questo approccio che devi prendere l'arcosina, a meno che tu non assuma convessità (nel qual caso devi solo controllare un vertice)
agentp

2
Devi prendere l'arcosina. Provalo su un gruppo di poligoni non convessi casuali e scoprirai che il test fallirà per alcuni poligoni se non prendi l'arcosina.
Luke Hutchison,

1
@CharlesBretana - anche se non ho eseguito il test di Luke, credo che abbia ragione. Questa è la natura della somma combinata con una scala non lineare [senza arcsin contro arcsin]. Considera cosa ha suggerito marsbear che hai respinto correttamente. Ti ha suggerito di "contare" e hai sottolineato che una manciata di valori di grandi dimensioni potrebbe superare un gran numero di valori di piccole dimensioni. Ora considera arcsin di ciascun valore vs not. Non è ancora vero che la mancata assunzione di arcsin attribuisce un peso errato a ciascun valore, quindi ha lo stesso difetto (anche se molto meno)?
ToolmakerSteve

47

Immagino che questa sia una domanda piuttosto vecchia, ma proverò comunque un'altra soluzione, perché è semplice e non matematicamente intensiva: utilizza solo l'algebra di base. Calcola l'area firmata del poligono. Se è negativo i punti sono in senso orario, se è positivo sono in senso antiorario. (Questo è molto simile alla soluzione Beta.)

Calcola l'area firmata: 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 )

O in pseudo-codice:

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

Nota che se stai solo controllando l'ordinamento, non devi preoccuparti di dividere per 2.

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


Era un refuso nella formula della tua area firmata sopra? Termina con "xn * y1 - x1 * yn"; quando credo che dovrebbe essere "x_n y_ {n + 1} - y_n x_ {n-1}" (almeno in LaTeX). D'altra parte, sono passati dieci anni da quando ho preso lezioni di algebra lineare.
Michael Eric Oberlin,

No. Se controlli la fonte , vedrai che la formula fa di nuovo riferimento al primo punto nell'ultimo termine (y1 e x1). (Mi dispiace, non ho molta familiarità con LaTeX, ma ho formattato i pedici per renderli più leggibili.)
Sean the Bean,

Ho usato questa soluzione e ha funzionato perfettamente per il mio uso. Si noti che se è possibile pianificare in anticipo e risparmiare altri due vettori nell'array, è possibile eliminare il confronto (o%) aggiungendo il primo vettore alla fine dell'array. In questo modo, esegui semplicemente il ciclo su tutti gli elementi, tranne l'ultimo (lunghezza-2 anziché lunghezza-1).
Eric Fortier,

2
@EricFortier - FWIW, piuttosto che ridimensionare un array possibilmente grande, un'alternativa efficiente è per ogni iterazione di salvare il suo punto come previousPointper la prossima iterazione. Prima di iniziare il loop, impostare previousPointsull'ultimo punto dell'array. Il compromesso è una copia variabile locale aggiuntiva ma un numero inferiore di accessi agli array. E, soprattutto, non è necessario toccare l'array di input.
ToolmakerSteve

2
@MichaelEricOberlin - è necessario chiudere il poligono, includendo il segmento di linea dall'ultimo punto al primo punto. (Un calcolo corretto sarà lo stesso, indipendentemente dal punto in cui inizia il poligono chiuso.)
ToolmakerSteve

38

Trova il vertice con la più piccola y (e la più grande x se ci sono legami). Lascia che sia il vertice Ae il vertice precedente nell'elenco Be il vertice successivo nell'elenco C. Ora calcola il segno del prodotto incrociato di ABe AC.


Riferimenti:


7
Questo è anche spiegato in en.wikipedia.org/wiki/Curve_orientation . Il punto è che il punto trovato deve trovarsi sullo scafo convesso, ed è necessario solo guardare localmente un singolo punto sullo scafo convesso (e i suoi vicini immediati) per determinare l'orientamento dell'intero poligono.
M Katz,

1
Scioccato e stupito, questo non ha ricevuto più voti. Per i poligoni semplici ( che è la maggior parte dei poligoni in alcuni campi ), questa risposta fornisce una O(1)soluzione. Tutte le altre risposte forniscono O(n)soluzioni per nil numero di punti poligonali. Per ottimizzazioni ancora più profonde, consultare la sottosezione Considerazioni pratiche del fantastico articolo di orientamento Curve di Wikipedia .
Cecil Curry,

8
Chiarimento: questa soluzione èO(1)solo se (A) questo poligono è convesso (nel qual caso qualsiasi vertice arbitrario risiede sullo scafo convesso e quindi è sufficiente) o (B) si conosce già il vertice con la coordinata Y più piccola. In caso contrario (ovvero, questo poligono non è convesso e non ne sai nulla),O(n)è necessariaunaricerca. Poiché non è richiesta alcuna somma, tuttavia, questo è ancora notevolmente più veloce di qualsiasi altra soluzione per poligoni semplici.
Cecil Curry,


1
@CecilCurry Penso che il tuo secondo commento spieghi perché questo non ha ricevuto più voti. Fornisce risposte errate in determinati scenari, senza alcuna menzione di tali limiti.
LarsH

24

Ecco una semplice implementazione in C # dell'algoritmo basata su questa risposta .

Supponiamo di avere un Vectortipo con Xe Yproprietà di tipo 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;
}

%è l'operatore modulo o resto che esegue l'operazione modulo che ( secondo Wikipedia ) trova il resto dopo la divisione di un numero per un altro.


6

Inizia da uno dei vertici e calcola l'angolo sotteso da ciascun lato.

Il primo e l'ultimo saranno zero (quindi salta quelli); per il resto, il seno dell'angolo sarà dato dal prodotto incrociato delle normalizzazioni alla lunghezza unitaria di (punto [n]-punto [0]) e (punto [n-1]-punto [0]).

Se la somma dei valori è positiva, il poligono viene disegnato in senso antiorario.


Visto che il prodotto incrociato si riduce sostanzialmente a un fattore di ridimensionamento positivo moltiplicato per il seno dell'angolo, è probabilmente meglio fare semplicemente un prodotto incrociato. Sarà più veloce e meno complicato.
ReaperUnreal,

4

Per quello che vale, ho usato questo mixin per calcolare l'ordine degli avvolgimenti per le app v3 dell'API di Google Maps.

Il codice sfrutta l'effetto collaterale delle aree poligonali: un ordine di avvolgimento in senso orario di vertici produce un'area positiva, mentre un ordine di avvolgimento in senso antiorario degli stessi vertici produce la stessa area di un valore negativo. Il codice utilizza anche una sorta di API privata nella libreria geometrica di Google Maps. Mi sentivo a mio agio nell'usarlo - usare a proprio rischio.

Esempio di utilizzo:

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

Esempio completo con unit test @ 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;
  }
})();

Provando questo ottengo esattamente il risultato opposto, un poligono disegnato in senso orario produce un'area negativa, mentre uno disegnato in senso antiorario produce positivo. In entrambi i casi, questo frammento è ancora molto utile per 5 anni, grazie.
Cameron Roberts,

@CameronRoberts La norma (vedi IETF in particolare per geoJson) è seguire la 'regola della mano destra'. Immagino che Google si stia lamentando. In tal caso, l'anello esterno deve essere in senso antiorario (cedendo l'area positiva) e gli anelli interni (fori) stanno avvolgendo in senso orario (l'area negativa da rimuovere dall'area principale).
allez l'OM il

4

Un'implementazione della risposta di Sean 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));

Abbastanza sicuro che sia giusto. Sembra funzionare :-)

Quei poligoni sembrano così, se ti stai chiedendo:


3

Questa è la funzione implementata per OpenLayers 2 . La condizione per avere un poligono in senso orario è area < 0, confermata da questo riferimento .

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 è una libreria di gestione delle mappe basata su javascript come googlemaps ed è scritta e utilizzata in openlayers 2.
MSS,

Puoi spiegarci un po 'cosa fa il tuo codice e perché lo stai facendo?
nbro,

@nbro questo codice implementa la risposta lhf . È facile mantenere la parte non OpenLayer in una pura funzione JavaScript avendo i vertici direttamente come parametro. Funziona bene e potrebbe essere adattato al caso di multipoligono .
allez l'OM il

2

Se si utilizza Matlab, la funzione ispolycwrestituisce true se i vertici del poligono sono in senso orario.


1

Come anche illustrato in questo articolo Wikipedia orientamento curva , in 3 punti p, qe rsul piano (cioè con le coordinate xey), si può calcolare il segno della seguente determinante

inserisci qui la descrizione dell'immagine

Se il determinante è negativo (cioè Orient(p, q, r) < 0), il poligono è orientato in senso orario (CW). Se il determinante è positivo (cioè Orient(p, q, r) > 0), il poligono è orientato in senso antiorario (CCW). Il determinante è zero (cioè Orient(p, q, r) == 0) se punti p, qe rsono collineari .

Nella formula precedente, abbiamo anteporre quelli davanti alle coordinate p, q e rperché stiamo usando coordinate omogenee .


@tibetty Puoi spiegare perché questo metodo non funzionerebbe in molte situazioni se il poligono è concavo?
nbro,

1
Si prega di guardare l'ultima tabella nel riferimento alla voce wiki nel tuo post. È facile per me dare un falso esempio ma è difficile dimostrarlo.
martedì

1
Si prega di guardare l'ultima tabella nel riferimento alla voce wiki nel tuo post. È facile per me dare un falso esempio ma è difficile dimostrarlo.
martedì

1
@tibetty è corretto. Non puoi semplicemente prendere tre punti lungo il poligono; potresti trovarti in una regione convessa o concava di quel poligono. Leggendo attentamente la wiki, si devono prendere tre punti lungo lo scafo convesso che racchiude il poligono . Da "considerazioni pratiche": "Non è necessario costruire lo scafo convesso di un poligono per trovare un vertice adatto. Una scelta comune è il vertice del poligono con la coordinata X più piccola. Se ce ne sono diversi, quello viene selezionata la coordinata Y più piccola. È garantito che sia [a] vertice dello scafo convesso del poligono. "
ToolmakerSteve

1
Da qui la risposta precedente di lhf , che è simile, e fa riferimento allo stesso articolo wiki, ma specifica un punto del genere. [Apparentemente non importa se si prende il più piccolo o il più grande, x o y, purché si eviti di essere nel mezzo; in effetti uno sta lavorando da un bordo del riquadro di delimitazione attorno al poligono, per garantire in una regione concava.]
ToolmakerSteve

0

Penso che per assegnare alcuni punti in senso orario tutti i bordi debbano essere positivi non solo la somma dei bordi. Se un bordo è negativo di almeno 3 punti vengono dati in senso antiorario.


È vero, ma fraintendi il concetto di ordine di avvolgimento di un poligono (in senso orario o antiorario). In un poligono interamente convesso, l'angolo in tutti i punti sarà in senso orario o tutto sarà in senso antiorario [come nella tua prima frase]. In un poligono con regione (e) concava, le "caverne" saranno nella direzione opposta, ma il poligono nel suo insieme ha ancora un interno ben definito, ed è considerato di conseguenza in senso orario o antiorario. Vedi en.wikipedia.org/wiki/…
ToolmakerSteve

0

La mia soluzione C # / LINQ si basa sui consigli sui prodotti incrociati di @charlesbretana di seguito. È possibile specificare un riferimento normale per l'avvolgimento. Dovrebbe funzionare fintanto che la curva si trova principalmente nel piano definito dal vettore in alto.

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;

        } 

    }
}

con un test unitario

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

Questa è la mia soluzione usando le spiegazioni nelle altre risposte:

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
Puoi specificare su quali altre risposte si basa esattamente questa risposta?
nbro,

0

Un metodo molto più semplice dal punto di vista computazionale, se conosci già un punto all'interno del poligono :

  1. Scegli qualsiasi segmento di linea dal poligono originale, i punti e le loro coordinate in quell'ordine.

  2. Aggiungi un punto "interno" noto e forma un triangolo.

  3. Calcola CW o CCW come suggerito qui con questi tre punti.


Forse questo funziona se il poligono è completamente convesso. Sicuramente non è affidabile se ci sono regioni concave: è facile scegliere un punto che si trova sul lato "sbagliato" di uno dei bordi della caverna, quindi collegarlo a quel bordo. Otterrà una risposta sbagliata.
ToolmakerSteve

Funziona anche se il poligono è concavo. Il punto deve essere all'interno di quel poligono concavo. Comunque io non sono sicuro di poligono complesso (non prova.)
Venkata Goli

"Funziona anche se il poligono è concavo." - Controesempio: poli (0,0), (1,1), (0,2), (2,2), (2,0). Segmento di linea (1,1), (0, 2). Se scegli un punto interno tra (1,1), (0,2), (1,2) per formare un triangolo -> (1,1), (0,2), (0,5,1,5)), otterrai avvolgimento opposto rispetto a se si sceglie un punto interno entro (0,0), (1,1), (1,0)> (1,1), (0,2), (0,5,0,5). Entrambi sono interni al poligono originale, ma hanno avvolgimenti opposti. Pertanto, uno di loro dà la risposta sbagliata.
ToolmakerSteve

In generale, se un poligono ha una regione concava, selezionare un segmento nella regione concava. Perché è concavo, puoi trovare due punti "interni" che si trovano sui lati opposti di quella linea. Poiché si trovano su lati opposti di quella linea, i triangoli formati hanno avvolgimenti opposti. Fine della prova.
ToolmakerSteve

0

Dopo aver testato diverse implementazioni inaffidabili, l'algoritmo che ha fornito risultati soddisfacenti per quanto riguarda l'orientamento CW / CCW è stato quello pubblicato da OP in questo thread ( shoelace_formula_3).

Come sempre, un numero positivo rappresenta un orientamento in senso antiorario, mentre un numero negativo in senso antiorario.


0

Ecco la soluzione rapida 3.0 basata sulle risposte sopra:

    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

Un'altra soluzione per questo;

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

Prendi tutti i vertici come un array come questo;

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

Soluzione per R per determinare la direzione e invertire se in senso orario (trovato necessario per gli oggetti 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

Sebbene queste risposte siano corrette, sono matematicamente più intense del necessario. Assumi le coordinate della mappa, dove il punto più a nord è il punto più alto sulla mappa. Trova il punto più a nord, e se 2 punti pareggiano, è il più a nord, quindi il più a est (questo è il punto che usa la risposta nella sua risposta). Nei tuoi punti,

punto [0] = (5,0)

punto [1] = (6,4)

punto [2] = (4,5)

punto [3] = (1,5)

punto [4] = (1,0)

Se assumiamo che P2 sia il punto più a nord, allora il punto precedente o successivo determinano in senso orario, CW o CCW. Poiché il punto più a nord si trova sulla parete nord, se P1 (precedente) a P2 si sposta verso est, la direzione è CW. In questo caso, si sposta verso ovest, quindi la direzione è in senso antiorario come dice la risposta accettata. Se il punto precedente non ha movimento orizzontale, lo stesso sistema si applica al punto successivo, P3. Se P3 è a ovest di P2, lo è, allora il movimento è in senso antiorario. Se il movimento da P2 a P3 è est, in questo caso è ovest, il movimento è CW. Supponiamo che nte, P2 nei dati, sia il punto più a nord rispetto a est e che il prv sia il punto precedente, P1 nei dati e nxt sia il punto successivo, P3 nei dati e [0] sia orizzontale o est / ovest dove ovest è minore di est e [1] è verticale.

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, sarebbe più sicuro attenersi alla matematica fondamentale mostrata nella risposta di lhf - grazie per averlo menzionato. La sfida nel ridurlo ai quadranti è che è una buona dose di lavoro per dimostrare che la tua formula è corretta in tutti i casi. Hai calcolato correttamente "più ovest"? In un poligono concavo in cui sia [1] che [3] sono "ovest e sud" di [2]? Hai gestito correttamente lunghezze diverse di [1] e [3] in quella situazione? Non ne ho idea, mentre se calcolo direttamente quell'angolo (o il suo determinante), sto usando formule ben note.
ToolmakerSteve

@ToolmakerSteve le istruzioni if ​​funzionano sempre se i 3 punti sono convessi. Le istruzioni if ​​torneranno, quindi otterrai la risposta giusta. Le istruzioni if ​​non torneranno, se la forma è concava ed estrema. Questo è quando devi fare i conti. La maggior parte delle immagini ha un quadrante, quindi quella parte è facile. Oltre il 99% delle mie chiamate alla subroutine sono gestite dalle istruzioni if.
VectorVortec,

Questo non risponde alla mia preoccupazione. Qual è quella formula? È il determinante dell'orientamento come indicato nel link wiki dalla risposta di lhf? Se è così, allora dillo. Spiega che ciò che stai facendo è eseguire controlli rapidi che gestiscono la maggior parte dei casi, per evitare la matematica standard. Se è così, allora la tua risposta ora ha senso per me. (Minor nit: sarebbe più facile da leggere se hai usato .xe .ydi una struttura, invece di [0]e [1]. Non sapevo cosa dicesse il tuo codice, la prima volta che l'ho guardato.)
ToolmakerSteve

Dato che non avevo fiducia nel tuo approccio, ho implementato l'approccio di lhf ; formula dal suo link. La parte lenta sta trovando il vertice appropriato - Ricerca O (N). Una volta trovato, il determinante è un'operazione O (1), usando 6 moltiplicazioni con 5 aggiunte. L'ultima parte è ciò che hai ottimizzato; ma l'hai fatto aggiungendo ulteriori if-test. Personalmente non posso giustificare l'adozione di un approccio non standard - dovrei verificare che ogni passaggio sia corretto - Ma grazie per un'interessante analisi dei quadranti!
ToolmakerSteve

0

Codice C # per implementare la risposta di 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
Questo sembra essere per le coordinate Y down-is-positive. Capovolgi CW / CCW per coordinate standard.
Warwick Allison,

0

Ecco una semplice implementazione di Python 3 basata su questa risposta (che, a sua volta, si basa sulla soluzione proposta nella risposta accettata )

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

-4

trova il centro di massa di questi punti.

supponiamo che ci siano linee da questo punto ai tuoi punti.

trova l'angolo tra due linee per line0 line1

di farlo per line1 e line2

...

...

se questo angolo aumenta monotonicamente di quanto non sia in senso antiorario,

altrimenti se diminuendolo monotonicamente è in senso orario

altro (non è monotonico)

non puoi decidere, quindi non è saggio


per "centro di massa" penso che intendi "centroide"?
Vicky Chijwani,

Probabilmente funziona se il poligono è interamente convesso. Ma meglio usare invece una risposta che funzionerà per i poligoni non convessi.
ToolmakerSteve
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.