web-dev-qa-db-fra.com

Point dans l'algorithme polygonal

J'ai vu que l'algorithme ci-dessous fonctionne pour vérifier si un point est dans un polygone donné à partir de ce link :

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

J'ai essayé cet algorithme et il fonctionne parfaitement. Malheureusement, je ne le comprends pas bien après avoir passé du temps à en avoir l’idée. 

Donc, si quelqu'un est capable de comprendre cet algorithme, expliquez-le-moi un peu. 

Je vous remercie.

54
Allan Jiang

L'algorithme est lancé à droite. À chaque itération de la boucle, le point de test est comparé à l'une des arêtes du polygone. La première ligne de l'if-test réussit si la coordonnée y du point se trouve dans la portée de Edge. La deuxième ligne vérifie si le point de test est à gauche de la ligne (je pense - je n'ai pas de papier de brouillon à disposition pour vérifier). Si tel est le cas, la ligne tracée à droite du point de test traverse cet Edge.

En inversant de manière répétée la valeur de c, l'algorithme compte le nombre de fois où la ligne droite traverse le polygone. S'il passe un nombre impair de fois, alors le point est à l'intérieur; si un nombre pair, le point est à l'extérieur.

Je serais préoccupé par a) la précision de l'arithmétique en virgule flottante et b) les effets d'un bord horizontal ou d'un point de test avec la même coordonnée y en tant que sommet.

45
Chowlett

Chowlett est correct dans tous les sens, forme et forme . L'algorithme suppose que si votre point est sur la ligne du polygone, il est alors extérieur - dans certains cas, c'est faux. Changer les deux opérateurs '>' en '> =' et changer '<' en '<=' résoudra ce problème.

bool PointInPolygon(Point point, Polygon polygon) {
  vector<Point> points = polygon.getPoints();
  int i, j, nvert = points.size();
  bool c = false;

  for(i = 0, j = nvert - 1; i < nvert; j = i++) {
    if( ( (points[i].y >= point.y ) != (points[j].y >= point.y) ) &&
        (point.x <= (points[j].x - points[i].x) * (point.y - points[i].y) / (points[j].y - points[i].y) + points[i].x)
      )
      c = !c;
  }

  return c;
}
19
Josh

Cela pourrait être aussi détaillé que cela pourrait l'être pour expliquer l'algorithme de lancer de rayons dans le code réel. Cela n’est peut-être pas optimisé, mais cela doit toujours venir après une parfaite compréhension du système.

    //method to check if a Coordinate is located in a polygon
public boolean checkIsInPolygon(ArrayList<Coordinate> poly){
    //this method uses the ray tracing algorithm to determine if the point is in the polygon
    int nPoints=poly.size();
    int j=-999;
    int i=-999;
    boolean locatedInPolygon=false;
    for(i=0;i<(nPoints);i++){
        //repeat loop for all sets of points
        if(i==(nPoints-1)){
            //if i is the last vertex, let j be the first vertex
            j= 0;
        }else{
            //for all-else, let j=(i+1)th vertex
            j=i+1;
        }

        float vertY_i= (float)poly.get(i).getY();
        float vertX_i= (float)poly.get(i).getX();
        float vertY_j= (float)poly.get(j).getY();
        float vertX_j= (float)poly.get(j).getX();
        float testX  = (float)this.getX();
        float testY  = (float)this.getY();

        // following statement checks if testPoint.Y is below Y-coord of i-th vertex
        boolean belowLowY=vertY_i>testY;
        // following statement checks if testPoint.Y is below Y-coord of i+1-th vertex
        boolean belowHighY=vertY_j>testY;

        /* following statement is true if testPoint.Y satisfies either (only one is possible) 
        -->(i).Y < testPoint.Y < (i+1).Y        OR  
        -->(i).Y > testPoint.Y > (i+1).Y

        (Note)
        Both of the conditions indicate that a point is located within the edges of the Y-th coordinate
        of the (i)-th and the (i+1)- th vertices of the polygon. If neither of the above
        conditions is satisfied, then it is assured that a semi-infinite horizontal line draw 
        to the right from the testpoint will NOT cross the line that connects vertices i and i+1 
        of the polygon
        */
        boolean withinYsEdges= belowLowY != belowHighY;

        if( withinYsEdges){
            // this is the slope of the line that connects vertices i and i+1 of the polygon
            float slopeOfLine   = ( vertX_j-vertX_i )/ (vertY_j-vertY_i) ;

            // this looks up the x-coord of a point lying on the above line, given its y-coord
            float pointOnLine   = ( slopeOfLine* (testY - vertY_i) )+vertX_i;

            //checks to see if x-coord of testPoint is smaller than the point on the line with the same y-coord
            boolean isLeftToLine= testX < pointOnLine;

            if(isLeftToLine){
                //this statement changes true to false (and vice-versa)
                locatedInPolygon= !locatedInPolygon;
            }//end if (isLeftToLine)
        }//end if (withinYsEdges
    }

    return locatedInPolygon;
}

