当前位置:首页>python>Python-道格拉斯-普克算法

Python-道格拉斯-普克算法

  • 2026-09-02 19:51:37
Python-道格拉斯-普克算法
软件:VS Code
目标:假设数据库里有一条河流的10个坐标数据,地图缩小时不需要显示全部坐标,这个时候可以对河流进行简化
一、原理
1.基本概念
道格拉斯-普克算法(Douglas-Peucker algorithm)是一种用于简化折线(由一系列点组成的曲线)的经典算法。
它的核心目的是:在保持曲线整体形状特征的前提下,通过减少点的数量来降低数据的复杂度。该算法广泛应用于计算机图形学和数据压缩。
2.算法步骤
假设有一条由n个点组成的折线,设定简化的阈值为E:
1)连接首尾:在折线的起始点A和终止点B之间连一条直线段AB(作为基准线)。
2)找最远点: 遍历起始点和终止点之间的所有中间点,计算每个点到直线AB的垂直距离。找到这些点中距离直线AB最大的点,记为P,其距离为 dmax。
判断与比较:
如果 dmax≤   E: 说明该段折线上所有的点与基准线的偏差都很小,那么这段中间的点全部可以剔除,直接用直线AB代替。如果 dmax>    E:
 说明点P是一个重要的特征点,必须保留。此时以点P为界,将原折线分割成AP和PB两部分。
3)递归处理: 对分割出的两部分折线分别重复上述步骤1)-3),直到所有点都被处理完毕。
点到直线的距离可以由鞋带公式推导得到
3.举例说明算法实现过程
数据:P1(0,0), P2(1,1), P3(2,-1), P4(5,10), P5(6,9), P6(7,11), P7(10,0), P8(12,1), P9(14,-1), P10(15,0)
第一步
处理线段:[P1, P10]
直线方程:y=0
计算中间点到线段距离:
P2:1, P3:1, P4:10, P5:9, P6:11, P7:0, P8:1, P9:1
最远点:p6(d=11)
判断:dmaxp6>2,
结果:保留p6,分割成[p1,p6]和[p6,p10]
第二步
处理线段:[P1, P6]
直线方程:11x−7y=0
计算中间点到线段距离:
P2:0.31, P3:2.22, P4:1.15, P5:0.23
最远点:p3(d=2.22)
判断:dmaxp3>2
结果:保留p3,分割成[P1,P3] 和 [P3,P6]
第三步
处理线段:[P1,P3] 
直线方程:x+2y=0
计算中间点到线段距离:
P2:1.34
最远点:p2(d=1.34)
判断:dmaxp2<2
结果:去掉p2,该分支结束
第四步
处理线段:[p3,p6]
直线方程:12x−5y−29=0
计算中间点到线段距离:
P4:1.46, P5:0.15
最远点:p4(d=1.46)
判断:dmaxp4<2
结果:去掉p4,p5,该分支结束
第五步
处理线段:[P6, P10]
直线方程:11x+8y−165=0
计算中间点到线段距离:
P7:4.04, P8:1.84, P9:1.40
最远点:p7(d=4.04)
判断:dmaxp7>2
结果:保留p7,分割成[P6,P7] 和 [P7,P10]
第六步
处理线段:[P6,P7]
无中间点,该分支结束
第七步
处理线段:[P7,P10]
直线方程:y=0
计算中间点到线段距离:
P8:1, P9:1
最远点:p8或p9(d=1)
判断:dmaxp8或dmaxp9<2
结果:去掉p8和p9,该分支结束
4.算法实现
1)导入库
import pandas as pdimport numpy as np
2)读取数据
data = pd.read_csv("douglas_peucker.csv", encoding='utf-8')
3)设置阈值
eps = 2  # 阈值E设为 2,
4)如果是直线段则无需化简,所有点都需保留,所以keeppts的所有元素都为true
if len(data) > 2:    # 初始化保留标记,首尾两点默认保留    keeppts = [False] * len(data)    keeppts[0] = True    keeppts[len(data) - 1] = True    
5)根据栈递归,距离判断,保留距离直线的距离大于阈值的点
 zhongjiandian = [(0, len(data) - 1)]        while zhongjiandian:        start, end = zhongjiandian.pop()                # 如果区间内没有中间点,跳过        if end - start < 2:            continue                    dmax = -1        farthestidx = -1                # 获取当前区间的起点和终点坐标        x1, y1 = float(data.iloc[start, 0]), float(data.iloc[start, 1])        x2, y2 = float(data.iloc[end, 0]), float(data.iloc[end, 1])                # 在当前区间内寻找最远点        for i in range(start + 1, end):            x0, y0 = float(data.iloc[i, 0]), float(data.iloc[i, 1])                        #计算每个中间点到直线的距离            # 分子: |(x2-x1)(y1-y0) - (x1-x0)(y2-y1)|            fenzi = abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1))            # 分母: sqrt((x2-x1)^2 + (y2-y1)^2)            fenmu = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)                        # 计算距离(处理分母为0的极端情况)            dis = fenzi / fenmu if fenmu != 0 else 0                        # 记录本区间内的最远点            if dis > dmax:                dmax = dis                farthestidx = i                         if dmax > eps:         keeppts[farthestidx] = True  # 标记保留该特征点         # 递归分裂:将左侧区间和右侧区间分别压入栈中         zhongjiandian.append((start, farthestidx))          zhongjiandian.append((farthestidx, end)) 
