JavaScript实现地图轨迹点抽稀

2,216 阅读2分钟

在处理矢量化数据时,记录中往往会有很多重复数据,对进一步数据处理带来诸多不便。多余的数据一方面浪费了较多的存储空间,另一方面造成所要表达的图形不光滑或不符合标准。因此要通过某种规则,在保证矢量曲线形状不变的情况下, 最大限度地减少数据点个数,这个过程称为抽稀

起因

公司做的一款应用,要实时显示人员位置,并显示走过的轨迹路线。然而轨迹点太多了,可能一天的数据就会有几千甚至几万个,过多点会影响性能,于是想到了抽稀

解决方案

使用Douglas-Peuker算法,也称抽稀算法

  1. 该算法是在曲线 起始点S和结束点E 之间连接一条直线SE,该直线为曲线的弦;
  2. 首先找到曲线上离直线距离最远的点A,计算其与曲线弦的距离B;
  3. 比较该距离与预先给定的阈值threshold的大小,如果小于threshold,则该直线段作为曲线的近似,该段曲线处理完毕;
  4. 如果距离大于阈值,则用A将曲线分为两段SA和EA,并分别对两段取信进行第1~3的处理;
  5. 当所有曲线都处理完毕时,依次连接各个分割点形成的折线,即可以作为曲线的近似

代码实现

// 计算两点间距离
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);