web-dev-qa-db-fra.com

Geo Fencing - point intérieur/extérieur du polygone

J'aimerais déterminer un polygone et implémenter un algorithme permettant de vérifier si un point est à l'intérieur ou à l'extérieur du polygone.

Est-ce que quelqu'un sait s'il existe un exemple d'algorithme similaire?

49
Niko Gamulin

Il suffit de jeter un coup d’œil sur le problème de point-in-polygone (PIP) .

29
Daniel Brückner

Si je me souviens bien, l’algorithme consiste à tracer une ligne horizontale à travers votre point de test. Comptez le nombre de lignes du polygone que vous coupez pour atteindre votre point.

Si la réponse est étrange, vous êtes à l'intérieur. Si la réponse est égale, vous êtes à l'extérieur.

Edit: Ouais, quel il a dit ( Wikipedia ):

alt text

63
Ian Boyd

Code C #

bool IsPointInPolygon(List<Loc> poly, Loc point)
{
    int i, j;
    bool c = false;
    for (i = 0, j = poly.Count - 1; i < poly.Count; j = i++)
    {
        if ((((poly[i].Lt <= point.Lt) && (point.Lt < poly[j].Lt)) 
                || ((poly[j].Lt <= point.Lt) && (point.Lt < poly[i].Lt))) 
                && (point.Lg < (poly[j].Lg - poly[i].Lg) * (point.Lt - poly[i].Lt) 
                    / (poly[j].Lt - poly[i].Lt) + poly[i].Lg))

            c = !c;
        }
    }

    return c;
}

Classe de loc

public class Loc
{
    private double lt;
    private double lg;

    public double Lg
    {
        get { return lg; }
        set { lg = value; }
    }

    public double Lt
    {
        get { return lt; }
        set { lt = value; }
    }

    public Loc(double lt, double lg)
    {
        this.lt = lt;
        this.lg = lg;
    }
}
34
kober

Après avoir cherché sur le Web et essayé diverses implémentations et les avoir portées du C++ au C #, j'ai finalement compris le code:

        public static bool PointInPolygon(LatLong p, List<LatLong> poly)
    {
        int n = poly.Count();

        poly.Add(new LatLong { Lat = poly[0].Lat, Lon = poly[0].Lon });
        LatLong[] v = poly.ToArray();

        int wn = 0;    // the winding number counter

        // loop through all edges of the polygon
        for (int i = 0; i < n; i++)
        {   // Edge from V[i] to V[i+1]
            if (v[i].Lat <= p.Lat)
            {         // start y <= P.y
                if (v[i + 1].Lat > p.Lat)      // an upward crossing
                    if (isLeft(v[i], v[i + 1], p) > 0)  // P left of Edge
                        ++wn;            // have a valid up intersect
            }
            else
            {                       // start y > P.y (no test needed)
                if (v[i + 1].Lat <= p.Lat)     // a downward crossing
                    if (isLeft(v[i], v[i + 1], p) < 0)  // P right of Edge
                        --wn;            // have a valid down intersect
            }
        }
        if (wn != 0)
            return true;
        else
            return false;

    }

    private static int isLeft(LatLong P0, LatLong P1, LatLong P2)
    {
        double calc = ((P1.Lon - P0.Lon) * (P2.Lat - P0.Lat)
                - (P2.Lon - P0.Lon) * (P1.Lat - P0.Lat));
        if (calc > 0)
            return 1;
        else if (calc < 0)
            return -1;
        else
            return 0;
    }

La fonction isLeft me posait des problèmes d'arrondi et j'ai passé des heures sans me rendre compte que je me trompais dans la conversion, alors pardonnez-moi pour les boiteux si je bloque à la fin de cette fonction.

Par ailleurs, il s'agit du code et de l'article originaux: http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm

12
Manuel Castro

Je pense qu'il existe une solution plus simple et plus efficace.

Voici le code en C++. Je devrais être simple à convertir en C #.

int pnpoly(int npol, float *xp, float *yp, float x, float y)
{
  int i, j, c = 0;
  for (i = 0, j = npol-1; i < npol; j = i++) {
    if ((((yp[i] <= y) && (y < yp[j])) ||
         ((yp[j] <= y) && (y < yp[i]))) &&
        (x < (xp[j] - xp[i]) * (y - yp[i]) / (yp[j] - yp[i]) + xp[i]))
      c = !c;
  }
  return c;
}
5
wael

Vous trouverez de loin la meilleure explication et mise en œuvre à l'adresse Point In Inclusion du nombre d'enroulement de polygone

Il y a même une implémentation C++ à la fin de l'article bien expliqué. Ce site contient également de très bons algorithmes/solutions pour d’autres problèmes liés à la géométrie.

