it-roy-ru.com

Точка в алгоритме многоугольника

Я видел, что приведенный ниже алгоритм работает, чтобы проверить, находится ли точка в данном многоугольнике из этой ссылки :

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

Я попробовал этот алгоритм, и он на самом деле работает просто отлично. Но, к сожалению, я не могу понять это хорошо, потратив некоторое время, пытаясь понять это. 

Поэтому, если кто-то может понять этот алгоритм, пожалуйста, объясните мне его немного. 

Спасибо.

54
Allan Jiang

Алгоритм направляет луч вправо. На каждой итерации цикла контрольная точка проверяется по одному из ребер многоугольника. Первая строка if-теста завершается успешно, если y-координата точки находится внутри области действия Edge. Вторая строка проверяет, находится ли контрольная точка слева от линии (я думаю - у меня нет какой-либо макулатуры для проверки). Если это правда, линия, проведенная вправо от контрольной точки, пересекает этот край.

Путем многократного инвертирования значения c алгоритм подсчитывает, сколько раз правая линия пересекает многоугольник. Если он пересекает нечетное количество раз, то точка находится внутри; если четное число, точка находится за пределами.

У меня были бы проблемы с а) точностью арифметики с плавающей точкой и б) эффектом наличия горизонтального края или контрольной точки с той же координатой y, что и у вершины.

45
Chowlett

Чоулетт корректен во всех отношениях, формах и формах Алгоритм предполагает, что если ваша точка находится на линии многоугольника, то это за пределами - в некоторых случаях это неверно. Изменение двух операторов «>» на «> =» и изменение «<» на «<=» исправят это.

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

Это может быть как можно более подробно для объяснения алгоритма трассировки лучей в реальном коде. Это не может быть оптимизировано, но это всегда должно происходить после полного понимания системы.

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

Только одно слово об оптимизации: неверно, что самый короткий (и/или кратчайший) код - самый быстро реализуемый. Это гораздо более быстрый процесс чтения и сохранения элемента из массива и его использования (возможно) много раз в ходе выполнения блока кода, чем для доступа к массиву каждый раз, когда это требуется. Это особенно важно, если массив очень большой. На мой взгляд, сохраняя каждый член массива в хорошо названной переменной, также легче оценить его назначение и, таким образом, сформировать гораздо более читаемый код. Просто мои два цента ...

6
apil.tamang

Алгоритм урезан до самых необходимых элементов. После того, как он был разработан и протестирован, все ненужные вещи были удалены. В результате вы не можете выполнить это легко, но это делает работу, а также в очень хорошем исполнении .


Я позволил себе перевести его на 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

Этот алгоритм работает в любом замкнутом многоугольнике, пока стороны многоугольника не пересекаются. Треугольник, пятиугольник, квадрат, даже очень изогнутая кусочно-линейная резинка, которая не пересекает себя. 

1) Определите свой многоугольник как направленную группу векторов. Под этим подразумевается, что каждая сторона многоугольника описывается вектором, который идет от вершины an к вершине an + 1. Векторы направлены так, что голова одного касается хвоста следующего, пока последний вектор не коснется хвоста первого. 

2) Выберите точку для тестирования внутри или снаружи многоугольника. 

3) Для каждого вектора Vn по периметру многоугольника найдите вектор Dn, который начинается в контрольной точке и заканчивается в хвосте Vn. Вычислите вектор Cn, определенный как DnXVn/DN * VN (X обозначает перекрестное произведение; * обозначает точечное произведение). Назовите величину Cn по имени Mn. 

4) Добавьте все Mn и назовите это количество K. 

5) Если K равно нулю, точка находится за пределами многоугольника. 

6) Если K не равно нулю, точка находится внутри многоугольника.

Теоретически, точка, лежащая на краю многоугольника, даст неопределенный результат.

Геометрическое значение K - это общий угол, по которому блоха, сидящая на нашей контрольной точке, «видела» муравья, идущего по краю многоугольника, идущего влево, минус угол, пройденный вправо. В замкнутом контуре муравей заканчивается там, где он начался .. За пределами многоугольника, независимо от местоположения, ответ равен нулю.
Внутри многоугольника, независимо от местоположения, ответ «один раз вокруг точки».


3
Mario

Этот метод проверяет, разрезал ли луч от точки (testx, testy) до O (0,0) стороны многоугольника или нет.

Есть хорошо известный вывод здесь : если луч из 1 точки разрезает стороны многоугольника на нечетное время, эта точка будет принадлежать многоугольнику, в противном случае эта точка будет находиться за пределами многоугольника. 

2
Thinhbk

Я думаю, что основная идея состоит в том, чтобы вычислять векторы из точки, по одному на ребро многоугольника. Если вектор пересекает одно ребро, то точка находится внутри многоугольника. Вогнутыми полигонами, если он пересекает нечетное число ребер, он тоже внутри (отказ от ответственности: хотя и не уверен, работает ли он для всех вогнутых полигонов).

1
Anders

Я изменил оригинальный код , чтобы сделать его немного более читабельным (также это использует Eigen). Алгоритм идентичен.

// 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

Вот реализация php этого:

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

Тестовый забег: 

        <?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

Чтобы расширить answer chowlette @, где вторая строка проверяет, находится ли точка слева от линии, Деривация не дается, но это то, что я разработал: Сначала это помогает представить 2 основные случаи: 

  • точка слева от строки . / или 
  • точка находится справа от линии / .

Если бы наша цель была направить луч горизонтально, куда бы он попал в отрезок линии. Наша точка слева или справа? Внутри или снаружи? Мы знаем его координату y, потому что она по определению совпадает с точкой. Какой будет координата х? 

Возьмите свою традиционную формулу строки y = mx + b. м - подъем над трассой. Здесь вместо этого мы пытаемся найти координату x точки на этом отрезке, которая имеет ту же высоту (y), что и наша точка

Поэтому мы решаем для x: x = (y - b)/m. m повышается во время прогона, поэтому он переходит при повышении или (yj - yi)/(xj - xi) становится (xj - xi)/(yj - yi). б - смещение от источника. Если мы примем yi в качестве основы для нашей системы координат, b станет yi. Наша точка testy является нашим входом, вычитание yi превращает всю формулу в смещение от yi.

Теперь у нас есть (xj - xi)/(yj - yi) или 1/m times y или (testy - yi): (xj - xi)(testy - yi)/(yj - yi), но testx не основан на yi, поэтому мы добавляем его обратно для сравнения двух (или нулевого testx)

1
derduher

Я использую этот алгоритм, но я добавил немного хитрости предварительной обработки, чтобы ускорить его. Мои полигоны имеют ~ 1000 ребер, и они не меняются, но мне нужно посмотреть, находится ли курсор внутри одного при каждом движении мыши. 

Я в основном делю высоту ограничивающего прямоугольника на интервалы равной длины, и для каждого из этих интервалов я составляю список ребер, которые лежат внутри/пересекаются с ним. 

Когда мне нужно найти точку, я могу вычислить - за O(1) время - в каком интервале она находится, а затем мне нужно проверить только те ребра, которые находятся в списке интервала.

Я использовал 256 интервалов, и это уменьшило количество ребер, которые мне нужно проверить, до 2-10 вместо ~ 1000.

0
Sly1024

Я изменил код, чтобы проверить, находится ли точка в многоугольнике, включая точку на краю. 

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