aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Schadt <kingdread@gmx.de>2026-10-16 00:12:07 +0200
committerDaniel Schadt <kingdread@gmx.de>2026-10-16 00:12:07 +0200
commitb98b74cbe4efafbe22aa7db759c1b95f89b7f7a3 (patch)
tree5ac9a203df54f0b606b1577b71ad82174536e7d8
parent485fcb9107957d391af3c30db5d577ed84f7e6e9 (diff)
downloadfietsboek-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.py71
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"]