高德轨迹去毛刺定位数据的两种种算法实践

232 阅读3分钟

最近做了一个项目,有一个需求要展示人员轨迹,定位数据是高德的,每分钟定位一次。开发过程中,后端负责数据接收入库,前端调后端接口查点位数据,再用高德轨迹功能绘制轨迹。完事部署到测试环境发现,只有轨迹中间的线贴合人员实际运动情况,轨迹两端带有很多尖刺形状,像高中生物课本上的神经末梢,枝枝杈杈不美观,没办法给客户交差。

从网上的查信息结合定位测试人员活动情况,了解到人员在静止状态时,GPS定位漂移情况会比较严重,运动过程中的点位倒没什么问题。先去看了高德开放平台的轨迹纠正功能,需要每个定位点的经纬度、速度和方向,我们没有速度和方向,用不了。这时候没办法回想起了大学时玩过的平移居中平均法算法,用窗口范围内点位经纬度求平均值作为当前点位的经纬度值,去平滑异常定位数据,实现很简单,样例代码如下

public static void applyCenteredMovingAverage(List<DeviceLocationRecord> data, int window) {
    if (window % 2 == 1) {
        throw new IllegalArgumentException("窗口大小必须是奇数");
    }

    int halfWindow = window / 2;

    // 遍历数据,从能够完整包含窗口的位置开始到结束
    for (int i = halfWindow; i < data.size() - halfWindow; i++) {
        double laSum = 0.000000;
        double lonSum = 0.000000;
        for (int j = -halfWindow; j <= halfWindow; j++) {
            laSum += data.get(i + j).getLatitude();
            lonSum += data.get(i + j).getLongitude();
        }
        data.get(i).setLocation(df.format(lonSum / (window + 1)) + "," + df.format(laSum / (window + 1)));
    }
}

跑出来的结果不是很理想,轨迹毛刺长度有所缓解,但还是枝枝杈杈,并且原本带有转弯的轨迹被算平均后,转弯的直角变成了倒圆角,异常数据没处理好,正常数据给搞乱了,不适合用来处理我的数据,遂舍弃。

后面盯着轨迹两端观察,觉得这个尖尖点位应该算作错误定位,不应该想着去缓解尖刺,而是应该舍弃,然后毛刺那个尖尖的点位左右两侧的直线,如果用向量表示的话,向量夹角是个钝角,大概在135°,可以用这个来判断异常点位,样例代码如下:

public static List<DeviceLocationRecord> applyVectorAngle(List<DeviceLocationRecord> data) {
    for (int i = 1; i < data.size() - 1; i++) {
        double[] v1 = new double[]{data.get(i).getLongitude() - findPre(data, i).getLongitude(), data.get(i).getLatitude() - findPre(data, i).getLatitude()};
        double[] v2 = new double[]{data.get(i + 1).getLongitude() - data.get(i).getLongitude(), data.get(i + 1).getLatitude() - data.get(i).getLatitude()};
        Double angle = angleBetweenVectors(v1, v2);
        if (angle.isNaN() || angle > 135) {
            data.get(i).setFlag(false);
        }
    }
    return data;
}

//寻找前一个点位
private static DeviceLocationRecord findPre(List<DeviceLocationRecord> data, int currentIndex) {
    DeviceLocationRecord pre = data.get(currentIndex - 1);
    while (!pre.isFlag()) {
        currentIndex--;
        if (currentIndex < 1) {
            break;
        }
        pre = data.get(currentIndex - 1);
    }
    return pre;
}


// 计算两个向量之间的夹角(返回角度制)
private static double angleBetweenVectors(double[] v1, double[] v2) {
    double dotProduct = dotProduct(v1, v2);
    double normV1 = norm(v1);
    double normV2 = norm(v2);
    double angle = Math.acos(dotProduct / (normV1 * normV2));

    return Math.toDegrees(angle); // 转换为角度制
}

// 计算向量的范数(L2范数)
private static double norm(double[] v) {
    return Math.sqrt(dotProduct(v, v));
}

// 计算两个向量的点积
private static double dotProduct(double[] v1, double[] v2) {
    if (v1.length != v2.length) {
        throw new IllegalArgumentException("向量长度不相等");
    }
    double product = 0.0;
    for (int i = 0; i < v1.length; i++) {
        product += v1[i] * v2[i];
    }
    return product;
}

定位数据经此处理后,过滤掉flag为false的返回给前端,效果很好,基本没影响到正常轨迹,且全部删掉了尖刺定位,堪称完美。这段代码中有个角度参数135,大家使用的过程中可结合自己的数据情况做修改。

以上,希望对大家有所帮助。