Android GPS定位軌跡抽稀之道格拉斯-普克(Douglas-Peuker)算法詳解

一、抽稀

通俗點講,直接舉個栗子吧:咱們知道運動軌跡其實是由不少個經緯度座標鏈接而成。那麼咱們是否須要將全部運動時記錄下來的經緯度座標都用來繪製軌跡呢?實際上是不必的,不少數據實際上是多餘的,實際上將這些多餘的數據剔除仍然能保證軌跡曲線形狀大體不變,並且還能讓曲線更平滑更節省存儲空間,相似這樣的過程咱們就稱之爲抽稀。抽稀的算法不少,這裏將介紹一種經典的算法:道格拉斯-普克(Douglas-Peuker)算法。html

二、道格拉斯-普克(Douglas-Peuker)算法

仍是舉個栗子吧,假設在平面座標系上有一條由N個座標點組成的曲線,已設定一個閾值epsilon
(1)首先,將起始點與結束點用直線鏈接, 再找出到該直線的距離最大,同時又大於閾值epsilon的點並記錄下該點的位置(這裏暫且稱其爲最大閾值點),如圖所示:git

 
 
 

(2)接着,以該點爲分界點,將整條曲線分割成兩段(這裏暫且稱之爲左曲線和右曲線),將這兩段曲線想象成獨立的曲線而後重複操做(1),找出兩邊的最大閾值點,如圖所示:github

 
 
 

(3)最後,重複操做(2)(1)直至再也找不到最大閾值點爲止,而後將全部最大閾值點按順序鏈接起來即可以獲得一條更簡化的,更平滑的,與原曲線十分近似的曲線,如圖所示:算法

 
 
 
 
 
 
 
 
 
 
 
 

三、如何實現?

OK,終於到代碼登場了,不廢話,上代碼:
Point類:函數

public class Point {
    double x;
    double y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
        System.out.print("(" + x + "," + y + ") ");
    }

    public static Point instance(int x, int y) {
        return new Point(x, y);
    }
}

DouglasPeuckerUtil 類:this

public class DouglasPeuckerUtil {

    public static void main(String[] args) {

        System.out.print("原始座標:");

        List<Point> points = new ArrayList<>();
        List<Point> result = new ArrayList<>();

        points.add(Point.instance(1, 1));
        points.add(Point.instance(2, 2));
        points.add(Point.instance(3, 4));
        points.add(Point.instance(4, 1));
        points.add(Point.instance(5, 0));
        points.add(Point.instance(6, 3));
        points.add(Point.instance(7, 5));
        points.add(Point.instance(8, 2));
        points.add(Point.instance(9, 1));
        points.add(Point.instance(10, 6));

        System.out.println("");
        System.out
                .println("=====================================================================");
        System.out.print("抽稀座標:");

        result = DouglasPeucker(points, 1);

        for (Point p : result) {
            System.out.print("(" + p.x + "," + p.y + ") ");
        }
    }

    public static List<Point> DouglasPeucker(List<Point> points, int epsilon) {
        // 找到最大閾值點,即操做(1)
        double maxH = 0;
        int index = 0;
        int end = points.size();
        for (int i = 1; i < end - 1; i++) {
            double h = H(points.get(i), points.get(0), points.get(end - 1));
            if (h > maxH) {
                maxH = h;
                index = i;
            }
        }

        // 若是存在最大閾值點,就進行遞歸遍歷出全部最大閾值點
        List<Point> result = new ArrayList<>();
        if (maxH > epsilon) {
            List<Point> leftPoints = new ArrayList<>();// 左曲線
            List<Point> rightPoints = new ArrayList<>();// 右曲線
            // 分別提取出左曲線和右曲線的座標點
            for (int i = 0; i < end; i++) {
                if (i <= index) {
                    leftPoints.add(points.get(i));
                    if (i == index)
                        rightPoints.add(points.get(i));
                } else {
                    rightPoints.add(points.get(i));
                }
            }

            // 分別保存兩邊遍歷的結果
            List<Point> leftResult = new ArrayList<>();
            List<Point> rightResult = new ArrayList<>();
            leftResult = DouglasPeucker(leftPoints, epsilon);
            rightResult = DouglasPeucker(rightPoints, epsilon);

            // 將兩邊的結果整合
            rightResult.remove(0);//移除重複點
            leftResult.addAll(rightResult);
            result = leftResult;
        } else {// 若是不存在最大閾值點則返回當前遍歷的子曲線的起始點
            result.add(points.get(0));
            result.add(points.get(end - 1));
        }
        return result;
    }

    /**
     * 計算點到直線的距離
     * 
     * @param p
     * @param s
     * @param e
     * @return
     */
    public static double H(Point p, Point s, Point e) {
        double AB = distance(s, e);
        double CB = distance(p, s);
        double CA = distance(p, e);

        double S = helen(CB, CA, AB);
        double H = 2 * S / AB;

        return H;
    }

    /**
     * 計算兩點之間的距離
     * 
     * @param p1
     * @param p2
     * @return
     */
    public static double distance(Point p1, Point p2) {
        double x1 = p1.x;
        double y1 = p1.y;

        double x2 = p2.x;
        double y2 = p2.y;

        double xy = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
        return xy;
    }

    /**
     * 海倫公式,已知三邊求三角形面積
     * 
     * @param cB
     * @param cA
     * @param aB
     * @return 面積
     */
    public static double helen(double CB, double CA, double AB) {
        double p = (CB + CA + AB) / 2;
        double S = Math.sqrt(p * (p - CB) * (p - CA) * (p - AB));
        return S;
    }

輸出結果:spa

 
 
 

OK,平面座標上的Douglas-Peuker算法已經基本實現了!可是若是換成經緯度呢?其實不用擔憂,地圖API通常都會提供計算兩個經緯度座標之間距離的函數,因此萬變不離其宗,思路仍是同樣的,大膽點,代碼啪啪啪的敲起來吧!.net

這裏有一個基於百度地圖實現的Demo供你們參考:
https://github.com/wnn1302/TrackDemocode

四、參考示例

道格拉斯-普克抽稀算法(C#)htm

Android地圖軌跡抽稀、動態繪製

相關文章
相關標籤/搜索