web-dev-qa-db-ja.com

2Dポイントがポリゴン内にあるかどうかをどうやって確認できますか?

ヒットテストで使用するために、高速ポリゴンアルゴリズム内の2次元点を作成しようとしています(例:Polygon.contains(p:Point))。効果的なテクニックについての提案は評価されるでしょう。

457
Scott Evernden

グラフィックスでは、整数を好みません。多くのシステムはUIの描画に整数を使用しますが(ピクセルは整数です)、たとえばmacOSはすべてにfloatを使用します。 macOSは点を知っているだけで、点は1ピクセルに変換することができますが、モニターの解像度によっては、他のものに変換されることもあります。網膜スクリーンでは、半分の点(0.5/0.5)がピクセルです。それでも、私はmacOSのUIが他のUIよりもかなり遅いことに気づいたことはありません。すべての3D API(OpenGLまたはDirect3D)はフロートや最新のグラフィックライブラリでも動作するので、GPUアクセラレーションを利用することがよくあります。

今、あなたはスピードがあなたの主な関心事であると言いました、さて、スピードのために行こう。高度なアルゴリズムを実行する前に、まず簡単なテストを行います。多角形の周囲に軸揃えの境界ボックスを作成します。これは非常に簡単で速く、すでにたくさんの計算を安全にすることができます。それはどのように機能しますか?多角形のすべての点にわたって繰り返し、XとYの最小値と最大値を見つけます。

例えば。 (9/1), (4/3), (2/7), (8/2), (3/6)というポイントがあります。これは、Xminが2、Xmaxが9、Yminが1、Ymaxが7であることを意味します。2つの辺(2/1)と(9/7)を持つ四角形の外側の点は、多角形内にありません。

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

これはあらゆる点で実行する最初のテストです。ご覧のとおり、このテストは非常に高速ですが、非常に粗いものです。境界矩形内にある点を処理するには、より洗練されたアルゴリズムが必要です。これを計算する方法はいくつかあります。どの方法が機能するかは、多角形に穴が開いている可能性があるのか​​、それとも常にソリッドになるのかによっても異なります。これは立体物の例です(1つは凸、1つは凹)。

Polygon without hole

そしてこれが穴のあるものです。

Polygon with hole

緑色のものは真ん中に穴があります!

上記の3つすべてのケースを処理でき、それでもかなり高速な最も簡単なアルゴリズムは、レイキャスティングです。このアルゴリズムの考え方は非常に簡単です。ポリゴンの外側のどこかから自分の点まで仮想光線を描き、それがポリゴンの側面に当たる頻度を数えます。ヒット数が偶数の場合は多角形の外側、奇数の場合は内側です。

Demonstrating how the ray cuts through a polygon

ワインディング数アルゴリズムが代わりの方法になります。多角形の線に非常に近い点ではより正確ですが、速度も遅くなります。レイキャスティングは、浮動小数点の精度が制限され丸めの問題があるため、多角形の辺に近すぎるポイントでは失敗する場合がありますが、実際問題としてはほとんど問題になりません。ビューアがすでに内側にあるか外側にあるかを認識します。

あなたはまだ上記のバウンディングボックスを持っています、覚えてる?境界ボックスの外側の点を選択して、それを光線の始点として使用するだけです。例えば。 (Xmin - e/p.y)の点は確かに多角形の外側にあります。

しかしeは何ですか? e(実際はイプシロン)はバウンディングボックスにパディングを与えます。私が言ったように、多角形線に近づきすぎると光線追跡は失敗します。バウンディングボックスは多角形に等しいかもしれないので(多角形が軸合わせされた長方形であるならば、バウンディングボックスは多角形自体に等しい!)、これを安全にするためにいくらかのパディングが必要です、それだけです。あなたはeをどれくらい大きく選ぶべきですか?大きすぎない。描画に使用する座標系の縮尺によって異なります。あなたのピクセルステップ幅が1.0であるならば、ちょうど1.0を選択してください(それでも0.1はうまくいったでしょう)

始点と終点の座標を持つ光線ができたので、問題は "は多角形内の点"から ""に変わります。サイド "#:。そのため、以前のようにポリゴンポイントを操作することはできません。実際の辺が必要になります。辺は常に2点で定義されます。

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

あなたはすべての側面に対して光線をテストする必要があります。光線をベクトルとみなし、すべての辺をベクトルとみなします。光線はそれぞれの側面に正確に1回、またはまったく衝突しないようにする必要があります。同じ側​​に二度当たることはできません。 2次元空間の2本の線は、平行でない限り、常に1回だけ交差します。平行でない場合は、交差しません。ただし、ベクトルの長さは限られているため、2つのベクトルは平行ではなく、交差しないことがあります。

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

