Skip to content

Commit a3edaec

Browse files
committed
fix polylines: iterative rdp, doc XY-only simplify, py39 compat, validate threshold
1 parent 570375a commit a3edaec

File tree

3 files changed

+46
-28
lines changed

3 files changed

+46
-28
lines changed

src/compas_cgal/polylines.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""Polyline utilities using CGAL."""
22

3-
from __future__ import annotations
4-
53
from typing import TYPE_CHECKING
64
from typing import List
75
from typing import Union
@@ -20,9 +18,12 @@
2018
__all__ = ["simplify_polylines", "simplify_polyline", "closest_points_on_polyline"]
2119

2220

23-
def simplify_polylines(polylines: list[PointsList], threshold: float) -> list[NDArray]:
21+
def simplify_polylines(polylines: List[PointsList], threshold: float) -> List[NDArray]:
2422
"""Simplify multiple polylines using Douglas-Peucker algorithm.
2523
24+
Simplification is performed in the XY plane only. For 3D polylines,
25+
Z coordinates are preserved but not considered in distance calculations.
26+
2627
Parameters
2728
----------
2829
polylines : list of array-like
@@ -45,13 +46,18 @@ def simplify_polylines(polylines: list[PointsList], threshold: float) -> list[ND
4546
3
4647
4748
"""
49+
if threshold < 0:
50+
raise ValueError("threshold must be non-negative")
4851
arrays = [np.asarray(p, dtype=np.float64) for p in polylines]
4952
return _simplify(arrays, threshold)
5053

5154

5255
def simplify_polyline(polyline: PointsList, threshold: float) -> NDArray:
5356
"""Simplify a single polyline using Douglas-Peucker algorithm.
5457
58+
Simplification is performed in the XY plane only. For 3D polylines,
59+
Z coordinates are preserved but not considered in distance calculations.
60+
5561
Parameters
5662
----------
5763
polyline : array-like

src/polylines.cpp

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,37 +33,46 @@ static double perpendicular_distance(
3333
return std::sqrt((px - proj_x) * (px - proj_x) + (py - proj_y) * (py - proj_y));
3434
}
3535

36-
// Recursive Douglas-Peucker
36+
// Iterative Douglas-Peucker (avoids stack overflow on large polylines)
3737
static void rdp_simplify(
3838
const compas::RowMatrixXd& polyline,
3939
int start, int end,
4040
double threshold,
4141
std::vector<bool>& keep)
4242
{
43-
if (end <= start + 1) return;
44-
45-
double max_dist = 0.0;
46-
int max_idx = start;
47-
48-
double x1 = polyline(start, 0);
49-
double y1 = polyline(start, 1);
50-
double x2 = polyline(end, 0);
51-
double y2 = polyline(end, 1);
52-
53-
for (int i = start + 1; i < end; ++i) {
54-
double dist = perpendicular_distance(
55-
polyline(i, 0), polyline(i, 1),
56-
x1, y1, x2, y2);
57-
if (dist > max_dist) {
58-
max_dist = dist;
59-
max_idx = i;
43+
std::vector<std::pair<int, int>> stack;
44+
stack.reserve(128); // Reasonable default to avoid reallocs
45+
stack.push_back({start, end});
46+
47+
while (!stack.empty()) {
48+
auto [seg_start, seg_end] = stack.back();
49+
stack.pop_back();
50+
51+
if (seg_end <= seg_start + 1) continue;
52+
53+
double max_dist = 0.0;
54+
int max_idx = seg_start;
55+
56+
double x1 = polyline(seg_start, 0);
57+
double y1 = polyline(seg_start, 1);
58+
double x2 = polyline(seg_end, 0);
59+
double y2 = polyline(seg_end, 1);
60+
61+
for (int i = seg_start + 1; i < seg_end; ++i) {
62+
double dist = perpendicular_distance(
63+
polyline(i, 0), polyline(i, 1),
64+
x1, y1, x2, y2);
65+
if (dist > max_dist) {
66+
max_dist = dist;
67+
max_idx = i;
68+
}
6069
}
61-
}
6270

63-
if (max_dist > threshold) {
64-
keep[max_idx] = true;
65-
rdp_simplify(polyline, start, max_idx, threshold, keep);
66-
rdp_simplify(polyline, max_idx, end, threshold, keep);
71+
if (max_dist > threshold) {
72+
keep[max_idx] = true;
73+
stack.push_back({seg_start, max_idx});
74+
stack.push_back({max_idx, seg_end});
75+
}
6776
}
6877
}
6978

src/polylines.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
#include <CGAL/AABB_segment_primitive.h>
99

1010
/**
11-
* @brief Simplify multiple 2D polylines using Douglas-Peucker algorithm.
11+
* @brief Simplify polylines using Douglas-Peucker algorithm.
12+
*
13+
* Simplification is performed in the XY plane only. For 3D polylines,
14+
* Z coordinates are preserved but not considered in distance calculations.
1215
*
1316
* @param polylines Vector of polylines, each as Nx2 or Nx3 matrix (float64)
14-
* @param threshold Squared distance threshold for simplification
17+
* @param threshold Distance threshold for simplification
1518
* @return std::vector<compas::RowMatrixXd> Simplified polylines
1619
*/
1720
std::vector<compas::RowMatrixXd>

0 commit comments

Comments
 (0)