diff options
| -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"] |
