吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2761|回复: 11
收起左侧

[Java 转载] 利用道格拉斯-普克算法优化压缩经纬度图形点位数(进阶版)

  [复制链接]
三木猿 发表于 2021-6-24 17:10
本帖最后由 三木猿 于 2021-7-2 16:55 编辑

代码已更新成进阶版,原版代码太过简陋,让我们的算法工程师看的很不顺眼,所以又更新了一版,这次的10000个点能简化成200多个点,并且不丢失轮廓
分得清吗,前一个图的有1103个点,后一个图有185个点。
道格拉斯-普克算法:懒得解释,请参考:https://zhuanlan.zhihu.com/p/355323735
管你看不看得懂,拿过去用就完了!
优化前: image.png
优化后: image.png

代码:
[Java] 纯文本查看 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import net.sf.geographiclib.Geodesic;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @ClassName: DPUtil
 * @Package: tos-product
 * @Description: 点位数优化
 * @Author: [email]sen.yang@youhualin.com[/email]
 * @Date: 2021/6/25 9:25
 * @Copyright: 悠桦林信息科技(上海)有限公司
 */

public class DpUtil {

    /**
     * 默认的点到轨迹线最大距离误差阈值(单位:米)
     */
    private static final double DEFAULTD_MAX = 30.0;

    /**
     * @param dis   经纬度字符串
     * @param range 控制数据经度压缩的极差
     */
    public static String smooth(String dis, double range) {
        String[] points = dis.split(";");
        List<String> strings = Arrays.asList(points);
        //拆分list,这里的意思是每10条算一条曲线,利用道格拉斯-普克算法对这条曲线进行优化
        List<List<String>> lists = CollectionUtils.splitList(strings, strings.size() / 2);
        List<TyPoint> tyPointList = new ArrayList<>();
        int id = 0;
        for (List<String> stringList : lists) {
            List<TyPoint> list = new ArrayList<>();
            for (int i = 0; i < stringList.size(); i++) {
                if (StringUtils.isNotBlank(stringList.get(i))) {
                    TyPoint data = new TyPoint();
                    data.setId(id);
                    String[] split = stringList.get(i).split(",");
                    data.setX(Double.parseDouble(split[0]));
                    data.setY(Double.parseDouble(split[1]));
                    list.add(data);
                    id++;
                }

            }
            List<TyPoint> list1 = dpAlgorithm(list, range);
            tyPointList.addAll(list1);
        }
        StringBuilder res = new StringBuilder();
        Map<Integer, TyPoint> collect = tyPointList.stream().distinct().collect(Collectors.toMap(TyPoint::getId, Function.identity(), (k1, k2) -> k2));
        for (int i = 0; i < strings.size(); i++) {
            TyPoint tyPoint = collect.get(i);
            if (tyPoint != null) {
                res.append(tyPoint.getX()).append(",").append(tyPoint.getY()).append(";");
            }
        }
        res.deleteCharAt(res.lastIndexOf(";"));
        return res.toString();
    }

    /**
     * DP算法入口
     * 传入压缩前的轨迹点集合
     * 输出压缩后的结果轨迹点集合
     *
     * @param originPoints 压缩前的轨迹点集合
     * @param dMax         点到轨迹线最大距离误差阈值
     * @return 优化结果
     */
    public static List<TyPoint> dpAlgorithm(List<TyPoint> originPoints, Double dMax) {
        List<TyPoint> resultPoints = new ArrayList<>();
        resultPoints.add(originPoints.get(0));//获取第一个原始经纬度点坐标并添加到过滤后的数组中
        resultPoints.add(originPoints.get(originPoints.size() - 1));//获取最后一个原始经纬度点坐标并添加到过滤后的数组中
        //最大距离误差阈值
        if (dMax == null) {
            dMax = DEFAULTD_MAX;
        }
        int start = 0;
        int end = originPoints.size() - 1;
        compression(originPoints, resultPoints, start, end, dMax);

        return resultPoints;
    }

    /**
     * 函数功能:根据最大距离限制,采用DP方法递归的对原始轨迹进行采样,得到压缩后的轨迹
     *
     * @param originPoints:原始经纬度坐标点数组
     * @param resultPoints:保持过滤后的点坐标数组
     * @param start:起始下标
     * @param end:终点下标
     * @param dMax:预先指定好的最大距离误差        计算
     */
    public static void compression(List<TyPoint> originPoints, List<TyPoint> resultPoints,
                                   int start, int end, double dMax) {
        if (start < end) {//递归进行的条件
            double maxDist = 0;//最大距离
            int curPt = 0;//当前下标
            for (int i = start + 1; i < end; i++) {
                //当前点到对应线段的距离
                double curDist = distToSegment(originPoints.get(start), originPoints.get(end), originPoints.get(i));
                if (curDist > maxDist) {
                    maxDist = curDist;
                    curPt = i;
                }//求出最大距离及最大距离对应点的下标
            }
            //若当前最大距离大于最大距离误差
            if (maxDist >= dMax) {
                resultPoints.add(originPoints.get(curPt));//将当前点加入到过滤数组中
                //将原来的线段以当前点为中心拆成两段,分别进行递归处理
                compression(originPoints, resultPoints, start, curPt, dMax);
                compression(originPoints, resultPoints, curPt, end, dMax);
            }
        }
    }

