Comment puis-je déterminer si un point 2D se trouve dans un polygone?

497

J'essaye de créer un jeûne point 2D l'intérieur d'un algorithme de polygone, pour une utilisation dans les tests de hit (par exemple Polygon.contains(p:Point)). Des suggestions de techniques efficaces seraient appréciées.

Scott Evernden
la source
Vous avez oublié de nous parler de vos perceptions sur la question de la droitière ou de la gaucherie - qui peut aussi être interprétée comme "à l'intérieur" vs "à l'extérieur" - RT
Richard T
13
Oui, je réalise maintenant que la question laisse de nombreux détails non spécifiés, mais à ce stade, je suis en quelque sorte intéressé à voir la variété des réponses.
Scott Evernden
4
Un polygone à 90 faces est appelé un enneacontagon et un polygone à 10 000 faces est appelé un myriagone.
"Le plus élégant" est hors de la cible, car j'ai eu du mal à trouver un algorithme de "travail du tout". Je dois le comprendre moi-même: stackoverflow.com/questions/14818567/…
davidkonrad

Réponses:

732

Pour les graphiques, je préfère ne pas préférer les entiers. De nombreux systèmes utilisent des entiers pour la peinture de l'interface utilisateur (les pixels sont des entiers après tout), mais macOS utilise par exemple float pour tout. macOS ne connaît que les points et un point peut se traduire par un pixel, mais selon la résolution du moniteur, il peut se traduire par autre chose. Sur les écrans rétiniens, un demi-point (0,5 / 0,5) correspond au pixel. Pourtant, je n'ai jamais remarqué que les interfaces utilisateur macOS sont beaucoup plus lentes que les autres interfaces utilisateur. Après tout, les API 3D (OpenGL ou Direct3D) fonctionnent également avec les flotteurs et les bibliothèques graphiques modernes profitent très souvent de l'accélération GPU.

Maintenant, vous avez dit que la vitesse est votre principale préoccupation, d'accord, allons-y pour la vitesse. Avant d'exécuter un algorithme sophistiqué, effectuez d'abord un test simple. Créer un cadre de délimitation aligné sur l'axe autour de votre polygone. C'est très facile, rapide et peut déjà vous permettre de faire de nombreux calculs. Comment ça marche? Itérer sur tous les points du polygone et trouver les valeurs min / max de X et Y.

Par exemple, vous avez les points (9/1), (4/3), (2/7), (8/2), (3/6). Cela signifie que Xmin est 2, Xmax est 9, Ymin est 1 et Ymax est 7. Un point à l'extérieur du rectangle avec les deux bords (2/1) et (9/7) ne peut pas être dans le polygone.

// p is your point, p.x is the x coord, p.y is the y coord
if (p.x < Xmin || p.x > Xmax || p.y < Ymin || p.y > Ymax) {
    // Definitely not within the polygon!
}

Il s'agit du premier test à exécuter pour n'importe quel point. Comme vous pouvez le voir, ce test est ultra rapide mais il est aussi très grossier. Pour gérer les points qui se trouvent dans le rectangle englobant, nous avons besoin d'un algorithme plus sophistiqué. Il y a deux façons de calculer cela. La méthode qui fonctionne dépend également du fait que le polygone peut avoir des trous ou sera toujours solide. Voici des exemples de solides (un convexe, un concave):

Polygone sans trou

Et en voici un avec un trou:

Polygone avec trou

Le vert a un trou au milieu!

L'algorithme le plus simple, qui peut gérer les trois cas ci-dessus et qui est encore assez rapide, est nommé ray casting . L'idée de l'algorithme est assez simple: dessinez un rayon virtuel de n'importe où en dehors du polygone jusqu'à votre point et comptez la fréquence à laquelle il frappe un côté du polygone. Si le nombre de coups est pair, il est en dehors du polygone, s'il est impair, il est à l'intérieur.

Démontrer comment le rayon traverse un polygone

L' algorithme du nombre d'enroulement serait une alternative, il est plus précis pour les points très proches d'une ligne polygonale mais il est également beaucoup plus lent. Le lancer de rayons peut échouer pour des points trop proches d'un côté de polygone en raison de la précision limitée en virgule flottante et des problèmes d'arrondi, mais en réalité ce n'est guère un problème, comme si un point se trouve aussi près d'un côté, il est souvent visuellement impossible même pour un spectateur pour reconnaître s'il est déjà à l'intérieur ou encore à l'extérieur.

Vous avez toujours la boîte englobante ci-dessus, vous vous souvenez? Choisissez simplement un point en dehors du cadre de sélection et utilisez-le comme point de départ pour votre rayon. Par exemple, le point (Xmin - e/p.y)est en dehors du polygone, c'est sûr.

Mais c'est quoi e? Eh bien, e(en fait epsilon) donne à la boîte englobante un peu de rembourrage . Comme je l'ai dit, le lancer de rayons échoue si nous commençons trop près d'une ligne de polygone. Étant donné que le cadre de délimitation peut être égal au polygone (si le polygone est un rectangle aligné sur l'axe, le cadre de délimitation est égal au polygone lui-même!), Nous avons besoin d'un rembourrage pour le rendre sûr, c'est tout. Quelle taille devriez-vous choisir e? Pas si gros. Cela dépend de l'échelle du système de coordonnées que vous utilisez pour le dessin. Si la largeur de votre pixel est de 1,0, choisissez simplement 1,0 (mais 0,1 aurait également fonctionné)

Maintenant que nous avons le rayon avec ses coordonnées de début et de fin, le problème passe de " est le point dans le polygone " à "à quelle fréquence le rayon intersecte-t-il un côté de polygone ". Par conséquent, nous ne pouvons pas simplement travailler avec les points du polygone comme auparavant, nous avons maintenant besoin des côtés réels. Un côté est toujours défini par deux points.

side 1: (X1/Y1)-(X2/Y2)
side 2: (X2/Y2)-(X3/Y3)
side 3: (X3/Y3)-(X4/Y4)
:

Vous devez tester le rayon contre tous les côtés. Considérez le rayon comme un vecteur et chaque côté comme un vecteur. Le rayon doit frapper chaque côté exactement une fois ou jamais du tout. Il ne peut pas toucher le même côté deux fois. Deux lignes dans l'espace 2D se coupent toujours exactement une fois, sauf si elles sont parallèles, auquel cas elles ne se coupent jamais. Cependant, comme les vecteurs ont une longueur limitée, deux vecteurs peuvent ne pas être parallèles et ne jamais se croiser car ils sont trop courts pour se rencontrer.

// Test the ray against all sides
int intersections = 0;
for (side = 0; side < numberOfSides; side++) {
    // Test if current side intersects with ray.
    // If yes, intersections++;
}
if ((intersections & 1) == 1) {
    // Inside of polygon
} else {
    // Outside of polygon
}

Jusqu'ici tout va bien, mais comment testez-vous si deux vecteurs se croisent? Voici du code C (non testé), qui devrait faire l'affaire:

#define NO 0
#define YES 1
#define COLLINEAR 2

