Douglas-Peucker算法
根据具体情况,减少表示多边形曲线的点,可以减少内存,同时对曲线进行操作的时间。这里介绍经典的Douglas–Peucker算法,相关文献:Algorithms for the reduction of the number of points
required to represent a digitized line or its caricature(下载链接)。看完论文,算法的基本思想总结如下:
将曲线的第一个点(作为anchor)和最后一个点(作为floater)连成一条直线,计算所有中间点到该直线的最大距离Dmax。
如果Dmax<D(预先设置的阈值),则该段只保留第一个和最后一个点,该段处理结束;反之,用Dmax对应的中间点将曲线分为两段,对这两段重复第一步和第二步。
直到递归结束后,将保留的点依次连接,组成简化后的曲线。
我们常说的Douglas-Peucker算法就是论文中的Method 2,论文中还介绍了Method1。Method 2的点数会比 Method 1多一点,同时运行时间时间只有Method 1的5%左右,所以Method 1很少用。下面将简单介绍下论文中Method 1。
- 将曲线的第一个点(作为anchor)和最后一个点(作为floater)连成一条直线,计算中间所有点到该直线的最大距离Dmax。
- 如果Dmax<D(预先设置的阈值),则只保留第一个和最后一个点,中间所有点全部删除,该曲线处理结束;如果Dmax>=D,则将用Dmax对应的点做新的floater。
- 重复第一步和第二步,floater慢慢移向anchor点,直到最大距离Dmax满足条件。这时将floater点作为新的anchor,又将原始曲线的最后一个点作为floater,重复第一步、第二步和第三步。
- 最后将作过anchor的点组成简化后的曲线。
示例演示
实现一个对二维曲线的简化。
#include <iostream>
#include <cmath>
#include <utility>
#include <vector>
#include <stdexcept>
using namespace std;
typedef std::pair<double, double> Point;
double PerpendicularDistance(const Point &pt, const Point &lineStart, const Point &lineEnd)
{
double dx = lineEnd.first - lineStart.first;
double dy = lineEnd.second - lineStart.second;
//Normalize
double mag = pow(pow(dx, 2.0) + pow(dy, 2.0), 0.5);
if (mag > 0.0)
{
dx /= mag; dy /= mag;
}
double pvx = pt.first - lineStart.first;
double pvy = pt.second - lineStart.second;
//Get dot product (project pv onto normalized direction)
double pvdot = dx * pvx + dy * pvy;
//Scale line direction vector
double dsx = pvdot * dx;
double dsy = pvdot * dy;
//Subtract this from pv
double ax = pvx - dsx;
double ay = pvy - dsy;
return pow(pow(ax, 2.0) + pow(ay, 2.0), 0.5);
}
void RamerDouglasPeucker(const vector<Point> &pointList, double epsilon, vector<Point> &out)
{
if (pointList.size() < 2)
throw invalid_argument("Not enough points to simplify");
// Find the point with the maximum distance from line between start and end
double dmax = 0.0;
size_t index = 0;
size_t end = pointList.size() - 1;
for (size_t i = 1; i < end; i++)
{
double d = PerpendicularDistance(pointList[i], pointList[0], pointList[end]);
if (d > dmax)
{
index = i;
dmax = d;
}
}
// If max distance is greater than epsilon, recursively simplify
if (dmax > epsilon)
{
// Recursive call
vector<Point> recResults1;
vector<Point> recResults2;
vector<Point> firstLine(pointList.begin(), pointList.begin() + index + 1);
vector<Point> lastLine(pointList.begin() + index, pointList.end());
RamerDouglasPeucker(firstLine, epsilon, recResults1);
RamerDouglasPeucker(lastLine, epsilon, recResults2);
// Build the result list
out.assign(recResults1.begin(), recResults1.end() - 1);
out.insert(out.end(), recResults2.begin(), recResults2.end());
if (out.size() < 2)
throw runtime_error("Problem assembling output");
}
else
{
//Just return start and end points
out.clear();
out.push_back(pointList[0]);
out.push_back(pointList[end]);
}
}
int main()
{
vector<Point> pointList;
vector<Point> pointListOut;
pointList.push_back(Point(0.0, 0.0));
pointList.push_back(Point(1.0, 0.1));
pointList.push_back(Point(2.0, -0.1));
pointList.push_back(Point(3.0, 5.0));
pointList.push_back(Point(4.0, 6.0));
pointList.push_back(Point(5.0, 7.0));
pointList.push_back(Point(6.0, 8.1));
pointList.push_back(Point(7.0, 9.0));
pointList.push_back(Point(8.0, 9.0));
pointList.push_back(Point(9.0, 9.0));
RamerDouglasPeucker(pointList, 1.0, pointListOut);
cout << "result" << endl;
for (size_t i = 0; i < pointListOut.size(); i++)
{
cout << pointListOut[i].first << "," << pointListOut[i].second << endl;
}
system("pause");
return 0;
}