ヒットテストで使用するために、ポリゴンアルゴリズム内に高速の 2Dポイントを作成しようとしています(例Polygon.contains(p:Point)
)効果的なテクニックの提案をいただければ幸いです。
ヒットテストで使用するために、ポリゴンアルゴリズム内に高速の 2Dポイントを作成しようとしています(例Polygon.contains(p:Point)
)効果的なテクニックの提案をいただければ幸いです。
回答:
グラフィックについては、整数を好むのではなく、多くのシステムはUIの描画に整数を使用しますが(ピクセルは結局intです)、たとえばmacOSはすべてに浮動小数点を使用します。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つは凹)。
そしてここに穴のあるものがあります:
緑は真ん中に穴があいています!
上記の3つすべてのケースを処理でき、それでもかなり高速な最も簡単なアルゴリズムは、レイキャスティングと呼ばれます。アルゴリズムの考え方は非常に単純です。ポリゴンの外側からポイントまで仮想光線を描画し、ポリゴンの側面に当たる頻度を数えます。ヒット数が偶数の場合はポリゴンの外側、奇数の場合は内側です。
巻き数のアルゴリズムは、代替となり、それは非常に近いポリゴンラインにあることのポイントのために、より正確であるが、それはまた、はるかに遅いです。レイキャスティングは、浮動小数点の精度と丸めの問題のため、ポリゴンの側面に近すぎるポイントでは失敗する可能性がありますが、実際にはほとんど問題になりません。ビューアがすでに内側か外側かを認識するビューア。
あなたはまだ上記の境界ボックスを持っています、覚えていますか?境界ボックスの外側の点を選択して、光線の開始点として使用してください。たとえば、ポイント(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回攻撃することはできません。2D空間の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(および)およびベクトル2(および)の2 つの端点です。したがって、2つのベクトル、4つのポイント、8つの座標があります。そして明確です。交差を増やし、何もしません。v1x1/v1y1
v1x2/v1y2
v2x1/v2y1
v2x2/v2y2
YES
NO
YES
NO
COLLINEARはどうですか?これは、両方のベクトルが同じ無限線上にあり、位置と長さに応じて、まったく交差しないか、無限の数の点で交差することを意味します。私はこのケースをどのように処理するか絶対にわかりません、私はどちらの方法でも交差として数えません。とにかく、浮動小数点の丸め誤差のため、実際にはこのケースはかなりまれです。より良いコードはおそらくテストしないでしょう== 0.0f
が、代わりにのようなもの< epsilon
で、イプシロンはかなり小さい数です。
より多くのポイントをテストする必要がある場合は、多角形の辺の線形方程式の標準形式をメモリに保持することで、全体を少し高速化できるため、毎回これらを再計算する必要はありません。これにより、ポリゴンサイドごとに3つの浮動小数点値をメモリに格納する代わりに、すべてのテストで2つの浮動小数点乗算と3つの浮動小数点減算を節約できます。これは、典型的なメモリと計算時間のトレードオフです。
最後に重要なことですが、問題を解決するために3Dハードウェアを使用する場合、興味深い代替手段があります。GPUにすべての作業を任せてください。画面外のペイントサーフェスを作成します。黒で完全に塗ります。次に、OpenGLまたはDirect3Dでポリゴン(または、ポイントがそれらのいずれかにあるかどうかをテストしたい場合はすべてのポリゴンをペイントしますが、どのポリゴンでもかまいません)をペイントして、ポリゴンを別のポリゴンで塗りつぶします色、例えば白。ポイントがポリゴン内にあるかどうかを確認するには、描画面からこのポイントの色を取得します。これは単なるO(1)メモリフェッチです。
もちろん、この方法は、描画面を大きくする必要がない場合にのみ使用できます。GPUメモリに収まらない場合、この方法はCPUで行うよりも遅くなります。巨大である必要があり、GPUが最新のシェーダーをサポートしている場合でも、上記のレイキャスティングをGPUシェーダーとして実装することでGPUを使用できますが、これは絶対に可能です。より多くのポリゴンまたは多数のポイントをテストする場合、これは報われます。一部のGPUは64〜256ポイントを並行してテストできることを考慮してください。ただし、CPUからGPUへのデータ転送とその逆の転送には常にコストがかかるため、ポイントまたはポリゴンのいずれかが動的で頻繁に変更されるいくつかの単純なポリゴンに対していくつかのポイントをテストするだけの場合、GPUアプローチはほとんど効果がありませんオフ。
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;
}
これは短くて効率的であり、凸型ポリゴンと凹型ポリゴンの両方で機能します。前に提案したように、最初に外接する四角形を確認し、ポリゴンの穴を個別に処理する必要があります。
この背後にある考え方は非常に簡単です。著者はそれを次のように説明しています:
テストポイントから半無限の光線を水平方向に(xを増やし、yを固定して)実行し、交差するエッジの数を数えます。各交差点で、光線は内側と外側を切り替えます。これは、ジョーダン曲線定理と呼ばれます。
変数cは、水平光線がエッジを横切るたびに、0から1および1から0に切り替わります。つまり、基本的には、交差するエッジの数が偶数か奇数かを追跡しています。0は偶数、1は奇数を意味します。
verty[i]
とのverty[j]
どちらかであるかどうかをチェックするtesty
ため、決して等しくなることはありません。
これは、この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;
}
// 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;
}
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;
}
点pと各ポリゴン頂点の間の角度の方向付き合計を計算します。全方向角が360度の場合、ポイントは内側になります。合計が0の場合、ポイントは外側です。
この方法はより堅牢で、数値の精度にあまり依存しないため、この方法の方が好きです。
交差数の計算中に頂点を「ヒット」できるため、交差数の均一性を計算する方法は制限されています。
編集:ちなみに、この方法は凹面と凸面のポリゴンで機能します。
編集:私は最近、このトピックに関するウィキペディアの記事全体を見つけました。
この質問はとても興味深いです。この投稿に対する他の回答とは異なる別の実用的なアイデアがあります。アイデアは、角度の合計を使用して、ターゲットが内側か外側かを決定することです。巻数として知られていますます。
xをターゲットポイントとします。配列[0、1、.... n]をエリアのすべてのポイントとします。ターゲットポイントをすべての境界ポイントに線で接続します。ターゲットポイントがこの領域内にある場合。すべての角度の合計は360度になります。そうでない場合、角度は360度未満になります。
このイメージを参照して、アイデアの基本的な理解を深めてください。
私のアルゴリズムは、時計回りが正の方向であることを前提としています。これは潜在的な入力です:
[[-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
エリックヘインズの記事 boboboboによって引用は本当に素晴らしいです。特に興味深いのは、アルゴリズムのパフォーマンスを比較する表です。角度の合計方法は、他の方法と比較して本当に悪いです。さらに興味深いのは、ルックアップグリッドを使用してポリゴンをさらに「イン」および「アウト」セクターに細分割するような最適化により、1000サイドを超えるポリゴンでもテストが非常に高速になることです。
とにかく、それは初期の段階ですが、私の投票は「クロッシング」法に向けられています。しかし、私はそれが最も簡潔に記述され、デビッド・バークによって成文化されたことを発見しました。実際の三角法が必要ないこと、そして凸面と凹面で機能し、辺の数が増えるにつれて適度に機能することを気に入っています。
ちなみに、ランダムポリゴンでテストした、エリックヘインズの記事のパフォーマンステーブルの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
nirgによる回答の Swiftバージョン:
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
}
}
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;
}
私は下の研究者だったとき、私はこの背中にいくつかの作業をしたマイケル・ストーンブレーカーあなたが知っている、思い付いた教授- アングル、PostgreSQLのなど。
最速の方法は、バウンディングボックスを最初に作成することです。これは、SUPERが高速だからです。境界ボックスの外側にある場合は、外側にあります。そうでなければ、あなたはもっと難しい仕事をします...
優れたアルゴリズムが必要な場合は、ジオワーク用のオープンソースプロジェクトのPostgreSQLソースコードを確認してください...
指摘したいのですが、右利きと左利きの洞察はありませんでした(「内側」と「外側」の問題としても表現できます...
更新
BKBのリンクは、数多くの合理的なアルゴリズムを提供しました。私は地球科学の問題に取り組んでいたため、緯度/経度で機能する解決策が必要でしたが、利き手に特有の問題があります。これは、狭い領域の内側の領域ですか、それとも大きな領域の領域ですか?答えは、頂点の「方向」が重要であることです。これは、左利きまたは右利きのどちらかであり、このようにして、任意の特定のポリゴンの「内側」としていずれかの領域を示すことができます。そのため、私の仕事では、そのページに列挙されているソリューション3を使用しました。
さらに、私の作業では、「オンライン」テストで個別の関数を使用しました。
...誰かが尋ねたので:頂点の数がいくつかの数を超えたときにバウンディングボックステストが最適であることがわかりました-必要に応じて、より長いテストを行う前に非常に迅速なテストを行ってください...バウンディングボックスは、最大のx、最小のx、最大のy、最小のyを組み合わせて、ボックスの4点を作成します...
次のヒント:グリッドスペースですべてのより洗練された「薄暗い」計算をすべて平面上の正の点で実行し、「実際の」経度/緯度に再投影して、考えられるエラーを回避しました。経度のライン180を交差したときと、極域を処理したときにラップアラウンドします。よくできました!
David Segondの答えは、ほぼ標準的な一般的な答えであり、Richard Tの答えが最も一般的な最適化ですが、他にもいくつかあります。その他の強力な最適化は、あまり一般的ではないソリューションに基づいています。たとえば、多数のポイントで同じポリゴンをチェックする場合、ポリゴンを三角形化すると、非常に高速なTIN検索アルゴリズムが多数あるため、処理速度が大幅に向上します。もう1つは、ポリゴンとポイントが低解像度の限られた平面上にある場合(画面表示など)、ポリゴンをメモリマップディスプレイバッファーに特定の色でペイントし、特定のピクセルの色をチェックして、それがあるかどうかを確認することです。ポリゴンで。
多くの最適化と同様に、これらは一般的なケースではなく特定のケースに基づいており、単一の使用ではなく償却時間に基づいて利益を生み出します。
この分野で働いているとき、私はJoeseph O'Rourkes 'Cの計算幾何学' ISBN 0-521-44034-3が非常に役立つことを発見しました。
簡単な解決策は、ポリゴンを三角形に分割し、ここで説明するように三角形をヒットテストすることです。
ポリゴンがCONVEXである場合は、より良いアプローチがあるかもしれません。ポリゴンを無限線のコレクションとして見てください。スペースを2つに分割する各行。どのポイントでも、ラインの片側にあるのか反対側にあるのかは簡単です。ポイントがすべてのラインの同じ側にある場合、それはポリゴンの内側にあります。
これは古いことに気づきましたが、誰かが興味を持っている場合に備えて、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;
}
テストポイントのサンプルメソッドを使用した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");
}
}
CGPathContainsPoint()
あなたの友達です。
CGPathContainsPoint()
問題の帰納的定義以上に美しいものはありません。ここで完全を期すために、プロローグのバージョンがあります。これは、レイキャスティングの背後にある考えも明確にするかもしれません。。
の単純化アルゴリズムのシミュレーションに基づく 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)
ここで、ポイントが平面全体ではなく、線分セグメントの左側(または右側)にあるかどうかを知る必要があるため、検索をこのセグメントのみに制限する必要がありますが、これはセグメント内にあるので簡単です垂直軸のYよりも高いのは、ラインの1つのポイントのみです。これはより強い制限であるため、最初にチェックする必要があるため、この要件を満たす行のみを最初に取得し、その位置をチェックします。ジョーダンカーブの定理により、ポリゴンに投影される光線は偶数のラインで交差する必要があります。これで完了です。光線を右にスローし、線と交差するたびに状態を切り替えます。しかし、私たちの実装では、与えられた制限を満たすソリューションのバッグの長さをチェックし、それに対する内部関係を決定することに協力しています。ポリゴンの各ラインに対して、これを行う必要があります。
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).
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;
}
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;
}
}
.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;
}
}
VBAバージョン:
注:ポリゴンがマップ内のエリアである場合、緯度/経度がX / Y(緯度= Y、経度= X)ではなく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
nirgの c ++ コードの Python実装を作成しました。
入力
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
驚いたことに誰も早くこれを取り上げなかったが、データベースを必要とする実用主義者のために:MongoDBはこれを含むGeoクエリに対して優れたサポートを提供している。
あなたが探しているのは:
db.neighborhoods.findOne({ジオメトリ:{$ geoIntersects:{$ geometry:{タイプ: "ポイント"、座標:["経度"、 "緯度"]}}}})
Neighborhoods
1つ以上のポリゴンを標準のGeoJson形式で格納するコレクションです。クエリがnullを返した場合、交差しません。交差していません。
ここに非常によく文書化されています:https: //docs.mongodb.com/manual/tutorial/geospatial-tutorial/
330の不規則なポリゴングリッドに分類された6,000以上のポイントのパフォーマンスは1分未満で、最適化がまったく行われず、それぞれのポリゴンでドキュメントを更新する時間が含まれていました。
レイキャスティングを使用していない、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倍遅くなります)。
レイキャスティングアルゴリズムで以下の特殊なケースに対処するには:
ポイントが複雑なポリゴンの内側にあるかどうかを確認します。この記事はそれらを簡単に解決する方法を提供するので、上記の場合に特別な処理は必要ありません。
これを行うには、目的のポイントをポリゴンの頂点に接続して形成される領域が、ポリゴン自体の領域と一致するかどうかを確認します。
または、ポイントからチェックポイントまでの2つの連続するポリゴン頂点の各ペアの内角の合計が360になるかどうかをチェックすることもできますが、分割や計算が含まれないため、最初のオプションの方が速いと感じています三角関数の逆関数。
ポリゴンに穴が開いているとどうなるかわかりませんが、メインのアイデアはこの状況に適応できるようです
数学コミュニティに質問を投稿することもできます。百万通りのやり方があると思う
java-scriptライブラリを探している場合は、Polygonクラスにjavascript google maps v3拡張機能があり、そこにポイントが存在するかどうかを検出します。
var polygon = new google.maps.Polygon([], "#000000", 1, 1, "#336699", 0.3);
var isWithinPolygon = polygon.containsLatLng(40, -90);
使用する場合 qt(Qt 4.3 +)、QPolygonの関数containsPointを使用できます
答えは、ポリゴンが単純か複雑かによって異なります。単純なポリゴンには、ラインセグメントの交差があってはなりません。したがって、穴を開けることはできますが、線を互いに交差させることはできません。複雑な領域は線の交点を持つことができます-したがって、それらは重なり合う領域、または1つの点だけで互いに接触する領域を持つことができます。
単純なポリゴンの場合、最適なアルゴリズムはレイキャスティング(交差数)アルゴリズムです。複雑なポリゴンの場合、このアルゴリズムはオーバーラップする領域内にあるポイントを検出しません。したがって、複雑なポリゴンの場合は、巻線数アルゴリズムを使用する必要があります。
以下は、両方のアルゴリズムをCで実装した優れた記事です。私はそれらを試しました、そして彼らはうまくいきます。
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)
}
これは@nirg回答のgolangバージョンです(@@ 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
}