Comment déterminer si une liste de points polygones est dans le sens des aiguilles d'une montre?

ayant une liste de points, comment trouver s'ils sont dans le sens des aiguilles d'une montre?

par exemple:

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

dirait qu'il est anti-horaire (ou l'inverse, pour certaines personnes).

210
demandé sur nbro 2009-07-22 18:24:33

20 réponses

certaines des méthodes suggérées échoueront dans le cas d'un polygone non convexe, tel qu'un croissant. Voici un simple qui fonctionnera avec des polygones non convexes (il fonctionnera même avec un polygone auto-intersecteur comme une figure-huit, vous disant si c'est la plupart du temps dans le sens des aiguilles d'une montre).

Somme sur les bords, (x 2 - x 1 )(y 2 + y 1 ). Si le résultat est positif, la courbe est dans le sens horaire, si il est négatif, la courbe est dans le sens antihoraire. (Le résultat est le double de la zone fermée, avec un + / - convention.)

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
344
répondu Beta 2016-01-04 18:32:56

le produit croisé mesure le degré de perpendicularité de deux vecteurs. Imaginez que chaque bord de votre polygone soit un vecteur dans le plan x-y d'un espace xyz tridimensionnel. Alors le produit croisé de deux arêtes successives est un vecteur dans la direction z, (direction z positive si le second segment est dans le sens des aiguilles d'une montre, moins direction z si elle est dans le sens contraire des aiguilles d'une montre). L'ampleur de ce vecteur est proportionnelle au sinus de l'angle entre les deux les bords d'origine, donc il atteint un maximum quand ils sont perpendiculaires, et s'amenuise pour disparaître quand les bords sont collinéaires (parallèle).

ainsi, pour chaque sommet (point) du polygone, calculer la magnitude du produit transversal des deux bords adjacents:

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

étiqueter les bords consécutivement comme

edgeA est le segment de point0 à point1 et

edgeB " entre point1 à point2

...

edgeE se situe entre point4 et point0 .

puis Vertex a ( point0 ) est entre

edgeE [de point4 à point0 ]

edgeA [de point0 à

"

ces deux bords sont eux-mêmes des vecteurs, dont les coordonnées x et y peuvent être déterminées en soustrayant les coordonnées de leurs points de début et fin:

edgeE = point0 - point4 = (1, 0) - (5, 0) = (-4, 0) et

edgeA = point1 - point0 = (6, 4) - (1, 0) = (5, 4) et

Et le produit vectoriel de ces deux bords contigus est calculé en utilisant le déterminant de la matrice suivante, qui est construit en mettant les coordonnées des deux vecteurs sous les symboles représentant les trois axes de coordonnées( i , j , & k ). La troisième coordonnée (zéro) - valorisée est là parce que le concept de produit croisé est une construction 3-D, et donc nous étendons ces vecteurs 2-D en 3-D afin d'appliquer le produit croisé:

 i    j    k 
-4    0    0
 1    4    0    

étant donné que tous les produits transversaux produisent un vecteur perpendiculaire au plan de deux vecteurs étant multiplié, le déterminant de la matrice ci-dessus n'a qu'un k , (ou axe z) composant.

La formule pour calculer la magnitude de la composante k ou de l'axe z est

a1*b2 - a2*b1 = -4* 4 - 0* 1 = -16

la grandeur de cette valeur ( -16 ), est une mesure du sinus de l'angle entre les 2 vecteurs originaux, multiplié par le produit des grandeurs des 2 vecteurs.

En fait, une autre formule pour sa valeur est

A X B (Cross Product) = |A| * |B| * sin(AB) .

ainsi, pour revenir à juste une mesure de l'angle vous devez diviser cette valeur, ( -16 ), par le produit des grandeurs des deux vecteurs.

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

ainsi la mesure du péché(AB) = -16 / 16.4924 = -.97014...

ceci est une mesure de si le segment suivant après le Sommet a plié pour la gauche ou la droite, et de combien. Il n'est pas nécessaire de prendre arc-sine. Tout ce qui nous importe, c'est son ampleur, et bien sûr son signe (positif ou négatif)!

faites ceci pour chacun des 4 autres points autour du chemin fermé, et additionnez les valeurs de ce calcul à chaque sommet..

si la somme finale est positive, vous êtes allé dans le sens des aiguilles d'une montre, négatif, dans le sens inverse des aiguilles d'une montre.

49
répondu Charles Bretana 2018-02-26 16:02:48

je suppose que c'est une question assez ancienne, mais je vais jeter une autre solution de toute façon, parce que c'est simple et pas mathématiquement intensif - il utilise juste l'algèbre de base. Calculer la superficie signée du polygone. Si il est négatif, les points sont dans le sens horaire, si elle est positive, ils sont dans le sens antihoraire. (Ceci est très similaire à la solution de Beta.)

calculer la zone signée: 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 )

ou en 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

notez que si vous ne vérifiez que la commande, vous n'avez pas besoin de diviser par 2.

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

42
répondu Sean the Bean 2015-05-26 17:48:50

voici un simple c # implémentation de l'algorithme basé sur cette réponse .

supposons que nous ayons un type Vector ayant X et Y propriétés de type 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;
}
19
répondu Olivier Jacot-Descombes 2018-02-25 11:18:03

