道格拉斯-普克抽稀算法,是用来对大量冗余的图形数据点进行压缩以提取必要的数据点。

传统的道格拉斯算法是通过递归方式实现的,如:

算法的基本思路是:对每一条曲线的首末点虚连一条直线,求所有点与直线的距离,并找出最大距离值dmax ,用dmax与限差D相比:若dmax <D,这条曲线上的中间点全部舍去;若dmax ≥D,保留dmax 对应的坐标点,并以该点为界,把曲线分为两部分,对这两部分重复使用该方法。

传统的道格拉斯-普克算法都是递归实现,然而有时候递归的层次太深的话会出现栈溢出的情况。在此,介绍一种非递归的算法。

改进D-P算法的具体步骤如下:

1、将首末两点加入队列,遍历队列。

2、计算出其它点到以首末点连线的直线的最大距离并和限差进行比较。

3、若大于等于限差,将这个点加入到队列中两点之间,并重新遍历序列,以相邻点作为始末点。若小于限差则将首末点中间点全部删除。

代码如下:

Point.java

package com.lwy.domain;

public class Point {

	private double x;
	private double y;
	public double getX() {
		return x;
	}
	public void setX(double x) {
		this.x = x;
	}
	public double getY() {
		return y;
	}
	public void setY(double y) {
		this.y = y;
	}
	public Point(double x, double y) {
		super();
		this.x = x;
		this.y = y;
	}
}
Douglas.java
<pre name="code" class="java">package com.lwy.douglas;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import com.lwy.domain.Point;

public class Douglas {

	// 存储用于处理的点的List列表
	private List<Point> list = null;

	// 给定的阈值(限差)threshold
	private final double threshold = 1;

	public Douglas(List<Point> list) {
		this.list = list;
	}

	/**
	 * 道格拉斯算法,处理List<Point>序列
	 * 先将首末两点加入points序列中,然后在list序列找出距离首末点连线距离的最大距离值dmax并与阈值进行比较,
	 * 若大于阈值则将这个点加入points序列,重新遍历points序列。否则将两点间的所有点(list)移除。
	 * 
	 * @return 返回经过道格拉斯算法后得到的点的序列
	 */
	public List<Point> douglasPeucker() {
		List<Point> points = new LinkedList<>();
		// 将首末两点加入到序列中
		points.add(this.list.get(0));
		points.add(list.get(list.size() - 1));
		for (int i = 0; i < points.size() - 1; i++) {
			int start = this.list.indexOf(points.get(i));
			int end = this.list.indexOf(points.get(i + 1));
			// 比较是否是相邻点
			if (end - start == 1) {
				continue;
			}
			String value = getMaxDistance(start, end);
			double maxDist = Double.parseDouble(value.split(",")[0]);
			// 大于限差将点加入points序列
			if (maxDist >= threshold) {
				int position = Integer.parseInt(value.split(",")[1]);
				points.add(i + 1, list.get(position));
				// 将循环变量i初始化,即重新遍历points序列
				i = -1;
			} else {
				/**
				 * 将两点间的点全部删除
				 */
				int removePosition = start + 1;
				for (int j = 0; j < end - start - 1; j++) {
					this.list.remove(removePosition);
				}
			}
		}
		return points;
	}

	/**
	 * 根据给定的始末点,计算出距离始末点直线的最远距离和点在list列表中的位置
	 * @param start 遍历list起始点
	 * @param end 遍历list终点
	 * @return maxDiatance + "," + position 返回最大距离+","+在list中位置
	 */
	private String getMaxDistance(int start, int end) {
		double maxDiatance = -1;
		int position = -1;
		double distance = getDistance(this.list.get(start), this.list.get(end));
		for (int i = start; i < end; i++) {
			double firstSide = getDistance(this.list.get(start),
					this.list.get(i));
			if (firstSide < threshold) {
				continue;
			}
			double lastSide = getDistance(this.list.get(end), this.list.get(i));
			if (lastSide < threshold) {
				continue;
			}
// 使用海伦公式求距离
			double p = (distance + firstSide + lastSide) / 2;
			double dis = Math.sqrt(p * (p - distance) * (p - firstSide)
					* (p - lastSide))
					/ distance;
			if (dis > maxDiatance) {
				maxDiatance = dis;
				position = i;
			}
		}
		return maxDiatance + "," + position;
	}

	// 两点间距离公式
	private double getDistance(Point first, Point last) {
		return Math.sqrt(Math.pow(first.getX() - last.getX(), 2)
				+ Math.pow(first.getY() - last.getY(), 2));
	}

	public static void main(String[] args) {
		Point point0 = new Point(1, 4);
		Point point1 = new Point(2, 3);
		Point point2 = new Point(4, 2);
		Point point3 = new Point(6, 6);
		Point point4 = new Point(7, 7);
		Point point5 = new Point(8, 6);
		Point point6 = new Point(9, 5);
		Point point7 = new Point(10, 10);
		List<Point> list = new ArrayList<>();
		list.add(point0);
		list.add(point1);
		list.add(point2);
		list.add(point3);
		list.add(point4);
		list.add(point5);
		list.add(point6);
		list.add(point7);
		Douglas douglas = new Douglas(list);
		List<Point> points = douglas.douglasPeucker();
		for (int i = 0; i < points.size(); i++) {
			System.out.println("(" + points.get(i).getX() + ","
					+ points.get(i).getY() + ")");
		}
	}
}



结果为:


(1.0,4.0)
(4.0,2.0)
(7.0,7.0)
(9.0,5.0)
(10.0,10.0)