これまでのところそれほどうまくいっていませんが、2つのベクトルが交差しているかどうかをどのようにテストしますか。ここにいくつかのCコード(未テスト)があります。

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

入力値は、ベクトル1(v1x1/v1y1およびv1x2/v1y2)およびベクトル2(v2x1/v2y1およびv2x2/v2y2)の2つの端点です。つまり、2つのベクトル、4つの点、8つの座標があります。 YESNOは明確です。 YESは交差を増やし、NOは何もしません。

COLLINEARはどうですか?これは、位置と長さに応じて、両方のベクトルが同じ無限線上にあること、つまりまったく交差しないこと、または無限の数の点で交差することを意味します。どうやってこの事件を処理するのかは絶対的によくわからない。どちらにしてもそれを交差点としては数えない。まあ、このケースはとにかく浮動小数点丸め誤差のために実際にはかなりまれです。より良いコードはおそらく== 0.0fをテストするのではなく、代わりに< epsilonのようなものをテストします。ここで、イプシロンはかなり小さい数です。

より多くの点をテストする必要がある場合は、多角形の辺の線形方程式の標準形をメモリに保存しておくことで、全体を少し高速化することができます。したがって、毎回これらを再計算する必要はありません。これにより、各テストで2回の浮動小数点乗算と3回の浮動小数点減算が節約され、ポリゴンサイドごとに3つの浮動小数点値がメモリに格納されます。これは典型的なメモリと計算時間のトレードオフです。

大事なことを言い忘れましたが:あなたが問題を解決するために3Dハードウェアを使っているのなら、興味深い代替策があります。 GPUにすべての作業を任せてください。画面外にある塗装面を作成します。黒で完全に塗りつぶします。今度はOpenGLかDirect3Dにあなたの多角形を塗らせなさい(またはポイントがそれらのどれかの内にあるかどうかちょうどテストしたいと思う場合あなたの多角形のすべてでも)そして多角形を別で塗りつぶしなさい色、例えば白。点が多角形内にあるかどうかを確認するには、描画面からこの点の色を取得します。これはO(1)メモリの取り出しです。

もちろん、この方法は描画面を大きくする必要がない場合にのみ使用できます。それがGPUメモリに収まらない場合、この方法はCPUでそれをするより遅くなります。それが巨大でなければならず、あなたのGPUが現代のシェーダをサポートしているならば、あなたはまだ絶対に可能であるGPUシェーダとして上に示されたレイキャスティングを実装することによってGPUを使うことができます。テストするポリゴン数やポイント数が多い場合、これは効果的です。一部のGPUでは、64〜256ポイントを同時にテストできると考えてください。ただし、CPUからGPUにデータを転送したり、GPUに転送したりすることは常にコストがかかるため、ポイントまたはポリゴンが動的で頻繁に変化する2、3の単純なポリゴンと比較してテストする場合オフ。

675
Mecki

私は次のコードが最善の解決策だと思います(ここから)。

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

引数

  • nvert:多角形の頂点数。最後に最初の頂点を繰り返すかどうかは、上記の記事で説明されています。
  • vertx、verty:多角形の頂点のx座標とy座標を含む配列。
  • testx、testy:テストポイントのX座標とY座標。

これは短くて効率的で、凸多角形と凹多角形の両方に機能します。前に示唆したように、あなたは最初に境界矩形をチェックしそして多角形の穴を別々に扱うべきです。

この背後にある考え方はかなり単純です。作者はそれを次のように説明します。

テストポイントから半無限の光線を水平に(xを増やして、yを固定して)出し、交差した辺の数を数えます。交差するたびに、光線は内側と外側を切り替えます。これはヨルダン曲線定理と呼ばれます。

水平光線がいずれかのEdgeと交差するたびに、変数cは0から1および1から0に切り替わります。そのため、基本的には交差するエッジの数が偶数か奇数かを追跡します。 0は偶数、1は奇数を意味します。

560
nirg

これは このRPIの教授 に由来する nirgによる回答 のC#版です。そのRPIソースからのコードの使用は帰属を必要とすることに注意してください。

