web-dev-qa-db-ja.com

ポリゴンアルゴリズムのポイント

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

このアルゴリズムを試しましたが、実際には完璧に機能します。しかし、悲しいことに、それを理解しようと少し時間を費やした後、私はそれをよく理解できません。

だから誰かがこのアルゴリズムを理解できるなら、少し説明してください。

ありがとうございました。

58
Allan Jiang

アルゴリズムは右へのレイキャスティングです。ループの各反復で、テストポイントはポリゴンのエッジの1つに対してチェックされます。ポイントのy座標がEdgeのスコープ内にある場合、ifテストの最初の行は成功します。 2番目の行は、テストポイントが行の左側にあるかどうかを確認します(確認するために手元にスクラップ紙がありません)。それが当てはまる場合、テストポイントから右方向に引かれた線はそのエッジと交差します。

cの値を繰り返し反転させることにより、アルゴリズムは右向きの線がポリゴンを横断する回数をカウントします。奇数回交差する場合、ポイントは内側にあります。偶数の場合、ポイントは外側にあります。

ただし、a)浮動小数点演算の精度、およびb)水平エッジ、または頂点と同じy座標を持つテストポイントの影響について懸念があります。

47
Chowlett

チャウレットは、あらゆる点で、形、形が正しい。アルゴリズムは、ポイントがポリゴンのライン上にある場合、それは外側にあると想定します-場合によっては、これはfalseです。 2つの「>」演算子を「> =」に変更し、「<」を「<=」に変更すると修正されます。

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

最適化に関する一言:最短(および/または最長)コードが最速で実装されているとは限りません。必要に応じて配列にアクセスするよりも、配列から要素を読み取って保存し、コードブロックの実行内で(おそらく)何回も使用する方がはるかに高速なプロセスです。これは、配列が非常に大きい場合に特に重要です。私の意見では、配列の各項を適切な名前の変数に格納することにより、その目的を評価することも簡単になり、はるかに読みやすいコードを作成できます。ちょうど私の2セント...

6
apil.tamang

元のコード を少し読みやすくするために変更しました(これも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;
}
3
Timmmm

このアルゴリズムは、多角形の辺が交差しない限り、閉じた多角形で機能します。三角形、五角形、正方形、非常に曲がりくねった区分線形のゴムバンドでさえ、交差しません。

1)多角形をベクトルの有向グループとして定義します。これは、ポリゴンのすべての辺が、頂点anから頂点an + 1に向かうベクトルによって記述されることを意味します。最後のベクトルが最初のベクトルの尾部に触れるまで、ベクトルの方向は次の尾部に触れるようになっています。

2)ポリゴンの内側または外側をテストするポイントを選択します。

3)ポリゴンの周囲に沿った各ベクトルVnについて、テストポイントで始まりVnの末尾で終わるベクトルDnを見つけます。 DnXVn/DN * VNとして定義されたベクトルCnを計算します(Xは外積を示し、*は内積を示します)。 Mnという名前でCnの大きさを呼び出します。

4)すべてのMnを追加し、この数量をKと呼びます。

5)Kがゼロの場合、ポイントはポリゴンの外側にあります。

6)Kがゼロでない場合、ポイントはポリゴン内にあります。

理論的には、ポリゴンのエッジ上にあるポイントは、未定義の結果を生成します。

Kの幾何学的な意味は、テストポイントに座っているノミが、多角形のエッジを左に歩くアリを見て、右に歩く角度を引いた合計角度です。閉回路では、アリは開始した場所で終了します。ポリゴンの外側では、場所に関係なく、答えはゼロです。
ポリゴンの内側では、場所に関係なく、答えは「ポイントの周囲に1回」です。


3
Mario

このメソッドは、ポイント(testx、testy)からO(0,0)までの光線がポリゴンの側面をカットするかどうかをチェックします。

よく知られた結論があります here :ある点からの光線が多角形の辺を奇数時間切断した場合、その点は多角形に属し、そうでなければその点は多角形の外側になります。

2
Thinhbk

基本的な考え方は、ポリゴンのエッジごとに1つ、ポイントからベクトルを計算することだと思います。ベクトルが1つのエッジと交差する場合、ポイントはポリゴン内にあります。凹面ポリゴンによって、奇数のエッジを横切る場合も内部にあります(免責事項:すべての凹面ポリゴンで機能するかどうかはわかりませんが)。

1
Anders

これの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

@chowletteの answer を展開するには、2番目の行がポイントが行の左側にあるかどうかをチェックします。

  • ポイントは、行. /の左側にあるか、
  • ポイントは/ .行の右側です

私たちのポイントが光線を水平に発射することである場合、それは線分にどこに当たるかです。私たちのポイントはそれの左ですか、それとも右ですか?内か外か?定義上、ポイントと同じであるため、y座標がわかります。 X座標はどうなりますか?

従来の線式y = mx + bを使用します。 mは実行中の上昇です。ここでは、代わりに、ポイントと同じ高さ(y)を持つラインセグメント上のポイントのx座標を見つけようとしています

したがって、x:x = (y - b)/mについて解きます。 mはrun over runであるため、これはrun over riseになるか、(yj - yi)/(xj - xi)(xj - xi)/(yj - yi)になります。 bはOriginからのオフセットです。 yiを座標系のベースとすると、bはyiになります。ポイントtestyは入力であり、yiを引くと、式全体がyiからのオフセットになります。

(xj - xi)/(yj - yi)または1/m倍のyまたは(testy - yi)(xj - xi)(testy - yi)/(yj - yi)がありますが、testxはyiに基づいていないので、2つ(またはtestxもゼロ)を比較するために追加し直します

1
derduher

これは私が使用するアルゴリズムですが、高速化するために少し前処理のトリックを追加しました。ポリゴンのエッジは1000個まであり、変化しませんが、マウスを動かすたびにカーソルが1つの内側にあるかどうかを調べる必要があります。

基本的に、境界矩形の高さを等しい長さの間隔に分割し、これらの間隔ごとに、その中にある/交差するエッジのリストをコンパイルします。

ポイントを検索する必要がある場合、in O(1) time-どの間隔にあるか)を計算できます。その後、間隔のリストにあるエッジのみをテストする必要があります。

256の間隔を使用したため、テストする必要があるエッジの数が〜1000ではなく2-10に減少しました。

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