道格拉斯-普克 抽稀算法 附javascript實現

道格拉斯-普克抽稀算法,是用來對大量冗餘的圖形數據點進行壓縮以提取必要的數據點。該算法實現抽稀的過程是:先將一條曲線首尾點虛連一條直線,求其他各點到該直線的距離,取其最大者與規定的臨界值相比較,若小於臨界值,則將直線兩端間各點所有捨去,不然將離該直線距離最大的點保留,並將原線條分紅兩部分,對每部分線條再實施該抽稀過程,直到結束。抽稀結果點數隨選取限差臨界值的增大而減小,應用時應根據精度來選取限差臨界值,以得到最好的效果。javascript


如下轉載自:垂距法與道格拉斯-普克法刪除冗餘頂點效率的比較html

道格拉斯- 普克法可描述爲:將一條曲線首末頂點虛連一條直線 ,求出其他各頂點到該直線的距離 ,選其最大者與規定的限差相比較 ,若小於等於限差 ,則將直線兩端間各點所有刪去;若大於限差 ,則離該直線距離最大的頂點保留 ,並以此爲界 ,把曲線分爲兩部分 ,對這兩部分重複使用上述方法 ,直至最終沒法做進一步的壓縮爲止 (見圖 3)。
imgjava

道格拉斯 2 普克法有一個十分突出的優勢 ,即它是一個總體算法 ,在通常狀況下可保留較大彎曲形態上的特徵點。經道格拉斯-普克法壓縮後獲得的圖形如圖 4所示。因爲該算法可準確刪除小彎曲上的定點 ,故能從體上有效地保持線要素的形態特徵。正是由於道格拉斯-普克法具備這樣突出的優勢 ,因此已經在線要素地自動製圖中獲得了較普遍的應用。但道格拉斯- 普克法較垂距法複雜 ,且一般編程實現時須要採用遞歸方 ,有必定的難度。
imggit

轉載endgithub

如下是javascript版本的實現算法

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
   <title>DouglasPeucker</title>    
</head>
<body>
   <div id="processBefore" style="background-color:#ccc;height:100px;position:relative;"></div>
        <div id="processAfter" style="background-color:#ccc;height:100px;margin-top:10px;position:relative;"></div>
</body>
<script type="text/javascript" src="Items/js/K1.source.js">
var points = [{
    x: 10,
    y: 10
}, {
    x: 20,
    y: 30
}, {
    x: 30,
    y: 12
}, {
    x: 35,
    y: 5
}, {
    x: 40,
    y: 22
}, {
    x: 50,
    y: 12
}, {
    x: 80,
    y: 40
}];
var Helper = {
    renderPointsTo: function(points, anchor) {
        var d;
        for (var i = 0, p, a = K(anchor); i < points.length; i++) {
            p = points[i];
            if (a) {
                a.appendChild(K('div', {}, {
                    position: 'absolute',
                    left: p.x + 'px',
                    top: p.y + 'px',
                    width: '5px',
                    height: '5px',
                    backgroundColor: 'green',
                    fontSize: '1px'
                }));
            }

        }
    }
};
Helper.renderPointsTo(points, 'processBefore');
var DouglasPeucker = {
    getProcessPoints: function(points, tolerance) {
        /// <summary>獲取處理後的點</summary>
        /// <param name="points" type="Array">包含點的數組</param>
        /// <param name="tolerance" type="Float">取樣臨界值</param>
        /// <returns type="Array" />
        if (!K.isArr(points) || !points.length) { //當points不是數組或沒有值時,直接返回一個空數組
            return [];
        }
        if (points.length < 3) return points; //小於3個點時不抽稀,由於1個或2個點沒法進行抽稀
        var firstPoint = 0,
            lastPoint = points.length - 1; //取開始點與結束點的下標
        var pointIndexsToKeep = []; //保存須要點下標的數組
        pointIndexsToKeep.push(firstPoint);
        pointIndexsToKeep.push(lastPoint); //開始與結束不進行處理,直接保留
        while (points[firstPoint] == points[lastPoint]) { //處理閉合狀況,閉合時,強制斷開
            lastPoint--;
        }
        this.reduce(points, firstPoint, lastPoint, tolerance, pointIndexsToKeep); //抽稀
        var resultPoints = []; //返回的點數組
        pointIndexsToKeep.sort(function(a, b) { //排序,這個可排可不排
            return a - b;
        });
        for (var i = 0; i < pointIndexsToKeep.length; i++) {
            resultPoints.push(points[pointIndexsToKeep[i]]);
        }
        return resultPoints;
    },
    reduce: function(points, firstPoint, lastPoint, tolerance, pointIndexsToKeep) {
        /// <summary>抽稀處理</summary>
        /// <param name="points" type="Array">待抽稀的數組</param>
        /// <param name="firstPoint" type="Integer">起點</param>
        /// <param name="lastPoint" type="Integer">終點</param>
        /// <param name="tolerance" type="Float">取樣臨界值</param>
        /// <param name="pointIndexsToKeep" type="Array">保留點下標的數組</param>
        var maxDis = 0,
            idxFarthest = 0; //定義最大長度及最遠點的下標
        for (var i = firstPoint, dis; i < lastPoint; i++) {
            dis = this.perpendicularDistance(points[firstPoint], points[lastPoint], points[i]); //獲取當前點到起點與
            if (dis > maxDis) { //保存最遠距離
                maxDis = dis;
                idxFarthest = i;
            }
        }
        if (maxDis > tolerance && idxFarthest != 0) { //若是最遠距離大於臨界值
            pointIndexsToKeep.push(idxFarthest);
            this.reduce(points, firstPoint, idxFarthest, tolerance, pointIndexsToKeep);
            this.reduce(points, idxFarthest, lastPoint, tolerance, pointIndexsToKeep);
        }
    },
    perpendicularDistance: function(beginPoint, endPoint, comparePoint) {
        /// <summary>計算給出的comparePoint到beginPoint與endPoint組成的直線的垂直距離</summary>
        /// <param name="beginPoint" type="Object">起始點</param>
        /// <param name="endPoint" type="Object">結束點</param>
        /// <param name="comparePoint" type="Object">比較點</param>
        /// <returns type="Float" />
        //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)|   *Area of triangle
        //Base = v((x1-x2)2+(y1-y2)2)                               *Base of Triangle*
        //Area = .5*Base*H                                          *Solve for height
        //Height = Area/.5/Base
        var area = Math.abs(0.5 * (beginPoint.x * endPoint.y + endPoint.x * comparePoint.y + comparePoint.x * beginPoint.y -
            endPoint.x * beginPoint.y - comparePoint.x * endPoint.y - beginPoint.x * comparePoint.y));
        var bottom = Math.sqrt(Math.pow(beginPoint.x - endPoint.x, 2) + Math.pow(beginPoint.y - endPoint.y, 2));
        var height = area / bottom * 2;
        return height;
    }
};
Helper.renderPointsTo(DouglasPeucker.getProcessPoints(points, 14), 'processAfter');
</script>
</html>

宣傳下個人區塊管理框架Magix:https://github.com/thx/magix編程

歡迎試用Magix、star與fork數組

相關文章
相關標籤/搜索