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