GPS轨迹压缩之Douglas-Peucker算法

前言

最近在做的IOT平台涉及到画轨迹线的业务。谈到轨迹线,设备上报上来的数据量巨大,甚至活跃的设备一天上报来的数据都甚至几十万。前端没法对这个数据去处理进行画线取轨迹图像。所以就有了轨迹压缩。

轨迹压缩算法

轨迹压缩算法分为两大类,分别是无损压缩和有损压缩,无损压缩算法主要包括哈夫曼编码,有损压缩算法又分为批处理方式和在线数据压缩方式,其中批处理方式又包括DP(Douglas-Peucker)算法、TD-TR(Top-Down Time-Ratio)算法和Bellman算法,在线数据压缩方式又包括滑动窗口、开放窗口、基于安全区域的方法等。

本次轨迹压缩决定采用相对简单的DP算法。

DP算法步骤如下:

(1)在轨迹曲线在曲线首尾两点A,B之间连接一条直线AB,该直线为曲线的弦;

(2)遍历曲线上其他所有点,求每个点到直线AB的距离,找到最大距离的点C,最大距离记为dmax;

(3)比较该距离dmax与预先定义的阈值Dmax大小,如果dmax<Dmax,则将该直线AB作为曲线段的近似,曲线段处理完毕;

(4)若dmax>=Dmax,则使C点将曲线AB分为AC和CB两段,并分别对这两段进行(1)~(3)步处理;

(5)当所有曲线都处理完毕时,依次连接各个分割点形成的折线,即为原始曲线的路径。

海伦公式

DP算法中需要求点到直线的距离,该距离指的是垂直欧式距离,即直线AB外的点C到直线AB的距离d,此处A、B、C三点均为经纬度坐标;我们采用三角形面积相等法求距离d,具体方法是:A、B、C三点构成三角形,该三角形的面积有两种求法,分别是普通方法(底x高/2)和海伦公式,海伦公式如下:在这里插入图片描述,其中p为半周长:在这里插入图片描述

假设有一个三角形,边长分别为a、b、c,三角形的面积S可由以下公式求得:
我们通过海伦公式求得三角形面积,然后就可以求得高的大小,此处高即为距离d。要想用海伦公式,必须求出A、B、C三点两两之间的距离,该距离公式也是一个数学公式。

​ 注意:求出距离后,要加上绝对值,以防止距离为负数。

平均误差

平均误差指的是压缩时忽略的那些点到对应线段的距离之和除以总点数得到的数值。

压缩率

压缩率的计算公式如下:在这里插入图片描述

具体代码实现

1.为了DP算法压缩之后能匹配到本身数据库查询出的结果记录,(因为结果记录列表的每一条字段是可伸缩的KV对且不固定)准备一个点的实体。

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class TYPoint {
    public String id;//点ID
    public double lng;//经度
    public double lat;//纬度
}

2.DP算法实现类

public class DPUtil {

    /**
     * 默认的点到轨迹线最大距离误差阈值(单位:米)
     */
    private static double defaultDMax = 30.0;

    /**
     * DP算法入口
     * 传入压缩前的轨迹点集合
     * 输出压缩后的结果轨迹点集合
     * @param originPoints 压缩前的轨迹点集合
     * @param dMax 点到轨迹线最大距离误差阈值
     * @return
     */
    public static List<TYPoint> dpAlgorithm(List<TYPoint> originPoints, Double dMax){
        List<TYPoint> resultPoints = new ArrayList<>();
        resultPoints.add(originPoints.get(0));//获取第一个原始经纬度点坐标并添加到过滤后的数组中
        resultPoints.add(originPoints.get(originPoints.size()-1));//获取最后一个原始经纬度点坐标并添加到过滤后的数组中
        //最大距离误差阈值
        if(dMax == null){
            dMax = defaultDMax;
        }
        int start = 0;
        int end = originPoints.size()-1;
        compression(originPoints,resultPoints,start,end,dMax);

        return resultPoints;
    }

    /**
     * 函数功能:根据最大距离限制,采用DP方法递归的对原始轨迹进行采样,得到压缩后的轨迹
     * @param originPoints:原始经纬度坐标点数组
     * @param resultPoints:保持过滤后的点坐标数组
     * @param start:起始下标
     * @param end:终点下标
     * @param dMax:预先指定好的最大距离误差 计算
     */
    public static void compression(List<TYPoint> originPoints, List<TYPoint> resultPoints,
                                     int start, int end, double dMax){
        if(start < end){//递归进行的条件
            double maxDist = 0;//最大距离
            int cur_pt = 0;//当前下标
            for(int i=start+1;i<end;i++){
                //当前点到对应线段的距离
                double curDist = distToSegment(originPoints.get(start),originPoints.get(end),originPoints.get(i));
                if(curDist > maxDist){
                    maxDist = curDist;
                    cur_pt = i;
                }//求出最大距离及最大距离对应点的下标
            }
            //若当前最大距离大于最大距离误差
            if(maxDist >= dMax){
                resultPoints.add(originPoints.get(cur_pt));//将当前点加入到过滤数组中
                //将原来的线段以当前点为中心拆成两段,分别进行递归处理
                compression(originPoints,resultPoints,start,cur_pt,dMax);
                compression(originPoints,resultPoints,cur_pt,end,dMax);
            }
        }
    }

