aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fietsboek/actions.py20
-rw-r--r--fietsboek/models/track.py40
-rw-r--r--fietsboek/transformers/__init__.py12
-rw-r--r--fietsboek/transformers/elevation.py5
-rw-r--r--fietsboek/updater/scripts/upd_20261015_gg7gfmpk4nxl062z.py69
5 files changed, 125 insertions, 21 deletions
diff --git a/fietsboek/actions.py b/fietsboek/actions.py
index a50884f..adc0616 100644
--- a/fietsboek/actions.py
+++ b/fietsboek/actions.py
@@ -216,27 +216,11 @@ def execute_transformers(request: Request, track: models.Track):
LOGGER.debug("Applied transformations match on %d, skipping", track.id)
return
- # We always start with the backup, that way we don't get "deepfried GPX"
- # files by having the same filters run multiple times on the same input.
- # They are not idempotent after all.
- manager = request.data_manager.open(track.id)
- backup_bytes = manager.decompress_backup()
- reloaded = convert.smart_convert(backup_bytes)
- path = reloaded.path()
-
- for transformer in settings:
- LOGGER.debug("Running %s with %r", transformer, transformer.parameters)
- transformer.execute(path)
-
- track.fast_set_path(path)
-
LOGGER.debug("Saving new transformers on %d", track.id)
track.transformers = serialized
- LOGGER.debug("Rebuilding cache for %d", track.id)
- request.dbsession.delete(track.cache)
- track.cache = None
- track.ensure_cache()
+ track.execute_transformers(request.data_manager)
+
request.dbsession.add(track.cache)
diff --git a/fietsboek/models/track.py b/fietsboek/models/track.py
index 6272aef..13787c8 100644
--- a/fietsboek/models/track.py
+++ b/fietsboek/models/track.py
@@ -54,6 +54,7 @@ from sqlalchemy import (
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .. import geo, util
+from ..data import DataManager
from .meta import Base
if TYPE_CHECKING:
@@ -626,6 +627,45 @@ class Track(Base):
return settings
return None
+ def execute_transformers(self, data_manager: DataManager):
+ """Makes sure that the transformers applied to this track match what they should be.
+
+ The track's data is loaded fresh from the backup (as given by the data manager).
+ """
+ # The conversion returns a Track, so it imports this model.
+ # pylint: disable=import-outside-toplevel,cyclic-import
+ from .. import convert, transformers
+
+ if self.transformers is None:
+ return
+
+ manager = data_manager.open(self.id)
+ backup_bytes = manager.decompress_backup()
+ reloaded = convert.smart_convert(backup_bytes)
+ path = reloaded.path()
+
+ if not isinstance(self.transformers, list):
+ raise ValueError("Track.transformers should be a list or None")
+
+ for transformer_id, parameters in self.transformers:
+ if not isinstance(parameters, dict):
+ LOGGER.debug("Invalid parameters on track %s: %r", self.id, parameters)
+ continue
+ transformer_cls = transformers.get_transformer(transformer_id)
+ if transformer_cls is None:
+ LOGGER.info("Unknown transformer set in track %s: %s", self.id, transformer_id)
+ continue
+ transformer = transformer_cls()
+ transformer.parameters = transformer.parameter_model()(**parameters)
+ LOGGER.debug("Running %s with %r", transformer, transformer.parameters)
+ transformer.execute(path)
+
+ self.fast_set_path(path)
+
+ LOGGER.debug("Rebuilding cache for %d", self.id)
+ self.cache = None
+ self.ensure_cache()
+
class TrackWithMetadata:
"""A class to add metadata to a :class:`Track`.
diff --git a/fietsboek/transformers/__init__.py b/fietsboek/transformers/__init__.py
index c6e159c..4542721 100644
--- a/fietsboek/transformers/__init__.py
+++ b/fietsboek/transformers/__init__.py
@@ -150,6 +150,18 @@ def list_transformers() -> list[type[Transformer]]:
]
+def get_transformer(identifier: str) -> type[Transformer] | None:
+ """Retrieve the transformer with the given identifier.
+
+ :param identifier: The identifier of the transformer to retrieve.
+ :return: The class of the transformer.
+ """
+ for transformer in list_transformers():
+ if transformer.identifier() == identifier:
+ return transformer
+ return None
+
+
def extract_from_request(request: Request) -> list[Transformer]:
"""Extracts the list of transformers to execute from the given request.
diff --git a/fietsboek/transformers/elevation.py b/fietsboek/transformers/elevation.py
index 15a5061..67d3ecf 100644
--- a/fietsboek/transformers/elevation.py
+++ b/fietsboek/transformers/elevation.py
@@ -226,15 +226,14 @@ class SmoothElevation(Transformer):
def _snr(self, path: geo.Path) -> float:
window_size = 5
- errors = []
+ errors: list[float] = []
for start in range(0, len(path.points) - window_size):
elevations = [point.elevation for point in path.points[start : start + window_size]]
xs = list(range(len(elevations)))
sl, intercept = statistics.linear_regression(xs, elevations)
errors.extend(
- abs(elevation - (x * sl + intercept))
- for x, elevation in enumerate(elevations)
+ abs(elevation - (x * sl + intercept)) for x, elevation in enumerate(elevations)
)
return statistics.mean(errors)
diff --git a/fietsboek/updater/scripts/upd_20261015_gg7gfmpk4nxl062z.py b/fietsboek/updater/scripts/upd_20261015_gg7gfmpk4nxl062z.py
new file mode 100644
index 0000000..4168c5d
--- /dev/null
+++ b/fietsboek/updater/scripts/upd_20261015_gg7gfmpk4nxl062z.py
@@ -0,0 +1,69 @@
+"""Revision upgrade script gg7gfmpk4nxl062z
+
+Active the "smooth elevation" transformer.
+
+Date created: 2026-10-15 14:22:06.047695
+"""
+from pathlib import Path
+
+from sqlalchemy import select
+from sqlalchemy.orm import Session
+from sqlalchemy.orm.attributes import flag_modified
+
+from fietsboek import models
+from fietsboek.data import DataManager
+from fietsboek.updater.script import UpdateScript
+
+update_id = 'gg7gfmpk4nxl062z'
+previous = [
+ '7fa8kflikvnlvuqe',
+]
+alembic_revision = 'b73ed8b45f67'
+
+
+class Up(UpdateScript):
+ def pre_alembic(self, config):
+ pass
+
+ def post_alembic(self, config):
+ data_manager = DataManager(Path(config["fietsboek.data_dir"]))
+ engine = self.dbengine(config)
+ session = Session(engine)
+
+ with Session(engine) as session:
+ query = select(models.Track)
+ for track in session.execute(query).scalars():
+ if track.type != models.track.TrackType.ORGANIC:
+ continue
+ self.tell(f"Applying smoothing to track {track.id}")
+ track.transformers.append(["smooth-elevation", {}])
+ flag_modified(track, "transformers")
+ track.execute_transformers(data_manager)
+ session.add(track)
+ session.add(track.cache)
+ session.commit()
+
+
+class Down(UpdateScript):
+ def pre_alembic(self, config):
+ pass
+
+ def post_alembic(self, config):
+ data_manager = DataManager(Path(config["fietsboek.data_dir"]))
+ engine = self.dbengine(config)
+ session = Session(engine)
+
+ with Session(engine) as session:
+ query = select(models.Track)
+ for track in session.execute(query).scalars():
+ if track.type != models.track.TrackType.ORGANIC:
+ continue
+ self.tell(f"Removing smoothing from track {track.id}")
+ unsmoothed = [[k, v] for k, v in track.transformers if k != "smooth-elevation"]
+ if unsmoothed == track.transformers:
+ continue
+ track.transformers = unsmoothed
+ track.execute_transformers(data_manager)
+ session.add(track)
+ session.add(track.cache)
+ session.commit()