6)根据keeppts的元素值判断哪些点需要保留
# 输出结果    print("简化后的节点顺序:")    for i in range(len(keeppts)):        if keeppts[i]:            print(f"P{i+1}: {data.iloc[i, 0]}, {data.iloc[i, 1]}")elif len(data) == 2:    print("就 2 个点,不用简化啦!")else:    print("坐标点个数不对!")
7)完整代码
import pandas as pdimport numpy as np# 1. 加载数据data = pd.read_csv("douglas_peucker.csv", encoding='utf-8')eps = 2  # 阈值E设为 2,if len(data) > 2:    # 初始化保留标记,首尾两点默认保留    keeppts = [False] * len(data)    keeppts[0] = True    keeppts[len(data) - 1] = True        # 使用栈模拟递归,存储待处理的区间 (start_index, end_index)    zhongjiandian = [(0, len(data) - 1)]        while zhongjiandian:        start, end = zhongjiandian.pop()                # 如果区间内没有中间点,跳过        if end - start < 2:            continue                    dmax = -1        farthestidx = -1                # 获取当前区间的起点和终点坐标        x1, y1 = float(data.iloc[start, 0]), float(data.iloc[start, 1])        x2, y2 = float(data.iloc[end, 0]), float(data.iloc[end, 1])                # 在当前区间内寻找最远点        for i in range(start + 1, end):            x0, y0 = float(data.iloc[i, 0]), float(data.iloc[i, 1])                        #计算每个中间点到直线的距离            # 分子: |(x2-x1)(y1-y0) - (x1-x0)(y2-y1)|            fenzi = abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1))            # 分母: sqrt((x2-x1)^2 + (y2-y1)^2)            fenmu = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)                        # 计算距离(处理分母为0的极端情况)            dis = fenzi / fenmu if fenmu != 0 else 0                        # 记录本区间内的最远点            if dis > dmax:                dmax = dis                farthestidx = i                # 找完整个区间的最远点后,再与阈值比较         if dmax > eps:            keeppts[farthestidx] = True  # 标记保留该特征点            # 递归分裂:将左侧区间和右侧区间分别压入栈中            zhongjiandian.append((start, farthestidx))             zhongjiandian.append((farthestidx, end))       # 输出结果    print("简化后的节点顺序:")    for i in range(len(keeppts)):        if keeppts[i]:            print(f"P{i+1}: {data.iloc[i, 0]}, {data.iloc[i, 1]}")elif len(data) == 2:    print("就 2 个点,不用简化啦!")else:    print("坐标点个数不对!")
8)效果

最新文章

随机文章