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?
Il suffit de jeter un coup d’œil sur le problème de point-in-polygone (PIP) .
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.
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;
}
}
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
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;
}
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.
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.
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;
}
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
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.
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.
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
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.
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;
}
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;
}
}