Un seul mot sur l’optimisation: Il n’est pas vrai que le code le plus court (et/ou le plus court) est le plus rapide à être implémenté. Il est beaucoup plus rapide de lire et de stocker un élément d’un tableau et de l’utiliser (éventuellement) plusieurs fois au cours de l’exécution du bloc de code plutôt que d’accéder au tableau chaque fois que cela est nécessaire. Ceci est particulièrement important si la matrice est extrêmement grande. À mon avis, en stockant chaque terme d'un tableau dans une variable bien nommée, il est également plus facile d'évaluer son objectif et de former ainsi un code beaucoup plus lisible. Juste mes deux cents...

6
apil.tamang

L'algorithme est réduit aux éléments les plus nécessaires. Après avoir été développé et testé, tous les éléments inutiles ont été supprimés. En conséquence, vous ne pouvez pas le comprendre facilement, mais cela fait le travail et aussi de très bonnes performances .


J'ai pris la liberté de le traduire en ActionScript-3 :

// not optimized yet (nvert could be left out)
public static function pnpoly(nvert: int, vertx: Array, verty: Array, x: Number, y: Number): Boolean
{
    var i: int, j: int;
    var c: Boolean = false;
    for (i = 0, j = nvert - 1; i < nvert; j = i++)
    {
        if (((verty[i] > y) != (verty[j] > y)) && (x < (vertx[j] - vertx[i]) * (y - verty[i]) / (verty[j] - verty[i]) + vertx[i]))
            c = !c;
    }
    return c;
}
3
Bitterblue

Cet algorithme fonctionne dans n'importe quel polygone fermé tant que les côtés du polygone ne se croisent pas. Triangle, pentagone, carré, voire une bande élastique très incurvée, linéaire par morceaux, qui ne se croise pas. 

1) Définissez votre polygone en tant que groupe dirigé de vecteurs. Cela signifie que chaque côté du polygone est décrit par un vecteur qui va d'un sommet à l'autre et d'un sommet + 1. Les vecteurs sont tellement orientés que la tête de l'un touche la queue du prochain jusqu'à ce que le dernier vecteur touche la queue du premier. 

2) Sélectionnez le point à tester à l'intérieur ou à l'extérieur du polygone. 

3) Pour chaque vecteur Vn le long du périmètre du polygone, le vecteur Dn commence au point de test et se termine à la queue de Vn. Calculez le vecteur Cn défini comme DnXVn/DN * VN (X représente un produit croisé; * indique un produit scalaire). Appelez la magnitude de Cn par le nom Mn. 

4) Ajoutez tout Mn et appelez cette quantité K. 

5) Si K est zéro, le point est en dehors du polygone. 

6) Si K n'est pas nul, le point est à l'intérieur du polygone.

Théoriquement, un point situé sur le bord du polygone produira un résultat non défini.

La signification géométrique de K est l’angle total selon lequel la puce assise sur notre point de test "a vu" la fourmi marchant sur le bord du polygone à gauche, moins l’angle à droite. Dans un circuit fermé, la fourmi se termine là où elle a commencé . En dehors du polygone, quel que soit son emplacement, la réponse est zéro.
À l’intérieur du polygone, quel que soit l’emplacement, la réponse est "une fois autour du point".


