常用的地图点压缩

1、算法应用

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

2、算法步骤

对每一条曲线的首末点虚连一条直线,求所有点与直线的距离,
并找出最大距离值dmax ,
用dmax与限差D相比:
若dmax < D ,这条曲线上的中间点所有舍去;
若dmax ≥D ,保留dmax 相应的坐标点,并以该点为界,把曲线分为两部分,对这两部分反复使用该方法。
控制限差值的大小可以控制抽稀的粒度。

3、算法精度

压缩精度取决于事先给定的阈值的大小,阈值越大,简化越多!

简化版


from math import sqrt, pow


def point_to_line_Distance(point_a, point_b, point_c):
    """
    计算点a到点b c所在直线的距离
    :param point_a:
    :param point_b:
    :param point_c:
    :return:
    """
    # 首先计算b c 所在直线的斜率和截距
    if point_b[0] == point_c[0]:
        return 9999999
    slope = (point_b[1] - point_c[1]) / (point_b[0] - point_c[0])
    intercept = point_b[1] - slope * point_b[0]

    # 计算点a到b c所在直线的距离
    distance = abs(slope * point_a[0] - point_a[1] + intercept) / sqrt(1 + pow(slope, 2))
    return distance


def douglas_peuker(point_list, threshold):
    """

    道格拉斯-普克抽稀算法
    :param point_list: like [[122.358638, 30.280378], [122.359314, 30.280649]]
    :param threshold: 0.003
    :return:
    """
    def diluting(point_list, threshold, qualify_list, disqualify_list):
        """
        抽稀
        :param threshold:
        :param disqualify_list:
        :param qualify_list:
        :param point_list:二维点列表
        :return:
        """
        if len(point_list) < 3:
            qualify_list.extend(point_list[::-1])
        else:
            # 找到与收尾两点连线距离最大的点
            max_distance_index, max_distance = 0, 0
            for index, point in enumerate(point_list):
                if index in [0, len(point_list) - 1]:
                    continue
                distance = point_to_line_Distance(point, point_list[0], point_list[-1])
                if distance > max_distance:
                    max_distance_index = index
                    max_distance = distance

            # 若最大距离小于阈值,则去掉所有中间点。 反之,则将曲线按最大距离点分割
            if max_distance < threshold:
                qualify_list.append(point_list[-1])
                qualify_list.append(point_list[0])
            else:
                # 将曲线按最大距离的点分割成两段
                sequence_a = point_list[:max_distance_index]
                sequence_b = point_list[max_distance_index:]

                for sequence in [sequence_a, sequence_b]:
                    if len(sequence) < 3 and sequence == sequence_b:
                        qualify_list.extend(sequence[::-1])
                    else:
                        disqualify_list.append(sequence)
        return qualify_list, disqualify_list

    def get_qualify_list(point_list, threshold):
        qualify_list = list()
        disqualify_list = list()

        qualify_list, disqualify_list = diluting(point_list, threshold, qualify_list, disqualify_list)
        while len(disqualify_list) > 0:
            qualify_list, disqualify_list = diluting(disqualify_list.pop(), threshold, qualify_list, disqualify_list)

        return qualify_list

    # 当返回值长度小于4时,减小 threshold的值
    if len(point_list) < 5:
        return point_list
    result = get_qualify_list(point_list, threshold)
    if len(result) < 4:
        while len(result) < 4:
            threshold = threshold * 0.9
            result = get_qualify_list(point_list, threshold)

    if len(result) > len(point_list):
        return point_list

    return result

优化版:


# -*- coding: utf-8 -*-
from math import sqrt, pow


def point_to_line_Distance(point_a, point_b, point_c):
    """
    计算点a到点b c所在直线的距离
    :param point_a:
    :param point_b:
    :param point_c:
    :return:
    """
    # 首先计算b c 所在直线的斜率和截距
    if point_b[0] == point_c[0]:
        return 9999999
    slope = (point_b[1] - point_c[1]) / (point_b[0] - point_c[0])
    intercept = point_b[1] - slope * point_b[0]

    # 计算点a到b c所在直线的距离
    distance = abs(slope * point_a[0] - point_a[1] + intercept) / sqrt(1 + pow(slope, 2))
    return distance


def douglas_peuker(point_list, threshold, lowerLimit=4, ceiling=None):
    """

    道格拉斯-普克抽稀算法
    :param point_list: like [[122.358638, 30.280378], [122.359314, 30.280649]]
    :param threshold: 0.003
    :param lowerLimit: 输出点个数必须大于lowerLimit, 如果没有, 不做设置 like: 4
    :param ceiling: 输出点个数必须小于ceiling, 如果没有,不做设置  like: 2500
    :return:
    """

    def diluting(point_list, threshold, qualify_list, disqualify_list):
        """
        抽稀
        :param threshold:
        :param disqualify_list:
        :param qualify_list:
        :param point_list:二维点列表
        :return:
        """
        if len(point_list) < 3:
            qualify_list.extend(point_list[::-1])
        else:
            # 找到与首尾两点连线距离最大的点
            max_distance_index, max_distance = 0, 0
            for index, point in enumerate(point_list):
                if index in [0, len(point_list) - 1]:
                    continue
                distance = point_to_line_Distance(point, point_list[0], point_list[-1])
                if distance > max_distance:
                    max_distance_index = index
                    max_distance = distance

            # 若最大距离小于阈值,则去掉所有中间点。 反之,则将曲线按最大距离点分割
            if max_distance < threshold:
                qualify_list.append(point_list[-1])
                qualify_list.append(point_list[0])
            else:
                # 将曲线按最大距离的点分割成两段
                sequence_a = point_list[:max_distance_index]
                sequence_b = point_list[max_distance_index:]

                for sequence in [sequence_a, sequence_b]:
                    if len(sequence) < 3 and sequence == sequence_b:
                        qualify_list.extend(sequence[::-1])
                    else:
                        disqualify_list.append(sequence)
        return qualify_list, disqualify_list

    def get_qualify_list(point_list, threshold):
        qualify_list = list()
        disqualify_list = list()

        qualify_list, disqualify_list = diluting(point_list, threshold, qualify_list, disqualify_list)
        while len(disqualify_list) > 0:
            qualify_list, disqualify_list = diluting(disqualify_list.pop(), threshold, qualify_list, disqualify_list)

        return qualify_list

    # 当输入点数小于5,直接输出
    if len(point_list) < 5:
        return point_list

    result = get_qualify_list(point_list, threshold)

    # 当返回值长度小于lowerLimit时,减小 threshold的值
    if len(result) < lowerLimit:
        while len(result) < lowerLimit:
            threshold = threshold * 0.9
            result = get_qualify_list(point_list, threshold)

    # 当返回值长度大于ceiling时,增大 threshold的值
    if ceiling and len(result) > ceiling:
        while len(result) > ceiling:
            threshold = threshold * 1.1
            result = get_qualify_list(point_list, threshold)

    if len(result) > len(point_list):
        return point_list

    return result