int areIntersecting(
    float v1x1, float v1y1, float v1x2, float v1y2,
    float v2x1, float v2y1, float v2x2, float v2y2
) {
    float d1, d2;
    float a1, a2, b1, b2, c1, c2;

    // Convert vector 1 to a line (line 1) of infinite length.
    // We want the line in linear equation standard form: A*x + B*y + C = 0
    // See: http://en.wikipedia.org/wiki/Linear_equation
    a1 = v1y2 - v1y1;
    b1 = v1x1 - v1x2;
    c1 = (v1x2 * v1y1) - (v1x1 * v1y2);

    // Every point (x,y), that solves the equation above, is on the line,
    // every point that does not solve it, is not. The equation will have a
    // positive result if it is on one side of the line and a negative one 
    // if is on the other side of it. We insert (x1,y1) and (x2,y2) of vector
    // 2 into the equation above.
    d1 = (a1 * v2x1) + (b1 * v2y1) + c1;
    d2 = (a1 * v2x2) + (b1 * v2y2) + c1;

    // If d1 and d2 both have the same sign, they are both on the same side
    // of our line 1 and in that case no intersection is possible. Careful, 
    // 0 is a special case, that's why we don't test ">=" and "<=", 
    // but "<" and ">".
    if (d1 > 0 && d2 > 0) return NO;
    if (d1 < 0 && d2 < 0) return NO;

    // The fact that vector 2 intersected the infinite line 1 above doesn't 
    // mean it also intersects the vector 1. Vector 1 is only a subset of that
    // infinite line 1, so it may have intersected that line before the vector
    // started or after it ended. To know for sure, we have to repeat the
    // the same test the other way round. We start by calculating the 
    // infinite line 2 in linear equation standard form.
    a2 = v2y2 - v2y1;
    b2 = v2x1 - v2x2;
    c2 = (v2x2 * v2y1) - (v2x1 * v2y2);

    // Calculate d1 and d2 again, this time using points of vector 1.
    d1 = (a2 * v1x1) + (b2 * v1y1) + c2;
    d2 = (a2 * v1x2) + (b2 * v1y2) + c2;

    // Again, if both have the same sign (and neither one is 0),
    // no intersection is possible.
    if (d1 > 0 && d2 > 0) return NO;
    if (d1 < 0 && d2 < 0) return NO;

    // If we get here, only two possibilities are left. Either the two
    // vectors intersect in exactly one point or they are collinear, which
    // means they intersect in any number of points from zero to infinite.
    if ((a1 * b2) - (a2 * b1) == 0.0f) return COLLINEAR;

    // If they are not collinear, they must intersect in exactly one point.
    return YES;
}

Les valeurs d'entrée sont les deux extrémités du vecteur 1 ( v1x1/v1y1et v1x2/v1y2) et du vecteur 2 ( v2x1/v2y1et v2x2/v2y2). Vous avez donc 2 vecteurs, 4 points, 8 coordonnées. YESet NOsont clairs. YESaugmente les intersections, NOne fait rien.

Et COLLINEAR? Cela signifie que les deux vecteurs se trouvent sur la même ligne infinie, selon la position et la longueur, ils ne se coupent pas du tout ou ils se coupent en un nombre infini de points. Je ne sais pas trop comment gérer ce cas, je ne le considérerais pas comme une intersection de toute façon. Eh bien, ce cas est assez rare dans la pratique de toute façon en raison d'erreurs d'arrondi à virgule flottante; un meilleur code ne testerait probablement pas, == 0.0fmais plutôt quelque chose comme < epsilon, où epsilon est un nombre plutôt petit.

Si vous devez tester un plus grand nombre de points, vous pouvez certainement accélérer un peu le tout en gardant en mémoire les formes standard d'équation linéaire des côtés du polygone, vous n'avez donc pas à les recalculer à chaque fois. Cela vous fera économiser deux multiplications en virgule flottante et trois soustractions en virgule flottante à chaque test en échange du stockage en mémoire de trois valeurs en virgule flottante par côté de polygone. C'est un compromis typique entre mémoire et temps de calcul.

Dernier point mais non le moindre: si vous pouvez utiliser du matériel 3D pour résoudre le problème, il existe une alternative intéressante. Laissez le GPU faire tout le travail pour vous. Créez une surface de peinture hors écran. Remplissez-le complètement avec la couleur noire. Maintenant, laissez OpenGL ou Direct3D peindre votre polygone (ou même tous vos polygones si vous voulez simplement tester si le point se trouve dans l'un d'eux, mais vous ne vous souciez pas de savoir lequel) et remplissez le ou les polygones avec un autre couleur, par exemple blanc. Pour vérifier si un point se trouve dans le polygone, obtenez la couleur de ce point à partir de la surface de dessin. Ceci est juste une extraction de mémoire O (1).

Bien sûr, cette méthode n'est utilisable que si votre surface de dessin n'a pas besoin d'être énorme. Si elle ne peut pas entrer dans la mémoire du GPU, cette méthode est plus lente que de le faire sur le CPU. Si cela devait être énorme et que votre GPU prend en charge les shaders modernes, vous pouvez toujours utiliser le GPU en implémentant le lancer de rayons illustré ci-dessus en tant que shader GPU, ce qui est absolument possible. Pour un plus grand nombre de polygones ou un grand nombre de points à tester, cela sera payant, sachez que certains GPU pourront tester 64 à 256 points en parallèle. Notez cependant que le transfert de données du CPU vers le GPU et vice-versa est toujours coûteux, donc pour simplement tester quelques points contre quelques polygones simples, où les points ou les polygones sont dynamiques et changent fréquemment, une approche GPU paiera rarement de.

Mecki
la source
26
+1 Réponse fantastique. En utilisant le matériel pour le faire, j'ai lu dans d'autres endroits que cela peut être lent car vous devez récupérer les données de la carte graphique. Mais j'aime beaucoup le principe de retirer la charge du processeur. Quelqu'un at-il de bonnes références sur la façon dont cela pourrait être fait dans OpenGL?
Gavin
3
+1 parce que c'est si simple! Le problème principal est que si votre polygone et votre point de test s'alignent sur une grille (ce qui n'est pas rare), alors vous devez faire face à des intersections "en double", par exemple, directement à travers un point de polygone! (donnant une parité de deux au lieu d'un). Obtient dans cette zone étrange: stackoverflow.com/questions/2255842/… . L'infographie est pleine de ces cas particuliers: simple en théorie, velu en pratique.
Jared Updike
7
@RMorrisey: Pourquoi pensez-vous que oui? Je ne vois pas comment cela échouerait pour un polygone concave. Le rayon peut quitter et rentrer dans le polygone plusieurs fois lorsque le polygone est concave, mais à la fin, le compteur de coups sera impair si le point est à l'intérieur et même s'il est à l'extérieur, également pour les polygones concaves.
Mecki
6
Le «Fast Winding Number Algorithm», décrit sur softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm fonctionne assez rapidement ...
SP
10
Votre utilisation de / pour séparer les coordonnées x et y est déroutante, elle se lit comme x divisé par y. Il est beaucoup plus clair d'utiliser x, y (c'est-à-dire x virgule y) Dans l'ensemble, une réponse utile.
Ash
583

