在处理矢量化数据时,记录中往往会有很多重复数据,对进一步数据处理带来诸多不便。多余的数据一方面浪费了较多的存储空间,另一方面造成所要表达的图形不光滑或不符合标准。因此要通过某种规则,在保证矢量曲线形状不变的情况下, 最大限度地减少数据点个数,这个过程称为抽稀
起因
公司做的一款应用,要实时显示人员位置,并显示走过的轨迹路线。然而轨迹点太多了,可能一天的数据就会有几千甚至几万个,过多点会影响性能,于是想到了抽稀
解决方案
使用Douglas-Peuker算法,也称抽稀算法。
- 该算法是在曲线 起始点S和结束点E 之间连接一条直线SE,该直线为曲线的弦;
- 首先找到曲线上离直线距离最远的点A,计算其与曲线弦的距离B;
- 比较该距离与预先给定的阈值threshold的大小,如果小于threshold,则该直线段作为曲线的近似,该段曲线处理完毕;
- 如果距离大于阈值,则用A将曲线分为两段SA和EA,并分别对两段取信进行第1~3的处理;
- 当所有曲线都处理完毕时,依次连接各个分割点形成的折线,即可以作为曲线的近似
代码实现
// 计算两点间距离
function calculationDistance(point1, point2) {
// point1[0]此处的字段要与自己定义的一致,如果是对象,可以如下操作;如果是json数据按照 point1.latitude 的形式操作
const x1 = point1[0];
const y1 = point1[1];
const x2 = point2[0];
const y2 = point2[1];
const xy = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
return xy;
}
// 计算点pX到点pA和pB所确定的直线的距离
function distToSegment(start, end, center) {
const a = Math.abs(calculationDistance(start, end));
const b = Math.abs(calculationDistance(start, center));
const c = Math.abs(calculationDistance(end, center));
const p = (a + b + c) / 2.0;
const s = Math.sqrt(Math.abs(p * (p - a) * (p - b) * (p - c)));
return (s * 2.0) / a;
}
// 递归方式压缩轨迹
function compressLine(coordinate, result, start, end, dMax) {
if (start < end) {
let maxDist = 0;
let currentIndex = 0;
const startPoint = coordinate[start];
const endPoint = coordinate[end];
for (let i = start + 1; i < end; i++) {
const currentDist = distToSegment(startPoint, endPoint, coordinate[i]);
if (currentDist > maxDist) {
maxDist = currentDist;
currentIndex = i;
}
}
if (maxDist >= dMax) {
// 将当前点加入到过滤数组中
// console.warn('maxDist'+maxDist);
result.push(coordinate[currentIndex]);
// 将原来的线段以当前点为中心拆成两段,分别进行递归处理
compressLine(coordinate, result, start, currentIndex, dMax);
compressLine(coordinate, result, currentIndex, end, dMax);
}
}
return result;
}
// 供调用的抽稀入口函数
/**
* coordinate 原始轨迹Array<{latitude,longitude}>
* dMax 允许的最大误差距离;默认为10,同时此值需参考 currentDist 值进行设置,例如 currentDist = 0.00000134724232,则 dMax 设置为 0.000001 比较合适
* resultLatLng 抽稀后的数据
*/
function douglasPeucker(coordinate, dMax = 10) {
if (!coordinate || !(coordinate.length > 2)) {
return null;
}
coordinate.forEach((item, index) => {
item.id = index;
});
const result = compressLine(coordinate, [], 0, coordinate.length - 1, dMax);
result.push(coordinate[0]);
result.push(coordinate[coordinate.length - 1]);
const resultLatLng = result.sort((a, b) => {
if (a.id < b.id) {
return -1;
} else if (a.id > b.id) return 1;
return 0;
});
resultLatLng.forEach((item) => {
item.id = 0;
});
return resultLatLng;
}
// 调用
datas= douglasPeucker(newData,0.000001);