GIS基础算法目录
GIS算法基础——左转算法拓扑生成
GIS算法基础——矢量数据压缩道格拉斯普克压缩算法(非递归实现)
GIS算法基础——矢量数据压缩道格拉斯普克算法(非递归实现)
道格拉斯-普克压缩算法
本博文用以梳理课堂及自学内容,转载请标明出处。
本人应用JS中的Canvas对算法结果进行可视化验证,在算法说明及实现中绘制验证部分省略
道格拉斯-普克算法 [1] (Douglas–Peucker algorithm,亦称为拉默-道格拉斯-普克算法、迭代适应点算法、分裂与合并算法)是将曲线近似表示为一系列点,并减少点的数量的一种算法。它的优点是具有平移和旋转不变性,给定曲线与阈值后,抽样结果一定。
其过程大致如下:
首先,将一条曲线首末点连一条直线;求出其余各点到该直线的距离;选其最大者与规定的临界值相比较–若大于临界值,则离该直线距离最大的点保留–否则将直线两端间各点全部舍去。
过程如下图所示:
第一步,连接首末点:
第二步取得最大距离点并与阈值比较:
比较剩余各点距离MN直线的距离,
在本例中点1位三个点中距离最远的点,
假设其与MN直线的距离大于阈值。
故下一步为连接M、点1以及点1、N,由于点1、M中间没有剩余的点则这段线不再进行处理。
第三步,对点1、N点线段进行前两步的操作
常见递归实现
算法流程
(1)连接曲线首尾两点A、B形成一条直线AB;
(2)计算曲线上离该直线段距离最大的点C,计算其与AB的距离d;
(3)比较该距离与预先给定的阈值的大小,如果小于阈值,则以该直线作为曲线的近似,该段曲线处理完毕。
(4)如果距离大于阈值,则用点C将曲线分为两段AC和BC,并分别对两段曲线进行步骤[1~3]的处理。
(5)当所有曲线都处理完毕后,依次连接各个分割点形成折线,作为原曲线的近似。
具体实现
网上很多了,在此不再赘述。本文重点为非递归实现,具体见下文。
JavaScript非递归实现
实现思路演示
核心:利用JS中数组对象来模拟栈的出栈以及入栈操作。
在过程中不再对点对象进行操作,而是利用栈操作点的索引进行记录。
利用一个简单的实例演示过程:
首先,初始化两个栈(JS数组)A和B,并将起点M压入A,终点N压入B。
然后计算两个栈中栈顶的两个点之间点与之的最远距离得到点1,并判断其距离大于阈值(若不满足条件则将B栈顶出栈并压入A),将其压入B中
此时两栈顶点之间没有其他点,则将B栈顶出栈并将其压入A中
继续利用栈顶点比较,重复上述过程,如图所示,下面过程假设判断过程所有点的距离均大于阈值
至此,所有满足条件没有被压缩的点的索引均存储于A栈中,再利用点集合和A中索引将点取出即可。
时间回溯至点1与点N连接时,如果距离最大点2的距离小于阈值,则将B栈顶元素(实例中及N点)出栈压入A栈。
这种情况下最终输出为M、点1、N
本例子只是为了演示而展示的最简单的情况。
具体算法步骤
(1)初始化两个栈A、B(JS数组),将起点压入A,终点压入B
(2)若B栈不为空,则转至(3);否则,结束并输出栈A。
(3)若栈顶元素之间有其他点,转至(4);否则,转至(6)。
(4)计算得到两个栈顶元素之间的点与栈顶元素连接直线的距离最大值,及其点索引。若该最大值大于设置的阈值,转至(5);否则,转至(6)。
(5)将该点索引压入B,转至(2)。
(6)将B栈顶元素出栈并压入A,转至(2)。
文字逻辑看起来很乱,所以说还是看流程图吧
实现代码
计算最大距离及其点索引方法
// 计算最大距离,用于非递归方法
function FindMostDistance(seriesPoints,start,end,loc) {
if (end - start <= 1) {
loc = end;
return 0;
}
let result = 0;
let startPoint = seriesPoints[start];
let endPoint = seriesPoints[end];
for (let i = start + 1; i < end; i++) {
var d = GetDistanceToLine(startPoint, endPoint, seriesPoints[i]);
if (d > result) {
result = d;
loc = i;
}
}
locate = loc;
return result;
}
let locate = 0;//用于表示数组索引
非递归道格拉斯算法主体部分
// 非递归道格拉斯抽稀算法(利用数组模拟入栈出栈)
function DouglasThinningMachine(spList,threshold) {
if (spList.length<=1) {
console.log("No Enough Data");
}
let max = spList.length;
// 创建两个数组记录索引值
let A = new Array();
let B = new Array();
A.push(0);
B.push(max - 1);
do {//循环主体
let d = FindMostDistance(spList, A[A.length-1], B[B.length-1],locate);//找到最大距离的点
if (d > threshold) {//如果超过阈值就压入B
B.push(locate);
}
else {//B栈顶的压入A中
A.push(B.pop());
}
} while(B.length > 0);
A = Array.from(new Set(A));
let result = new Array();
for (let index of A) {//利用索引构建结果
result.push(spList[index]);
}
locate = 0;//初始化全局变量
return result;
}
实际过程中问题
(1)闭合图形如何压缩
先删除尾点,压缩完成后恢复
实现效果
原图:中国兰伯特投影省级行政区划图
阈值为1000时:
压缩比接近50%,但是在这样的视角肉眼看来基本无法分别
压缩比为:
阈值为10000时:
复杂线段肉眼可见的开始变得简单
压缩比为:
阈值为20000时:
可以看出来大量的点已经被舍弃
压缩比为:
阈值为50000时:
压缩比为:
总结
在本次实践中,发现在结果DP压缩比接近50%的情况下,大部分线段在视觉上依然与原线段保持一致,说明了DP压缩算法能够在压缩矢量数据的同时极大程度的保留原线段的趋势等等几何形态。
本学期除了有算法课程,还有底层一点的计算机图形学课程(手撸像素那种),所以对于用递归实现的算法一直耿耿于怀(因为太慢、效率太低),就一直在想怎么样非递归实现。
在此过程中,我有了一定的思路并且实践了很多次没成功。有一天午睡的时候,脑子里一直在想这个,然后也不知道我是睡着了还是没睡着,突然脑子里有一个那种逻辑的想法,我又在脑子里过了一遍,发现可行,我立马清醒了打开电脑改了代码,然后就成功了。
我?????灵感真的太重要了吧。我怀疑自己是某种碳基硅基融合生物了好几天,因为那个时候睡觉的时候真的在脑子里运行了想到的代码逻辑。事实证明,要多睡午觉,晚上就别睡了起来码代码(误)。
如果对大家有帮助的话,点赞收藏一下,欢迎回踩,谢谢大家,一起共同进步!