道格拉斯轨迹抽稀算法

一. 道格拉斯-普克算法

道格拉斯-普克算法(Douglas-Peucker Algorithm),也称为道格拉斯-普克折线简化算法,是一种用于将折线数据简化为较少点的算法。该算法的目标是减少点的数量,同时保持原始折线的形状。它在计算机图形学和地理信息系统中有广泛应用,特别是在处理路线、边界或其他多段线数据时。

以下是道格拉斯-普克算法的基本步骤:

1. 输入折线:输入由若干点组成的折线。

2. 设定阈值:设定一个阈值距离(epsilon),折线上的某个中间点到首尾连线之间的垂直距离的最大容忍值),用来控制简化精度和保留细节程度,值越小保真度越高

3. 递归简化

    a.从折线的第一个点到最后一个点绘制一条直线。

    b.找到折线上距离这条直线最远的点,如果该点到直线的距离小于阈值,则删除该点及其之间的点,保留该直线。

    c.如果最远的点距离大于或等于阈值,则将折线在该点处分割为两段,分别对这两段递归地应用上述步骤。

4. 结束条件:当所有的点到对应直线的距离都小于阈值时,递归结束。

二. java 实现

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 道格拉斯-普克算法(抽稀轨迹算法)
 * @Author: HY
 * @Date: 2025/07/18
 */
public class DouglasPeucker {

    /**
     * 道格拉斯-普克算法
     * @param points 输入点集
     * @param epsilon 阈值
     * @param lonKey 经度键
     * @param latKey 纬度键
     * @return
     */
    public static List<Map<String, Object>> douglasPeucker(List<Map<String, Object>> points, double epsilon, String lonKey, String latKey) {
        // // 如果点集为空或点数小于3,直接返回原点集
        if (points == null || points.size() < 3) {
            return points;
        }
        // 终点索引
        int end = points.size();
        // 最大距离
        double dmax = 0;
        // 记录最大距离点的索引
        int index = 0;
        // 遍历点集的每个点(除了起点和终点),计算其到起点和终点构成的线段的垂直距离
        for (int i = 1; i < end - 1; i++) {
            double d = perpendicularDistance(points.get(i), points.get(0), points.get(end - 1), lonKey, latKey);
            // 更新最大距离和对应的索引
            if (d > dmax) {
                index = i;
                dmax = d;
            }
        }

        List<Map<String, Object>> result;
        // 如果最大距离大于阈值 epsilon,则递归地处理子点集
        if (dmax > epsilon) {
            List<Map<String, Object>> recResults1 = douglasPeucker(points.subList(0, index + 1), epsilon, lonKey, latKey);
            List<Map<String, Object>> recResults2 = douglasPeucker(points.subList(index, end), epsilon, lonKey, latKey);

            result = new ArrayList<>(recResults1);
            // 移除重复点(子点集中包含相同点)
            result.remove(result.size() - 1);
            result.addAll(recResults2);
        } else {
            // 如果最大距离小于或等于阈值 epsilon,则只保留起点和终点
            result = new ArrayList<>();
            result.add(points.get(0));
            result.add(points.get(end - 1));
        }
        // 返回简化后的点集
        return result;
    }

