From 10f18239fb26ae06737f121997058e5e097eea7b Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi Date: Fri, 13 Mar 2026 07:45:35 -0700 Subject: [PATCH 1/7] feat: add Ramer-Douglas-Peucker polyline simplification algorithm --- geometry/ramer_douglas_peucker.py | 177 ++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 geometry/ramer_douglas_peucker.py diff --git a/geometry/ramer_douglas_peucker.py b/geometry/ramer_douglas_peucker.py new file mode 100644 index 000000000000..d7a9c918b07a --- /dev/null +++ b/geometry/ramer_douglas_peucker.py @@ -0,0 +1,177 @@ +""" +Ramer-Douglas-Peucker (RDP) algorithm for polyline simplification. + +Given a curve represented as a sequence of points and a tolerance epsilon, +the algorithm recursively reduces the number of points while preserving the +overall shape of the curve. Points that deviate from the simplified line by +less than epsilon are removed. + +Time complexity: O(n log n) on average, O(n²) worst case +Space complexity: O(n) for the recursion stack + +References: +- https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm +- Ramer, U. (1972). "An iterative procedure for the polygonal approximation + of plane curves". Computer Graphics and Image Processing. 1 (3): 244-256. +- Douglas, D.; Peucker, T. (1973). "Algorithms for the reduction of the number + of points required to represent a digitized line or its caricature". + Cartographica. 10 (2): 112-122. +""" + +from __future__ import annotations + +import math +from collections.abc import Sequence + +# --------------------------------------------------------------------------- +# Internal helpers +# --------------------------------------------------------------------------- + + +def _euclidean_distance(a: tuple[float, float], b: tuple[float, float]) -> float: + """Return the Euclidean distance between two 2-D points. + + >>> _euclidean_distance((0.0, 0.0), (3.0, 4.0)) + 5.0 + >>> _euclidean_distance((1.0, 1.0), (1.0, 1.0)) + 0.0 + """ + return math.hypot(b[0] - a[0], b[1] - a[1]) + + +def _perpendicular_distance( + p: tuple[float, float], + a: tuple[float, float], + b: tuple[float, float], +) -> float: + """Return the perpendicular distance from point *p* to the line through *a* and *b*. + + The result is the absolute value of the signed area of the triangle (a, b, p) + divided by the length of segment ab, which equals the altitude of that triangle + from p. + + >>> _perpendicular_distance((4.0, 0.0), (0.0, 0.0), (0.0, 3.0)) + 4.0 + >>> _perpendicular_distance((4.0, 0.0), (0.0, 0.0), (0.0, 3.0)) + 4.0 + >>> # order of a and b does not affect the result + >>> _perpendicular_distance((4.0, 0.0), (0.0, 3.0), (0.0, 0.0)) + 4.0 + >>> _perpendicular_distance((4.0, 1.0), (0.0, 1.0), (0.0, 4.0)) + 4.0 + >>> _perpendicular_distance((2.0, 1.0), (-2.0, 1.0), (-2.0, 4.0)) + 4.0 + """ + px, py = p + ax, ay = a + bx, by = b + numerator = abs((by - ay) * px - (bx - ax) * py + bx * ay - by * ax) + denominator = _euclidean_distance(a, b) + if denominator == 0.0: + # a and b coincide; fall back to point-to-point distance + return _euclidean_distance(p, a) + return numerator / denominator + + +# --------------------------------------------------------------------------- +# Public API +# --------------------------------------------------------------------------- + + +def ramer_douglas_peucker( + points: Sequence[tuple[float, float]], + epsilon: float, +) -> list[tuple[float, float]]: + """Simplify a polyline using the Ramer-Douglas-Peucker algorithm. + + Parameters + ---------- + points: + An ordered sequence of ``(x, y)`` tuples that form the polyline. + epsilon: + Maximum allowable perpendicular deviation. Points whose distance + to the simplified segment is less than or equal to *epsilon* are + discarded. Must be non-negative. + + Returns + ------- + list[tuple[float, float]] + A simplified list of ``(x, y)`` points that is a subset of *points*. + + Raises + ------ + ValueError + If *epsilon* is negative. + + Examples + -------- + Collinear points - middle point is redundant for any positive epsilon: + + >>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 0.0), (2.0, 0.0)], epsilon=0.5) + [(0.0, 0.0), (2.0, 0.0)] + + Empty / tiny inputs are returned unchanged: + + >>> ramer_douglas_peucker([], epsilon=1.0) + [] + >>> ramer_douglas_peucker([(0.0, 0.0)], epsilon=1.0) + [(0.0, 0.0)] + >>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 1.0)], epsilon=1.0) + [(0.0, 0.0), (1.0, 1.0)] + + A simple square outline where epsilon removes mid-edge points: + + >>> square = [ + ... (0.0, 0.0), (1.0, 0.0), (2.0, 0.0), + ... (2.0, 1.0), (2.0, 2.0), (1.0, 2.0), + ... (0.0, 2.0), (0.0, 1.0), + ... ] + >>> ramer_douglas_peucker(square, epsilon=0.7) + [(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 1.0)] + + A polygonal chain simplified to a single segment for large epsilon: + + >>> chain = [(0.0, 0.0), (2.0, 0.5), (3.0, 3.0), (6.0, 3.0), (8.0, 4.0)] + >>> ramer_douglas_peucker(chain, epsilon=3.0) + [(0.0, 0.0), (8.0, 4.0)] + + Zero epsilon keeps all points: + + >>> ramer_douglas_peucker([(0.0, 0.0), (0.5, 0.1), (1.0, 0.0)], epsilon=0.0) + [(0.0, 0.0), (0.5, 0.1), (1.0, 0.0)] + """ + if epsilon < 0: + msg = f"epsilon must be non-negative, got {epsilon!r}" + raise ValueError(msg) + + pts = list(points) + + if len(pts) < 3: + return pts + + # Find the point with the greatest perpendicular distance from the line + # connecting the first and last points. + start, end = pts[0], pts[-1] + max_dist = 0.0 + max_index = 0 + for i in range(1, len(pts) - 1): + dist = _perpendicular_distance(pts[i], start, end) + if dist > max_dist: + max_dist = dist + max_index = i + + if max_dist > epsilon: + # Recursively simplify both halves and join them (drop the duplicate + # point at the junction). + left = ramer_douglas_peucker(pts[: max_index + 1], epsilon) + right = ramer_douglas_peucker(pts[max_index:], epsilon) + return left[:-1] + right + + # All intermediate points are within tolerance - keep only the endpoints. + return [start, end] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 7bfabc3b3c83bc8baee540d8e567abc170e406e5 Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi Date: Fri, 13 Mar 2026 07:55:35 -0700 Subject: [PATCH 2/7] Use descriptive parameter names --- geometry/ramer_douglas_peucker.py | 37 ++++++++++++++++--------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/geometry/ramer_douglas_peucker.py b/geometry/ramer_douglas_peucker.py index d7a9c918b07a..c446018c5021 100644 --- a/geometry/ramer_douglas_peucker.py +++ b/geometry/ramer_douglas_peucker.py @@ -28,7 +28,9 @@ # --------------------------------------------------------------------------- -def _euclidean_distance(a: tuple[float, float], b: tuple[float, float]) -> float: +def _euclidean_distance( + point_1: tuple[float, float], point_2: tuple[float, float] +) -> float: """Return the Euclidean distance between two 2-D points. >>> _euclidean_distance((0.0, 0.0), (3.0, 4.0)) @@ -36,25 +38,24 @@ def _euclidean_distance(a: tuple[float, float], b: tuple[float, float]) -> float >>> _euclidean_distance((1.0, 1.0), (1.0, 1.0)) 0.0 """ - return math.hypot(b[0] - a[0], b[1] - a[1]) + return math.hypot(point_2[0] - point_1[0], point_2[1] - point_1[1]) def _perpendicular_distance( - p: tuple[float, float], - a: tuple[float, float], - b: tuple[float, float], + point: tuple[float, float], + line_start: tuple[float, float], + line_end: tuple[float, float], ) -> float: - """Return the perpendicular distance from point *p* to the line through *a* and *b*. + """Return the perpendicular distance from *point* to the line through + *line_start* and *line_end*. - The result is the absolute value of the signed area of the triangle (a, b, p) - divided by the length of segment ab, which equals the altitude of that triangle - from p. + The result is the absolute value of the signed area of the triangle + (line_start, line_end, point) divided by the length of the segment, which + equals the altitude of that triangle from point. >>> _perpendicular_distance((4.0, 0.0), (0.0, 0.0), (0.0, 3.0)) 4.0 - >>> _perpendicular_distance((4.0, 0.0), (0.0, 0.0), (0.0, 3.0)) - 4.0 - >>> # order of a and b does not affect the result + >>> # order of line_start and line_end does not affect the result >>> _perpendicular_distance((4.0, 0.0), (0.0, 3.0), (0.0, 0.0)) 4.0 >>> _perpendicular_distance((4.0, 1.0), (0.0, 1.0), (0.0, 4.0)) @@ -62,14 +63,14 @@ def _perpendicular_distance( >>> _perpendicular_distance((2.0, 1.0), (-2.0, 1.0), (-2.0, 4.0)) 4.0 """ - px, py = p - ax, ay = a - bx, by = b + px, py = point + ax, ay = line_start + bx, by = line_end numerator = abs((by - ay) * px - (bx - ax) * py + bx * ay - by * ax) - denominator = _euclidean_distance(a, b) + denominator = _euclidean_distance(line_start, line_end) if denominator == 0.0: - # a and b coincide; fall back to point-to-point distance - return _euclidean_distance(p, a) + # line_start and line_end coincide; fall back to point-to-point distance + return _euclidean_distance(point, line_start) return numerator / denominator From 56b681e672ed6c65c0a789fe251642ef60bb79c3 Mon Sep 17 00:00:00 2001 From: John Law Date: Mon, 18 May 2026 22:52:08 +0100 Subject: [PATCH 3/7] Update geometry/ramer_douglas_peucker.py --- geometry/ramer_douglas_peucker.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/geometry/ramer_douglas_peucker.py b/geometry/ramer_douglas_peucker.py index c446018c5021..30d8b72a0923 100644 --- a/geometry/ramer_douglas_peucker.py +++ b/geometry/ramer_douglas_peucker.py @@ -74,11 +74,6 @@ def _perpendicular_distance( return numerator / denominator -# --------------------------------------------------------------------------- -# Public API -# --------------------------------------------------------------------------- - - def ramer_douglas_peucker( points: Sequence[tuple[float, float]], epsilon: float, From 3f5c2b468136f2f8c0ff3b845b9e5eb280a742f5 Mon Sep 17 00:00:00 2001 From: John Law Date: Mon, 18 May 2026 22:52:14 +0100 Subject: [PATCH 4/7] Update geometry/ramer_douglas_peucker.py --- geometry/ramer_douglas_peucker.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/geometry/ramer_douglas_peucker.py b/geometry/ramer_douglas_peucker.py index 30d8b72a0923..2d0c7f7b00c3 100644 --- a/geometry/ramer_douglas_peucker.py +++ b/geometry/ramer_douglas_peucker.py @@ -23,10 +23,6 @@ import math from collections.abc import Sequence -# --------------------------------------------------------------------------- -# Internal helpers -# --------------------------------------------------------------------------- - def _euclidean_distance( point_1: tuple[float, float], point_2: tuple[float, float] From d403421dae31d4ed8aac33df4e69f954ee5b036d Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi <41567902+AliAlimohammadi@users.noreply.github.com> Date: Tue, 19 May 2026 11:58:30 -0700 Subject: [PATCH 5/7] Update ramer_douglas_peucker.py --- geometry/ramer_douglas_peucker.py | 187 ++++++++++++++++-------------- 1 file changed, 101 insertions(+), 86 deletions(-) diff --git a/geometry/ramer_douglas_peucker.py b/geometry/ramer_douglas_peucker.py index 2d0c7f7b00c3..7e966e8d7ee6 100644 --- a/geometry/ramer_douglas_peucker.py +++ b/geometry/ramer_douglas_peucker.py @@ -1,31 +1,24 @@ """ -Ramer-Douglas-Peucker (RDP) algorithm for polyline simplification. +Ramer–Douglas–Peucker polyline simplification algorithm. -Given a curve represented as a sequence of points and a tolerance epsilon, -the algorithm recursively reduces the number of points while preserving the -overall shape of the curve. Points that deviate from the simplified line by -less than epsilon are removed. +Given a sequence of 2-D points and a tolerance epsilon, the algorithm +reduces the number of points while preserving the overall shape of the curve. -Time complexity: O(n log n) on average, O(n²) worst case -Space complexity: O(n) for the recursion stack +Time complexity: O(n log n) average, O(n²) worst case +Space complexity: O(n) References: -- https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm -- Ramer, U. (1972). "An iterative procedure for the polygonal approximation - of plane curves". Computer Graphics and Image Processing. 1 (3): 244-256. -- Douglas, D.; Peucker, T. (1973). "Algorithms for the reduction of the number - of points required to represent a digitized line or its caricature". - Cartographica. 10 (2): 112-122. + https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm """ from __future__ import annotations import math -from collections.abc import Sequence def _euclidean_distance( - point_1: tuple[float, float], point_2: tuple[float, float] + point_a: tuple[float, float], + point_b: tuple[float, float], ) -> float: """Return the Euclidean distance between two 2-D points. @@ -34,7 +27,7 @@ def _euclidean_distance( >>> _euclidean_distance((1.0, 1.0), (1.0, 1.0)) 0.0 """ - return math.hypot(point_2[0] - point_1[0], point_2[1] - point_1[1]) + return math.hypot(point_b[0] - point_a[0], point_b[1] - point_a[1]) def _perpendicular_distance( @@ -42,12 +35,17 @@ def _perpendicular_distance( line_start: tuple[float, float], line_end: tuple[float, float], ) -> float: - """Return the perpendicular distance from *point* to the line through + """Return the distance from *point* to the line **segment** between *line_start* and *line_end*. - The result is the absolute value of the signed area of the triangle - (line_start, line_end, point) divided by the length of the segment, which - equals the altitude of that triangle from point. + When the perpendicular projection of *point* onto the infinite line falls + within the segment, this equals the perpendicular distance to that line. + When the projection falls outside the segment, the distance to the nearest + endpoint is returned instead (projection clamped to [0, 1]). + + This is the correct distance measure for the Ramer–Douglas–Peucker + algorithm: using the infinite-line distance can incorrectly discard points + whose projection lies beyond a segment endpoint. >>> _perpendicular_distance((4.0, 0.0), (0.0, 0.0), (0.0, 3.0)) 4.0 @@ -58,109 +56,126 @@ def _perpendicular_distance( 4.0 >>> _perpendicular_distance((2.0, 1.0), (-2.0, 1.0), (-2.0, 4.0)) 4.0 + >>> # projection falls outside the segment; distance to nearest endpoint + >>> round(_perpendicular_distance((0.0, 2.0), (1.0, 0.0), (3.0, 0.0)), 6) + 2.236068 """ px, py = point ax, ay = line_start bx, by = line_end - numerator = abs((by - ay) * px - (bx - ax) * py + bx * ay - by * ax) - denominator = _euclidean_distance(line_start, line_end) - if denominator == 0.0: + dx, dy = bx - ax, by - ay + seg_len_sq = dx * dx + dy * dy + if seg_len_sq == 0.0: # line_start and line_end coincide; fall back to point-to-point distance return _euclidean_distance(point, line_start) - return numerator / denominator + # Project point onto the segment line, then clamp t to [0, 1] so the + # nearest point is always on the segment rather than the infinite line. + t = max(0.0, min(1.0, ((px - ax) * dx + (py - ay) * dy) / seg_len_sq)) + nearest_x = ax + t * dx + nearest_y = ay + t * dy + return math.hypot(px - nearest_x, py - nearest_y) def ramer_douglas_peucker( - points: Sequence[tuple[float, float]], + pts: list[tuple[float, float]], epsilon: float, ) -> list[tuple[float, float]]: - """Simplify a polyline using the Ramer-Douglas-Peucker algorithm. + """Simplify a polyline using the Ramer–Douglas–Peucker algorithm. + + Given a sequence of 2-D points and a maximum allowable deviation + *epsilon* (>= 0), returns a simplified list of points such that no + discarded point is farther than *epsilon* from the simplified polyline. Parameters ---------- - points: - An ordered sequence of ``(x, y)`` tuples that form the polyline. + pts: + Ordered sequence of ``(x, y)`` points describing the polyline. epsilon: - Maximum allowable perpendicular deviation. Points whose distance - to the simplified segment is less than or equal to *epsilon* are - discarded. Must be non-negative. + Maximum allowable distance of any discarded point from the + simplified polyline. Must be non-negative. Returns ------- list[tuple[float, float]] - A simplified list of ``(x, y)`` points that is a subset of *points*. + Simplified list of ``(x, y)`` points. The first and last points of + *pts* are always preserved. Raises ------ ValueError If *epsilon* is negative. + References + ---------- + https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm + Examples -------- - Collinear points - middle point is redundant for any positive epsilon: - - >>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 0.0), (2.0, 0.0)], epsilon=0.5) - [(0.0, 0.0), (2.0, 0.0)] - - Empty / tiny inputs are returned unchanged: - >>> ramer_douglas_peucker([], epsilon=1.0) [] >>> ramer_douglas_peucker([(0.0, 0.0)], epsilon=1.0) [(0.0, 0.0)] - >>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 1.0)], epsilon=1.0) - [(0.0, 0.0), (1.0, 1.0)] - - A simple square outline where epsilon removes mid-edge points: - - >>> square = [ - ... (0.0, 0.0), (1.0, 0.0), (2.0, 0.0), - ... (2.0, 1.0), (2.0, 2.0), (1.0, 2.0), - ... (0.0, 2.0), (0.0, 1.0), - ... ] - >>> ramer_douglas_peucker(square, epsilon=0.7) - [(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 1.0)] - - A polygonal chain simplified to a single segment for large epsilon: - - >>> chain = [(0.0, 0.0), (2.0, 0.5), (3.0, 3.0), (6.0, 3.0), (8.0, 4.0)] - >>> ramer_douglas_peucker(chain, epsilon=3.0) - [(0.0, 0.0), (8.0, 4.0)] - - Zero epsilon keeps all points: - - >>> ramer_douglas_peucker([(0.0, 0.0), (0.5, 0.1), (1.0, 0.0)], epsilon=0.0) - [(0.0, 0.0), (0.5, 0.1), (1.0, 0.0)] + >>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 0.0)], epsilon=1.0) + [(0.0, 0.0), (1.0, 0.0)] + >>> # middle point is within epsilon – it is discarded + >>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 0.1), (2.0, 0.0)], epsilon=0.5) + [(0.0, 0.0), (2.0, 0.0)] + >>> # middle point exceeds epsilon – it is kept + >>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 1.0), (2.0, 0.0)], epsilon=0.5) + [(0.0, 0.0), (1.0, 1.0), (2.0, 0.0)] + >>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 0.5), (2.0, 0.0)], epsilon=-1.0) + Traceback (most recent call last): + ... + ValueError: epsilon must be non-negative, got -1.0 """ if epsilon < 0: msg = f"epsilon must be non-negative, got {epsilon!r}" raise ValueError(msg) - pts = list(points) - if len(pts) < 3: - return pts - - # Find the point with the greatest perpendicular distance from the line - # connecting the first and last points. - start, end = pts[0], pts[-1] - max_dist = 0.0 - max_index = 0 - for i in range(1, len(pts) - 1): - dist = _perpendicular_distance(pts[i], start, end) - if dist > max_dist: - max_dist = dist - max_index = i - - if max_dist > epsilon: - # Recursively simplify both halves and join them (drop the duplicate - # point at the junction). - left = ramer_douglas_peucker(pts[: max_index + 1], epsilon) - right = ramer_douglas_peucker(pts[max_index:], epsilon) - return left[:-1] + right - - # All intermediate points are within tolerance - keep only the endpoints. - return [start, end] + return list(pts) + + # --------------------------------------------------------------------------- + # Iterative, stack-based implementation. + # + # The naive recursive approach copies sublists at every level via slicing + # (pts[:max_index+1] / pts[max_index:]), which is O(n) per call and makes + # the overall algorithm O(n²) in memory even for well-balanced splits. An + # explicit stack operating on index ranges avoids all copying and also + # eliminates the risk of hitting Python's recursion limit for long polylines. + # --------------------------------------------------------------------------- + n = len(pts) + + # keep[i] is True when pts[i] must appear in the output. + keep: list[bool] = [False] * n + keep[0] = True + keep[-1] = True + + # Stack of (start_index, end_index) pairs still to be examined. + stack: list[tuple[int, int]] = [(0, n - 1)] + + while stack: + start, end = stack.pop() + if end - start < 2: + # Only one interior candidate at most; nothing to split further. + continue + + # Find the interior point with the greatest distance to the segment. + max_dist = 0.0 + max_index = start + for i in range(start + 1, end): + dist = _perpendicular_distance(pts[i], pts[start], pts[end]) + if dist > max_dist: + max_dist = dist + max_index = i + + if max_dist > epsilon: + keep[max_index] = True + stack.append((start, max_index)) + stack.append((max_index, end)) + # else: all interior points are within epsilon; discard them all. + + return [pts[i] for i in range(n) if keep[i]] if __name__ == "__main__": From b8d87e69b58ee9b4bab6813e5e36c56d184b9f3b Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi <41567902+AliAlimohammadi@users.noreply.github.com> Date: Tue, 19 May 2026 12:01:07 -0700 Subject: [PATCH 6/7] Update ramer_douglas_peucker.py From f3e1a56cf00447b3578c5f0f8185e79c617c654d Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi <41567902+AliAlimohammadi@users.noreply.github.com> Date: Tue, 19 May 2026 12:02:39 -0700 Subject: [PATCH 7/7] Update ramer_douglas_peucker.py --- geometry/ramer_douglas_peucker.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/geometry/ramer_douglas_peucker.py b/geometry/ramer_douglas_peucker.py index 7e966e8d7ee6..a03bbb2e5086 100644 --- a/geometry/ramer_douglas_peucker.py +++ b/geometry/ramer_douglas_peucker.py @@ -1,5 +1,5 @@ """ -Ramer–Douglas–Peucker polyline simplification algorithm. +Ramer-Douglas-Peucker polyline simplification algorithm. Given a sequence of 2-D points and a tolerance epsilon, the algorithm reduces the number of points while preserving the overall shape of the curve. @@ -43,7 +43,7 @@ def _perpendicular_distance( When the projection falls outside the segment, the distance to the nearest endpoint is returned instead (projection clamped to [0, 1]). - This is the correct distance measure for the Ramer–Douglas–Peucker + This is the correct distance measure for the Ramer-Douglas-Peucker algorithm: using the infinite-line distance can incorrectly discard points whose projection lies beyond a segment endpoint. @@ -80,7 +80,7 @@ def ramer_douglas_peucker( pts: list[tuple[float, float]], epsilon: float, ) -> list[tuple[float, float]]: - """Simplify a polyline using the Ramer–Douglas–Peucker algorithm. + """Simplify a polyline using the Ramer-Douglas-Peucker algorithm. Given a sequence of 2-D points and a maximum allowable deviation *epsilon* (>= 0), returns a simplified list of points such that no @@ -117,10 +117,10 @@ def ramer_douglas_peucker( [(0.0, 0.0)] >>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 0.0)], epsilon=1.0) [(0.0, 0.0), (1.0, 0.0)] - >>> # middle point is within epsilon – it is discarded + >>> # middle point is within epsilon - it is discarded >>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 0.1), (2.0, 0.0)], epsilon=0.5) [(0.0, 0.0), (2.0, 0.0)] - >>> # middle point exceeds epsilon – it is kept + >>> # middle point exceeds epsilon - it is kept >>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 1.0), (2.0, 0.0)], epsilon=0.5) [(0.0, 0.0), (1.0, 1.0), (2.0, 0.0)] >>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 0.5), (2.0, 0.0)], epsilon=-1.0)