    /**
     * 函数功能:使用三角形面积(使用海伦公式求得)相等方法计算点pX到点pA和pB所确定的直线的距离
     * @param pA:起始点
     * @param pB:结束点
     * @param pX:第三个点
     * @return distance:点pX到pA和pB所在直线的距离
     */
    public static double distToSegment(TYPoint pA,TYPoint pB,TYPoint pX){
        double a = Math.abs(geoDist(pA, pB));
        double b = Math.abs(geoDist(pA, pX));
        double c = Math.abs(geoDist(pB, pX));
        double p = (a+b+c)/2.0;
        double s = Math.sqrt(Math.abs(p*(p-a)*(p-b)*(p-c)));
        double d = s*2.0/a;
        return d;
    }

    /**
     * 函数功能:根据数学公式求两个经纬度点之间的距离
     * @param pA:起始点
     * @param pB:结束点
     * @return distance:距离
     */
    public static double geoDist(TYPoint pA,TYPoint pB){
        double radLat1 = Rad(pA.lat);
        double radLat2 = Rad(pB.lat);
        double delta_lon = Rad(pB.lng - pA.lng);
        double top_1 = Math.cos(radLat2) * Math.sin(delta_lon);
        double top_2 = Math.cos(radLat1) * Math.sin(radLat2) - Math.sin(radLat1) * Math.cos(radLat2) * Math.cos(delta_lon);
        double top = Math.sqrt(top_1 * top_1 + top_2 * top_2);
        double bottom = Math.sin(radLat1) * Math.sin(radLat2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.cos(delta_lon);
        double delta_sigma = Math.atan2(top, bottom);
        double distance = delta_sigma * 6378137.0;
        return distance;
    }

    /**
     * 函数功能:角度转弧度
     * @param d:角度
     * @return 返回的是弧度
     */
    public static double Rad(double d){
        return d * Math.PI / 180.0;
    }

}

从入口代码可知dMax是可以传进来的,也就是点到轨迹直线的偏移量阈值是可以设置的。这里我设置默认dMax为30.0测试了一下,4135条数据查询出来有500条,当设置dMax为100时查询出只有289条记录了。具体看业务方需要以多大的压缩限度去压缩。

  • 2
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是GPS轨迹压缩Douglas-Peucker算法的C#实现代码,包含导入.txt经度纬度数据,输出压缩后的文本: ```C# using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace DouglasPeuckerCompression { class Program { static void Main(string[] args) { // 读取.txt文件中的经纬度数据 List<Point> pointList = new List<Point>(); string filePath = "input.txt"; if (File.Exists(filePath)) { string[] lines = File.ReadAllLines(filePath); foreach (string line in lines) { string[] arr = line.Split(','); double latitude = double.Parse(arr[0]); double longitude = double.Parse(arr[1]); pointList.Add(new Point(latitude, longitude)); } } else { Console.WriteLine("文件不存在!"); return; } // Douglas-Peucker算法进行轨迹压缩 double epsilon = 0.001; // 误差阈值 List<Point> resultList = new List<Point>(); douglasPeucker(pointList, 0, pointList.Count - 1, epsilon, resultList); // 输出压缩后的文本 using (StreamWriter sw = new StreamWriter("output.txt")) { foreach (Point point in resultList) { sw.WriteLine($"{point.latitude},{point.longitude}"); } } Console.WriteLine("处理完成!"); } // 计算两点之间的距离 static double Distance(Point p1, Point p2) { double R = 6371e3; // 地球半径 double lat1 = Math.PI * p1.latitude / 180; double lat2 = Math.PI * p2.latitude / 180; double deltaLat = Math.PI * (p2.latitude - p1.latitude) / 180; double deltaLon = Math.PI * (p2.longitude - p1.longitude) / 180; double a = Math.Sin(deltaLat / 2) * Math.Sin(deltaLat / 2) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Sin(deltaLon / 2) * Math.Sin(deltaLon / 2); double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)); return R * c; } // 计算垂距离 static double PerpendicularDistance(Point p, Point start, Point end) { double area = Math.Abs((start.longitude - p.longitude) * (end.latitude - p.latitude) - (end.longitude - p.longitude) * (start.latitude - p.latitude)); double bottom = Distance(start, end); return area / bottom; } // Douglas-Peucker算法 static void douglasPeucker(List<Point> pointList, int startIndex, int endIndex, double epsilon, List<Point> resultList) { double dmax = 0; int index = 0; int n = pointList.Count; for (int i = startIndex + 1; i < endIndex; i++) { double d = PerpendicularDistance(pointList[i], pointList[startIndex], pointList[endIndex]); if (d > dmax) { dmax = d; index = i; } } if (dmax > epsilon) { List<Point> recResults1 = new List<Point>(); List<Point> recResults2 = new List<Point>(); douglasPeucker(pointList, startIndex, index, epsilon, recResults1); douglasPeucker(pointList, index, endIndex, epsilon, recResults2); for (int i = 0; i < recResults1.Count - 1; i++) { resultList.Add(recResults1[i]); } for (int i = 0; i < recResults2.Count; i++) { resultList.Add(recResults2[i]); } } else { resultList.Add(pointList[startIndex]); resultList.Add(pointList[endIndex]); } } } class Point { public double latitude; public double longitude; public Point(double latitude, double longitude) { this.latitude = latitude; this.longitude = longitude; } } } ``` 该代码实现了Douglas-Peucker算法用于GPS轨迹压缩的过程,通过读取.txt文件中的经度纬度数据,计算并压缩轨迹,最终将压缩后的经度纬度数据输出到output.txt文件中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值