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:
+72
-24
@@ -514,6 +514,11 @@ def compute_trips_for_device(device_id):
|
||||
|
||||
def list_trips(device_id, limit=200):
|
||||
with get_cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT odometer_baseline FROM devices WHERE id = %s",
|
||||
(device_id,),
|
||||
)
|
||||
base_row = cur.fetchone()
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, start_time, end_time, distance_km, duration_s,
|
||||
@@ -521,18 +526,22 @@ def list_trips(device_id, limit=200):
|
||||
end_lat, end_lon, point_count
|
||||
FROM trips
|
||||
WHERE device_id = %s
|
||||
ORDER BY start_time DESC
|
||||
LIMIT %s
|
||||
ORDER BY start_time ASC
|
||||
""",
|
||||
(device_id, limit),
|
||||
(device_id,),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
return [
|
||||
{
|
||||
baseline = float(base_row[0]) if base_row and base_row[0] is not None else 0.0
|
||||
# Simulate a continuous odometer: baseline + cumulative trip distance.
|
||||
cumulative = baseline
|
||||
trips = []
|
||||
for r in rows:
|
||||
dist = float(r[3]) if r[3] is not None else 0.0
|
||||
trips.append({
|
||||
"id": r[0],
|
||||
"start_datetime": r[1].isoformat(),
|
||||
"end_datetime": r[2].isoformat(),
|
||||
"distance_km": r[3],
|
||||
"distance_km": dist,
|
||||
"duration_seconds": r[4],
|
||||
"average_speed_kmh": r[5],
|
||||
"max_speed_kmh": r[6],
|
||||
@@ -541,11 +550,12 @@ def list_trips(device_id, limit=200):
|
||||
"end_lat": r[9],
|
||||
"end_lon": r[10],
|
||||
"point_count": r[11],
|
||||
"odo_start_km": None,
|
||||
"odo_end_km": None,
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
"odo_start_km": round(cumulative, 1),
|
||||
"odo_end_km": round(cumulative + dist, 1),
|
||||
})
|
||||
cumulative += dist
|
||||
trips.reverse() # newest first for display
|
||||
return trips[:limit]
|
||||
|
||||
|
||||
def get_trip_route(device_id, trip_id):
|
||||
@@ -559,7 +569,7 @@ def get_trip_route(device_id, trip_id):
|
||||
return None
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT recorded_at, gps_lat, gps_lon, speed
|
||||
SELECT recorded_at, gps_lat, gps_lon, speed, odometer
|
||||
FROM tracking_points
|
||||
WHERE device_id = %s AND recorded_at BETWEEN %s AND %s
|
||||
AND gps_lat IS NOT NULL AND gps_lon IS NOT NULL
|
||||
@@ -568,6 +578,55 @@ def get_trip_route(device_id, trip_id):
|
||||
(device_id, trip_row[0], trip_row[1]),
|
||||
)
|
||||
points = cur.fetchall()
|
||||
cur.execute(
|
||||
"SELECT COALESCE(SUM(distance_km), 0) FROM trips WHERE device_id = %s AND start_time < %s",
|
||||
(device_id, trip_row[0]),
|
||||
)
|
||||
prior = cur.fetchone()
|
||||
cur.execute(
|
||||
"SELECT odometer_baseline FROM devices WHERE id = %s",
|
||||
(device_id,),
|
||||
)
|
||||
base_row = cur.fetchone()
|
||||
baseline = float(base_row[0]) if base_row and base_row[0] is not None else 0.0
|
||||
prior_km = float(prior[0]) if prior and prior[0] is not None else 0.0
|
||||
|
||||
import math
|
||||
|
||||
def hav(lat1, lon1, lat2, lon2):
|
||||
r = 6371.0088
|
||||
dlat = math.radians(lat2 - lat1)
|
||||
dlon = math.radians(lon2 - lon1)
|
||||
a = (math.sin(dlat / 2) ** 2
|
||||
+ math.cos(math.radians(lat1)) * math.cos(math.radians(lat2))
|
||||
* math.sin(dlon / 2) ** 2)
|
||||
return r * 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
||||
|
||||
result_points = []
|
||||
cumulative = 0.0
|
||||
prev = None
|
||||
for p in points:
|
||||
lat = float(p[1])
|
||||
lon = float(p[2])
|
||||
speed = p[3] or 0
|
||||
raw_odo = p[4]
|
||||
if prev is not None:
|
||||
cumulative += hav(prev[0], prev[1], lat, lon)
|
||||
prev = (lat, lon)
|
||||
if raw_odo is not None and float(raw_odo) > 0:
|
||||
odometer = float(raw_odo)
|
||||
else:
|
||||
odometer = baseline + prior_km + cumulative
|
||||
result_points.append({
|
||||
"datetime": p[0].isoformat(),
|
||||
"latitude": lat,
|
||||
"longitude": lon,
|
||||
"speed": speed,
|
||||
# Mirror the Pi's route_points: OBD speed when we have it,
|
||||
# otherwise the point is sourced from GPS.
|
||||
"speed_source": "obd" if speed > 0 else "gps",
|
||||
"odometer": round(odometer, 1),
|
||||
})
|
||||
return {
|
||||
"trip": {
|
||||
"distance_km": trip_row[2],
|
||||
@@ -575,18 +634,7 @@ def get_trip_route(device_id, trip_id):
|
||||
"average_speed_kmh": trip_row[4],
|
||||
"max_speed_kmh": trip_row[5],
|
||||
},
|
||||
"points": [
|
||||
{
|
||||
"datetime": p[0].isoformat(),
|
||||
"latitude": p[1],
|
||||
"longitude": p[2],
|
||||
"speed": p[3] or 0,
|
||||
# Mirror the Pi's route_points: OBD speed when we have it,
|
||||
# otherwise the point is sourced from GPS.
|
||||
"speed_source": "obd" if (p[3] or 0) > 0 else "gps",
|
||||
}
|
||||
for p in points
|
||||
],
|
||||
"points": result_points,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
<strong id="trip-replay-time"></strong>
|
||||
<span id="trip-replay-speed" class="text-muted"></span>
|
||||
</div>
|
||||
<div id="trip-replay-odometer" class="text-muted mt-1" style="font-size:0.75rem;min-height:1rem"></div>
|
||||
<div id="trip-replay-location" class="text-muted mt-1" style="font-size:0.75rem;min-height:1rem"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user