diff options
| author | Lex Neva <github.com@lexneva.name> | 2022-08-25 16:19:54 -0400 |
|---|---|---|
| committer | Lex Neva <github.com@lexneva.name> | 2023-02-20 15:27:35 -0500 |
| commit | dae4573bd2b11e9dcf5c9afe93104ef91befda2e (patch) | |
| tree | ec97bf125b888bf7744da10f9b6c0a2927df4c84 /lib | |
| parent | 8b98083ac723e4145a7c41483f7dda10f722566f (diff) | |
add smooth_path
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/utils/geometry.py | 56 |
1 files changed, 56 insertions, 0 deletions
diff --git a/lib/utils/geometry.py b/lib/utils/geometry.py index 789f8720..c07172c7 100644 --- a/lib/utils/geometry.py +++ b/lib/utils/geometry.py @@ -7,6 +7,8 @@ import math from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, MultiPoint, GeometryCollection from shapely.geometry import Point as ShapelyPoint +from scipy.interpolate import splprep, splev +import numpy as np def cut(line, distance, normalized=False): @@ -147,6 +149,60 @@ def cut_path(points, length): return [Point(*point) for point in subpath.coords] +def _remove_duplicate_coordinates(coords_array): + """Remove consecutive duplicate points from an array. + + Arguments: + coords_array -- numpy.array + + Returns: + a numpy.array of coordinates, minus consecutive duplicates + """ + + differences = np.diff(coords_array, axis=0) + zero_differences = np.isclose(differences, 0) + keepers = np.r_[True, np.any(zero_differences == False, axis=1)] # noqa: E712 + + return coords_array[keepers] + + +def smooth_path(path, smoothness=100.0): + """Smooth a path of coordinates. + + Arguments: + path -- an iterable of coordinate tuples or Points + smoothness -- float, how much smoothing to apply. Bigger numbers + smooth more. + + Returns: + A list of Points. + """ + + # splprep blows up on duplicated consecutive points with "Invalid inputs" + coords = _remove_duplicate_coordinates(np.array(path)) + num_points = len(coords) + + # splprep's s parameter seems to depend on the number of points you pass + # as per the docs, so let's normalize it. + s = round(smoothness / 100 * num_points) + + # .T transposes the array (for some reason splprep expects + # [[x1, x2, ...], [y1, y2, ...]] + tck, fp, ier, msg = splprep(coords.T, s=s, nest=-1, full_output=1) + if ier > 0: + from ..debug import debug + debug.log(f"error {ier} smoothing path: {msg}") + return path + + # Evaluate the spline curve at many points along its length to produce the + # smoothed point list. 2 * num_points seems to be a good number, but it + # does produce a lot of points. + smoothed_x_values, smoothed_y_values = splev(np.linspace(0, 1, num_points * 2), tck[0]) + coords = np.array([smoothed_x_values, smoothed_y_values]).T + + return [Point(x, y) for x, y in coords] + + class Point: def __init__(self, x: float, y: float): self.x = x |
