fix: smooth GPS-derived replay speed to kill jitter spikes
route_points (Pi) and get_trip_route (server) derived GPS speed from a single 5s segment, so GPS position jitter could read 150+ km/h when the car wasn't going that fast. Both now average over a trailing ~30s window (same as the max-speed calc), so spikes are damped.
This commit is contained in:
+22
-6
@@ -285,12 +285,28 @@ def route_points(raw_rows, max_points=2000, odometer_baseline=0.0, prior_km=0.0)
|
|||||||
for index, row in enumerate(rows):
|
for index, row in enumerate(rows):
|
||||||
row["speed_source"] = "obd" if row["speed"] > 0 else "gps"
|
row["speed_source"] = "obd" if row["speed"] > 0 else "gps"
|
||||||
if row["speed"] <= 0 and index:
|
if row["speed"] <= 0 and index:
|
||||||
distance_km, segment_speed = _segment(row, rows[index - 1])
|
# Derive GPS speed over a trailing ~30s window, not the raw 5s
|
||||||
row["speed"] = (
|
# segment — a single segment can spike 100+ km/h on GPS jitter
|
||||||
segment_speed
|
# (same smoothing build_trip_summaries uses for max speed).
|
||||||
if distance_km * 1000 >= GPS_MOTION_METERS
|
window_start = max(0, index - GPS_SPEED_WINDOW)
|
||||||
else 0
|
start_row = rows[window_start]
|
||||||
)
|
seconds = (
|
||||||
|
row["datetime"] - start_row["datetime"]
|
||||||
|
).total_seconds()
|
||||||
|
if seconds > 0:
|
||||||
|
window_km = haversine_km(
|
||||||
|
(start_row["latitude"], start_row["longitude"]),
|
||||||
|
(row["latitude"], row["longitude"]),
|
||||||
|
)
|
||||||
|
window_speed = window_km / seconds * 3600
|
||||||
|
row["speed"] = (
|
||||||
|
window_speed
|
||||||
|
if window_km * 1000 >= GPS_MOTION_METERS
|
||||||
|
and window_speed <= MAX_PLAUSIBLE_SPEED_KMH
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
row["speed"] = 0
|
||||||
if index:
|
if index:
|
||||||
segment_km, _ = _segment(row, rows[index - 1])
|
segment_km, _ = _segment(row, rows[index - 1])
|
||||||
cumulative_km += segment_km
|
cumulative_km += segment_km
|
||||||
|
|||||||
+15
-8
@@ -625,6 +625,7 @@ def get_trip_route(device_id, trip_id):
|
|||||||
result_points = []
|
result_points = []
|
||||||
cumulative = 0.0
|
cumulative = 0.0
|
||||||
prev = None
|
prev = None
|
||||||
|
gps_window = [] # trailing (lat, lon, datetime) points for GPS speed smoothing
|
||||||
for p in points:
|
for p in points:
|
||||||
lat = float(p[1])
|
lat = float(p[1])
|
||||||
lon = float(p[2])
|
lon = float(p[2])
|
||||||
@@ -632,18 +633,24 @@ def get_trip_route(device_id, trip_id):
|
|||||||
raw_odo = p[4]
|
raw_odo = p[4]
|
||||||
speed_source = "obd" if speed > 0 else "gps"
|
speed_source = "obd" if speed > 0 else "gps"
|
||||||
# Mirror the Pi's route_points GPS-speed fallback: when OBD speed is
|
# Mirror the Pi's route_points GPS-speed fallback: when OBD speed is
|
||||||
# missing (adapter dropout), derive speed from the previous point's
|
# missing (adapter dropout), derive speed from a trailing ~30s window
|
||||||
# movement so the replay doesn't flatline at 0 km/h while the car moves.
|
# of movement (not the previous point) so the replay doesn't flatline
|
||||||
if speed <= 0 and prev is not None:
|
# at 0 km/h while the car moves — and a single jittery 5s segment can't
|
||||||
seconds = (p[0] - prev[2]).total_seconds()
|
# spike the readout.
|
||||||
|
if speed <= 0 and gps_window:
|
||||||
|
start = gps_window[0]
|
||||||
|
seconds = (p[0] - start[2]).total_seconds()
|
||||||
if seconds > 0:
|
if seconds > 0:
|
||||||
dist_km = hav(prev[0], prev[1], lat, lon)
|
dist_km = hav(start[0], start[1], lat, lon)
|
||||||
seg_speed = dist_km / seconds * 3600
|
w_speed = dist_km / seconds * 3600
|
||||||
if seg_speed <= 220 and dist_km * 1000 >= 8:
|
if w_speed <= 220 and dist_km * 1000 >= 8:
|
||||||
speed = seg_speed
|
speed = w_speed
|
||||||
if prev is not None:
|
if prev is not None:
|
||||||
cumulative += hav(prev[0], prev[1], lat, lon)
|
cumulative += hav(prev[0], prev[1], lat, lon)
|
||||||
prev = (lat, lon, p[0])
|
prev = (lat, lon, p[0])
|
||||||
|
gps_window.append((lat, lon, p[0]))
|
||||||
|
if len(gps_window) > 6:
|
||||||
|
gps_window.pop(0)
|
||||||
if raw_odo is not None and float(raw_odo) > 0:
|
if raw_odo is not None and float(raw_odo) > 0:
|
||||||
odometer = float(raw_odo)
|
odometer = float(raw_odo)
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user