Fix segment avg_speed: derive from distance/time; tighten speed bounds to reject false matches
This commit is contained in:
@@ -25,10 +25,17 @@ CONFORMANCE_MAX_FRAC = 0.30 # max fraction of interior points allowed to deviat
|
||||
# Rejects false matches from long circuit rides where the track passes the
|
||||
# segment start early and the segment end hours later.
|
||||
_MIN_SPEED_MS: dict[str, float] = {
|
||||
'cycling': 1.0, # ~3.6 km/h — slowest realistic climb
|
||||
'running': 0.5, # ~1.8 km/h
|
||||
'cycling': 2.0, # ~7.2 km/h — below any realistic cyclist even on brutal climbs
|
||||
'running': 0.8, # ~2.9 km/h
|
||||
}
|
||||
_MIN_SPEED_DEFAULT = 0.2 # hiking / walking / unknown
|
||||
_MIN_SPEED_DEFAULT = 0.3 # hiking / walking / unknown
|
||||
|
||||
# Maximum geometric speed per sport in m/s — rejects GPS glitch matches.
|
||||
_MAX_SPEED_MS: dict[str, float] = {
|
||||
'cycling': 30.0, # ~108 km/h
|
||||
'running': 12.0, # ~43 km/h
|
||||
}
|
||||
_MAX_SPEED_DEFAULT = 20.0
|
||||
|
||||
# ── fast distance approximation ───────────────────────────────────────────────
|
||||
|
||||
@@ -207,7 +214,10 @@ def _extract_effort(
|
||||
) -> SegmentEffort:
|
||||
elapsed_s = track.times[j] - track.times[i]
|
||||
started_at = (track.started_at + timedelta(seconds=track.times[i])).replace(microsecond=0)
|
||||
avg_speed = _avg_nonnull(track.speeds, i, j)
|
||||
# Always derive avg speed from segment distance / elapsed time. Device-recorded
|
||||
# speed is unreliable across formats (m/s vs km/h in older FIT files) and
|
||||
# averaging instantaneous GPS speed over a slice gives different results anyway.
|
||||
avg_speed = (seg.distance_m / elapsed_s * 3.6) if elapsed_s > 0 else None
|
||||
avg_hr_raw = _avg_nonnull(track.hrs, i, j)
|
||||
avg_hr = int(round(avg_hr_raw)) if avg_hr_raw is not None else None
|
||||
avg_pwr_raw = _avg_nonnull(track.powers, i, j)
|
||||
@@ -259,12 +269,13 @@ def detect_one(track: ActivityTrack, seg: Segment) -> list[SegmentEffort]:
|
||||
# No end found — no more efforts possible starting at or after start_idx.
|
||||
break
|
||||
|
||||
# Reject implausibly slow matches (e.g. circuit rides matching start
|
||||
# early and end hours later on a second pass through the area).
|
||||
# Reject implausibly slow or fast matches.
|
||||
elapsed = track.times[end_idx] - track.times[start_idx]
|
||||
if elapsed > 0:
|
||||
geo_speed = seg.distance_m / elapsed
|
||||
min_speed = _MIN_SPEED_MS.get(track.sport, _MIN_SPEED_DEFAULT)
|
||||
if seg.distance_m / elapsed < min_speed:
|
||||
max_speed = _MAX_SPEED_MS.get(track.sport, _MAX_SPEED_DEFAULT)
|
||||
if geo_speed < min_speed or geo_speed > max_speed:
|
||||
search_from = start_idx + 1
|
||||
continue
|
||||
|
||||
|
||||
Reference in New Issue
Block a user