    /**
     * 计算点到线段的垂直距离
     * @param pt
     * @param lineStart
     * @param lineEnd
     * @param lonKey
     * @param latKey
     * @return
     */
    private static double perpendicularDistance(Map<String, Object> pt, Map<String, Object> lineStart,
                                                Map<String, Object> lineEnd, String lonKey, String latKey) {
        // 获取线段起点和终点的经纬度
        double lineEndLon = Double.valueOf(String.valueOf(lineEnd.get(lonKey)));
        double lineStartLon = Double.valueOf(String.valueOf(lineStart.get(lonKey)));
        double lineEndLat = Double.valueOf(String.valueOf(lineEnd.get(latKey)));
        double lineStartLat = Double.valueOf(String.valueOf(lineStart.get(latKey)));
        // 计算线段的向量 dx 和 dy
        double dx = lineEndLon - lineStartLon;
        double dy = lineEndLat - lineStartLat;
        // 计算向量的模(长度)
        double mag = Math.sqrt(dx * dx + dy * dy);
        if (mag > 0.0) {
            // 归一化 dx
            dx /= mag;
            // 归一化 dy
            dy /= mag;
        }
        // 获取点的经纬度
        double ptLon = Double.valueOf(String.valueOf(pt.get(lonKey)));
        double ptLat = Double.valueOf(String.valueOf(pt.get(latKey)));
        // 计算起点到点的向量 pvx 和 pvy
        double pvx = ptLon - lineStartLon;
        double pvy = ptLat - lineStartLat;
        // 计算投影长度 pvdot
        double pvdot = dx * pvx + dy * pvy;
        // 计算垂直向量 ax 和 ay
        double ax = pvx - pvdot * dx;
        double ay = pvy - pvdot * dy;
        // 返回点到线段的垂直距离
        return Math.sqrt(ax * ax + ay * ay);
    }


}

三. 测试并绘图

我们用一段轨迹数据进行测试:

