diff --git a/web/fleet-core.js b/web/fleet-core.js index 3dce904..7fbbdf9 100644 --- a/web/fleet-core.js +++ b/web/fleet-core.js @@ -623,6 +623,26 @@ const SELECTION_PALETTE = [ '#84cc16', // lime ]; +// 12-colour palette for per-trip colouring inside a single vehicle's day. +// Cycles past 12 (rare). +const TRIP_PALETTE = [ + '#ef4444', // red + '#f97316', // orange + '#f59e0b', // amber + '#84cc16', // lime + '#10b981', // emerald + '#06b6d4', // cyan + '#3b82f6', // blue + '#8b5cf6', // violet + '#ec4899', // pink + '#facc15', // yellow + '#14b8a6', // teal + '#a855f7', // purple +]; +function _tripColor(tripId) { + return TRIP_PALETTE[((tripId - 1) % TRIP_PALETTE.length + TRIP_PALETTE.length) % TRIP_PALETTE.length]; +} + let _tripAnimRAF = null; // vehicleId → { plate, driver, color, payload | null } const _selection = new Map(); @@ -723,20 +743,27 @@ async function _fetchAndDraw(map, vid) { ); } catch (err) { entry.payload = { error: err.message || String(err), trips: [] }; - return; } - // Draw this vehicle's all-day routes as a static overlay. - _drawVehicleDayPaths(map, vid, entry.payload, entry.color); + // Drawing decision is mode-dependent and happens in _renderDock, + // so just stash the payload here. } -function _drawVehicleDayPaths(map, vid, payload, color) { +function _drawVehicleDayPaths(map, vid, payload, fixedColor) { + // If `fixedColor` is set (multi-mode), every trip uses that colour and the + // vehicle reads as one route. If null (single-mode), each trip is coloured + // independently from TRIP_PALETTE so the day's segments are visually + // distinguishable. _clearVehicleLayers(map, vid); const trips = (payload.trips || []).filter(t => t.path && t.path.coordinates); if (trips.length === 0) return; const features = trips.map(t => ({ type: 'Feature', geometry: t.path, - properties: { trip_id: t.trip_id, vehicle_id: vid }, + properties: { + trip_id: t.trip_id, + vehicle_id: vid, + color: fixedColor || _tripColor(t.trip_id), + }, })); const srcId = `vroute-${vid}`; const layerId = `vroute-line-${vid}`; @@ -750,7 +777,7 @@ function _drawVehicleDayPaths(map, vid, payload, color) { source: srcId, layout: { 'line-join': 'round', 'line-cap': 'round' }, paint: { - 'line-color': color, + 'line-color': ['get', 'color'], 'line-width': ['interpolate', ['linear'], ['zoom'], 8, 1.5, 14, 3, 17, 5], 'line-opacity': 0.85, }, @@ -773,12 +800,23 @@ function _renderDock(map, els) { return; } + // Redraw all polylines from scratch so we transition cleanly between + // single-mode (per-trip colours) and multi-mode (per-vehicle colour). + for (const [vid] of _selection) _clearVehicleLayers(map, vid); if (_selection.size === 1) { + const [[vid, entry]] = _selection; + if (entry.payload && !entry.payload.error) { + _drawVehicleDayPaths(map, vid, entry.payload, null); // per-trip palette + } _renderSingle(map, els); } else { + for (const [vid, entry] of _selection) { + if (entry.payload && !entry.payload.error) { + _drawVehicleDayPaths(map, vid, entry.payload, entry.color); + } + } _renderMulti(map, els); } - // Fit map to union of all selected vehicles' route bounds _fitSelectionBounds(map); } @@ -808,8 +846,10 @@ function _renderSingle(map, els) { els.list.innerHTML = '