使用Point2D和凸包算法获取地理围栏

 需求

根据传入的经纬度坐标点集合计算这些坐标点的外围,即地理围栏(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;
    }
}

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值