1、抽稀
通俗点讲,直接举个栗子吧:我们知道运动轨迹实际上是由很多个经纬度坐标连接而成。那么我们是否需要将所有运动时记录下来的经纬度坐标都用来绘制轨迹呢?其实是没必要的,很多数据其实是多余的,实际上将这些多余的数据剔除仍然能保证轨迹曲线形状大致不变,而且还能让曲线更平滑更节省存储空间,类似这样的过程我们就称之为抽稀。抽稀的算法很多,这里将介绍一种经典的算法:道格拉斯-普克(Douglas-Peuker)算法。
2、道格拉斯-普克(Douglas-Peuker)算法
还是举个栗子吧,假设在平面坐标系上有一条由N个坐标点组成的曲线,已设定一个阈值epsilon。
(1)首先,将起始点与结束点用直线连接, 再找出到该直线的距离最大,同时又大于阈值epsilon的点并记录下该点的位置(这里暂且称其为最大阈值点),如图所示:![](https://images2018.cnblogs.com/blog/1006977/201807/1006977-20180712172612403-1632989239.png)
(2)接着,以该点为分界点,将整条曲线分割成两段(这里暂且称之为左曲线和右曲线),将这两段曲线想象成独立的曲线然后重复操作(1),找出两边的最大阈值点,如图所示:
![](https://images2018.cnblogs.com/blog/1006977/201807/1006977-20180712172634319-1439498367.png)
(3)最后,重复操作(2)(1)直至再也找不到最大阈值点为止,然后将所有最大阈值点按顺序连接起来便可以得到一条更简化的,更平滑的,与原曲线十分近似的曲线,如图所示:
![](https://images2018.cnblogs.com/blog/1006977/201807/1006977-20180712172651701-1705496730.png)
代码实现
先计算出距离开始、结束点距离最大的点。
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); }}
public class DouglasPeuckerUtil { public static void main(String[] args) { System.out.print("原始坐标:"); Listpoints = 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; }
链接:https://www.jianshu.com/p/bf595477a124來源:简书简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。