fix: per-source elevation params — strava_export vs barometric vs raw GPS
Previous thresholds (10 m GPS, 5 m barometric, 30 s MA) were calibrated for raw noisy GPS. Strava-exported FIT files carry elevation already pre-processed by Strava (smooth 1 m quantisation, no steps > 5 m), so the aggressive filtering suppressed real climbing — avg −17 % error across 37 reference activities. New strategy, keyed on source + altitude_source: strava_export → MA 5 s, threshold 1.0 m fit_file / barometric → no MA, threshold 1.5 m fit_file / gps → MA 5 s, threshold 2.0 m unknown non-strava → MA 5 s, threshold 1.5 m Result on 37 cross-referenced activities: avg −2.8 %, std 4.6 %, 37/37 within ±15 % (was 0/37). Both paths — initial import (metrics._elevation) and bulk recalculate (dem.recalculate_elevation_hysteresis) — now use the same elevation_params() function from metrics.py.
This commit is contained in:
@@ -19,6 +19,8 @@ import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from bincio.extract.metrics import elevation_params
|
||||
|
||||
# Sample one GPS point per N seconds when building the DEM query.
|
||||
# SRTM30 resolution is ~30 m; at 30 km/h cycling that's ~3 s per tile —
|
||||
# sampling every 10 s is more than enough.
|
||||
@@ -346,10 +348,10 @@ def recalculate_elevation_hysteresis(user_dir: Path, activity_id: str) -> dict:
|
||||
if len(elevations) < 2:
|
||||
raise ValueError("Not enough elevation data to compute gain/loss")
|
||||
|
||||
# Determine source-aware threshold
|
||||
detail = json.loads(json_path.read_text(encoding="utf-8"))
|
||||
altitude_source = detail.get("altitude_source", "unknown")
|
||||
threshold = 1.0 if altitude_source == "barometric" else 3.0
|
||||
source = detail.get("source") or ""
|
||||
ma_window, threshold = elevation_params(altitude_source, source)
|
||||
|
||||
# Strip leading no-fix zeros (same logic as metrics._elevation)
|
||||
if elevations and abs(elevations[0]) < 0.5:
|
||||
@@ -358,8 +360,7 @@ def recalculate_elevation_hysteresis(user_dir: Path, activity_id: str) -> dict:
|
||||
elevations = elevations[i:]
|
||||
break
|
||||
|
||||
# Pre-smooth to suppress noise, then accumulate with low dead-band
|
||||
smoothed = _moving_average(elevations, _MA_WINDOW_S)
|
||||
smoothed = _moving_average(elevations, ma_window) if ma_window > 1 else elevations
|
||||
gain, loss = _hysteresis_gain_loss(smoothed, threshold)
|
||||
gain_r = round(gain, 1)
|
||||
loss_r = round(loss, 1)
|
||||
@@ -385,5 +386,7 @@ def recalculate_elevation_hysteresis(user_dir: Path, activity_id: str) -> dict:
|
||||
"elevation_gain_m": gain_r,
|
||||
"elevation_loss_m": loss_r,
|
||||
"threshold_m": threshold,
|
||||
"ma_window_s": ma_window,
|
||||
"altitude_source": altitude_source,
|
||||
"source": source,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user