trouver le sommet avec le plus petit y (et le plus grand x s'il y a des liens). Que le vertex soit A et que les prochains sommets de la liste soient B et C . Maintenant, calculez le signe 151970920 du produit croisé AB et AC .


, les Références:

12
répondu lhf 2018-02-25 11:22:26

commence par un des sommets, et calcule l'angle sous-tendu de chaque côté.

le premier et Le dernier sera égal à zéro (donc ignorer ces); pour le reste, le sinus de l'angle sera donné par le produit vectoriel des normalisations pour unité de longueur (point[n]-[0]) et ([n-1]-[0]).

si la somme des valeurs est positive, alors votre polygone est dessiné dans le sens anti-horaire.

6
répondu Steve Gilham 2009-07-22 14:31:08

pour ce que ça vaut, j'ai utilisé ce mixin pour calculer l'ordre d'enroulement des applications V3 de L'API GoogleMaps.

le code tire parti de l'effet secondaire des surfaces polygonales: un ordre d'enroulement des vertex dans le sens des aiguilles d'une montre produit une surface positive, tandis qu'un ordre d'enroulement des mêmes vertex dans le sens contraire des aiguilles d'une montre produit la même surface qu'une valeur négative. Le code utilise également une sorte d'API privée dans la bibliothèque de géométrie de Google Maps. Je me suis sentie à l'aise de l'utiliser à vos risques et périls.

exemple d'utilisation:

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

exemple Complet avec les tests unitaires @ 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;
  }
})();
3
répondu Steve Jansen 2013-12-20 05:35:34

Une mise en œuvre de de Sean réponse 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));

Je suis sûr que c'est juste. Il semble être au travail :-)

ces polygones ressemblent à ceci, si vous vous demandez:

3
répondu mpen 2017-10-06 20:36:03

si vous utilisez Matlab, la fonction ispolycw retourne true si les sommets des polygones sont dans le sens des aiguilles d'une montre.

2
répondu Frederick 2018-02-25 11:10:01

comme aussi expliqué dans cet article de Wikipedia orientation de la courbe , donné 3 points p , q et r sur le plan (i.e. avec des coordonnées x et y), Vous pouvez calculer le signe du déterminant suivant

enter image description here

si le déterminant est négatif (c.-à-d. Orient(p, q, r) < 0 ), alors le polygone est orienté dans le sens des aiguilles d'une montre (CW). Si le déterminant est positif (c'est à dire Orient(p, q, r) > 0 ), le polygone est orienté dans le sens inverse des aiguilles d'une montre (CCW). Le déterminant est zéro (c'est-à-dire Orient(p, q, r) == 0 ) si les points p , q et r sont collinéaire .

dans la formule ci-dessus, nous préparons ceux en face des coordonnées de p , q et r parce que nous utilisons des coordonnées homogènes .

2
répondu Ian 2018-02-25 21:07:31

c'est la fonction implémentée pour OpenLayers 2 . La condition pour avoir un polygone dans le sens des aiguilles d'une montre est area < 0 , il a confirmé par cette référence .

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
répondu MSS 2018-02-26 10:24:10

je pense que pour certains points dans le sens des aiguilles toutes les arêtes doivent être positifs, non seulement la somme des bords. Si un bord est négatif, au moins 3 points sont donnés dans le sens contraire des aiguilles d'une montre.

0
répondu daniel 2015-02-24 12:41:31

ma solution c# / LINQ est basée sur le Conseil produit croisé de @charlesbretana ci-dessous. Vous pouvez spécifier une référence normale pour le remontage. Il devrait fonctionner aussi longtemps que la courbe est la plupart du temps dans le plan défini par le vecteur.

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;

        } 

    }
}

avec un essai unitaire

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
répondu bradgonesurfing 2016-06-24 07:21:13

C'est ma solution en utilisant les explications dans les autres réponses:

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
répondu Gianni Spear 2018-02-25 11:06:31

une méthode beaucoup plus simple de calcul, si vous connaissez déjà un point à l'intérieur du polygone :

  1. choisissez un segment de ligne du polygone original, les points et leurs coordonnées dans cet ordre.

  2. Ajouter un point" intérieur " et former un triangle.

  3. calculer CW ou CCW comme suggéré ici avec ces trois points.

0
répondu Venkata Goli 2018-02-25 11:16:46

après avoir testé plusieurs implémentations non fiables, l'algorithme qui a fourni des résultats satisfaisants concernant l'orientation de la CW/CCW était celui, posté par OP dans ce" thread 151930920 "( shoelace_formula_3 ).

comme toujours, un nombre positif représente une orientation CW, alors qu'un nombre négatif CCW.

0
répondu Marjan Moderc 2018-02-25 11:19:57

voici la solution swift 3.0 basée sur les réponses ci-dessus:

    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
répondu Toby Evetts 2018-02-28 17:26:08

une autre solution pour cela;

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

prenez tous les sommets comme un tableau comme celui-ci;

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épondu Ind 2018-05-03 11:58:46

Solution pour R pour déterminer la direction et l'inverse si des aiguilles d'une montre (jugé nécessaire pour owin objets):

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
répondu dez 2018-06-29 04:25:19

trouver le centre de masse de ces points.

supposez qu'il y ait des lignes de ce point à vos points.

trouver l'angle entre deux lignes pour la ligne0 ligne 1

que pour la ligne 1 et la ligne 2

...

...

si cet angle augmente de façon monotone qu'il est dans le sens contraire des aiguilles d'une montre,

autrement si monotone le décroissant est dans le sens des aiguilles d'une montre

d'autre (il n'est pas monotonical)

vous ne pouvez pas décider, donc il n'est pas sage

-4
répondu ufukgun 2009-07-22 14:38:14