[
    {
        "lon": 116.18077096092645,
        "lat": 39.82972125077484
    },
    {
        "lon": 116.18543759756221,
        "lat": 39.83384169984885
    },
    {
        "lon": 116.18978385707646,
        "lat": 39.836675015819736
    },
    {
        "lon": 116.19580831666053,
        "lat": 39.83873617511611
    },
    {
        "lon": 116.20015802072965,
        "lat": 39.84028245901544
    },
    {
        "lon": 116.20819657395447,
        "lat": 39.84182923628859
    },
    {
        "lon": 116.21389586270857,
        "lat": 39.84311760979335
    },
    {
        "lon": 116.2239612967453,
        "lat": 39.84363254409158
    },
    {
        "lon": 116.23100560891714,
        "lat": 39.84414778009955
    },
    {
        "lon": 116.24173360970468,
        "lat": 39.844149315134246
    },
    {
        "lon": 116.25648563166709,
        "lat": 39.844924969754715
    },
    {
        "lon": 116.26789452706356,
        "lat": 39.84492500427325
    },
    {
        "lon": 116.27695459835786,
        "lat": 39.84595555503944
    },
    {
        "lon": 116.29339563613757,
        "lat": 39.84621432067166
    },
    {
        "lon": 116.30312693117173,
        "lat": 39.84698808148795
    },
    {
        "lon": 116.31822871851455,
        "lat": 39.846988162439345
    },
    {
        "lon": 116.33735946081424,
        "lat": 39.848278054763824
    },
    {
        "lon": 116.36052202196686,
        "lat": 39.84905319770456
    },
    {
        "lon": 116.37159368013329,
        "lat": 39.8500819934585
    },
    {
        "lon": 116.38133119454574,
        "lat": 39.84956807067465
    },
    {
        "lon": 116.39342465613703,
        "lat": 39.84467398497395
    },
    {
        "lon": 116.39208072259237,
        "lat": 39.83771451702947
    },
    {
        "lon": 116.39275352557928,
        "lat": 39.82869227509937
    },
    {
        "lon": 116.38805351532164,
        "lat": 39.82095785691925
    },
    {
        "lon": 116.3689164844676,
        "lat": 39.81399681178365
    },
    {
        "lon": 116.36522866886082,
        "lat": 39.817089829824596
    },
    {
        "lon": 116.35817415110228,
        "lat": 39.81734885027083
    },
    {
        "lon": 116.35112329257458,
        "lat": 39.818122814365864
    },
    {
        "lon": 116.33903805720354,
        "lat": 39.81812352391813
    },
    {
        "lon": 116.32964000314797,
        "lat": 39.81709266947837
    },
    {
        "lon": 116.32225621884953,
        "lat": 39.816835107805275
    },
    {
        "lon": 116.31151661809798,
        "lat": 39.81399955310869
    },
    {
        "lon": 116.29708663441937,
        "lat": 39.811164953133044
    },
    {
        "lon": 116.28668762658344,
        "lat": 39.810909661912575
    },
    {
        "lon": 116.27795929015656,
        "lat": 39.80936100576076
    },
    {
        "lon": 116.27057502662655,
        "lat": 39.807813585387294
    },
    {
        "lon": 116.2625272352796,
        "lat": 39.80678492645521
    },
    {
        "lon": 116.25179807811588,
        "lat": 39.803952558997224
    },
    {
        "lon": 116.24271228636962,
        "lat": 39.800591521315
    },
    {
        "lon": 116.24237865824188,
        "lat": 39.794661476536106
    },
    {
        "lon": 116.24105034180303,
        "lat": 39.78796410308138
    },
    {
        "lon": 116.24606943830133,
        "lat": 39.78408725879004
    },
    {
        "lon": 116.25514649411684,
        "lat": 39.7828090250577
    },
    {
        "lon": 116.26319676507592,
        "lat": 39.78409600433977
    },
    {
        "lon": 116.26856235695232,
        "lat": 39.78589802392628
    },
    {
        "lon": 116.27326061022006,
        "lat": 39.79053971150864
    },
    {
        "lon": 116.28802469237348,
        "lat": 39.795179168578244
    },
    {
        "lon": 116.29171757823627,
        "lat": 39.79647013298796
    },
    {
        "lon": 116.30245498683587,
        "lat": 39.796466911637026
    },
    {
        "lon": 116.31050962401144,
        "lat": 39.79440259587534
    },
    {
        "lon": 116.319571890507,
        "lat": 39.791306219594304
    },
    {
        "lon": 116.32527805333154,
        "lat": 39.788726501730224
    },
    {
        "lon": 116.33165574052987,
        "lat": 39.78717825788604
    },
    {
        "lon": 116.33803350166528,
        "lat": 39.785372258200454
    },
    {
        "lon": 116.34441137969532,
        "lat": 39.78408210209486
    },
    {
        "lon": 116.35313915179154,
        "lat": 39.78330770327375
    },
    {
        "lon": 116.35951702160338,
        "lat": 39.786144969553646
    },
    {
        "lon": 116.36388075690627,
        "lat": 39.789240189100184
    },
    {
        "lon": 116.36458181044418,
        "lat": 39.79342015680996
    },
    {
        "lon": 116.3740498931237,
        "lat": 39.79918336441068
    },
    {
        "lon": 116.38647302381577,
        "lat": 39.80201981908829
    },
    {
        "lon": 116.39519843781073,
        "lat": 39.80176253016282
    },
    {
        "lon": 116.4035903232118,
        "lat": 39.801762626981855
    },
    {
        "lon": 116.4109799689245,
        "lat": 39.802535592760876
    },
    {
        "lon": 116.41840815830915,
        "lat": 39.7994405227453
    },
    {
        "lon": 116.42042807917198,
        "lat": 39.794538600568444
    },
    {
        "lon": 116.41774339753727,
        "lat": 39.79041093707161
    },
    {
        "lon": 116.40901929853038,
        "lat": 39.78524973216199
    },
    {
        "lon": 116.40096359928032,
        "lat": 39.78034663717051
    },
    {
        "lon": 116.39323530521693,
        "lat": 39.77622099366141
    },
    {
        "lon": 116.38215559294622,
        "lat": 39.773125000644
    },
    {
        "lon": 116.36905851829664,
        "lat": 39.770805417185386
    },
    {
        "lon": 116.35764640611399,
        "lat": 39.76899736612964
    },
    {
        "lon": 116.34287236559413,
        "lat": 39.76771020792225
    },
    {
        "lon": 116.33380696811037,
        "lat": 39.76668062777776
    },
    {
        "lon": 116.31937204168696,
        "lat": 39.76436006350232
    }
]

测试代码:

    public static void main(String[] args) throws Exception{
        ObjectMapper mapper = new ObjectMapper();

        List<Map<String, Object>> data = mapper.readValue(new File("D:/line.json"), 
                new TypeReference<List<Map<String, Object>>>() {});
        System.out.println(douglasPeucker(data, 0.01, "lon", "lat"));
    }

结果:

其中红线是原始轨迹, 绿线是抽稀后的轨迹

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纯洁的小魔鬼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值