1、道格拉斯-普克抽稀算法說明
道格拉斯-普克抽稀算法是用來對大量冗餘的圖形數據點進行壓縮以提取必要的數據點。
該算法實現抽稀的過程是:
1)對曲線的首末點虛連一條直線,求曲線上所有點與直線的距離,並找出最大距離值dmax,用dmax與事先給定的閾值D相比:
2)若dmax<D,則將這條曲線上的中間點全部捨去;則該直線段作爲曲線的近似,該段曲線處理完畢。
若dmax≥D,保留dmax對應的座標點,並以該點爲界,把曲線分爲兩部分,對這兩部分重複使用該方法,即重複1),2)步,直到所有dmax均<D,即完成對曲線的抽稀。
顯然,本算法的抽稀精度也與閾值相關,閾值越大,簡化程度越大,點減少的越多,反之,化簡程度越低,點保留的越多,形狀也越趨於原曲線。
2、舉例說明
輸出結果:1.0 4.0,4.0 2.0,7.0 7.0,9.0 5.0,10.0 10.0,其中2 3,6 6,8 6兩個座標被抽稀掉了。
示意圖:
假設在平面座標系上有一條由N個座標點組成的曲線,已設定一個閾值epsilon。
(1)首先,將起始點與結束點用直線連接, 再找出到該直線的距離最大,同時又大於閾值epsilon的點並記錄下該點的位置(這裏暫且稱其爲最大閾值點),如圖所示:
(2)接着,以該點爲分界點,將整條曲線分割成兩段(這裏暫且稱之爲左曲線和右曲線),將這兩段曲線想象成獨立的曲線然後重複操作(1),找出兩邊的最大閾值點,如圖所示:
(3)最後,重複操作(2)(1)直至再也找不到最大閾值點爲止,然後將所有最大閾值點按順序連接起來便可以得到一條更簡化的,更平滑的,與原曲線十分近似的曲線,如圖所示:
3、Java代碼實現
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 類:
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;
}
輸出結果:
4 Python代碼實現
4.1 遞歸實現
4.2 非遞歸實現
參考:
1、https://www.jianshu.com/p/bf595477a124
2、https://zhuanlan.zhihu.com/p/74906781
3、https://blog.csdn.net/cdl2008sky/article/details/7701769