feat: simulated odometer in trip replay + trip list (continuous across trips)

- Pi route_points: emit per-point odometer (raw OBD value or baseline+prior+within-trip distance)
- Pi get_trip_route: SELECT ODOMETER, compute prior-trips distance + baseline
- Pi list_trips: fallback now starts from baseline (was 0)
- Server get_trip_route: per-point odometer (raw or simulated) with haversine accumulation
- Server list_trips: odo_start/end from baseline + cumulative (was hardcoded None)
- history.js: Odometer line in replay scrubber below speed
- Both history templates: trip-replay-odometer element
This commit is contained in:
Mathieu Moran
2026-08-12 16:59:00 -04:00
parent cfd9d03e81
commit 0d5aea5947
6 changed files with 114 additions and 29 deletions
+20 -4
View File
@@ -487,9 +487,12 @@ def list_trips():
START_ODOMETER, END_ODOMETER
FROM trips ORDER BY START_DATETIME ASC"""
).fetchall()
# If real OBD odometer is available use it; otherwise fall back to cumulative distance
# If real OBD odometer is available use it; otherwise fall back to
# baseline + cumulative distance (continuous across trips).
from modules import app_settings
baseline = app_settings._number_value("odometer_baseline", float, 0)
result = []
cumulative_km = 0.0
cumulative_km = baseline
for row in rows:
dist = row[7] or 0
start_odo = row[12] if row[12] is not None else round(cumulative_km, 1)
@@ -530,11 +533,19 @@ def get_trip_route(trip_id, max_points=2000):
if trip is None:
return None
rows = conn.execute(
"""SELECT id, DATETIME, GPSX, GPSY, SPEED
"""SELECT id, DATETIME, GPSX, GPSY, SPEED, ODOMETER
FROM tracking WHERE id BETWEEN ? AND ?
ORDER BY DATETIME ASC, id ASC""",
(trip[8], trip[9]),
).fetchall()
prior = conn.execute(
"""SELECT COALESCE(SUM(DISTANCE_KM), 0)
FROM trips WHERE START_DATETIME < ?""",
(trip[1],),
).fetchone()
from modules import app_settings
baseline = app_settings._number_value("odometer_baseline", float, 0)
prior_km = prior[0] if prior and prior[0] is not None else 0.0
return {
"trip": {
"id": trip[0],
@@ -546,7 +557,12 @@ def get_trip_route(trip_id, max_points=2000):
"average_speed_kmh": trip[6],
"max_speed_kmh": trip[7],
},
"points": app_trip.route_points(rows, max_points=max_points),
"points": app_trip.route_points(
rows,
max_points=max_points,
odometer_baseline=baseline,
prior_km=prior_km,
),
}
+14 -1
View File
@@ -216,7 +216,7 @@ def build_trip_summaries(raw_rows):
return trips
def route_points(raw_rows, max_points=2000):
def route_points(raw_rows, max_points=2000, odometer_baseline=0.0, prior_km=0.0):
rows = normalize_tracking_rows(raw_rows)
filtered_rows = []
for row in rows:
@@ -226,6 +226,7 @@ def route_points(raw_rows, max_points=2000):
continue
filtered_rows.append(row)
rows = filtered_rows
cumulative_km = 0.0
for index, row in enumerate(rows):
row["speed_source"] = "obd" if row["speed"] > 0 else "gps"
if row["speed"] <= 0 and index:
@@ -235,6 +236,17 @@ def route_points(raw_rows, max_points=2000):
if distance_km * 1000 >= GPS_MOTION_METERS
else 0
)
if index:
segment_km, _ = _segment(row, rows[index - 1])
cumulative_km += segment_km
# Odometer per point: use the raw OBD value when the vehicle reports
# one; otherwise simulate it as baseline + prior trips + within-trip
# distance (continuous across trips, matching get_overview_stats).
raw_odometer = row.get("odometer")
if raw_odometer is not None and raw_odometer > 0:
row["odometer"] = raw_odometer
else:
row["odometer"] = odometer_baseline + prior_km + cumulative_km
if len(rows) <= max_points:
selected = rows
else:
@@ -250,4 +262,5 @@ def route_points(raw_rows, max_points=2000):
"longitude": row["longitude"],
"speed": round(row["speed"], 1),
"speed_source": row["speed_source"],
"odometer": round(row["odometer"], 1) if row["odometer"] is not None else None,
} for row in selected]