From b98b74cbe4efafbe22aa7db759c1b95f89b7f7a3 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Fri, 16 Oct 2026 00:12:07 +0200 Subject: simplify climb profile colors this does two things: mainly, it restricts the number of different areas that we color in the climb height profile, which avoids very thin lines in the profile it also changes the SVG structure to include less polygons, which can improve the rendering a bit --- fietsboek/trackmap.py | 71 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/fietsboek/trackmap.py b/fietsboek/trackmap.py index df21af1..d2f16e0 100644 --- a/fietsboek/trackmap.py +++ b/fietsboek/trackmap.py @@ -171,7 +171,7 @@ def _ascension_to_color(ascension: float) -> str: return "#57bf93" -def height_profile(track: geo.Path, size: tuple[int, int] = (300, 300)) -> str: +def height_profile(track: geo.Path, size: tuple[int, int] = (300, 300), simplify: int = 10) -> str: """Generates a height profile for the given track. The output will be the image as a SVG source. @@ -181,6 +181,8 @@ def height_profile(track: geo.Path, size: tuple[int, int] = (300, 300)) -> str: :param track: Path for which to generate a height profile. :param size: Display size of the image (embedded as metadata). + :param simplify: Number of colored areas to simplify the colorization. If + 0, do not simplify anything. :return: The height profile as SVG image. """ # pylint: disable=too-many-locals @@ -199,18 +201,46 @@ def height_profile(track: geo.Path, size: tuple[int, int] = (300, 300)) -> str: 'xmlns="http://www.w3.org/2000/svg">\n' ) - cur_x = 0.0 - for left, right in zip(track.points, track.points[1:]): - right_x = cur_x + (left.distance(right) / path_len) * 1000 - left_y = (left.elevation - min_elev) / delta_elev * 1000 - right_y = (right.elevation - min_elev) / delta_elev * 1000 - ascension = (right.elevation - left.elevation) / max(left.distance(right), 0.001) - color = _ascension_to_color(ascension) - out.write( - f'\n' + polyline = [] + last = None + dist = 0.0 + for point in track.points: + if last is not None: + dist += last.distance(point) + cur_x = dist / path_len * 1000 + cur_y = (point.elevation - min_elev) / delta_elev * 1000 + polyline.append((cur_x, cur_y, dist, point.elevation)) + last = point + + if simplify: + simplified = _visvalignam_whyatt(polyline, simplify) + else: + simplified = polyline + color_areas = [ + ( + right_x, + _ascension_to_color((right_elev - left_elev) / max(right_dist - left_dist, 0.001)), + ) + for ((_, _, left_dist, left_elev), (right_x, _, right_dist, right_elev)) in zip( + simplified, simplified[1:] ) - cur_x = right_x + ] + + cur_x = 0.0 + cur_y = 0.0 + i = 0 + for limit, color in color_areas: + out.write(f' limit: + break + i += 1 + out.write(f"{p_x},0") + out.write(f'" fill="{color}" stroke="{color}" />\n') + cur_x = p_x + cur_y = p_y out.write("") @@ -219,4 +249,21 @@ def height_profile(track: geo.Path, size: tuple[int, int] = (300, 300)) -> str: return result +def _visvalignam_whyatt( + polyline: list[tuple[float, float, float, float]], + target: int, +) -> list[tuple[float, float, float, float]]: + polyline = polyline[:] + while len(polyline) > target: + areas = ( + 1 / 2 * abs(x0 * y1 + x1 * y2 + x2 * y0 - x0 * y2 - x1 * y0 - x2 * y1) + for ((x0, y0, _, _), (x1, y1, _, _), (x2, y2, _, _)) in zip( + polyline, polyline[1:], polyline[2:] + ) + ) + index = min(enumerate(areas), key=lambda x: x[1])[0] + del polyline[index + 1] + return polyline + + __all__ = ["to_web_mercator", "TrackMapRenderer", "render", "height_profile"] -- cgit v1.2.3