-- 003_trips_for_day_rpc.sql -- Returns one row per trip on p_date, with the path reconstructed from -- tracksolid.position_history points falling inside the trip window. -- -- path_geojson : LineString GeoJSON, deck.gl reads {type, coordinates} directly. -- timestamps_rel: integer seconds elapsed since trip start, one per vertex. -- Frontend offsets these to "seconds since 00:00 of selected date" -- so a single TripsLayer can animate every trip on one timeline. -- -- HAVING count >= 2 drops trips with too few points to draw a line (rare, -- but happens for very short trips that started/ended between GPS pings). CREATE OR REPLACE FUNCTION public.trips_for_day( p_date date, p_cost_centres text[] DEFAULT NULL ) RETURNS TABLE ( trip_id bigint, imei text, vehicle_name text, vehicle_number text, cost_centre text, start_time timestamptz, end_time timestamptz, distance_km numeric, path_geojson json, timestamps_rel int[] ) LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public, tracksolid AS $$ WITH day_trips AS ( SELECT v.* FROM public.trips_viz_v1 v WHERE v.start_time::date = p_date AND (p_cost_centres IS NULL OR v.cost_centre = ANY(p_cost_centres)) ) SELECT dt.trip_id, dt.imei, dt.vehicle_name, dt.vehicle_number, dt.cost_centre, dt.start_time, dt.end_time, dt.distance_km, ST_AsGeoJSON(ST_MakeLine(ph.geom ORDER BY ph.gps_time))::json AS path_geojson, array_agg( EXTRACT(EPOCH FROM ph.gps_time - dt.start_time)::int ORDER BY ph.gps_time ) AS timestamps_rel FROM day_trips dt JOIN tracksolid.position_history ph ON ph.imei = dt.imei AND ph.gps_time BETWEEN dt.start_time AND dt.end_time AND ph.geom IS NOT NULL GROUP BY dt.trip_id, dt.imei, dt.vehicle_name, dt.vehicle_number, dt.cost_centre, dt.start_time, dt.end_time, dt.distance_km HAVING count(ph.geom) >= 2; $$; GRANT EXECUTE ON FUNCTION public.trips_for_day(date, text[]) TO viz_anon;