看到网络上大量重复的博客,书写类似的代码。但英文解释并不清晰,希望通过此博客给出详尽解释。
import java.awt.geom.Point2D;
import java.util.List;
public class GeoUtils {
/**
* 判断点是否在多边形内
* <p>
* 整个算法的思路为:作点平行于y轴的射线,这样就可以直接比较交点是否大于点(x,y)中y的值,可以简化判断。
*
* @param point 检测点
* @param pts 多边形的顶点
* @return 点在多边形内返回true, 否则返回false
*/
public static boolean isPtInPoly(Point2D.Double point, List<Point2D.Double> pts) {
int polygonSidesCount = pts.size();
//如果点位于多边形的顶点或边上,也算做点在多边形内,直接返回true
boolean boundOrVertex = true;
// 射线与多边形相交的次数
int intersectCount = 0;
//浮点类型计算时候与0比较时候的容差
double precision = 2e-10;
// 相邻两点
Point2D.Double p1, p2;
// 当前点
Point2D.Double p = point;
// 线段起点
p1 = pts.get(0);
for (int i = 1; i <= polygonSidesCount; ++i) {
//按照点录入的顺序依次检查相邻两点组成的线段和当前的点的关系。
if (p.equals(p1)) {
//如果当前点就是多边形的顶点之一,直接返回。
return boundOrVertex;
}
// 线段终点
p2 = pts.get(i % polygonSidesCount);
if (p.x < Math.min(p1.x, p2.x) || p.x > Math.max(p1.x, p2.x)) {
//点在x轴上的映射,明显超出了线段在x轴上的投影。此时冲着上做射线,肯定没有交点,直接跳过。
p1 = p2;
continue;
}
if (p.x > Math.min(p1.x, p2.x) && p.x < Math.max(p1.x, p2.x)) {
// 当前点在线段于x轴上的投影内
if (p.y <= Math.max(p1.y, p2.y)) {
//当前点的 y 坐标小于 线段对y轴投影的最大值
if (p1.x == p2.x && p.y >= Math.min(p1.y, p2.y)) {
// 若线段同样是平行于y轴,则可以断定,当前点,在多边形的这条垂直于x轴的边上。
return boundOrVertex;
}
if (p1.y == p2.y) {
// 若线段为平行于x轴的水平线
if (p1.y == p.y) {
//当前点正好位于该水平线上,直接返回该点位于多边形的一条边上。
return boundOrVertex;
} else {
//如果当前点在水平线以下,增加一个交点。
++intersectCount;
}
} else {
// 如果不是水平线,则用两点式求当前点的x带入多边形线段的直线方程后,对应的y的坐标
double xInSideLineFormulaResultY = (p.x - p1.x) * (p2.y - p1.y) / (p2.x - p1.x) + p1.y;
if (Math.abs(p.y - xInSideLineFormulaResultY) < precision) {
// 误差允许范围内,改点就在线段上,则表明,该点位于多边形的一个边上。
return boundOrVertex;
}
if (p.y < xInSideLineFormulaResultY) {
// 如果线段上取得的y比当前点的y要大,当前做向上的射线,肯定交于上方的一个点。
++intersectCount;
}
}
}
} else {
// 当前点不在线段投影到x轴的区间中
if (p.x == p2.x && p.y <= p2.y) {
// 但恰好位于线段终点的x坐标对应的平行于y轴上的线上的低于终点y的一点
// 此时检查下一点能否将其x边界包含。
Point2D.Double p3 = pts.get((i + 1) % polygonSidesCount);
if (p.x >= Math.min(p1.x, p3.x) && p.x <= Math.max(p1.x, p3.x)) {
// 若当前点的x坐标位于 p1和p3组成的线段关于x轴的投影中,则记为该点的射线只穿过端点一次。
++intersectCount;
} else {
// 若当前点的x坐标不能包含在p1和p3组成的线段关于x轴的投影中,则点射线通过的两条线段组成了一个弯折的部分,
// 此时我们记射线穿过该端点两次
intersectCount += 2;
}
// 此判断的核心思路是由点在两条线段的内部还是外部去思考得出的
}
}
// 进行下一个线段的判断
p1 = p2;
}
if (intersectCount % 2 == 0) {
//偶数在多边形外
return false;
} else {
//奇数在多边形内
return true;
}
}
}