Je pense que le code suivant est la meilleure solution (prise à partir d' ici ):

int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
{
  int i, j, c = 0;
  for (i = 0, j = nvert-1; i < nvert; j = i++) {
    if ( ((verty[i]>testy) != (verty[j]>testy)) &&
     (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
       c = !c;
  }
  return c;
}

Arguments

  • nvert : nombre de sommets du polygone. La question de savoir s'il faut répéter le premier sommet à la fin a été discutée dans l'article mentionné ci-dessus.
  • vertx, verty : tableaux contenant les coordonnées x et y des sommets du polygone.
  • testx, testy : coordonnées X et y du point de test.

Il est à la fois court et efficace et fonctionne à la fois pour les polygones convexes et concaves. Comme suggéré précédemment, vous devez d'abord vérifier le rectangle englobant et traiter séparément les trous de polygone.

L'idée derrière cela est assez simple. L'auteur le décrit comme suit:

Je lance un rayon semi-infini horizontalement (en augmentant x, en fixant y) à partir du point de test, et je compte le nombre d'arêtes qu'il traverse. À chaque croisement, le rayon bascule entre l'intérieur et l'extérieur. C'est ce qu'on appelle le théorème de la courbe de Jordan.

La variable c passe de 0 à 1 et de 1 à 0 chaque fois que le rayon horizontal traverse un bord. Donc, fondamentalement, il vérifie si le nombre d'arêtes traversées est pair ou impair. 0 signifie pair et 1 signifie impair.

nirg
la source
5
Question. Quelles sont exactement les variables que je lui transmets? Que représentent-ils?
tekknolagi
9
@Mick Il vérifie cela verty[i]et verty[j]est de chaque côté de testysorte qu'ils ne sont jamais égaux.
Peter Wood
4
Ce code n'est pas robuste et je ne recommanderais pas de l'utiliser. Voici un lien donnant une analyse détaillée: www-ma2.upc.es/geoc/Schirra-pointPolygon.pdf
Mikola
13
Cette approche a en fait des limites (cas limites): la vérification du point (15,20) dans le polygone [(10,10), (10,20), (20,20), (20,10)] renverra faux au lieu de vrai. Idem avec (10,20) ou (20,15). Pour tous les autres cas, l'algorithme fonctionne parfaitement bien et les faux négatifs dans les cas de bord conviennent à mon application.
Alexander Pacha
10
@Alexander, c'est en fait par conception: en gérant les limites gauche et inférieure dans le sens opposé aux limites supérieure et droite, si deux polygones distincts partagent une arête, tout point le long de cette arête sera situé dans un et un seul polygone. ..une propriété utile.
Wardw
69

Voici une version C # de la réponse donnée par nirg , qui vient de ce professeur RPI . Notez que l'utilisation du code de cette source RPI nécessite une attribution.

Une case à cocher englobante a été ajoutée en haut. Cependant, comme le souligne James Brown, le code principal est presque aussi rapide que la case à cocher elle-même, de sorte que la case à cocher peut en fait ralentir l'opération globale, dans le cas où la plupart des points que vous vérifiez se trouvent à l'intérieur de la zone de délimitation . Vous pouvez donc laisser la case de délimitation cochée, ou une alternative serait de précalculer les boîtes de délimitation de vos polygones s'ils ne changent pas trop souvent de forme.

public bool IsPointInPolygon( Point p, Point[] polygon )
{
    double minX = polygon[ 0 ].X;
    double maxX = polygon[ 0 ].X;
    double minY = polygon[ 0 ].Y;
    double maxY = polygon[ 0 ].Y;
    for ( int i = 1 ; i < polygon.Length ; i++ )
    {
        Point q = polygon[ i ];
        minX = Math.Min( q.X, minX );
        maxX = Math.Max( q.X, maxX );
        minY = Math.Min( q.Y, minY );
        maxY = Math.Max( q.Y, maxY );
    }

    if ( p.X < minX || p.X > maxX || p.Y < minY || p.Y > maxY )
    {
        return false;
    }

    // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
    bool inside = false;
    for ( int i = 0, j = polygon.Length - 1 ; i < polygon.Length ; j = i++ )
    {
        if ( ( polygon[ i ].Y > p.Y ) != ( polygon[ j ].Y > p.Y ) &&
             p.X < ( polygon[ j ].X - polygon[ i ].X ) * ( p.Y - polygon[ i ].Y ) / ( polygon[ j ].Y - polygon[ i ].Y ) + polygon[ i ].X )
        {
            inside = !inside;
        }
    }

    return inside;
}
M Katz
la source
5
Fonctionne très bien, merci, j'ai converti en JavaScript. stackoverflow.com/questions/217578/…
Philipp Lenssen
2
C'est> 1000x plus rapide que d'utiliser GraphicsPath.IsVisible !! La case à cocher de délimitation rend la fonction environ 70% plus lente.
James Brown
Non seulement GraphicsPath.IsVisible () est beaucoup plus lent, mais cela ne fonctionne pas non plus avec de très petits polygones dont le côté se situe dans la plage de 0,01f
Patrick de l'équipe NDepend,
50

Voici une variante JavaScript de la réponse de M. Katz basée sur l'approche de Nirg:

function pointIsInPoly(p, polygon) {
    var isInside = false;
    var minX = polygon[0].x, maxX = polygon[0].x;
    var minY = polygon[0].y, maxY = polygon[0].y;
    for (var n = 1; n < polygon.length; n++) {
        var q = polygon[n];
        minX = Math.min(q.x, minX);
        maxX = Math.max(q.x, maxX);
        minY = Math.min(q.y, minY);
        maxY = Math.max(q.y, maxY);
    }

    if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY) {
        return false;
    }

    var i = 0, j = polygon.length - 1;
    for (i, j; i < polygon.length; j = i++) {
        if ( (polygon[i].y > p.y) != (polygon[j].y > p.y) &&
                p.x < (polygon[j].x - polygon[i].x) * (p.y - polygon[i].y) / (polygon[j].y - polygon[i].y) + polygon[i].x ) {
            isInside = !isInside;
        }
    }

    return isInside;
}
Philipp Lenssen
la source
32

Calculez la somme orientée des angles entre le point p et chacun des sommets du polygone. Si l'angle orienté total est de 360 ​​degrés, le point est à l'intérieur. Si le total est 0, le point est à l'extérieur.

J'aime mieux cette méthode car elle est plus robuste et moins dépendante de la précision numérique.

Les méthodes qui calculent la régularité du nombre d'intersections sont limitées car vous pouvez «frapper» un sommet pendant le calcul du nombre d'intersections.

EDIT: By The Way, cette méthode fonctionne avec des polygones concaves et convexes.

EDIT: J'ai récemment trouvé un article complet de Wikipedia sur le sujet.

David Segonds
la source
1
Non, ce n'est pas vrai. Cela fonctionne quelle que soit la convexité du polygone.
David Segonds,
2
@DarenW: Un seul acos par sommet; d'autre part, cet algorithme devrait être le plus facile à implémenter dans SIMD car il n'a absolument aucune ramification.
Jasper Bekkers
1
@emilio, si le point est loin du triangle, je ne vois pas comment l'angle formé par le point et les deux sommets du triangle sera de 90 degrés.
David Segonds
2
Utilisez d'abord la case à cocher pour résoudre les cas «le point est loin». Pour trig, vous pouvez utiliser des tables prégénérées.
JOM
3
C'est la solution optimale, car elle est O (n), et nécessite un calcul minimal. Fonctionne pour tous les polygones. J'ai recherché cette solution il y a 30 ans lors de mon premier emploi chez IBM. Ils l'ont signé et l'utilisent encore aujourd'hui dans leurs technologies SIG.
Dominic Cerisano
24

Cette question est tellement intéressante. J'ai une autre idée réalisable différente des autres réponses à ce post. L'idée est d'utiliser la somme des angles pour décider si la cible est à l'intérieur ou à l'extérieur. Mieux connu sous le nom de numéro d'enroulement .

Soit x le point cible. Soit array [0, 1, .... n] les tous les points de la zone. Connectez le point cible à chaque point frontière avec une ligne. Si le point cible se trouve à l'intérieur de cette zone. La somme de tous les angles sera de 360 ​​degrés. Sinon, les angles seront inférieurs à 360.

Référez-vous à cette image pour avoir une compréhension de base de l'idée: entrez la description de l'image ici

Mon algorithme suppose que le sens horaire est le sens positif. Voici une entrée potentielle:

[[-122.402015, 48.225216], [-117.032049, 48.999931], [-116.919132, 45.995175], [-124.079107, 46.267259], [-124.717175, 48.377557], [-122.92315, 47.047963], [-122.402015, 48.225216]]

Voici le code python qui implémente l'idée:

def isInside(self, border, target):
degree = 0
for i in range(len(border) - 1):
    a = border[i]
    b = border[i + 1]

    # calculate distance of vector
    A = getDistance(a[0], a[1], b[0], b[1]);
    B = getDistance(target[0], target[1], a[0], a[1])
    C = getDistance(target[0], target[1], b[0], b[1])

    # calculate direction of vector
    ta_x = a[0] - target[0]
    ta_y = a[1] - target[1]
    tb_x = b[0] - target[0]
    tb_y = b[1] - target[1]

    cross = tb_y * ta_x - tb_x * ta_y
    clockwise = cross < 0

    # calculate sum of angles
    if(clockwise):
        degree = degree + math.degrees(math.acos((B * B + C * C - A * A) / (2.0 * B * C)))
    else:
        degree = degree - math.degrees(math.acos((B * B + C * C - A * A) / (2.0 * B * C)))

if(abs(round(degree) - 360) <= 3):
    return True
return False
Junbang Huang
la source
21

L' article d'Eric Haines cité par bobobobo est vraiment excellent. Les tableaux comparant les performances des algorithmes sont particulièrement intéressants; la méthode de sommation d'angle est vraiment mauvaise par rapport aux autres. Il est également intéressant de noter que les optimisations telles que l'utilisation d'une grille de recherche pour subdiviser davantage le polygone en secteurs "entrant" et "sortant" peuvent rendre le test incroyablement rapide même sur des polygones avec> 1000 côtés.

Quoi qu'il en soit, ce n'est que le début, mais mon vote va à la méthode des «croisements», qui est à peu près ce que Mecki décrit, je pense. Cependant, je l'ai trouvé le plus succinctement décrit et codifié par David Bourke . J'aime qu'il n'y ait pas de véritable trigonométrie requise, et cela fonctionne pour les convexes et les concaves, et il fonctionne assez bien lorsque le nombre de côtés augmente.

Soit dit en passant, voici l'une des tables de performances de l'article d'Eric Haines pour l'intérêt, les tests sur les polygones aléatoires.

                       number of edges per polygon
                         3       4      10      100    1000
MacMartin               2.9     3.2     5.9     50.6    485
Crossings               3.1     3.4     6.8     60.0    624
Triangle Fan+edge sort  1.1     1.8     6.5     77.6    787
Triangle Fan            1.2     2.1     7.3     85.4    865
Barycentric             2.1     3.8    13.8    160.7   1665
Angle Summation        56.2    70.4   153.6   1403.8  14693

Grid (100x100)          1.5     1.5     1.6      2.1      9.8
Grid (20x20)            1.7     1.7     1.9      5.7     42.2
Bins (100)              1.8     1.9     2.7     15.1    117
Bins (20)               2.1     2.2     3.7     26.3    278
Gavin
la source
11

Version rapide de la réponse de nirg :

extension CGPoint {
    func isInsidePolygon(vertices: [CGPoint]) -> Bool {
        guard !vertices.isEmpty else { return false }
        var j = vertices.last!, c = false
        for i in vertices {
            let a = (i.y > y) != (j.y > y)
            let b = (x < (j.x - i.x) * (y - i.y) / (j.y - i.y) + i.x)
            if a && b { c = !c }
            j = i
        }
        return c
    }
}
bzz
la source
Cela a un potentiel de division par zéro dans le calcul de b. Il suffit de calculer "b" et la ligne suivante avec "c" si le calcul pour "a" montre qu'il existe une possibilité de recoupement. Aucune possibilité si les deux points sont au-dessus ou les deux points en dessous - ce qui est décrit par le calcul de "a".
anorskdev
11

Vraiment comme la solution publiée par Nirg et éditée par bobobobo. Je viens de le rendre compatible javascript et un peu plus lisible pour mon utilisation:

function insidePoly(poly, pointx, pointy) {
    var i, j;
    var inside = false;
    for (i = 0, j = poly.length - 1; i < poly.length; j = i++) {
        if(((poly[i].y > pointy) != (poly[j].y > pointy)) && (pointx < (poly[j].x-poly[i].x) * (pointy-poly[i].y) / (poly[j].y-poly[i].y) + poly[i].x) ) inside = !inside;
    }
    return inside;
}
Dave Seidman
la source
8

J'ai travaillé sur ce sujet lorsque j'étais chercheur sous Michael Stonebraker - vous savez, le professeur qui a proposé Ingres , PostgreSQL , etc.

Nous avons réalisé que le moyen le plus rapide était de faire d'abord une boîte englobante parce que c'est SUPER rapide. Si c'est en dehors de la zone de délimitation, c'est à l'extérieur. Sinon, vous faites le travail le plus difficile ...

Si vous voulez un excellent algorithme, regardez le code source du projet open source PostgreSQL pour le travail de géo ...

Je tiens à souligner que nous n'avons jamais eu un aperçu de la droitier vs gaucher (également exprimable comme un problème "à l'intérieur" vs "à l'extérieur" ...


MISE À JOUR

Le lien de BKB a fourni un bon nombre d'algorithmes raisonnables. Je travaillais sur des problèmes liés aux sciences de la Terre et j'avais donc besoin d'une solution qui fonctionne en latitude / longitude, et elle a le problème particulier de la neutralité - la zone est-elle à l'intérieur de la zone la plus petite ou la plus grande? La réponse est que la "direction" des sommets est importante - elle est soit pour gaucher soit pour droitier et de cette façon, vous pouvez indiquer l'une ou l'autre zone comme "à l'intérieur" d'un polygone donné. En tant que tel, mon travail a utilisé la solution trois énumérée sur cette page.

De plus, mon travail a utilisé des fonctions distinctes pour les tests "en ligne".

... Depuis que quelqu'un a demandé: nous avons compris que les tests de boîte englobante étaient meilleurs lorsque le nombre de sommets dépassait un certain nombre - faites un test très rapide avant de faire le test plus long si nécessaire ... Une boîte englobante est créée en prenant simplement le le plus grand x, le plus petit x, le plus grand y et le plus petit y et les assembler pour faire quatre points d'une boîte ...

Une autre astuce pour ceux qui suivent: nous avons fait tous nos calculs plus sophistiqués et "atténuant la lumière" dans un espace de grille tous en points positifs sur un plan, puis re-projeté en longitude / latitude "réelle", évitant ainsi d'éventuelles erreurs de s'enrouler quand on franchit la ligne 180 de longitude et lors de la manipulation des régions polaires. Fonctionne très bien!

Richard T
la source
Et si je n'ai pas la boîte englobante? :)
Scott Evernden
8
Vous pouvez facilement créer un cadre de sélection - ce ne sont que les quatre points qui utilisent le plus grand et le moins x et le plus grand et le moins y. Plus à venir.
Richard T
"... en évitant d'éventuelles erreurs d'habillage lorsque l'on franchit la ligne 180 de longitude et lors de la manipulation des régions polaires." pouvez-vous peut-être décrire cela plus en détail? Je pense que je peux comprendre comment déplacer tout `` vers le haut '' pour éviter que mon polygone ne traverse la longitude 0, mais je ne sais pas comment gérer les polygones qui contiennent l'un des pôles ...
tiritea
6

La réponse de David Segond est à peu près la réponse générale standard, et celle de Richard T est l'optimisation la plus courante, bien qu'il en existe d'autres. D'autres optimisations fortes reposent sur des solutions moins générales. Par exemple, si vous allez vérifier le même polygone avec beaucoup de points, la triangulation du polygone peut accélérer considérablement les choses car il existe un certain nombre d'algorithmes de recherche de TIN très rapides. Un autre est que si le polygone et les points sont sur un plan limité à basse résolution, par exemple un affichage à l'écran, vous pouvez peindre le polygone sur un tampon d'affichage mappé en mémoire dans une couleur donnée et vérifier la couleur d'un pixel donné pour voir s'il se trouve dans les polygones.

Comme de nombreuses optimisations, celles-ci sont basées sur des cas spécifiques plutôt que généraux et donnent des avantages basés sur le temps amorti plutôt que sur une seule utilisation.

Travaillant dans ce domaine, j'ai trouvé que la géométrie de calcul de Joeseph O'Rourkes en C 'ISBN 0-521-44034-3 était d'une grande aide.

SmacL
la source
4

La solution triviale serait de diviser le polygone en triangles et de tester les triangles comme expliqué ici

Si votre polygone est CONVEX, il pourrait y avoir une meilleure approche. Regardez le polygone comme une collection de lignes infinies. Chaque ligne divisant l'espace en deux. pour chaque point, il est facile de dire si c'est d'un côté ou de l'autre de la ligne. Si un point se trouve du même côté de toutes les lignes, il se trouve à l'intérieur du polygone.

shoosh
la source
très rapide, et peut être appliqué à des formes plus générales. vers 1990, nous avions des "curvigons" où certains côtés étaient des arcs de cercle. En analysant ces côtés en coins circulaires et une paire de triangles joignant le coin à l'origine (polygone centroïde), le test de frappe a été rapide et facile.
DarenW
1
obtenu des références sur ces curvigons?
shoosh
J'aimerais aussi une référence pour les curvigons.
Joel à Gö
Vous avez raté une solution importante pour le cas des polygones convexes: en comparant le point à une diagonale, vous pouvez diviser par deux le nombre de sommets. Et en répétant ce processus, vous réduisez à un seul triangle dans les opérations Log (N) plutôt que N.
Yves Daoust
4

Je me rends compte que c'est vieux, mais voici un algorithme de lancer de rayons implémenté dans Cocoa, au cas où quelqu'un serait intéressé. Je ne sais pas si c'est la façon la plus efficace de faire les choses, mais cela peut aider quelqu'un.

- (BOOL)shape:(NSBezierPath *)path containsPoint:(NSPoint)point
{
    NSBezierPath *currentPath = [path bezierPathByFlatteningPath];
    BOOL result;
    float aggregateX = 0; //I use these to calculate the centroid of the shape
    float aggregateY = 0;
    NSPoint firstPoint[1];
    [currentPath elementAtIndex:0 associatedPoints:firstPoint];
    float olderX = firstPoint[0].x;
    float olderY = firstPoint[0].y;
    NSPoint interPoint;
    int noOfIntersections = 0;

    for (int n = 0; n < [currentPath elementCount]; n++) {
        NSPoint points[1];
        [currentPath elementAtIndex:n associatedPoints:points];
        aggregateX += points[0].x;
        aggregateY += points[0].y;
    }

    for (int n = 0; n < [currentPath elementCount]; n++) {
        NSPoint points[1];

        [currentPath elementAtIndex:n associatedPoints:points];
        //line equations in Ax + By = C form
        float _A_FOO = (aggregateY/[currentPath elementCount]) - point.y;  
        float _B_FOO = point.x - (aggregateX/[currentPath elementCount]);
        float _C_FOO = (_A_FOO * point.x) + (_B_FOO * point.y);

        float _A_BAR = olderY - points[0].y;
        float _B_BAR = points[0].x - olderX;
        float _C_BAR = (_A_BAR * olderX) + (_B_BAR * olderY);

        float det = (_A_FOO * _B_BAR) - (_A_BAR * _B_FOO);
        if (det != 0) {
            //intersection points with the edges
            float xIntersectionPoint = ((_B_BAR * _C_FOO) - (_B_FOO * _C_BAR)) / det;
            float yIntersectionPoint = ((_A_FOO * _C_BAR) - (_A_BAR * _C_FOO)) / det;
            interPoint = NSMakePoint(xIntersectionPoint, yIntersectionPoint);
            if (olderX <= points[0].x) {
                //doesn't matter in which direction the ray goes, so I send it right-ward.
                if ((interPoint.x >= olderX && interPoint.x <= points[0].x) && (interPoint.x > point.x)) {  
                    noOfIntersections++;
                }
            } else {
                if ((interPoint.x >= points[0].x && interPoint.x <= olderX) && (interPoint.x > point.x)) {
                     noOfIntersections++;
                } 
            }
        }
        olderX = points[0].x;
        olderY = points[0].y;
    }
    if (noOfIntersections % 2 == 0) {
        result = FALSE;
    } else {
        result = TRUE;
    }
    return result;
}
diatrevolo
la source
5
Notez que si vous le faites vraiment dans Cocoa, vous pouvez utiliser la méthode [NSBezierPath containsPoint:].
ThomasW
4

Version Obj-C de la réponse de nirg avec exemple de méthode pour tester les points. La réponse de Nirg a bien fonctionné pour moi.

- (BOOL)isPointInPolygon:(NSArray *)vertices point:(CGPoint)test {
    NSUInteger nvert = [vertices count];
    NSInteger i, j, c = 0;
    CGPoint verti, vertj;

    for (i = 0, j = nvert-1; i < nvert; j = i++) {
        verti = [(NSValue *)[vertices objectAtIndex:i] CGPointValue];
        vertj = [(NSValue *)[vertices objectAtIndex:j] CGPointValue];
        if (( (verti.y > test.y) != (vertj.y > test.y) ) &&
        ( test.x < ( vertj.x - verti.x ) * ( test.y - verti.y ) / ( vertj.y - verti.y ) + verti.x) )
            c = !c;
    }

    return (c ? YES : NO);
}

- (void)testPoint {

    NSArray *polygonVertices = [NSArray arrayWithObjects:
        [NSValue valueWithCGPoint:CGPointMake(13.5, 41.5)],
        [NSValue valueWithCGPoint:CGPointMake(42.5, 56.5)],
        [NSValue valueWithCGPoint:CGPointMake(39.5, 69.5)],
        [NSValue valueWithCGPoint:CGPointMake(42.5, 84.5)],
        [NSValue valueWithCGPoint:CGPointMake(13.5, 100.0)],
        [NSValue valueWithCGPoint:CGPointMake(6.0, 70.5)],
        nil
    ];

    CGPoint tappedPoint = CGPointMake(23.0, 70.0);

    if ([self isPointInPolygon:polygonVertices point:tappedPoint]) {
        NSLog(@"YES");
    } else {
        NSLog(@"NO");
    }
}

exemple de polygone

Jon
la source
2
Bien sûr, dans Objective-C, CGPathContainsPoint()c'est votre ami.
Zev Eisenberg
@ZevEisenberg mais ce serait trop facile! Merci pour la note. Je déterrerai ce projet à un moment donné pour voir pourquoi j'ai utilisé une solution personnalisée. Je ne savais probablement pasCGPathContainsPoint()
Jon
4

Il n'y a rien de plus beau qu'une définition inductive d'un problème. Par souci d'exhaustivité ici, vous avez une version en prologue qui pourrait également clarifier les pensées derrière le casting de rayons :

Basé sur la simulation de l'algorithme de simplicité dans http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

Certains prédicats d'aide:

exor(A,B):- \+A,B;A,\+B.
in_range(Coordinate,CA,CB) :- exor((CA>Coordinate),(CB>Coordinate)).

inside(false).
inside(_,[_|[]]).
inside(X:Y, [X1:Y1,X2:Y2|R]) :- in_range(Y,Y1,Y2), X > ( ((X2-X1)*(Y-Y1))/(Y2-Y1) +      X1),toggle_ray, inside(X:Y, [X2:Y2|R]); inside(X:Y, [X2:Y2|R]).

get_line(_,_,[]).
get_line([XA:YA,XB:YB],[X1:Y1,X2:Y2|R]):- [XA:YA,XB:YB]=[X1:Y1,X2:Y2]; get_line([XA:YA,XB:YB],[X2:Y2|R]).

L'équation d'une ligne donnée 2 points A et B (ligne (A, B)) est:

                    (YB-YA)
           Y - YA = ------- * (X - XA) 
                    (XB-YB) 

Il est important que le sens de rotation de la ligne soit réglé dans le sens horaire pour les limites et dans le sens antihoraire pour les trous. Nous allons vérifier si le point (X, Y), c'est-à-dire le point testé est au demi-plan gauche de notre droite (c'est une question de goût, ça pourrait aussi être le côté droit, mais aussi la direction des frontières les lignes doivent être changées dans ce cas), c'est de projeter le rayon du point vers la droite (ou la gauche) et de reconnaître l'intersection avec la ligne. Nous avons choisi de projeter le rayon dans le sens horizontal (encore une fois c'est une question de goût, cela pourrait aussi se faire en vertical avec des restrictions similaires), nous avons donc:

               (XB-XA)
           X < ------- * (Y - YA) + XA
               (YB-YA) 

Maintenant, nous devons savoir si le point se trouve uniquement sur le côté gauche (ou droit) du segment de ligne, pas sur tout le plan, nous devons donc restreindre la recherche uniquement à ce segment, mais cela est facile car être à l'intérieur du segment un seul point de la ligne peut être supérieur à Y sur l'axe vertical. Comme il s'agit d'une restriction plus forte, elle doit être la première à vérifier, nous prenons donc uniquement les lignes répondant à cette exigence, puis vérifions sa possession. Selon le théorème de la courbe de Jordan, tout rayon projeté sur un polygone doit se croiser sur un nombre pair de lignes. Donc, nous avons terminé, nous allons lancer le rayon vers la droite, puis chaque fois qu'il coupe une ligne, basculer son état. Cependant, dans notre mise en œuvre, nous allons vérifier la longueur du sachet de solutions répondant aux restrictions données et décider de leur intégration. cela doit être fait pour chaque ligne du polygone.

is_left_half_plane(_,[],[],_).
is_left_half_plane(X:Y,[XA:YA,XB:YB], [[X1:Y1,X2:Y2]|R], Test) :- [XA:YA, XB:YB] =  [X1:Y1, X2:Y2], call(Test, X , (((XB - XA) * (Y - YA)) / (YB - YA) + XA)); 
                                                        is_left_half_plane(X:Y, [XA:YA, XB:YB], R, Test).

in_y_range_at_poly(Y,[XA:YA,XB:YB],Polygon) :- get_line([XA:YA,XB:YB],Polygon),  in_range(Y,YA,YB).
all_in_range(Coordinate,Polygon,Lines) :- aggregate(bag(Line),    in_y_range_at_poly(Coordinate,Line,Polygon), Lines).

traverses_ray(X:Y, Lines, Count) :- aggregate(bag(Line), is_left_half_plane(X:Y, Line, Lines, <), IntersectingLines), length(IntersectingLines, Count).

% This is the entry point predicate
inside_poly(X:Y,Polygon,Answer) :- all_in_range(Y,Polygon,Lines), traverses_ray(X:Y, Lines, Count), (1 is mod(Count,2)->Answer=inside;Answer=outside).
jdavid_1385
la source
3

La version C # de la réponse de nirg est ici: je vais juste partager le code. Cela peut faire gagner du temps à quelqu'un.

public static bool IsPointInPolygon(IList<Point> polygon, Point testPoint) {
            bool result = false;
            int j = polygon.Count() - 1;
            for (int i = 0; i < polygon.Count(); i++) {
                if (polygon[i].Y < testPoint.Y && polygon[j].Y >= testPoint.Y || polygon[j].Y < testPoint.Y && polygon[i].Y >= testPoint.Y) {
                    if (polygon[i].X + (testPoint.Y - polygon[i].Y) / (polygon[j].Y - polygon[i].Y) * (polygon[j].X - polygon[i].X) < testPoint.X) {
                        result = !result;
                    }
                }
                j = i;
            }
            return result;
        }
Uğur Gümüşhan
la source
cela fonctionne dans la plupart des cas mais c'est faux et ne fonctionne pas toujours correctement! utilisez la solution de M Katz qui est correcte
Lukas Hanacek
3

Version Java:

public class Geocode {
    private float latitude;
    private float longitude;

    public Geocode() {
    }

    public Geocode(float latitude, float longitude) {
        this.latitude = latitude;
        this.longitude = longitude;
    }

    public float getLatitude() {
        return latitude;
    }

    public void setLatitude(float latitude) {
        this.latitude = latitude;
    }

    public float getLongitude() {
        return longitude;
    }

    public void setLongitude(float longitude) {
        this.longitude = longitude;
    }
}

public class GeoPolygon {
    private ArrayList<Geocode> points;

    public GeoPolygon() {
        this.points = new ArrayList<Geocode>();
    }

    public GeoPolygon(ArrayList<Geocode> points) {
        this.points = points;
    }

    public GeoPolygon add(Geocode geo) {
        points.add(geo);
        return this;
    }

    public boolean inside(Geocode geo) {
        int i, j;
        boolean c = false;
        for (i = 0, j = points.size() - 1; i < points.size(); j = i++) {
            if (((points.get(i).getLongitude() > geo.getLongitude()) != (points.get(j).getLongitude() > geo.getLongitude())) &&
                    (geo.getLatitude() < (points.get(j).getLatitude() - points.get(i).getLatitude()) * (geo.getLongitude() - points.get(i).getLongitude()) / (points.get(j).getLongitude() - points.get(i).getLongitude()) + points.get(i).getLatitude()))
                c = !c;
        }
        return c;
    }

}
YongJiang Zhang
la source
2

Port .Net:

    static void Main(string[] args)
    {

        Console.Write("Hola");
        List<double> vertx = new List<double>();
        List<double> verty = new List<double>();

        int i, j, c = 0;

        vertx.Add(1);
        vertx.Add(2);
        vertx.Add(1);
        vertx.Add(4);
        vertx.Add(4);
        vertx.Add(1);

        verty.Add(1);
        verty.Add(2);
        verty.Add(4);
        verty.Add(4);
        verty.Add(1);
        verty.Add(1);

        int nvert = 6;  //Vértices del poligono

        double testx = 2;
        double testy = 5;


        for (i = 0, j = nvert - 1; i < nvert; j = i++)
        {
            if (((verty[i] > testy) != (verty[j] > testy)) &&
             (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]))
                c = 1;
        }
    }
Aladar
la source
2

VERSION VBA:

Remarque: N'oubliez pas que si votre polygone est une zone d'une carte, la latitude / longitude sont des valeurs Y / X par opposition à X / Y (latitude = Y, longitude = X) en raison de ce que je comprends, ce sont des implications historiques remontant à quand La longitude n'était pas une mesure.

MODULE DE CLASSE: CPoint

Private pXValue As Double
Private pYValue As Double

'''''X Value Property'''''

Public Property Get X() As Double
    X = pXValue
End Property

Public Property Let X(Value As Double)
    pXValue = Value
End Property

'''''Y Value Property'''''

Public Property Get Y() As Double
    Y = pYValue
End Property

Public Property Let Y(Value As Double)
    pYValue = Value
End Property

MODULE:

Public Function isPointInPolygon(p As CPoint, polygon() As CPoint) As Boolean

    Dim i As Integer
    Dim j As Integer
    Dim q As Object
    Dim minX As Double
    Dim maxX As Double
    Dim minY As Double
    Dim maxY As Double
    minX = polygon(0).X
    maxX = polygon(0).X
    minY = polygon(0).Y
    maxY = polygon(0).Y

    For i = 1 To UBound(polygon)
        Set q = polygon(i)
        minX = vbMin(q.X, minX)
        maxX = vbMax(q.X, maxX)
        minY = vbMin(q.Y, minY)
        maxY = vbMax(q.Y, maxY)
    Next i

    If p.X < minX Or p.X > maxX Or p.Y < minY Or p.Y > maxY Then
        isPointInPolygon = False
        Exit Function
    End If


    ' SOURCE: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

    isPointInPolygon = False
    i = 0
    j = UBound(polygon)

    Do While i < UBound(polygon) + 1
        If (polygon(i).Y > p.Y) Then
            If (polygon(j).Y < p.Y) Then
                If p.X < (polygon(j).X - polygon(i).X) * (p.Y - polygon(i).Y) / (polygon(j).Y - polygon(i).Y) + polygon(i).X Then
                    isPointInPolygon = True
                    Exit Function
                End If
            End If
        ElseIf (polygon(i).Y < p.Y) Then
            If (polygon(j).Y > p.Y) Then
                If p.X < (polygon(j).X - polygon(i).X) * (p.Y - polygon(i).Y) / (polygon(j).Y - polygon(i).Y) + polygon(i).X Then
                    isPointInPolygon = True
                    Exit Function
                End If
            End If
        End If
        j = i
        i = i + 1
    Loop   
End Function

Function vbMax(n1, n2) As Double
    vbMax = IIf(n1 > n2, n1, n2)
End Function

Function vbMin(n1, n2) As Double
    vbMin = IIf(n1 > n2, n2, n1)
End Function


Sub TestPointInPolygon()

    Dim i As Integer
    Dim InPolygon As Boolean

'   MARKER Object
    Dim p As CPoint
    Set p = New CPoint
    p.X = <ENTER X VALUE HERE>
    p.Y = <ENTER Y VALUE HERE>

'   POLYGON OBJECT
    Dim polygon() As CPoint
    ReDim polygon(<ENTER VALUE HERE>) 'Amount of vertices in polygon - 1
    For i = 0 To <ENTER VALUE HERE> 'Same value as above
       Set polygon(i) = New CPoint
       polygon(i).X = <ASSIGN X VALUE HERE> 'Source a list of values that can be looped through
       polgyon(i).Y = <ASSIGN Y VALUE HERE> 'Source a list of values that can be looped through
    Next i

    InPolygon = isPointInPolygon(p, polygon)
    MsgBox InPolygon

End Sub
Colin Stadig
la source
2

Je l' ai fait une implémentation Python de de nirg c ++ Code :

Contributions

  • bounding_points: nœuds qui composent le polygone.
  • bounding_box_positions: points candidats à filtrer. (Dans mon implémentation créée à partir de la boîte englobante.

    (Les entrées sont des listes de tuples au format: [(xcord, ycord), ...])

Retour

  • Tous les points qui se trouvent à l'intérieur du polygone.
def polygon_ray_casting(self, bounding_points, bounding_box_positions):
    # Arrays containing the x- and y-coordinates of the polygon's vertices.
    vertx = [point[0] for point in bounding_points]
    verty = [point[1] for point in bounding_points]
    # Number of vertices in the polygon
    nvert = len(bounding_points)
    # Points that are inside
    points_inside = []

    # For every candidate position within the bounding box
    for idx, pos in enumerate(bounding_box_positions):
        testx, testy = (pos[0], pos[1])
        c = 0
        for i in range(0, nvert):
            j = i - 1 if i != 0 else nvert - 1
            if( ((verty[i] > testy ) != (verty[j] > testy))   and
                    (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]) ):
                c += 1
        # If odd, that means that we are inside the polygon
        if c % 2 == 1: 
            points_inside.append(pos)


    return points_inside

Encore une fois, l'idée est tirée d' ici

Noresourses
la source
2

Surpris, personne n'a évoqué cela plus tôt, mais pour les pragmatiques qui ont besoin d'une base de données: MongoDB a un excellent support pour les requêtes Geo, y compris celle-ci.

Ce que vous recherchez, c'est:

db.neighborshoods.findOne ({geometry: {$ geoIntersects: {$ geometry: {type: "Point", coordonnées: ["longitude", "latitude"]}}}})

Neighborhoodsest la collection qui stocke un ou plusieurs polygones au format GeoJson standard. Si la requête renvoie null, elle n'est pas intersectée, sinon elle l'est.

Très bien documenté ici: https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/

Les performances de plus de 6 000 points classés dans une grille de polygones irréguliers de 330 étaient inférieures à une minute sans aucune optimisation et incluant le temps de mise à jour des documents avec leur polygone respectif.

Santiago M. Quintero
la source
1

Voici un point dans le test de polygone en C qui n'utilise pas le lancer de rayons. Et cela peut fonctionner pour les zones qui se chevauchent (auto-intersections), voir l' use_holesargument.

/* math lib (defined below) */
static float dot_v2v2(const float a[2], const float b[2]);
static float angle_signed_v2v2(const float v1[2], const float v2[2]);
static void copy_v2_v2(float r[2], const float a[2]);

/* intersection function */
bool isect_point_poly_v2(const float pt[2], const float verts[][2], const unsigned int nr,
                         const bool use_holes)
{
    /* we do the angle rule, define that all added angles should be about zero or (2 * PI) */
    float angletot = 0.0;
    float fp1[2], fp2[2];
    unsigned int i;
    const float *p1, *p2;

    p1 = verts[nr - 1];

    /* first vector */
    fp1[0] = p1[0] - pt[0];
    fp1[1] = p1[1] - pt[1];

    for (i = 0; i < nr; i++) {
        p2 = verts[i];

        /* second vector */
        fp2[0] = p2[0] - pt[0];
        fp2[1] = p2[1] - pt[1];

        /* dot and angle and cross */
        angletot += angle_signed_v2v2(fp1, fp2);

        /* circulate */
        copy_v2_v2(fp1, fp2);
        p1 = p2;
    }

    angletot = fabsf(angletot);
    if (use_holes) {
        const float nested = floorf((angletot / (float)(M_PI * 2.0)) + 0.00001f);
        angletot -= nested * (float)(M_PI * 2.0);
        return (angletot > 4.0f) != ((int)nested % 2);
    }
    else {
        return (angletot > 4.0f);
    }
}

/* math lib */

static float dot_v2v2(const float a[2], const float b[2])
{
    return a[0] * b[0] + a[1] * b[1];
}

static float angle_signed_v2v2(const float v1[2], const float v2[2])
{
    const float perp_dot = (v1[1] * v2[0]) - (v1[0] * v2[1]);
    return atan2f(perp_dot, dot_v2v2(v1, v2));
}

static void copy_v2_v2(float r[2], const float a[2])
{
    r[0] = a[0];
    r[1] = a[1];
}

Remarque: c'est l'une des méthodes les moins optimales car elle inclut beaucoup d'appels à atan2f, mais elle peut intéresser les développeurs qui lisent ce fil (dans mes tests, son ~ 23x est plus lent que la méthode d'intersection de lignes).

