aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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"]