バウンディングボックスのチェックが上部に追加されました。ただし、James Brown氏が指摘するように、メインコードはバウンディングボックスチェック自体とほぼ同じ速さであるため、チェックするポイントの大部分がバウンディングボックス内にある場合、バウンディングボックスチェックは実際には全体の操作を遅くします。 。そのため、バウンディングボックスをチェックアウトしたままにしておくことも、多角形のバウンディングボックスを変形しすぎない場合は事前計算することもできます。

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

    // http://www.ecse.rpi.edu/Homepages/wrf/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;
}
61
M Katz

Nirgのアプローチに基づくM. Katzによる答えのJavaScriptによる変形です。

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;
}
43
Philipp Lenssen

点pと各多角形の頂点の間の角度の方向のある合計を計算します。全方位角が360度の場合、点は内側です。合計が0の場合、ポイントは外側です。

この方法はより堅牢で数値精度への依存度が低いため、この方法の方が好きです。

交差数の計算中に頂点に「ヒット」する可能性があるため、交差数の均一性を計算する方法は制限されています。

編集:ちなみに、この方法は凹面と凸面の多角形で動作します。

編集:私は最近、トピックに関する全体 ウィキペディアの記事 を見つけました。

28
user13673

Boboboboが引用した Eric Hainesの記事 は、本当に優れています。特に興味深いのは、アルゴリズムのパフォーマンスを比較した表です。角度の合計方法は他のものに比べて本当に悪いです。さらに興味深いのは、ルックアップグリッドを使用してポリゴンをさらに "in"と "out"のセクタに細分化するような場合、1000辺を超えるポリゴンでもテストが非常に速くなることです。

とにかく、それは初期の頃ですが、私の投票は「クロッシング」方法に行きます。しかし、私はそれが最もうまく説明されていることに気付いた David Bourke によって体系化された。本当の三角法が必要ないこと、そしてそれが凸面と凹面のために働くこと、そしてそれが辺の数が増えるにつれてそれが適度にうまく機能するのが好きです。

ちなみに、これはEric Hainesの記事に掲載されている、ランダムポリゴンでテストしたパフォーマンステーブルの1つです。

                       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
18
Gavin

この質問はとても興味深いです。私はこの記事の他の答えとは異なるもう一つの実行可能な考えを持っています。

これは、角度の合計を使ってターゲットが内側にあるか外側にあるかを判断することです。ターゲットがエリア内にある場合、ターゲットと2つの境界点ごとの角度の合計は360になります。ターゲットが外側にある場合、合計は360にはなりません。角度には方向があります。角度が逆になると、角度はマイナスになります。これは 巻き数 を計算するのと同じです。

アイデアの基本的な理解を得るためにこのイメージを参照してください: enter image description here

私のアルゴリズムは時計回りが正の方向であると仮定しています。これが潜在的な入力です。

[[-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]]

以下は、このアイディアを実装するPythonコードです。

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
17
Junbang Huang

の素早いバージョン/ 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
    }
}
8
bzz

私が Michael Stonebraker の研究者だったときに私はこれについていくつかの仕事をしました - あなたは知っています、 IngresPostgreSQL など.

私たちは、最速の方法はまずSUPERが速いのでバウンディングボックスを使うことであることに気づきました。境界ボックスの外側にある場合は外側にあります。そうでなければ、あなたはより難しい仕事をします...

優れたアルゴリズムが必要な場合は、ジオワーク用のオープンソースプロジェクトPostgreSQLのソースコードを参照してください。