ideasman42
la source
0

Pour détecter un hit sur Polygon, nous devons tester deux choses:

  1. Si le point est à l'intérieur de la zone du polygone. (peut être accompli par Ray-Casting Algorithm)
  2. Si Point est sur la bordure du polygone (peut être accompli par le même algorithme qui est utilisé pour la détection de point sur la polyligne (ligne)).
VJ
la source
0

Pour traiter les cas spéciaux suivants dans l' algorithme de lancer de rayons :

  1. Le rayon chevauche l'un des côtés du polygone.
  2. Le point est à l'intérieur du polygone et le rayon passe par un sommet du polygone.
  3. Le point est en dehors du polygone et le rayon touche juste l'un des angles du polygone.

Cochez Déterminer si un point se trouve à l'intérieur d'un polygone complexe . L'article fournit un moyen facile de les résoudre, de sorte qu'aucun traitement spécial ne sera requis pour les cas ci-dessus.

Justin
la source
0

Vous pouvez le faire en vérifiant si la zone formée en connectant le point souhaité aux sommets de votre polygone correspond à la zone du polygone lui-même.

Ou vous pouvez vérifier si la somme des angles intérieurs de votre point à chaque paire de deux sommets de polygone consécutifs à votre point de contrôle est de 360, mais j'ai le sentiment que la première option est plus rapide car elle n'implique pas de divisions ni de calculs de l'inverse des fonctions trigonométriques.