3
Mario

Cette méthode vérifie si le rayon du point (testx, testy) à O (0,0) coupe ou non les côtés du polygone.

Il existe une conclusion bien connue ici : si un rayon de 1 point coupe les côtés d'un polygone pendant un temps impair, ce point appartiendra au polygone, sinon ce point sera à l'extérieur du polygone. 

2
Thinhbk

Je pense que l’idée de base est de calculer les vecteurs à partir du point, un par bord du polygone. Si le vecteur traverse un bord, le point se trouve dans le polygone. Par polygones concaves, si elle croise un nombre impair d'arêtes, elle se trouve également à l'intérieur (disclaimer: bien que je ne sois pas sûr que cela fonctionne pour tous les polygones concaves).

1
Anders

J'ai changé le code original pour le rendre un peu plus lisible (également avec Eigen) L'algorithme est identique.

// This uses the ray-casting algorithm to decide whether the point is inside
// the given polygon. See https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm
bool pnpoly(const Eigen::MatrixX2d &poly, float x, float y)
{
    // If we never cross any lines we're inside.
    bool inside = false;

    // Loop through all the edges.
    for (int i = 0; i < poly.rows(); ++i)
    {
        // i is the index of the first vertex, j is the next one.
        // The original code uses a too-clever trick for this.
        int j = (i + 1) % poly.rows();

        // The vertices of the Edge we are checking.
        double xp0 = poly(i, 0);
        double yp0 = poly(i, 1);
        double xp1 = poly(j, 0);
        double yp1 = poly(j, 1);

        // Check whether the Edge intersects a line from (-inf,y) to (x,y).

        // First check if the line crosses the horizontal line at y in either direction.
        if ((yp0 <= y) && (yp1 > y) || (yp1 <= y) && (yp0 > y))
        {
            // If so, get the point where it crosses that line. This is a simple solution
            // to a linear equation. Note that we can't get a division by zero here -
            // if yp1 == yp0 then the above if be false.
            double cross = (xp1 - xp0) * (y - yp0) / (yp1 - yp0) + xp0;

            // Finally check if it crosses to the left of our test point. You could equally
            // do right and it should give the same result.
            if (cross < x)
                inside = !inside;
        }
    }
    return inside;
}
1
Timmmm

Voici une implémentation php de ceci:

<?php
class Point2D {

    public $x;
    public $y;

    function __construct($x, $y) {
        $this->x = $x;
        $this->y = $y;
    }

    function x() {
        return $this->x;
    }

    function y() {
        return $this->y;
    }

}

class Point {

    protected $vertices;

    function __construct($vertices) {

        $this->vertices = $vertices;
    }

    //Determines if the specified point is within the polygon. 
    function pointInPolygon($point) {
        /* @var $point Point2D */
    $poly_vertices = $this->vertices;
    $num_of_vertices = count($poly_vertices);

    $Edge_error = 1.192092896e-07;
    $r = false;

    for ($i = 0, $j = $num_of_vertices - 1; $i < $num_of_vertices; $j = $i++) {
        /* @var $current_vertex_i Point2D */
        /* @var $current_vertex_j Point2D */
        $current_vertex_i = $poly_vertices[$i];
        $current_vertex_j = $poly_vertices[$j];

        if (abs($current_vertex_i->y - $current_vertex_j->y) <= $Edge_error && abs($current_vertex_j->y - $point->y) <= $Edge_error && ($current_vertex_i->x >= $point->x) != ($current_vertex_j->x >= $point->x)) {
            return true;
        }

        if ($current_vertex_i->y > $point->y != $current_vertex_j->y > $point->y) {
            $c = ($current_vertex_j->x - $current_vertex_i->x) * ($point->y - $current_vertex_i->y) / ($current_vertex_j->y - $current_vertex_i->y) + $current_vertex_i->x;

            if (abs($point->x - $c) <= $Edge_error) {
                return true;
            }

            if ($point->x < $c) {
                $r = !$r;
            }
        }
    }

    return $r;
}

Essai: 

