判断一个点是否在一个区域中时可以使用射线法,一下将介绍射线法的思路以及实现过程:
一、思路
射线法就是做一条从该点出发的射线,当穿过区域边界的次数为偶数时该点在区域外,如果是奇数则在区域内:
原理:
一条射线穿过区域的边界时只有两种情况:一种是穿入一种是穿出。对于射线来说,只要一次穿入必定对应一次穿出。
1、所以当一个点在区域外时,作出的射线经过区域边界时一定是穿入,一次穿入对应一次穿出,所以最终的穿越次数一定是偶数;
2、当该点在区域内时,第一次穿越边界一定是穿出,下一次如果穿入就又会对应一次穿出,所以区域内的点会多出单独的一次穿出,所以最终的穿越总数是奇数;
二、实现
既然知道了原理和思路,那我们进行具体实现;
为了简单处理,模拟的射线是向左射出的水平射线,并使用了java中的Point2D类,该类用于存储点的横纵坐标,方便处理;
在实现前需要知道几种特殊情况:
1、点在边界上,在边界上应该属于区域内,所以我们只需要增加相应的逻辑判断,只要点在边界上就属于区域内;
2、当射线穿过交点时:
这种情况应该算几次穿越呢,我们可以这样设想。因为我们模拟水平射线,如果射线与一条线段有交点,那么这条线段的两个端点一定在射线的两侧,所以可以将穿过的交点统一看做是射线的上侧,如果两个端点都在射线的一侧那就不算穿越,如果两个端点都在射线的两侧就算一次穿越,所以:
P1,P2都在射线一侧,不算穿越,P1,P3在射线两侧,算一次穿越,所以该点在区域内;
3、射线穿过边界线:
这种情况可以当作第一种情况处理,因为按照第一种情况P1,P2都在射线上侧,在射线的一侧,所以不算穿越;
java代码
/**
* 判断该点是否在区域内
* 说明:该点出发的射线模拟为向左射出的水平射线
*
* @param pointList
* @param point
* @return
*/
public static Boolean isInArea(List<Point2D.Double> pointList, Point2D.Double point) {
Boolean flag = true; //是否在区域内标识
Point2D.Double p, p1, p2;
Integer across = 0; //穿越次数
double precision = 2e-10; //浮点类型计算时候的比较容差
p = point;
for (int i = 0; i < pointList.size(); i++) {
p1 = pointList.get(i);
int j = (i + 1) >= pointList.size() ? 0 : (i + 1);
p2 = pointList.get(j);
// 1、判断是否在点上
if (p1.equals(p) || p2.equals(p)) {
return flag;
}
// 2、该点是否在两个端点之间
if (p.y <= Math.max(p1.y, p2.y) && p.y >= Math.min(p1.y, p2.y)) {
// 3、判断该点是否在两点连成的线段上
if (p.x <= Math.max(p1.x, p2.x) && p.x >= Math.min(p1.x, p2.x)) {
// 其一:该线段是水平线
if (p1.y == p2.y) {
if (p.y == p1.y) {
return flag;
}
}
// 其二:该线段是垂直线
if (p1.x == p2.x) {
if (p.x == p1.x) {
return flag;
}
}
// 其三:该线段是斜线
double xianShangY = p1.y + (p.x - p1.x) * (p2.y - p1.y) / (p2.x - p1.x);
if (Math.abs(xianShangY - p.y) < precision) {
// 在线上
return flag;
}
}
// 4、判断两个端点是否越过该点(因为是左水平射线)
if (p.x <= Math.min(p1.x, p2.x)) {
continue;
}
/*
该点位于两个端点之间,判断是否穿过顶点(穿过顶点时,顶点按照在射线上部处理)
穿过顶点时,只有两个端点在射线两侧的才按照穿越处理
*/
if (p1.y != p2.y && p.y != Math.min(p1.y, p2.y)) {
across++;
}
}
}
if (across % 2 == 0) {
// 偶数,在外部
return !flag;
}
return flag;
}