ポイントがポリゴン内にあるかどうかを判断しようとしています。 PolygonはPointオブジェクトの配列によって定義されます。ポイントがポリゴンの境界ボックス内にあるかどうかは簡単にわかりますが、実際のポリゴン内にあるかどうかはわかりません。可能であれば、C#とWinFormsのみを使用したいと思います。この単純なタスクを実行するためにOpenGLまたは何かを呼び出すのは避けたいです。
ここに私がこれまでに持っているコードがあります:
private void CalculateOuterBounds()
{
//m_aptVertices is a Point[] which holds the vertices of the polygon.
// and X/Y min/max are just ints
Xmin = Xmax = m_aptVertices[0].X;
Ymin = Ymax = m_aptVertices[0].Y;
foreach(Point pt in m_aptVertices)
{
if(Xmin > pt.X)
Xmin = pt.X;
if(Xmax < pt.X)
Xmax = pt.X;
if(Ymin > pt.Y)
Ymin = pt.Y;
if(Ymax < pt.Y)
Ymax = pt.Y;
}
}
public bool Contains(Point pt)
{
bool bContains = true; //obviously wrong at the moment :)
if(pt.X < Xmin || pt.X > Xmax || pt.Y < Ymin || pt.Y > Ymax)
bContains = false;
else
{
//figure out if the point is in the polygon
}
return bContains;
}
this を参照してください。これはc ++であり、c#でも同じように実行できます。
凸多角形の場合は簡単すぎます:
多角形が凸の場合、多角形を最初の頂点からの「パス」と見なすことができます。パスを構成するすべてのラインセグメントの同じ側に常にある場合、ポイントはこのポリゴンの内部にあります。
P0(x0、y0)とP1(x1、y1)の間の線分を考えると、別の点P(x、y)は線分に対して次の関係を持ちます。計算(y-y0)(x1-x0)-(x-x0)(y1-y0)
0より小さい場合、Pはラインセグメントの右側にあり、0より大きい場合はPが左側にあり、0に等しい場合、Pはラインセグメントにあります。
C#のコードは次のとおりです。Edgeのケースはチェックしませんでした。
_ public static bool IsInPolygon(Point[] poly, Point point)
{
var coef = poly.Skip(1).Select((p, i) =>
(point.Y - poly[i].Y)*(p.X - poly[i].X)
- (point.X - poly[i].X) * (p.Y - poly[i].Y))
.ToList();
if (coef.Any(p => p == 0))
return true;
for (int i = 1; i < coef.Count(); i++)
{
if (coef[i] * coef[i - 1] < 0)
return false;
}
return true;
}
_
私はシンプルな長方形でうまくテストします:
_ Point[] pts = new Point[] { new Point { X = 1, Y = 1 },
new Point { X = 1, Y = 3 },
new Point { X = 3, Y = 3 },
new Point { X = 3, Y = 1 } };
IsInPolygon(pts, new Point { X = 2, Y = 2 }); ==> true
IsInPolygon(pts, new Point { X = 1, Y = 2 }); ==> true
IsInPolygon(pts, new Point { X = 0, Y = 2 }); ==> false
_
Linqクエリの説明:
poly.Skip(1)
==>は、poly
リストの_1
_の位置から開始し、_(point.Y - poly[i].Y)*(p.X - poly[i].X) - (point.X - poly[i].X) * (p.Y - poly[i].Y)
_によって方向を計算する新しいリストを作成します(前述)参照された段落で)。同様の例(別の操作を使用):
_lst = 2,4,8,12,7,19
lst.Skip(1) ==> 4,8,12,7,19
lst.Skip(1).Select((p,i)=>p-lst[i]) ==> 2,4,4,-5,12
_
ここでコードを確認しましたが、すべて問題があります。
最適な方法は次のとおりです。
/// <summary>
/// Determines if the given point is inside the polygon
/// </summary>
/// <param name="polygon">the vertices of polygon</param>
/// <param name="testPoint">the given point</param>
/// <returns>true if the point is inside the polygon; otherwise, false</returns>
public static bool IsPointInPolygon4(PointF[] polygon, PointF 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;
}
受け入れられた答えは、私のプロジェクトではうまくいきませんでした。見つかったコードを使用することになりました here 。
public static bool IsInPolygon(Point[] poly, Point p)
{
Point p1, p2;
bool inside = false;
if (poly.Length < 3)
{
return inside;
}
var oldPoint = new Point(
poly[poly.Length - 1].X, poly[poly.Length - 1].Y);
for (int i = 0; i < poly.Length; i++)
{
var newPoint = new Point(poly[i].X, poly[i].Y);
if (newPoint.X > oldPoint.X)
{
p1 = oldPoint;
p2 = newPoint;
}
else
{
p1 = newPoint;
p2 = oldPoint;
}
if ((newPoint.X < p.X) == (p.X <= oldPoint.X)
&& (p.Y - (long) p1.Y)*(p2.X - p1.X)
< (p2.Y - (long) p1.Y)*(p.X - p1.X))
{
inside = !inside;
}
oldPoint = newPoint;
}
return inside;
}
レイキャスティングアルゴリズムを使用できます。 ポリゴンの問題点 については、ウィキペディアのページで詳しく説明されています。
外部からそのポイントまでの光線がポリゴンの境界に触れる回数を数えるのと同じくらい簡単です。偶数回接触すると、ポイントはポリゴンの外側にあります。奇数回タッチすると、ポイントは内部にあります。
レイが接触する回数をカウントするには、レイとすべてのポリゴンサイドの交差をチェックします。
meowNET anwserは、ポリゴンのポリゴン頂点を含まず、水平エッジ上に正確にポイントします。正確な「包括的」アルゴリズムが必要な場合:
public static bool IsInPolygon(this Point point, IEnumerable<Point> polygon)
{
bool result = false;
var a = polygon.Last();
foreach (var b in polygon)
{
if ((b.X == point.X) && (b.Y == point.Y))
return true;
if ((b.Y == a.Y) && (point.Y == a.Y) && (a.X <= point.X) && (point.X <= b.X))
return true;
if ((b.Y < point.Y) && (a.Y >= point.Y) || (a.Y < point.Y) && (b.Y >= point.Y))
{
if (b.X + (point.Y - b.Y) / (a.Y - b.Y) * (a.X - b.X) <= point.X)
result = !result;
}
a = b;
}
return result;
}
私の答えはここから取られます: リンク
Cコードを取得してC#に変換し、動作させました
static bool pnpoly(PointD[] poly, PointD pnt )
{
int i, j;
int nvert = poly.Length;
bool c = false;
for (i = 0, j = nvert - 1; i < nvert; j = i++)
{
if (((poly[i].Y > pnt.Y) != (poly[j].Y > pnt.Y)) &&
(pnt.X < (poly[j].X - poly[i].X) * (pnt.Y - poly[i].Y) / (poly[j].Y - poly[i].Y) + poly[i].X))
c = !c;
}
return c;
}
次の例でテストできます:
PointD[] pts = new PointD[] { new PointD { X = 1, Y = 1 },
new PointD { X = 1, Y = 2 },
new PointD { X = 2, Y = 2 },
new PointD { X = 2, Y = 3 },
new PointD { X = 3, Y = 3 },
new PointD { X = 3, Y = 1 }};
List<bool> lst = new List<bool>();
lst.Add(pnpoly(pts, new PointD { X = 2, Y = 2 }));
lst.Add(pnpoly(pts, new PointD { X = 2, Y = 1.9 }));
lst.Add(pnpoly(pts, new PointD { X = 2.5, Y = 2.5 }));
lst.Add(pnpoly(pts, new PointD { X = 1.5, Y = 2.5 }));
lst.Add(pnpoly(pts, new PointD { X = 5, Y = 5 }));
完全なアルゴリズムとCコードは http://alienryderflex.com/polygon/ で入手できます。
c#/ winformsに変換するのは簡単です。
Kai Hormann(エルランゲン大学)およびAlexander Agathos(アテネ大学)によるこの素晴らしい15ページの論文をお勧めします。最高のアルゴリズムをすべて統合し、巻線ソリューションとレイキャスティングソリューションの両方を検出できるようにします。
このアルゴリズムは実装が面白く、価値があります。しかし、それは非常に複雑であるため、その一部を直接使用しても意味がありません。代わりに、最も効率的で汎用性の高いアルゴリズムが必要な場合は、これがそれであると確信しています。
アルゴリズムは非常に高度に最適化されているため、複雑になります。そのため、理解して実装するには多くの読書が必要になります。ただし、レイキャストアルゴリズムとワインディングナンバーアルゴリズムの両方の利点が組み合わされており、結果は両方の回答を一度に提供する単一の数値になります。結果がゼロより大きく奇数の場合、ポイントは完全に含まれますが、結果が偶数の場合、ポイントは折り返されるポリゴンのセクションに含まれます。
幸運を。
(OPが使用しているように)整数で動作するPointInPolygon関数のビジネスクリティカルな実装は、水平線、垂直線、および対角線についてユニットテストされ、線上のポイントはテストに含まれます(関数はtrueを返します)。
これは古い質問のようですが、トレースの以前のすべての例にはいくつかの欠点があります。水平または垂直のポリゴンライン、ポリゴン境界ライン、またはエッジの順序(時計回り、反時計回り)を考慮しないでください。
次の関数は、エッジ、頂点、およびエッジと頂点の内側と外側のポイントを使用して、私が思いついたテスト(正方形、菱形、対角線、合計124テスト)に合格します。
コードは、ポリゴン座標の連続するペアごとに次のことを行います。
アルゴリズムは、必要に応じてfloatおよびdoubleに簡単に適合させることができます。
補足として-過去10年近くに、ポリゴンのポイントをチェックし、場合によっては失敗するソフトウェアがどれほど作成されたのだろうか。
public static bool IsPointInPolygon(Point point, IList<Point> polygon)
{
var intersects = new List<int>();
var a = polygon.Last();
foreach (var b in polygon)
{
if (b.X == point.X && b.Y == point.Y)
{
return true;
}
if (b.X == a.X && point.X == a.X && point.X >= Math.Min(a.Y, b.Y) && point.Y <= Math.Max(a.Y, b.Y))
{
return true;
}
if (b.Y == a.Y && point.Y == a.Y && point.X >= Math.Min(a.X, b.X) && point.X <= Math.Max(a.X, b.X))
{
return true;
}
if ((b.Y < point.Y && a.Y >= point.Y) || (a.Y < point.Y && b.Y >= point.Y))
{
var px = (int)(b.X + 1.0 * (point.Y - b.Y) / (a.Y - b.Y) * (a.X - b.X));
intersects.Add(px);
}
a = b;
}
intersects.Sort();
return intersects.IndexOf(point.X) % 2 == 0 || intersects.Count(x => x < point.X) % 2 == 1;
}
これは古い質問ですが、Saeedの回答を最適化しました。
public static bool IsInPolygon(this List<Point> poly, Point point)
{
var coef = poly.Skip(1).Select((p, i) =>
(point.y - poly[i].y) * (p.x - poly[i].x)
- (point.x - poly[i].x) * (p.y - poly[i].y));
var coefNum = coef.GetEnumerator();
if (coef.Any(p => p == 0))
return true;
int lastCoef = coefNum.Current,
count = coef.Count();
coefNum.MoveNext();
do
{
if (coefNum.Current - lastCoef < 0)
return false;
lastCoef = coefNum.Current;
}
while (coefNum.MoveNext());
return true;
}
IEnumeratorsとIEnumerablesを使用します。