        <?php
        $vertices = array();

        array_Push($vertices, new Point2D(120, 40));
        array_Push($vertices, new Point2D(260, 40));
        array_Push($vertices, new Point2D(45, 170));
        array_Push($vertices, new Point2D(335, 170));
        array_Push($vertices, new Point2D(120, 300));
        array_Push($vertices, new Point2D(260, 300));


        $Point = new Point($vertices);
        $point_to_find = new Point2D(190, 170);
        $isPointInPolygon = $Point->pointInPolygon($point_to_find);
        echo $isPointInPolygon;
        var_dump($isPointInPolygon);
1
Daniel

Pour développer le @ réponse de @ chowlette où la deuxième ligne vérifie si le point se trouve à gauche de la ligne, imaginez 2 cas de base: 

  • le point est à gauche de la ligne . / ou 
  • le point est à droite de la ligne / .

Si nous voulions lancer un rayon horizontalement, il toucherait le segment de droite. Notre point est à gauche ou à droite de celui-ci? À l'intérieur ou à l'extérieur? Nous connaissons sa coordonnée y car, par définition, il est identique au point. Quelle serait la coordonnée x? 

Prenez votre formule de ligne traditionnelle y = mx + b. m est la montée sur la course. Ici, nous essayons plutôt de trouver la coordonnée x du point sur ce segment de droite qui a la même hauteur (y) que notre point

Nous résolvons donc pour x: x = (y - b)/m. m est une augmentation, alors ceci devient une augmentation ou (yj - yi)/(xj - xi) devient (xj - xi)/(yj - yi). b est le décalage par rapport à l'origine. Si nous supposons que yi est la base de notre système de coordonnées, b devient yi. Notre point testy correspond à notre entrée. Soustraire yi transforme la formule entière en un décalage de yi.

Nous avons maintenant (xj - xi)/(yj - yi) ou 1/m fois y ou (testy - yi): (xj - xi)(testy - yi)/(yj - yi) mais testx n'étant pas basé sur yi, nous le rajoutons pour comparer les deux (ou zéro testx également)

1
derduher

C'est l'algorithme que j'utilise, mais j'ai ajouté un peu de ruse de prétraitement pour l'accélérer. Mes polygones ont environ 1 000 bords et ils ne changent pas, mais je dois vérifier si le curseur est placé à l'intérieur de chaque point de la souris. 

En gros, je scinde la hauteur du rectangle de délimitation en intervalles de longueur égale et, pour chacun de ces intervalles, je compile la liste des arêtes situées à l'intérieur ou en intersection avec ce dernier. 

Lorsque je dois rechercher un point, je peux calculer - dans O(1) heure - dans quel intervalle il se trouve, puis je n'ai plus qu'à tester les arêtes figurant dans la liste de l'intervalle.

J'ai utilisé 256 intervalles, ce qui a réduit le nombre d'arêtes à tester à 2-10 au lieu de ~ 1000.

0
Sly1024

J'ai modifié le code pour vérifier si le point est dans un polygone, y compris le point sur un bord. 

bool point_in_polygon_check_Edge(const vec<double, 2>& v, vec<double, 2> polygon[], int point_count, double Edge_error = 1.192092896e-07f)
{
    const static int x = 0;
    const static int y = 1;
    int i, j;
    bool r = false;
    for (i = 0, j = point_count - 1; i < point_count; j = i++)
    {
        const vec<double, 2>& pi = polygon[i);
        const vec<double, 2>& pj = polygon[j];
        if (fabs(pi[y] - pj[y]) <= Edge_error && fabs(pj[y] - v[y]) <= Edge_error && (pi[x] >= v[x]) != (pj[x] >= v[x]))
        {
            return true;
        }

        if ((pi[y] > v[y]) != (pj[y] > v[y]))
        {
            double c = (pj[x] - pi[x]) * (v[y] - pi[y]) / (pj[y] - pi[y]) + pi[x];
            if (fabs(v[x] - c) <= Edge_error)
            {
                return true;
            }
            if (v[x] < c)
            {
                r = !r;
            }
        }
    }
    return r;
}
0
rawdev