一. 道格拉斯-普克算法
道格拉斯-普克算法(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"));
}
结果:
其中红线是原始轨迹, 绿线是抽稀后的轨迹