J'ai modifié et utilisé l'implémentation C++ et également créé une implémentation C #. Vous voulez absolument utiliser l'algorithme de nombre d'enroulement car il est plus précis que l'algorithme de croisement des bords et il est très rapide.

4
eesh

Juste un avertissement (en utilisant answer comme je ne peux pas commenter), si vous voulez utiliser un point dans un polygone pour la séparation géographique, vous devez modifier votre algorithme pour qu'il fonctionne avec des coordonnées sphériques. -180 longitude est la même chose que 180 longitude et le point-en-polygone se cassera dans une telle situation. 

2
Justin Zhang

La solution complète dans asp.Net C #, vous pouvez voir le détail complet ici, vous pouvez voir comment trouver un point (lat, lon) que ce soit à l’intérieur ou à l’extérieur du polygone en utilisant la latitude et les longitudes? Référence de l'article Lien

balise statique privée checkPointExistsInGeofencePolygon (chaîne latérale, chaîne latérale, chaîne lng) {

    List<Loc> objList = new List<Loc>();
    // sample string should be like this strlatlng = "39.11495,-76.873259|39.114588,-76.872808|39.112921,-76.870373|";
    string[] arr = latlnglist.Split('|');
    for (int i = 0; i <= arr.Length - 1; i++)
    {
        string latlng = arr[i];
        string[] arrlatlng = latlng.Split(',');

        Loc er = new Loc(Convert.ToDouble(arrlatlng[0]), Convert.ToDouble(arrlatlng[1]));
        objList.Add(er);
    }
    Loc pt = new Loc(Convert.ToDouble(lat), Convert.ToDouble(lng));

    if (IsPointInPolygon(objList, pt) == true)
    {
          return true;
    }
    else
    {
           return false;
    }
}
private static bool IsPointInPolygon(List<Loc> poly, Loc point)
{
    int i, j;
    bool c = false;
    for (i = 0, j = poly.Count - 1; i < poly.Count; j = i++)
    {
        if ((((poly[i].Lt <= point.Lt) && (point.Lt < poly[j].Lt)) |
            ((poly[j].Lt <= point.Lt) && (point.Lt < poly[i].Lt))) &&
            (point.Lg < (poly[j].Lg - poly[i].Lg) * (point.Lt - poly[i].Lt) / (poly[j].Lt - poly[i].Lt) + poly[i].Lg))
            c = !c;
    }
    return c;
}
1
user1920890

Vérifier si un point est à l'intérieur d'un polygone ou non -

Considérons le polygone qui a les sommets a1, a2, a3, a4, a5. Les étapes suivantes devraient vous aider à déterminer si le point P se situe à l’intérieur du polygone ou à l’extérieur.

Calcule la surface vectorielle du triangle formé par Edge a1-> a2 et les vecteurs reliant a2 à P et P à a1. De même, calculez la surface vectorielle de chacun des triangles possibles ayant un côté comme côté du polygone et les deux autres points reliant P à ce côté.

Pour qu'un point soit à l'intérieur d'un polygone, chacun des triangles doit avoir une surface positive. Même si l'un des triangles a une surface négative, le point P est en dehors du polygone. 

Pour calculer l'aire d'un triangle donné les vecteurs représentant ses 3 arêtes, voir http://www.jtaylor1142001.net/calcjat/Solutions/VCrossProduct/VCPATriangle.htm

0
Arnkrishn

