diff options
| author | Daniel Schadt <kingdread@gmx.de> | 2026-10-16 00:12:07 +0200 |
|---|---|---|
| committer | Daniel Schadt <kingdread@gmx.de> | 2026-10-16 00:12:07 +0200 |
| commit | b98b74cbe4efafbe22aa7db759c1b95f89b7f7a3 (patch) | |
| tree | 5ac9a203df54f0b606b1577b71ad82174536e7d8 | |
| parent | 485fcb9107957d391af3c30db5d577ed84f7e6e9 (diff) | |
| download | fietsboek-b98b74cbe4efafbe22aa7db759c1b95f89b7f7a3.tar.gz fietsboek-b98b74cbe4efafbe22aa7db759c1b95f89b7f7a3.tar.bz2 fietsboek-b98b74cbe4efafbe22aa7db759c1b95f89b7f7a3.zip | |
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
| -rw-r--r-- | fietsboek/trackmap.py | 71 |
1 files 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'<polygon points="{cur_x},0 {cur_x},{-left_y} {right_x},{-right_y} {right_x},0" ' - f'fill="{color}" stroke="{color}" />\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'<polygon points="{cur_x},0 {cur_x},{-cur_y} ') + while i < len(polyline): + p_x, p_y, _, _ = polyline[i] + out.write(f"{p_x},{-p_y} ") + if p_x > 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("</svg>") @@ -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"] |
