需求
根据传入的经纬度坐标点集合计算这些坐标点的外围,即地理围栏(geo-fencing)
经过在网上的搜索,认为凸包算法可以满足本需求,但是网上的凸包算法都是针对整形的坐标点,而本需求的坐标点都是double类型,故将网上的凸包算法修改为如下版本(采用的是Graham扫描法,时间复杂度:O(n㏒n))
package com.allqj.housing_library_java.util;
import java.awt.geom.Point2D;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class ConvexHullAlgorithmUtil {
/**
* @Author
* @Description 凸包算法步骤
* 1. 把所有点放在二维坐标系中,则纵坐标最小的点一定是凸包上的点,例P0。
* 2. 把所有点的坐标平移一下,使 P0 作为原点。
* 3. 计算各个点相对于 P0 的幅角 α ,按从小到大的顺序对各个点排序。当 α 相同时,距离 P0 比较近的排在前面。
* (以上是准备步骤,以下开始求凸包)
* 以上,我们已经知道了凸包上的第一个点 P0 和第二个点 P1,我们把它们放在栈里面。现在从步骤3求得的那个结果里,把 P1 后面的那个点拿出来做当前点,即 P2 。接下来开始找第三个点:
* 4. 连接P0和栈顶的那个点,得到直线 L 。看当前点是在直线 L 的右边还是左边。如果在直线的右边就执行步骤5;如果在直线上,或者在直线的左边就执行步骤6。
* 5. 如果在右边,则栈顶的那个元素不是凸包上的点,把栈顶元素出栈。执行步骤4。
* 6. 当前点是凸包上的点,把它压入栈,执行步骤7。
* 7. 检查当前的点 P2 是不是步骤3那个结果的最后一个元素。是最后一个元素的话就结束。如果不是的话就把 P2 后面那个点做当前点,返回步骤4。
* 排序的目的:直接按照顺序遍历所有顶点一次
* @Date 2020/10/26
* @Param [pointList]
* @return java.util.List<java.awt.geom.Point2D.Double>
**/
public static List<Point2D.Double> GrahamScan(List<Point2D.Double> pointList){
//排序
List<Point2D.Double> points = pointList.stream()
.sorted(Comparator.comparingDouble(Point2D.Double::getY).thenComparingDouble(Point2D.Double::getX))
.distinct()
.collect(Collectors.toList());
int pointSize = points.size();
int[] stack = new int[pointSize+2];
int p = 0;
//一个O(n)的循环
for (int i = 0; i < pointSize; i++) {
while (p >= 2 && cross(points.get(stack[p - 2]), points.get(i), points.get(stack[p - 1])) > 0)
p--;
stack[p++] = i;
}
int inf = p + 1;
for (int i = pointSize -2; i >= 0; i--){
if (equal(points.get(stack[p-2]), points.get(i))) continue;
while (p >= inf && cross(points.get(stack[p-2]), points.get(i), points.get(stack[p-1])) > 0)
p--;
stack[p++] = i;
}
int len = Math.max(p - 1, 1);
List<Point2D.Double> ret = new ArrayList<>();
for (int i = 0; i < len; i++){
ret.add(points.get(stack[i]));
}
return ret;
}
private static int cross(Point2D.Double o, Point2D.Double a, Point2D.Double b){
BigDecimal ox = new BigDecimal(o.x);
BigDecimal oy = new BigDecimal(o.y);
BigDecimal ax = new BigDecimal(a.x);
BigDecimal ay = new BigDecimal(a.y);
BigDecimal bx = new BigDecimal(b.x);
BigDecimal by = new BigDecimal(b.y);
// double v = (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
return ax.subtract(ox).multiply(by.subtract(oy)).subtract(ay.subtract(oy).multiply(bx.subtract(ox))).compareTo(BigDecimal.ZERO);
}
private static boolean equal(Point2D.Double a, Point2D.Double b){
return a.x == b.x && a.y == b.y;
}
}