Le problème est plus facile si votre polygone est convexe. Si tel est le cas, vous pouvez effectuer un test simple pour chaque ligne pour voir si le point est à l'intérieur ou à l'extérieur de cette ligne (extension à l'infini dans les deux sens). Sinon, pour les polygones concaves, tracez un rayon imaginaire de votre repère à l'infini (dans n'importe quelle direction). Comptez combien de fois il traverse une ligne de démarcation. Odd signifie que le point est à l'intérieur, même que le point est à l'extérieur.

Ce dernier algorithme est plus compliqué qu'il n'y paraît. Vous devrez faire très attention à ce qui se passe lorsque votre rayon imaginaire atteint exactement l'un des sommets du polygone.

Si votre rayon imaginaire va dans la direction -x, vous pouvez choisir uniquement de compter les lignes comprenant au moins un point dont la coordonnée y est strictement inférieure à la coordonnée y de votre point. C'est ainsi que la plupart des cas étranges Edge fonctionnent correctement.

0
Dietrich Epp

J'ajoute un détail pour aider les gens qui vivent dans le ... sud de la Terre! résultats. 

Le moyen le plus simple d'utiliser les valeurs absolues des points Lat et Long de tous les points. Et dans ce cas, l'algo de Jan Kobersky est parfait.

0
Peter

le polygone est défini comme une liste séquentielle de paires de points A, B, C ... A . aucun côté A-B, B-C ... ne croise aucun autre côté

Déterminer la boîte Xmin, Xmax, Ymin, Ymax

cas 1, le point de test P est en dehors de la boîte

cas 2 le point de test P se trouve à l'intérieur de la boîte: 

Déterminez le 'diamètre' D de la boîte {[[Xmin, Ymin] - [Xmax, Ymax]}} (et ajoutez un petit plus pour éviter toute confusion possible avec D sur un côté 

Déterminer les gradients M de tous les côtés

Trouver un gradient Mt le plus différent de tous les gradients M

La ligne de test part de P au gradient Mt d’une distance D.

Définir le nombre d'intersections à zéro

Pour chacun des côtés A-B, B-C, testez l'intersection de P-D avec un côté De son début jusqu'à, mais sans inclure, sa fin. Incrémenter le nombre d'intersections Si nécessaire. Notez qu'une distance nulle de P à l'intersection indique que P est sur un côté

Un compte impair indique que P est à l'intérieur du polygone

0
david n laine

Si vous avez un simple polygone (aucune des lignes ne se croisent) et que vous n’avez pas de trous, vous pouvez également trianguler le polygone, ce que vous ferez probablement de toute façon dans une application SIG pour tracer un TIN, puis testez les points dans chaque Triangle. Si vous avez un petit nombre d'arêtes dans le polygone mais un grand nombre de points, c'est rapide.

Pour un point intéressant en triangle, voir texte du lien

Sinon, utilisez certainement la règle d'enroulement plutôt que le croisement des bords, le croisement des bords pose de véritables problèmes avec les points sur les bords, ce qui est très probable si vos données sont générées à partir d'un GPS.

0
Martin Beckett

La réponse de Jan est géniale.

Voici le même code utilisant la classe GeoCoordinate à la place.

using System.Device.Location;

...

public static bool IsPointInPolygon(List<GeoCoordinate> poly, GeoCoordinate point)
{
    int i, j;
    bool c = false;
    for (i = 0, j = poly.Count - 1; i < poly.Count; j = i++)
    {
        if ((((poly[i].Latitude <= point.Latitude) && (point.Latitude < poly[j].Latitude))
                || ((poly[j].Latitude <= point.Latitude) && (point.Latitude < poly[i].Latitude)))
                && (point.Longitude < (poly[j].Longitude - poly[i].Longitude) * (point.Latitude - poly[i].Latitude)
                    / (poly[j].Latitude - poly[i].Latitude) + poly[i].Longitude))
            c = !c;
    }

    return c;
}
0
Fidel

J'ai traduit la méthode c # en php et j'ai ajouté de nombreux commentaires pour comprendre le code.

Description de PolygonHelps:
Vérifiez si un point est à l'intérieur ou à l'extérieur d'un polygone. Cette procédure utilise les coordonnées GPS et fonctionne lorsque le polygone a une petite zone géographique.


CONTRIBUTION:
$ poly: tableau de points: liste de sommets de polygones; [{Point}, {Point}, ...];
$ point: point à vérifier; Point: {"lat" => "x.xxx", "lng" => "y.yyy"}


Lorsque $ c est faux, le nombre d'intersections avec le polygone est pair, le point est donc en dehors du polygone;
Lorsque $ c est vrai, le nombre d'intersections avec un polygone est impair, le point est donc à l'intérieur du polygone;
$ n est le nombre de sommets dans le polygone;
Pour chaque sommet dans un polygone, la méthode calcule la ligne passant par le sommet actuel et le sommet précédent et vérifie si les deux lignes ont un point d'intersection.
$ c change lorsque le point d'intersection existe.
Ainsi, la méthode peut renvoyer true si le point est à l’intérieur du polygone, sinon false.

class PolygonHelps {

    public static function isPointInPolygon(&$poly, $point){

        $c = false; 
        $n = $j = count($poly);


        for ($i = 0, $j = $n - 1; $i < $n; $j = $i++){

            if ( ( ( ( $poly[$i]->lat <= $point->lat ) && ( $point->lat < $poly[$j]->lat ) ) 
                || ( ( $poly[$j]->lat <= $point->lat ) && ( $point->lat < $poly[$i]->lat ) ) ) 

            && ( $point->lng <   ( $poly[$j]->lng - $poly[$i]->lng ) 
                               * ( $point->lat    - $poly[$i]->lat ) 
                               / ( $poly[$j]->lat - $poly[$i]->lat ) 
                               +   $poly[$i]->lng ) ){

                $c = !$c;
            }
        }

        return $c;
    }
}
0
Pietro La Grotta