    /**
     * 函数功能:使用三角形面积(使用海伦公式求得)相等方法计算点pX到点pA和pB所确定的直线的距离
     *
     * @param pA:起始点
     * @param pB:结束点
     * @param pX:第三个点
     * @return distance:点pX到pA和pB所在直线的距离
     */
    public static double distToSegment(TyPoint pA, TyPoint pB, TyPoint pX) {
        double a = Math.abs(Geodesic.WGS84.Inverse(pA.y, pA.x, pB.y, pB.x).s12);
        double b = Math.abs(Geodesic.WGS84.Inverse(pA.y, pA.x, pX.y, pX.x).s12);
        double c = Math.abs(Geodesic.WGS84.Inverse(pB.y, pB.x, pX.y, pX.x).s12);
        double p = (a + b + c) / 2.0;
        double s = Math.sqrt(Math.abs(p * (p - a) * (p - b) * (p - c)));
        return s * 2.0 / a;
    }
    /**
     * 返回一个点是否在一个多边形边界上
     *
     * @param polygon 多边形坐标点列表
     * @param point1   待判断点
     * @return true 点在多边形边上,false 点不在多边形边上。
     */
    public static boolean isPointInPolygonBoundary(String point1, String polygon) {
        String[] split = point1.split(",");
        TyPoint point = new TyPoint(0, Double.parseDouble(split[0]), Double.parseDouble(split[1]));
        List<TyPoint> mPoints = new ArrayList<>();
        String[] split1 = polygon.split(";");
        for (int i = 0; i < split1.length; i++) {
            String[] split2 = split1[i].split(",");
            mPoints.add(new TyPoint(i, Double.parseDouble(split2[0]), Double.parseDouble(split2[1])));
        }
        for (int i = 0; i < mPoints.size(); i++) {
            TyPoint p1 = mPoints.get(i);
            TyPoint p2 = mPoints.get((i + 1) % mPoints.size());
            // 取多边形任意一个边,做点point的水平延长线,求解与当前边的交点个数

            // point 在p1p2 底部 --> 无交点
            if (point.y < Math.min(p1.y, p2.y))
                continue;
            // point 在p1p2 顶部 --> 无交点
            if (point.y > Math.max(p1.y, p2.y))
                continue;

            // p1p2是水平线段,要么没有交点,要么有无限个交点
            if (p1.y == p2.y) {
                double minX = Math.min(p1.x, p2.x);
                double maxX = Math.max(p1.x, p2.x);
                // point在水平线段p1p2上,直接return true
                if ((point.y == p1.y) && (point.x >= minX && point.x <= maxX)) {
                    return true;
                }
            } else { // 求解交点
                double x = (point.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;
                if (x == point.x) // 当x=point.x时,说明point在p1p2线段上
                    return true;
            }
        }
        return false;
    }

}
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
class TyPoint {
    public int id;//点ID
    public double x;//经度
    public double y;//纬度
}

免费评分

参与人数 4吾爱币 +4 热心值 +3 收起 理由
闪的好快啊 + 1 热心回复!
vethenc + 1 + 1 牛逼克拉斯!
hack_wangyu + 1 + 1 谢谢@Thanks!
52jcool + 1 + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 三木猿 发表于 2021-7-2 16:04
niusan521 发表于 2021-7-2 12:56
上来直接撸代码看的人一脸懵逼,优化的突出点和算法精髓讲解下。。

但是大概原理你可以搜一下道格拉斯普客算法,原理比较简单
 楼主| 三木猿 发表于 2021-7-2 16:03
niusan521 发表于 2021-7-2 12:56
上来直接撸代码看的人一脸懵逼,优化的突出点和算法精髓讲解下。。

都说了不是算法工程师,咋解说,我自己都看不懂
 楼主| 三木猿 发表于 2021-6-24 17:11
大部分内容靠百度,具体哪个帖子忘了,谁找到了可以发出来,我置顶
 楼主| 三木猿 发表于 2021-6-24 17:12
本人非算法工程师,java开发一枚,别找我写啥算法,头给你卸了
52jcool 发表于 2021-6-24 17:32
高端,牛逼大了,收藏学习
sam喵喵 发表于 2021-6-24 18:06
边缘变平滑了。
大佬请教下,这么整的作用是什么
 楼主| 三木猿 发表于 2021-6-24 18:53
sam喵喵 发表于 2021-6-24 18:06
边缘变平滑了。
大佬请教下,这么整的作用是什么

点位过多,前端处理时就会卡死,需要在后端压缩下点位数量
sam喵喵 发表于 2021-6-24 19:28
三木猿 发表于 2021-6-24 18:53
点位过多,前端处理时就会卡死,需要在后端压缩下点位数量

这算法好野蛮,丢失细节颇多
恰巧 发表于 2021-6-24 21:05
看的好蒙啊
列明 发表于 2021-6-25 00:12
是粉紅色區域的邊啊。
niusan521 发表于 2021-7-2 12:56
上来直接撸代码看的人一脸懵逼,优化的突出点和算法精髓讲解下。。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-22 16:50

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表