一、抽稀
通俗点讲,直接举个栗子吧:咱们知道运动轨迹其实是由不少个经纬度坐标链接而成。那么咱们是否须要将全部运动时记录下来的经纬度坐标都用来绘制轨迹呢?实际上是不必的,不少数据实际上是多余的,实际上将这些多余的数据剔除仍然能保证轨迹曲线形状大体不变,并且还能让曲线更平滑更节省存储空间,相似这样的过程咱们就称之为抽稀。抽稀的算法不少,这里将介绍一种经典的算法:道格拉斯-普克(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 points = new ArrayList<>();
List 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 DouglasPeucker(List 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 result = new ArrayList<>();
if (maxH > epsilon) {
List leftPoints = new ArrayList<>();// 左曲线
List 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 leftResult = new ArrayList<>();
List 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
四、参考示例