判断点P是否在多边形中是计算几何中一个非常基本但是十分重要的算法。以点P为端点,向左方作射线L,由于多边形是有界的,所以射线L的左端一定在多边形外,考虑沿着L从无穷远处开始自左向右移动,遇到和多边形的第一个交点的时候,进入到了多边形的内部,遇到第二个交点的时候,离开了多边形,……所以很容易看出当L和多边形的交点数目C是奇数的时候,P在多边形内,是偶数的话P在多边形外。
但是有些特殊情况要加以考虑。如图下图(a)(b)(c)(d)所示。在图(a)中,L和多边形的顶点相交,这时候交点只能计算一个;在图(b)中,L和多边形顶点的交点不应被计算;在图(c)和(d) 中,L和多边形的一条边重合,这条边应该被忽略不计。如果L和多边形的一条边重合,这条边应该被忽略不计。
为了统一起见,我们在计算射线L和多边形的交点的时候,1。对于多边形的水平边不作考虑;2。对于多边形的顶点和L相交的情况,如果该顶点是其所属的边上纵坐标较大的顶点,则计数,否则忽略;3。对于P在多边形边上的情形,直接可判断P属于多边行。由此得出算法的伪代码如下:
count ← 0;
以P为端点,作从右向左的射线L;
for 多边形的每条边s
do if P在边s上
then return true;
if s不是水平的
then if s的一个端点在L上
if 该端点是s两端点中纵坐标较大的端点
then count ← count+1
else if s和L相交
then count ← count+1;
if count mod 2 = 1
then return true;
else return false;
其中做射线L的方法是:设P'的纵坐标和P相同,横坐标为正无穷大(很大的一个正数),则P和P'就确定了射线L。
判断点是否在多边形中的这个算法的时间复杂度为O(n)。
另外还有一种算法是用带符号的三角形面积之和与多边形面积进行比较,这种算法由于使用浮点数运算所以会带来一定误差,不推荐大家使用。
二、判断点是否在多边形中(操作)
为加快判别速度,首先计算多边形的外包矩形,判断点是否落在外包矩形内,只有满足落在外包矩形内的条件的点,才进入下一步“射线法”的计算。
下面是C#实现的代码
/// <summary>
/// 功能:判断点是否在多边形内
/// 方法:求解通过该点的水平线与多边形各边的交点
/// 结论:单边交点为奇数,成立!
/// 参数: 返回1表示肯定在多边形内;-1肯定不在多边形内;0表示在多边形的边上;
/// Point p 指定的某个点
/// Point[] ptPolygon 多边形的各个顶点坐标(首末点可以不一致)
/// int nCount 多边形定点的个数
/// </summary>
/// <param name="p"></param>
/// <param name="ptPolygon"></param>
/// <returns></returns>
public static int PtInPolygon(Point p, Point[] ptPolygon)
{
int nCount = ptPolygon.Length;
bool isBeside = false;// 记录是否在多边形的边上
#region 矩形外区域
double maxx;
double maxy;
double minx;
double miny;
if (nCount > 0)
{
maxx = ptPolygon[0].X;
minx = ptPolygon[0].X;
maxy = ptPolygon[0].Y;
miny = ptPolygon[0].Y;
for (int j = 1; j < nCount; j++)
{
if (ptPolygon[j].X >= maxx)
maxx = ptPolygon[j].X;
else if (ptPolygon[j].X <= minx)
minx = ptPolygon[j].X;
if (ptPolygon[j].Y >= maxy)
maxy = ptPolygon[j].Y;
else if (ptPolygon[j].Y <= miny)
miny = ptPolygon[j].Y;
}
if ((p.X > maxx) || (p.X < minx) || (p.Y > maxy) || (p.Y < miny))
return -1;
}
#endregion
#region 射线法
int nCross = 0;
for (int i = 0; i < nCount; i++)
{
Point p1 = ptPolygon[i];
Point p2 = ptPolygon[(i + 1) % nCount];
// 求解 y=p.y 与 p1p2 的交点
if (p1.Y == p2.Y) // p1p2 与 y=p0.y平行
{
if (p.Y == p1.Y && p.X >= min(p1.X, p2.X) && p.X <= max(p1.X, p2.X))
{
isBeside = true;
continue;
}
}
if (p.Y < min(p1.Y, p2.Y) || p.Y > max(p1.Y, p2.Y)) // 交点在p1p2延长线上
continue;
// 求交点的 X 坐标 --------------------------------------------------------------
double x = (double)(p.Y - p1.Y) * (double)(p2.X - p1.X) / (double)(p2.Y - p1.Y) + p1.X;
if (x > p.X)
nCross++; // 只统计单边交点
else if (x == p.X)
isBeside = true;
}
if (isBeside)
return 0;//多边形边上
else if (nCross % 2 == 1)// 单边交点为偶数,点在多边形之外 ---
return 1;//多边形内
return -1;//多边形外
#endregion
}
private static int min(int x, int y)
{
if (x > y)
return y;
else
return x;
}
private static int max(int x, int y)
{
if (x > y)
return x;
else
return y;
}