多边形中的 C# 点
Posted
技术标签:
【中文标题】多边形中的 C# 点【英文标题】:C# Point in polygon 【发布时间】:2011-05-13 16:25:22 【问题描述】:我正在尝试确定一个点是否在多边形内。 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;
【问题讨论】:
您总是可以只使用Region
类。
@Saeed 我相信它们都是凸的。 @leppie,我不熟悉 Region
类。想为我写一些代码吗?
@jb:不,很容易学。
【参考方案1】:
我检查了这里的代码,都有问题。
最好的方法是:
/// <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;
【讨论】:
这很好用,回来确保你不会像我一样用 Ints 不假思索地实现它!一定要使用浮动。导致除法的舍入错误使该方法在一小部分时间内失败......非常烦人! 像魅力一样工作。我正在使用它来确定给定位置是否位于封闭多边形内(映射解决方案)。现在,最困难的部分。理解代码:P 这是迄今为止最好的解决方案,恕我直言。 接受的答案对我来说不好,但你的答案是完美的。谢谢! 次要 nit:polygon.Count() 可能是 polygon.Length。 Length 正在调用 System.Array.get_Length,它直接(并且有效地)获取长度。而 .Count() 调用的是 IEnumerable 上的扩展方法,效率较低。【参考方案2】:接受的答案在我的项目中对我不起作用。我最终使用了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;
【讨论】:
好答案。但是,为什么您需要在计算中的某些坐标上添加long
?如果你有十进制坐标,它会把事情搞砸。是复制/粘贴错误还是我遗漏了什么?
这很好用,我非常高兴。谢谢!!
如果所讨论的多边形少于三个点,则它是无效的,而不是测试的情况。【参考方案3】:
请参阅this,它是用 c++ 编写的,并且可以以相同的方式在 c# 中完成。
对于凸多边形来说太容易了:
如果多边形是凸的,那么可以 将多边形视为从 第一个顶点。一个点在 如果它是这个多边形的内部 总是站在所有人的同一边 构成路径的线段。
给定 P0 之间的线段 (x0,y0) 和 P1 (x1,y1),另一个点 P(x,y) 有如下关系 到线段。计算 (y - y0) (x1 - x0) - (x - x0) (y1 - y0)
如果小于 0 则 P 为 线段的右侧,如果更大 大于 0 它在左边,如果等于 0 则位于线段上。
这是它的 c# 代码,我没有检查边缘情况。
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
【讨论】:
嗯,它有效,但我不完全确定如何。介意解释一下吗?主要是 coef linq 语句部分。 不喜欢这段代码缺乏可调试性。宁愿看到没有 linq 语法的代码 这未通过我的一项测试。考虑一个靠近矩形角的点。多边形 = [0, 0, 2, 0, 2, 2, 0, 2] 和点 = 3, 2。该算法将这一点作为内部返回:/ @JacobMcKay:正如我写的那样,代码可能不安全,因为当时我在一分钟内写了它并且没有尝试不同的测试(只是一个测试),这就是我写的:“我没有检查边缘情况。”该代码只是解释如何实现该想法的示例。当然,它需要测试并覆盖边缘情况。 对于那些想知道这个解决方案有什么问题的人(对于凸多边形): 1. 它完全忽略了最后一条线段 2. 如果该点打开,将触发“在线段在线”检查器完全是线,而不仅仅是线段(因此它可以匹配形状之外的点)【参考方案4】:您可以使用光线投射算法。在Point in polygon problem 的***页面中有详细描述。
这就像计算从外部到该点的光线接触多边形边界的次数一样简单。如果它接触偶数次,则该点位于多边形之外。如果它接触奇数次,则该点在里面。
要计算射线接触的次数,请检查射线与所有多边形边之间的交点。
【讨论】:
【参考方案5】: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)) if ((a.X <= point.X) && (point.X <= b.X)) return true; if ((b.X <= point.X) && (point.X <= a.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;
【讨论】:
我用航空温度包络线(=多边形)对此进行了测试,这是唯一通过我所有单元测试的算法。所有其他人都未能检测到外边缘上的某些点。 @Marco 虽然其他算法应该是一致的 - 例如,它们应该包括底部和左侧边缘上的点,而不是顶部和右侧边缘上的点。就是这样,如果你有两个镶嵌多边形,任何给定的点都被报告为肯定在一个而不是另一个。如果您有一个包含所有边缘的算法,它将双重报告一个点在多边形接触的两个多边形中 需要添加|| (a.X >= point.X) && (point.X >= b.X)) 用于水平线检查 谢谢JLi,你是对的。我编辑了答案以考虑 a.X>b.X 的情况。 (我选择拆分成几个“如果”以最大限度地提高可读性)【参考方案6】:完整的算法和 C 代码可在http://alienryderflex.com/polygon/ 获得 将其转换为 c# / winforms 将是微不足道的。
【讨论】:
这正是 wpf 无限方便的场景:msdn.microsoft.com/en-us/library/ms608753.aspx【参考方案7】:我的回答取自这里:Link
我把 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 ));
【讨论】:
这正是@meowNET 在下面所做的,不是吗? 不是真的,它相似但不一样。仔细看看@N4ppeL 我就是这么做的。我认为你错了。很容易看出循环是相同的。那么您的(polygon[i].Y > point.Y) != (polygon[j].Y > point.Y)
与下面的第一个相同,而您的后半部分和第二个 if 仅在 > 和
【参考方案8】:
我对整数工作的 PointInPolygon 函数的关键业务实现(就像 OP 似乎正在使用的那样)针对水平、垂直和对角线进行了单元测试,线上的点包含在测试中(函数返回 true)。
这似乎是一个老问题,但之前的所有追踪示例都有一些缺陷:不考虑水平或垂直多边形线、多边形边界线或边的顺序(顺时针、逆时针)。
以下函数通过了我提出的测试(正方形、菱形、对角交叉,总共 124 次测试),边、顶点以及边和顶点的内部和外部都有点。
代码对每对连续的多边形坐标执行以下操作:
-
检查多边形顶点是否等于点
检查点是在水平线上还是垂直线上
计算(作为双精度)并收集相交并转换为整数
排序相交,因此边的顺序不会影响算法
检查点是否在偶数相交上(等于 - 在多边形中)
检查点 x 坐标之前的相交数是否为奇数 - 在多边形中
如有必要,算法可以很容易地适应浮点数和双精度数。
附带说明 - 我想知道在过去近 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;
【讨论】:
【参考方案9】:对于那些使用 NET Core 的用户,Region.IsVisible 可从 NET Core 3.0 获得。添加包System.Drawing.Common后,
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace Example
class Program
static bool IsPointInsidePolygon(Point[] polygon, Point point)
var path = new GraphicsPath();
path.AddPolygon(polygon);
var region = new Region(path);
return region.IsVisible(point);
static void Main(string[] args)
Point vt1 = new Point(0, 0);
Point vt2 = new Point(100, 0);
Point vt3 = new Point(100, 100);
Point vt4 = new Point(0, 100);
Point[] polygon = vt1, vt2, vt3, vt4 ;
Point pt = new Point(50, 50);
bool isPointInsidePolygon = IsPointInsidePolygon(polygon, pt);
Console.WriteLine(isPointInsidePolygon);
不太重要的是,添加 System.Drawing.Common 包会使发布文件夹的大小增加 400 KB。也许与自定义代码相比,此实现也可能更慢(在 i7-8665u 上,函数定时为 18 毫秒)。但是,我还是更喜欢这样,因为少了一件需要担心的事情。
【讨论】:
【参考方案10】:你真正需要的是 4 行来实现缠绕数方法。但首先,参考 System.Numerics 以使用复杂库。下面的代码假设您已经平移了一个点循环(存储在 cpyArr 中),因此您的候选点位于 0,0。
对于每个点对,使用第一个点创建一个复数 c1,使用第二个点创建一个复数 c2(循环中的前 2 行)
现在这里有一些复数论。将 c1 和 c2 视为向量的复数表示。要从向量 c1 到向量 c2,可以将 c1 乘以复数 Z (c1Z=c2)。 Z 旋转 c1 使其指向 c2。然后它还会拉伸或挤压 c1,使其与 c2 相匹配。为了得到这样一个神奇的数字 Z,你需要将 c2 除以 c1(因为 c1Z=c2,Z=c2/c1)。你可以查看你高中关于除复数的笔记或使用微软提供的方法。得到这个数字后,我们真正关心的是相位角。
要使用缠绕方法,我们将所有相位相加,如果点在该区域内,我们应该 +/- 2 pi。否则,总和应为 0
我添加了极端情况,“字面意思”。如果您的相位角是 +/- pi,那么您就在点对之间的边缘。在那种情况下,我会说该点是该区域的一部分并跳出循环
/// <param name="cpyArr">An array of 2 coordinates (points)</param>
public static bool IsOriginInPolygon(double[,] cpyArr)
var sum = 0.0;
var tolerance = 1e-4;
var length = cpyArr.GetLength(0);
for (var i = 0; i < length-1; i++)
//convert vertex point pairs to complex numbers for simplified coding
var c2 = new Complex(cpyArr[i+1, 0], cpyArr[i+1, 1]);
var c1 = new Complex(cpyArr[i, 0], cpyArr[i, 1]);
//find the rotation angle from c1 to c2 when viewed from the origin
var phaseDiff = Complex.Divide(c2, c1).Phase;
//add the rotation angle to the sum
sum += phaseDiff;
//immediately exit the loop if the origin is on the edge of polygon or it is one of the vertices of the polygon
if (Math.Abs(Math.Abs(phaseDiff) - Math.PI) < tolerance || c1.Magnitude < tolerance || c2.Magnitude < tolerance)
sum = Math.PI * 2;
break;
return Math.Abs((Math.PI*2 ) - Math.Abs(sum)) < tolerance;
【讨论】:
您好,非常感谢您的回答!这个问题暗示了一个应该返回布尔值的函数,你介意更新你的答案吗?【参考方案11】:我推荐 Kai Hormann(埃尔兰根大学)和 Alexander Agathos(雅典大学)撰写的这篇精彩的 15 页论文。它整合了所有最佳算法,并允许您检测缠绕和光线投射解决方案。
The Point in Polygon Problem for Arbitrary Polygons
该算法的实现很有趣,而且非常值得。然而,它是如此复杂,以至于我直接对它的任何部分都毫无意义。相反,我会坚持说,如果您想要最有效和最通用的算法,我敢肯定就是这样。
算法变得复杂,因为它是高度优化的,因此需要大量阅读才能理解和实现。然而,它结合了光线投射和缠绕数算法的优点,结果是一个数字同时提供了两个答案。如果结果大于零且为奇数,则该点被完全包含,但如果结果为偶数,则该点包含在折回自身的多边形部分中。
祝你好运。
【讨论】:
【参考方案12】:这是一个老问题,但我优化了 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。
【讨论】:
【参考方案13】:如果您在画布上绘制形状,这是一种快速简便的解决方案。
private void Canvas_MouseMove(object sender, MouseEventArgs e)
if (e.OriginalSource is Polygon)
//do something
“多边形”可以是 System.Windows.Shapes 中的任何形状。
【讨论】:
以上是关于多边形中的 C# 点的主要内容,如果未能解决你的问题,请参考以下文章