JAVA实现小精度多边形等距外扩

涉及技术

  • 腾讯地图
  • jdk1.8

问题描述

开发一款应用,前端在地图上标记经纬度坐标点集合形成一个多边形,现需要在后端将多边形做一个等距外扩或者收缩。

在网上查了好多的算法,经过尝试验证,在小经度多边形进行收缩外扩都或多或少存在一些问题,因此自己参考写出了

一个多边形外扩收缩的算法。

JAVA代码

坐标点实体(Point)
/**
 * 地图上一个点的经纬度
 *
 * @author yanggl
 * @version 1.0
 * @date 2022/5/9 15:03
 */
public class Point implements Serializable {

    private static final long serialVersionUID = 45122365987322547L;

    /**
     * 经度
     */
    private Double longitude;

    /**
     * 纬度
     */
    private Double latitude;

    public Double getLongitude() {
        return longitude;
    }

    public void setLongitude(Double longitude) {
        this.longitude = longitude;
    }

    public Double getLatitude() {
        return latitude;
    }

    public void setLatitude(Double latitude) {
        this.latitude = latitude;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("{");
        sb.append("longitude=").append(longitude);
        sb.append(", latitude=").append(latitude);
        sb.append('}');
        return sb.toString();
    }

}
向量工具类 (VectorUtil)
/**
 * 向量工具类
 *
 * @author yanggl
 * @version 1.0
 * @date 2022/7/8 10:29
 */
public class VectorUtil {

    /**
     * 向量求模数
     */
    public static double norm(Point point) {
        return Math.sqrt(point.getLongitude() * point.getLongitude() + point.getLatitude() * point.getLatitude());
    }

    /**
     * 向量加法
     */
    public static Point add(Point point1, Point point2) {
        Point point = new Point();
        point.setLongitude(point1.getLongitude() + point2.getLongitude());
        point.setLatitude(point1.getLatitude() + point2.getLatitude());
        return point;
    }

    /**
     * 向量减法
     */
    public static Point sub(Point point1, Point point2) {
        Point point = new Point();
        point.setLongitude(point1.getLongitude() - point2.getLongitude());
        point.setLatitude(point1.getLatitude() - point2.getLatitude());
        return point;
    }

    /**
     * 向量乘法
     */
    public static Point mul(Point p, double d) {
        Point point = new Point();
        point.setLongitude(p.getLongitude() * d);
        point.setLatitude(p.getLatitude() * d);
        return point;
    }

    /**
     * 向量叉乘
     */
    public static double cross(Point p1, Point p2) {
        return p1.getLongitude() * p2.getLatitude() - p1.getLatitude() * p2.getLongitude();
    }

}
核心工具类 (DistanceUtil)
/**
 * 工具类
 *
 * @author yanggl
 * @version 1.0
 * @date 2022/7/8 10:23
 */
public class DistanceUtil {

    /**
     * 操作多边形扩张/收缩
     *
     * @param points 待扩/收缩张多边形
     * @param dist   距离 正数扩张,负数收缩
     * @return 扩张/收缩后的多边形
     */
    public static List<Point> expansion(List<Point> points, double dist) {
        if (points.size() < 3) {
            throw new RuntimeException("not enough 3 point");
        }
        List<Point> npList = new LinkedList<>();
        List<Double> lngList = new LinkedList<>();
        List<Double> latList = new LinkedList<>();

        for (Point point : points) {
            lngList.add(point.getLongitude());
            latList.add(point.getLatitude());
        }
        Double minLng = Collections.min(lngList);
        Double minLat = Collections.min(latList);

        for (Point point : points) {
            Point p = new Point();
            p.setLongitude(point.getLongitude() - minLng);
            p.setLatitude(point.getLatitude() - minLat);
            npList.add(p);
        }

        List<Point> list = enlarge(npList, dist);
        LinkedList<Point> resultList = new LinkedList<>();
        for (Point point : list) {
            Point np = new Point();
            np.setLongitude(point.getLongitude() + minLng);
            np.setLatitude(point.getLatitude() + minLat);
            resultList.add(np);
        }

        return resultList;
    }

    /**
     * 多边形外扩/收缩
     */
    private static List<Point> enlarge(List<Point> points, double dist) {
        LinkedList<Point> resultList = new LinkedList<>();
        int size = points.size();
        double angle = dist * dmi();
        angle = isConvex(points) ? angle : -angle;

        for (int i = 0; i < size; i++) {
            Point pi = points.get(i);
            Point v1 = VectorUtil.sub(points.get((i - 1 + size) % size), pi);
            Point v2 = VectorUtil.sub(points.get((i + 1) % size), pi);
            double norm1 = VectorUtil.norm(v1);
            double norm2 = VectorUtil.norm(v2);

            if (norm1 <= 0 || norm2 <= 0) {
                // 剔除重复点(或距离极近的点)
                points.remove(i);
                return enlarge(points, dist);
            }

            Point normalizeV1 = VectorUtil.mul(v1, 1 / norm1);
            Point normalizeV2 = VectorUtil.mul(v2, 1 / norm2);
            double sinTheta = VectorUtil.cross(normalizeV1, normalizeV2);
            Point qi = VectorUtil.add(pi, VectorUtil.mul(VectorUtil.add(normalizeV1, normalizeV2), angle / sinTheta));
            resultList.add(qi);
        }

        return resultList;
    }

    /**
     * 计算1米代表的角度 这里只是近似计算,事实上随着纬度的变化其变动范围将受到影响
     */
    private static double dmi() {
        double s = 6378.137 * 1000;
        return 1 / (Math.PI / 180 * s);
    }

    /**
     * 判断是否凸多边形
     */
    private static boolean isConvex(List<Point> points) {
        double area = 0;
        Point p1 = VectorUtil.sub(points.get(1), points.get(0));
        Point p2;
        for (int i = 2; i < points.size(); i++) {
            p2 = VectorUtil.sub(points.get(i), points.get(0));
            area += VectorUtil.cross(p1, p2);
            p1 = p2;
        }
        return area > 0;
    }

    /**
     * 多边形周长
     */
    private static double perimeter(List<Point> points) {
        int size = points.size();
        double perimeter = 0.0d;
        Point point;
        for (int i = 0; i < size; i++) {
            point = points.get(i);
            perimeter += VectorUtil.norm(VectorUtil.sub(points.get((i - 1 + size) % size), point));
        }
        return perimeter;
    }

}

经测试,对于小经度多边形的外扩是能够正常的。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值