光线投射法:从目标点出发引一条射线,看这条射线和多边形所有边的交点数目。如果有奇数个交点,则说明在内部,如果有偶数个交点,则说明在外部。
具体实现及备注在算法中
public class LocationUtil {
public static void main(String[] args) {
// 被检测的经纬度点
CurrentPoint currentPoint=new CurrentPoint(116.08992734704537d,30.11111719172894d);
// 商业区域(百度多边形区域经纬度集合)
String partitionLocation = "117_31,127_31,120_33";
System.out.println(isInPolygon(currentPoint, partitionLocation));
}
/**
* 判断当前位置是否在多边形区域内
* <p>
* 经度为X轴 纬度为Y轴
*
* @param currentPoint 当前点
* @param partitionLocation 区域顶点 格式:经度_纬度,经度_纬度,经度_纬度
* @return
*/
public static boolean isInPolygon(CurrentPoint currentPoint, String partitionLocation) {
Point2D.Double point = new Point2D.Double(currentPoint.getLng(), currentPoint.getLat());
List<Point2D.Double> pointList = new ArrayList();
String[] strList = partitionLocation.split(",");
for (String str : strList) {
String[] points = str.split("_");
Point2D.Double polygonPoint = new Point2D.Double(Double.parseDouble(points[0]), Double.parseDouble(points[1]));
pointList.add(polygonPoint);
}
return isPtInPoly(point, pointList);
}
/**
* 判断点是否在多边形内,如果点位于多边形的顶点或边上,也算做点在多边形内,直接返回true
* 原理:基于检测点向Y轴正方向做一条衍射线,和多边形的交点为奇数时则在多边形内,反之则不在
*
* @param point 检测点
* @param pts 多边形的顶点
* @return 点在多边形内返回true, 否则返回false
*/
private static boolean isPtInPoly(Point2D.Double point, List<Point2D.Double> pts) {
int N = pts.size();
boolean boundOrVertex = true; //如果点位于多边形的顶点或边上,也算做点在多边形内,直接返回true
int intersectCount = 0;//x的交叉点计数
double precision = 2e-10; //浮点类型计算时候与0.较时候的容差
Point2D.Double p1, p2;//相邻边界顶点
Point2D.Double p = point; //当前点
p1 = pts.get(0);
for (int i = 1; i <= N; ++i) {
if (p.equals(p1)) {
return boundOrVertex;
}
p2 = pts.get(i % N);
if (p.x < Math.min(p1.x, p2.x) || p.x > Math.max(p1.x, p2.x)) {//X轴坐标不在两者之前,则一定无交点
p1 = p2;
continue;
}
if (p.x > Math.min(p1.x, p2.x) && p.x < Math.max(p1.x, p2.x)) {
if (p.y <= Math.max(p1.y, p2.y)) {//校验点Y轴坐标大于两者最大值则无交点,小于才有可能存在交点
if (p1.x == p2.x && p.y >= Math.min(p1.y, p2.y)) {// 当前逻辑永远不成立,算法当前判断毫无意义
return boundOrVertex;
}
if (p1.y == p2.y) {//p1 p2 平行于X轴
if (p1.y == p.y) {//校验点p在这条边上则直接返回成功 否则记录交点+1
return boundOrVertex;
} else {
++intersectCount;
}
} else {
double xinters = (p.x - p1.x) * (p2.y - p1.y) / (p2.x - p1.x) + p1.y;//计算斜率 p p1 的斜率 大于 p2 p1 的斜率则一定存在交点
if (Math.abs(p.y - xinters) < precision) {//p p1 的斜率 等于 p2 p1 的斜率 则p 在 p1 p2 这条边上,则一定在多边形内
return boundOrVertex;
}
if (p.y < xinters) {
++intersectCount;
}
}
}
} else {// p 与 p1 或 p2 中的一个点X轴坐标相等,也就是p向Y轴正方向做一条衍射线,正好与定点相交场景
if (p.x == p2.x && p.y <= p2.y) {//p在p2点的正下方时,引入p3点
Point2D.Double p3 = pts.get((i + 1) % N);
if (p.x >= Math.min(p1.x, p3.x) && p.x <= Math.max(p1.x, p3.x)) {//p.x 在 p1.x 和 p3.x 之间则该点的记为1个交点,p目前在多边行内部,反之为2个
++intersectCount;
} else {
intersectCount += 2;
}
}
}
p1 = p2;//以p2为起点继续判定边与校验点的交点
}
if (intersectCount % 2 == 0) {//偶数在多边形外
return false;
} else { //奇数在多边形内
return true;
}
}
}
补充:
上述算法当多边形存在覆盖时,判断将存在巨大漏洞,如下图所示:
此时我这边的解决方案是在保存多边形时校验是否存在覆盖场景,存在则提示用户调整多边形。
判断原理:任何不相邻的两条边没有交点,算法实现如下:
/**
* 判断多边形不相邻的线段是否存在交点
*
* @param partitionLocation 区域顶点 格式:经度_纬度,经度_纬度,经度_纬度
* @return
*/
public static boolean linesIntersect(String partitionLocation) {
List<Point2D.Double> pointList = new ArrayList();
String[] strList = partitionLocation.split(",");
for (String str : strList) {
if (StringUtils.isNotEmpty(str)) {
String[] points = str.split("_");
Point2D.Double polygonPoint = new Point2D.Double(Double.parseDouble(points[0]), Double.parseDouble(points[1]));
pointList.add(polygonPoint);
}
}
int size = pointList.size();
for (int i = 0; i < size; i++) {
Point2D.Double point1 = pointList.get(i % size);
Point2D.Double point2 = pointList.get((i + 1) % size);
for (int j = i + 2; j < size; j++) {
//最后一条边必与第一条边有交集,直接跳过
if (i == 0 && j == size - 1) {
continue;
}
//判断是否存在交点,存在则返回true
Point2D.Double point3 = pointList.get(j % size);
Point2D.Double point4 = pointList.get((j + 1) % size);
if (Line2D.linesIntersect(point1.x, point1.y, point2.x, point2.y, point3.x, point3.y, point4.x, point4.y)) {
return true;
}
}
}
return false;
}