1竖哩、抽稀
通俗點講椅贱,直接舉個栗子吧:我們知道運動軌跡實際上是由很多個經(jīng)緯度坐標(biāo)連接而成。那么我們是否需要將所有運動時記錄下來的經(jīng)緯度坐標(biāo)都用來繪制軌跡呢策严?其實是沒必要的,很多數(shù)據(jù)其實是多余的饿敲,實際上將這些多余的數(shù)據(jù)剔除仍然能保證軌跡曲線形狀大致不變妻导,而且還能讓曲線更平滑更節(jié)省存儲空間,類似這樣的過程我們就稱之為抽稀怀各。抽稀的算法很多栗竖,這里將介紹一種經(jīng)典的算法:道格拉斯-普克(Douglas-Peuker)算法。
2渠啤、道格拉斯-普克(Douglas-Peuker)算法
還是舉個栗子吧狐肢,假設(shè)在平面坐標(biāo)系上有一條由N個坐標(biāo)點組成的曲線,已設(shè)定一個閾值epsilon沥曹。
(1)首先份名,將起始點與結(jié)束點用直線連接碟联, 再找出到該直線的距離最大,同時又大于閾值epsilon的點并記錄下該點的位置(這里暫且稱其為最大閾值點)僵腺,如圖所示:
(2)接著鲤孵,以該點為分界點,將整條曲線分割成兩段(這里暫且稱之為左曲線和右曲線)辰如,將這兩段曲線想象成獨立的曲線然后重復(fù)操作(1)普监,找出兩邊的最大閾值點,如圖所示:
(3)最后琉兜,重復(fù)操作(2)(1)直至再也找不到最大閾值點為止凯正,然后將所有最大閾值點按順序連接起來便可以得到一條更簡化的,更平滑的豌蟋,與原曲線十分近似的曲線廊散,如圖所示:
2、如何實現(xiàn)梧疲?
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 類:
public class DouglasPeuckerUtil {
public static void main(String[] args) {
System.out.print("原始坐標(biāo):");
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("抽稀坐標(biāo):");
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;
}
}
// 如果存在最大閾值點,就進(jìn)行遞歸遍歷出所有最大閾值點
List<Point> result = new ArrayList<>();
if (maxH > epsilon) {
List<Point> leftPoints = new ArrayList<>();// 左曲線
List<Point> rightPoints = new ArrayList<>();// 右曲線
// 分別提取出左曲線和右曲線的坐標(biāo)點
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));
}
}
// 分別保存兩邊遍歷的結(jié)果
List<Point> leftResult = new ArrayList<>();
List<Point> rightResult = new ArrayList<>();
leftResult = DouglasPeucker(leftPoints, epsilon);
rightResult = DouglasPeucker(rightPoints, epsilon);
// 將兩邊的結(jié)果整合
rightResult.remove(0);//移除重復(fù)點
leftResult.addAll(rightResult);
result = leftResult;
} else {// 如果不存在最大閾值點則返回當(dāng)前遍歷的子曲線的起始點
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;
}
輸出結(jié)果:
OK米者,平面坐標(biāo)上的Douglas-Peuker算法已經(jīng)基本實現(xiàn)了!但是如果換成經(jīng)緯度呢慢洋?其實不用擔(dān)心塘雳,地圖API一般都會提供計算兩個經(jīng)緯度坐標(biāo)之間距離的函數(shù),所以萬變不離其宗普筹,思路還是一樣的败明,大膽點,代碼啪啪啪的敲起來吧太防!
這里有一個基于百度地圖實現(xiàn)的Demo供大家參考:
https://github.com/wnn1302/TrackDemo