私は指摘したいのですが、私たちは右利きと左利きについての洞察を得たことはありません(「内」対「外」の問題としても表現できます...


更新

BKBのリンクは、かなりの数の妥当なアルゴリズムを提供しました。私は地球科学の問題に取り組んでいたので、緯度/経度で機能する解決策を必要としていました、そしてそれは利き手の特有の問題を持っています。答えは、頂点の「方向」が重要であるということです。それは左手または右手のどちらかであり、このようにしていずれかの領域を任意のポリゴンの「内側」として指定できます。そのように、私の仕事はそのページで列挙された解決策3を使いました。

さらに、私の仕事は "オンライン"テストのために別々の関数を使っていました。

...誰かが尋ねたので:私たちは、頂点の数がある数を超えたときにバウンディングボックステストが最善であると考えました - 必要ならばより長いテストをする前に非常に速いテストをしてください。最大のx、最小のx、最大のy、最小のy。

私たちはすべてのより洗練された「薄暗い」計算を平面上のすべての正の点でグリッド空間で行い、それから「本当の」経度/緯度に再投影して、の誤差を避けました。人が経度線180を横切ったときや極地を扱うときに折り返します。うまくいった!

7
Richard T

Nirgによって投稿されboboboboによって編集されたソリューションが本当に好きです。私はちょうどそれを私の使用のためにJavaScriptにやさしく、もう少し読みやすくしました:

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

David Segondの答えはほとんど標準的な一般的な答えであり、Richard Tのものが最も一般的な最適化です。他の強力な最適化は、あまり一般的ではない解決策に基づいています。たとえば、同じポリゴンを多数のポイントでチェックしようとしている場合、ポリゴンを三角測量すると、非常に高速なTIN検索アルゴリズムがいくつかあるため、作業が大幅にスピードアップします。もう1つは、ポリゴンと点が低解像度で限られた平面上にある場合(スクリーンディスプレイなど)、ポリゴンをメモリマップディスプレイバッファに特定の色でペイントし、特定のピクセルの色をチェックしてそれがあるかどうかポリゴンで。

多くの最適化と同様に、これらは一般的なケースではなく特定のケースに基づいており、単回使用ではなく償却時間に基づいて利益をもたらします。

この分野で働いていて、私はC 'ISBN 0-521-44034-3のJoeseph O'RourkesのComputation Geometryが大きな助けになることを発見しました。

5
SmacL

テストポイントのためのサンプルメソッドを持つnirgの答えのObj-Cバージョン。 Nirgの答えは私にはうまくいった。

- (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");
    }
}

sample polygon

3
Jon

私も360の合計が凸多角形のために働くことを考えました、しかし、これは本当ではありません。

このサイトには、これを正確に示すNiceダイアグラムと、ヒットテストに関する優れた説明があります。 Gamasutra - 新年への衝突:衝突検出

3
Dave

私はこれが古いことを認識していますが、誰かが興味を持った場合に備えて、これがCocoaで実装されたレイキャスティングアルゴリズムです。それが物事を行うための最も効率的な方法であるかどうかわからないが、それは誰かを助けるかもしれません。

- (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;
}
3
diatrevolo

簡単な解決策は、多角形を三角形に分割し、説明されているように ここで三角形をヒットテストすることです

多角形がCONVEXの場合は、もっと良い方法があります。多角形を無限の線の集まりとして見てください。スペースを2つに分割する各行。どの点でも、それが線の片側にあるのか反対側にあるのかを言うのは簡単です。点がすべての線の同じ側にある場合は、多角形の内側にあります。

3
shoosh

Nirgの答えのC#版はこちら:コードを共有するだけです。時間を節約することができます。

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

帰納的に問題を定義する以上に有益なものはありません。ここで完全を期すために、ray castingの背後にある考えを明確にするかもしれないprologのバージョンがあります。

http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html の単純化アルゴリズムのシミュレーションに基づいて

いくつかのヘルパー述語:

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]).

2点AとBが与えられた直線(Line(A、B))の方程式は、次のとおりです。

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

ラインの回転方向は、境界では時計回りに、穴では反時計回りに設定することが重要です。私たちは点(X、Y)、すなわちテストされた点が私たちの線の左半分の平面にあるかどうかをチェックしようとしています(それは好みの問題です、それはまた右側ですが境界の方向でもありえます)その場合は線を変更する必要があります。これは、光線をその点から右(または左)に投影し、線との交差を確認するためです。光線を水平方向に投影することを選択しました(これも好みの問題です。垂直方向にも同様の制限があります)。したがって、次のようになります。

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

ここで、点が平面全体ではなく、線分の左側(または右側)のみにあるかどうかを知る必要があるため、検索をこの線分だけに制限する必要がありますが、線分の内側にあるのでこれは簡単です。線の1つの点だけが垂直軸のYより高くなることができます。これはより強い制限であるため、最初に確認する必要があります。したがって、最初にこの要件を満たす行だけを取得し、次にその可能性を確認します。 Jordan Curveの定理によると、多角形に投影される光線は偶数の線で交差しなければなりません。これで作業は完了です。光線を右に投げてから、線と交差するたびに状態を切り替えます。しかし、私たちの実装では、与えられた制限を満たす一連のソリューションの長さをチェックし、それに対する内的関係を決定するのが目的です。多角形の各線に対してこれを行う必要があります。

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).
2
jdavid_1385

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

}
2
YongJiang Zhang

VBAバージョン:

注:ポリゴンがマップ内の領域で、緯度/経度がX/YではなくY/Xの値である場合(Latitude = Y、経度= X)、過去からの過去の影響が原因であることに注意してください。経度は測定値ではありませんでした。

クラスモジュール: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

モジュール:

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
1
Colin Stadig

.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;
        }
    }
1
Aladar

