aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fietsboek/transformers/__init__.py95
-rw-r--r--fietsboek/transformers/elevation.py106
2 files changed, 107 insertions, 94 deletions
diff --git a/fietsboek/transformers/__init__.py b/fietsboek/transformers/__init__.py
index 330e699..7df700c 100644
--- a/fietsboek/transformers/__init__.py
+++ b/fietsboek/transformers/__init__.py
@@ -11,7 +11,6 @@ function to load and apply transformers.
from abc import ABC, abstractmethod
from collections.abc import Callable, Iterable, Mapping
-from itertools import islice
from typing import Literal, NamedTuple, TypeVar
from gpxpy.gpx import GPX, GPXTrackPoint
@@ -128,99 +127,6 @@ class Transformer(ABC):
"""
-class FixNullElevation(Transformer):
- """A transformer that fixes points with zero elevation."""
-
- @classmethod
- def identifier(cls) -> str:
- return "fix-null-elevation"
-
- @classmethod
- def name(cls) -> TranslationString:
- return _("transformers.fix-null-elevation.title")
-
- @classmethod
- def description(cls) -> TranslationString:
- return _("transformers.fix-null-elevation.description")
-
- @classmethod
- def parameter_model(cls) -> type[Parameters]:
- return Parameters
-
- @property
- def parameters(self) -> Parameters:
- return Parameters()
-
- @parameters.setter
- def parameters(self, value):
- pass
-
- def execute(self, gpx: GPX):
- def all_points():
- return gpx.walk(only_points=True)
-
- def rev_points():
- # We cannot use reversed(gpx.walk(...)) since that is not a
- # generator, so we do it manually.
- return (
- point
- for track in reversed(gpx.tracks)
- for segment in reversed(track.segments)
- for point in reversed(segment.points)
- )
-
- # First, from the front
- self.fixup(all_points)
- # Then, from the back
- self.fixup(rev_points)
-
- @classmethod
- def fixup(cls, points: Callable[[], Iterable[GPXTrackPoint]]):
- """Fixes the given GPX points.
-
- This iterates over the points and checks for the first point that has a
- non-zero elevation, and a slope that doesn't exceed 100%. All previous
- points will have their elevation adjusted to match this first "good
- point".
-
- :param points: A function that generates the iterable of points.
- """
- max_slope = 1.0
-
- bad_until = 0
- final_elevation = 0.0
- for i, (point, next_point) in enumerate(zip(points(), islice(points(), 1, None))):
- if (
- point.elevation is not None
- and point.elevation != 0.0
- and cls.slope(point, next_point) < max_slope
- ):
- bad_until = i
- final_elevation = point.elevation
- break
-
- for point in islice(points(), None, bad_until):
- point.elevation = final_elevation
-
- @staticmethod
- def slope(point_a: GPXTrackPoint, point_b: GPXTrackPoint) -> float:
- """Returns the slope between two GPX points.
-
- This is defined as delta_h / euclid_distance.
-
- :param point_a: First point.
- :param point_b: Second point.
- :return: The slope, as percentage.
- """
- if point_a.elevation is None or point_b.elevation is None:
- return 0.0
- delta_h = abs(point_a.elevation - point_b.elevation)
- dist = point_a.distance_2d(point_b)
- if dist == 0.0 or dist is None:
- return 0.0
- return delta_h / dist
-
-
def list_transformers() -> list[type[Transformer]]:
"""Returns a list of all available transformers.
@@ -228,6 +134,7 @@ def list_transformers() -> list[type[Transformer]]:
"""
# pylint: disable=import-outside-toplevel,cyclic-import
from .breaks import RemoveBreaks
+ from .elevation import FixNullElevation
return [
FixNullElevation,
diff --git a/fietsboek/transformers/elevation.py b/fietsboek/transformers/elevation.py
new file mode 100644
index 0000000..0e6f3b0
--- /dev/null
+++ b/fietsboek/transformers/elevation.py
@@ -0,0 +1,106 @@
+"""Transformers that deal with elevation changes in the track."""
+from collections.abc import Callable, Iterable
+from itertools import islice
+
+from gpxpy.gpx import GPX, GPXTrackPoint
+from pyramid.i18n import TranslationString
+
+from . import Parameters, Transformer
+
+_ = TranslationString
+
+
+class FixNullElevation(Transformer):
+ """A transformer that fixes points with zero elevation."""
+
+ @classmethod
+ def identifier(cls) -> str:
+ return "fix-null-elevation"
+
+ @classmethod
+ def name(cls) -> TranslationString:
+ return _("transformers.fix-null-elevation.title")
+
+ @classmethod
+ def description(cls) -> TranslationString:
+ return _("transformers.fix-null-elevation.description")
+
+ @classmethod
+ def parameter_model(cls) -> type[Parameters]:
+ return Parameters
+
+ @property
+ def parameters(self) -> Parameters:
+ return Parameters()
+
+ @parameters.setter
+ def parameters(self, value):
+ pass
+
+ def execute(self, gpx: GPX):
+ def all_points():
+ return gpx.walk(only_points=True)
+
+ def rev_points():
+ # We cannot use reversed(gpx.walk(...)) since that is not a
+ # generator, so we do it manually.
+ return (
+ point
+ for track in reversed(gpx.tracks)
+ for segment in reversed(track.segments)
+ for point in reversed(segment.points)
+ )
+
+ # First, from the front
+ self.fixup(all_points)
+ # Then, from the back
+ self.fixup(rev_points)
+
+ @classmethod
+ def fixup(cls, points: Callable[[], Iterable[GPXTrackPoint]]):
+ """Fixes the given GPX points.
+
+ This iterates over the points and checks for the first point that has a
+ non-zero elevation, and a slope that doesn't exceed 100%. All previous
+ points will have their elevation adjusted to match this first "good
+ point".
+
+ :param points: A function that generates the iterable of points.
+ """
+ max_slope = 1.0
+
+ bad_until = 0
+ final_elevation = 0.0
+ for i, (point, next_point) in enumerate(zip(points(), islice(points(), 1, None))):
+ if (
+ point.elevation is not None
+ and point.elevation != 0.0
+ and cls.slope(point, next_point) < max_slope
+ ):
+ bad_until = i
+ final_elevation = point.elevation
+ break
+
+ for point in islice(points(), None, bad_until):
+ point.elevation = final_elevation
+
+ @staticmethod
+ def slope(point_a: GPXTrackPoint, point_b: GPXTrackPoint) -> float:
+ """Returns the slope between two GPX points.
+
+ This is defined as delta_h / euclid_distance.
+
+ :param point_a: First point.
+ :param point_b: Second point.
+ :return: The slope, as percentage.
+ """
+ if point_a.elevation is None or point_b.elevation is None:
+ return 0.0
+ delta_h = abs(point_a.elevation - point_b.elevation)
+ dist = point_a.distance_2d(point_b)
+ if dist == 0.0 or dist is None:
+ return 0.0
+ return delta_h / dist
+
+
+__all__ = ["FixNullElevation"]