用鼠标画线,得到的点会很多,怎么样可以进行优化,去掉一些点,就像flash中的铅笔工具那样。
通过搜素,发现了道格拉斯普克(Douglas-Peuker)算法。
道格拉斯-普克算法(Douglas–Peucker algorithm,亦称为拉默-道格拉斯-普克算法、迭代适应点算法、分裂与合并算法)是将曲线近似表示为一系列点,并减少点的数量的一种算法。它的优点是具有平移和旋转不变性,给定曲线与阈值后,抽样结果一定。
道格拉斯-普克算法(Douglas–Peucker algorithm,亦称为拉默-道格拉斯-普克算法、迭代适应点算法、分裂与合并算法)是将曲线近似表示为一系列点,并减少点的数量的一种算法。该算法的原始类型分别由乌尔斯·拉默(Urs Ramer)于1972年以及大卫·道格拉斯(David Douglas)和托马斯·普克(Thomas Peucker)于1973年提出,并在之后的数十年中由其他学者予以完善。
算法的基本思路是:对每一条曲线的首末点虚连一条直线,求所有点与直线的距离,并找出最大距离值dmax ,用dmax与限差D相比:若dmax<D,这条曲线上的中间点全部舍去;若dmax ≥D,保留dmax 对应的坐标点,并以该点为界,把曲线分为两部分,对这两部分重复使用该方法。
经典的Douglas-Peucker算法步骤如下:
(1)在曲线首尾两点A,B之间连接一条直线AB,该直线为曲线的弦;
(2)得到曲线上离该直线段距离最大的点C,计算其与AB的距离d;
(3)比较该距离与预先给定的阈值threshold的大小,如果小于threshold,则该直线段作为曲线的近似,该段曲线处理完毕。
(4)如果距离大于阈值,则用C将曲线分为两段AC和BC,并分别对两段取弦进行1~3的处理。
(5)当所有曲线都处理完毕时,依次连接各个分割点形成的折线,即可以作为曲线的近似。
代码如下(typescript)
export class SimplifyPathUtil {
/**
* 使用Douglas-Peucker算法对曲线进行化简
* @param vertexs 构成曲线的点
* @param threshold 阈值 点到相邻两点所构成的线段的距离小于阈值时,可以被去掉。
* @return {IPoint[]} 平滑后的点的数组
*/
public static simplify(vertexs: IPoint[], threshold: number): IPoint[] {
const flags: boolean[] = [];
flags[0] = true;
flags[vertexs.length - 1] = true;
SimplifyPathUtil.simplifySeg(vertexs, threshold * threshold, 0, vertexs.length - 1, flags);
const result: IPoint[] = [];
for (let i: number = 0; i < vertexs.length; i++) {
if (flags[i]) {
result.push(vertexs[i]);
}
}
return result;
}
private static simplifySeg(vertexs: IPoint[], threshold: number, ind0: number, ind1: number, flags: boolean[]): void {
// flags[ind0] = true;
// flags[ind1] = true;
if (ind1 - ind0 <= 1) {
return;
}
// 在曲线首尾两点A,B之间连接一条直线AB,该直线为曲线的弦;
const p0: IPoint = vertexs[ind0];
const p1: IPoint = vertexs[ind1];
// 得到曲线上离该直线段距离最大的点C,计算其与AB的距离d;
let maxD: number = 0;
let maxInd: number = -1;
for(let i: number = ind0 + 1; i < ind1; i++) {
const p: IPoint = vertexs[i];
const d: number = LineUtil.distancePointToSegment(p, p0, p1);
if (d > maxD) {
maxD = d;
maxInd = i;
}
}
// 比较该距离与预先给定的阈值threshold的大小,如果小于threshold,则该直线段作为曲线的近似,该段曲线处理完毕。
if (maxD < threshold) {
return;
}
// 如果距离大于阈值,则用C将曲线分为两段AC和BC,并分别对两段取弦进行1~3的处理。
flags[maxInd] = true;
SimplifyPathUtil.simplifySeg(vertexs, threshold, ind0, maxInd, flags);
SimplifyPathUtil.simplifySeg(vertexs, threshold, maxInd, ind1, flags);
}
}用到了LineUtil中的方法,计算点到线段的距离,代码如下:
/**
* 点到线段的最短距离
* @param p
* @param p1
* @param p2
* @returns {number}
*/
public static distancePointToSegment(p: IPoint, p1: IPoint, p2: IPoint): number {
const pt: IPoint = LineUtil.pointToSegmentDisPoint(p, p1, p2);
return PointUtils.distance(pt, p);
}
/**
* 点到线段的最短距离的点。
* @param p
* @param p1
* @param p2
* @returns {IPoint}
*/
public static pointToSegmentDisPoint(p: IPoint, p1: IPoint, p2: IPoint): IPoint {
const cross: number = (p2.x - p1.x) * (p.x - p1.x) + (p2.y - p1.y) * (p.y - p1.y);
if (cross <= 0) {
return p1;
}
const d2: number = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y);
if (cross >= d2) {
return p2;
}
const r: number = cross / d2;
return {
x: p1.x + (p2.x - p1.x) * r,
y: p1.y + (p2.y - p1.y) * r
};
}PointUtil中计算两点的距离,代码如下
/**
* 两点之间的距离
* @param p1
* @param p2
* @returns {number}
*/
public static distance(p1: IPoint, p2: IPoint): number {
const dx: number = p1.x - p2.x;
const dy: number = p1.y - p2.y;
return Math.sqrt(dx * dx + dy * dy);
}IPoint的定义
/**
* 抽象的点。
*/
export interface IPoint {
x: number;
y: number;
}参考资料:
* adobe的工具
* https://helpx.adobe.com/illustrator/using/simplify_paths.html
* https://help.adobe.com/en_US/as3/mobile/WS948100b6829bd5a6441c2d912897db4bb0-8000.html
*
* 参考:
* https://pro.arcgis.com/zh-cn/pro-app/tool-reference/cartography/simplify-polygon.htm
* https://github.com/mattdesl/simplify-path 一个开源库,实现了Douglas-Peuker算法
* https://stackoverflow.com/questions/3032630/optimizing-simplifying-a-path
*
* Douglas-Peucker算法:
* https://zhuanlan.zhihu.com/p/136286488
* https://blog.csdn.net/qq_34510308/article/details/64539434
* https://www.cnblogs.com/atong/archive/2013/03/06/2946539.html
* http://paperjs.org/tutorials/paths/smoothing-simplifying-flattening/ 很强大,不光有Douglas-Peuker算法,还有平滑算法。可以拿来用或者拿来学习。
《空间轨迹分析与应用》
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。