nirgの c ++ コード のPython実装を作成しました。

入力

  • bounding_points:多角形を構成するノード。
  • bounding_box_positions:フィルタする候補点。 (私の実装ではバウンディングボックスから作成しました。

    (入力はタプルのリストで、フォーマットは[(xcord, ycord), ...]です)

返品

  • 多角形の内側にあるすべての点。
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

繰り返しになりますが、アイデアはここ から取られます

1
Noresourses

データベースを必要とする実用主義者のために、驚くほど早く誰もこれを思い付かせませんでした:MongoDBはこれを含むGeoクエリを非常によくサポートしています。

あなたが探しているのは、

db.neighborhoods.findOne({geometry:{$ geoIntersects:{$ geometry:{タイプ: "Point"、座標:["経度"、 "緯度"]}}}}))

Neighborhoodsは、1つ以上のポリゴンを標準のGeoJson形式で格納するコレクションです。クエリがnullを返す場合は交差しません。それ以外の場合は交差しません。

ここで非常によく文書化されています。 https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/

330の不規則なポリゴングリッドに分類された6,000点を超えるパフォーマンスは、最適化をまったく行わず、ドキュメントをそれぞれのポリゴンで更新する時間も含めて、1分未満でした。

レイキャスティングアルゴリズム での以下の特別なケースを扱うために:

  1. 光線はポリゴンの側面の1つに重なります。
  2. 点はポリゴンの内側にあり、光線はポリゴンの頂点を通過します。
  3. ポイントはポリゴンの外側にあり、光線はポリゴンの角度の1つにちょうど接しています。

点が複雑な多角形の内側にあるかどうかを判断する。この記事ではそれらを簡単に解決する方法を提供しているので、上記の場合に特別な処理は必要ありません。

0
Justin

NirgによるソリューションのScalaバージョン(境界矩形の事前チェックが別々に行われると仮定します):

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)
}
0
Michael-7

答えは、単純なポリゴンか複雑なポリゴンかによって異なります。単純多角形は線分の交点を持ってはいけません。だから彼らは穴を持つことができますが、線は互いに交差することはできません。複雑な領域は線の交点を持つことができます - そのためそれらは重なり合う領域、またはちょうど一点で互いに接触する領域を持つことができます。

単純な多角形の場合、最良のアルゴリズムはレイキャスティング(交差数)アルゴリズムです。複雑な多角形の場合、このアルゴリズムは重なり合う領域の内側にある点を検出しません。そのため、複雑な多角形の場合は、ワインディング数アルゴリズムを使用する必要があります。

これは、両方のアルゴリズムをCで実装した優れた記事です。私はそれらを試しました、そして、彼らはうまくいきます。

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

0
Timmy_A

Polygonのヒットを検出するために、2つのことをテストする必要があります。

  1. Pointがポリゴン領域の内側にある場合(レイキャスティングアルゴリズムによって達成することができます)
  2. Pointが多角形の境界上にある場合(ポリライン(線)上の点検出に使用されるものと同じアルゴリズムで実現できます)。
0
V.J.

レイキャスティングを使用していないCのポリゴンテストのポイントです。そしてそれは重なっている領域(自己交差)に対しても有効です。引数use_holesを参照してください。

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

注:これはatan2fへの呼び出しを多数含んでいるのであまり最適ではない方法の1つですが、このスレッドを読む開発者にとっては興味深いかもしれません(私のテストでは線交差法よりも23倍遅くなります)。

0
ideasman42

Javaスクリプトライブラリを探しているのであれば、Polygonクラス用のjavascript google maps v3拡張機能があり、そこにポイントがあるかどうかを検出します。

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

Google Extention Github

0
shana

これは@nirg答えのゴラン版です(@@ m-katzによるC#コードに触発された)

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
}
0
SamTech

qt (Qt 4.3+)を使うとき、QPolygonの関数 containsPoint を使うことができます。

0
Peter

これを行うには、目的の点をポリゴンの頂点に接続して形成された領域が、ポリゴン自体の領域と一致するかどうかを確認します。

または、チェックポイントまでの2つの連続するポリゴン頂点の各ペアまでの内角の合計が360になるかどうかを確認できますが、分割や計算が含まれないため、最初のオプションの方が速いようです三角関数の逆関数.

ポリゴンの中に穴が開いているとどうなるかわかりませんが、メインのアイデアはこの状況に適応できると思われます

あなたは同様に数学のコミュニティに質問を投稿することができます。私は彼らにそれをする百万の方法があると思います

0
user5193682