Je ne sais pas ce qui se passe si votre polygone a un trou à l'intérieur mais il me semble que l'idée principale peut être adaptée à cette situation

Vous pouvez également poster la question dans une communauté mathématique. Je parie qu'ils ont un million de façons de le faire

user5193682
la source
0

Si vous recherchez une bibliothèque de scripts java, il existe une extension javascript google maps v3 pour la classe Polygon pour détecter si un point y réside ou non.

var polygon = new google.maps.Polygon([], "#000000", 1, 1, "#336699", 0.3);
var isWithinPolygon = polygon.containsLatLng(40, -90);

Google Extention Github

shana
la source
0

La réponse dépend de si vous avez des polygones simples ou complexes. Les polygones simples ne doivent pas avoir d'intersections de segments de ligne. Ils peuvent donc avoir des trous mais les lignes ne peuvent pas se croiser. Les régions complexes peuvent avoir des intersections de lignes - elles peuvent donc avoir les régions qui se chevauchent, ou des régions qui se touchent juste par un seul point.

Pour les polygones simples, le meilleur algorithme est l'algorithme de lancer de rayons (nombre de croisements). Pour les polygones complexes, cet algorithme ne détecte pas les points qui se trouvent à l'intérieur des régions qui se chevauchent. Donc, pour les polygones complexes, vous devez utiliser l'algorithme de nombre de bobinage.

Voici un excellent article avec l'implémentation C des deux algorithmes. Je les ai essayés et ils fonctionnent bien.

http://geomalgorithms.com/a03-_inclusion.html

Timmy_A
la source
0

Version Scala de la solution par nirg (suppose que la pré-vérification du rectangle englobant est effectuée séparément):

def inside(p: Point, polygon: Array[Point], bounds: Bounds): Boolean = {

  val length = polygon.length

  @tailrec
  def oddIntersections(i: Int, j: Int, tracker: Boolean): Boolean = {
    if (i == length)
      tracker
    else {
      val intersects = (polygon(i).y > p.y) != (polygon(j).y > p.y) && p.x < (polygon(j).x - polygon(i).x) * (p.y - polygon(i).y) / (polygon(j).y - polygon(i).y) + polygon(i).x
      oddIntersections(i + 1, i, if (intersects) !tracker else tracker)
    }
  }

  oddIntersections(0, length - 1, tracker = false)
}
Michael-7
la source
0

Voici la version Golang de la réponse @nirg (inspirée du code C # par @@ m-katz)

func isPointInPolygon(polygon []point, testp point) bool {
    minX := polygon[0].X
    maxX := polygon[0].X
    minY := polygon[0].Y
    maxY := polygon[0].Y

    for _, p := range polygon {
        minX = min(p.X, minX)
        maxX = max(p.X, maxX)
        minY = min(p.Y, minY)
        maxY = max(p.Y, maxY)
    }

    if testp.X < minX || testp.X > maxX || testp.Y < minY || testp.Y > maxY {
        return false
    }

    inside := false
    j := len(polygon) - 1
    for i := 0; i < len(polygon); i++ {
        if (polygon[i].Y > testp.Y) != (polygon[j].Y > testp.Y) && testp.X < (polygon[j].X-polygon[i].X)*(testp.Y-polygon[i].Y)/(polygon[j].Y-polygon[i].Y)+polygon[i].X {
            inside = !inside
        }
        j = i
    }

    return inside
}
SamTech
la source