The live network map: reported vehicle positions on a MapLibre basemap with filters, details and freshness notices.
| Reference | Value |
|---|---|
| Subject and boundary | Map components, runtime layers, freshness states and the checks a map change needs. |
| Verified source | mapRuntime.svelte.ts, vehicleMotion.ts, vehicleProjection.ts, motion/, mapCamera.ts, BottomSheet.svelte, refresh-basemap.yml at main 9733eb26d92533f56cdc8bc46b81b93fbb5987c8, checked November 10, 2026. |
| Support state | Implemented. |
MapHero composes selection, camera and live-store lifecycle. mapSelectionSession.svelte.ts owns selected-detail retention, URL identity and family leases through focused selection-session modules. mapRuntime owns custom layers, motion, pointer interaction, source feeds, emphasis and cleanup for the mounted map; style reloads restore sources before replaying emphasis. The local overlay and detail components arrange values and pass user actions back to MapHero.
MapFilters owns its disclosure state and delegates filter values to FilterStore. Map tools and independent panels are outside the content-section bulk action and are not governed by sections-choice. MapFilterRail sizes the collapsed rail from its buttons. MapMotionControl uses the raw and smooth store values, saved in localStorage transit:motion-mode: reported positions by default, estimated positions as an explicit choice. Out-of-service vehicles (no trip) are hidden by default; the Out-of-service vehicles filter shows them and is kept in the URL as out_of_service=1.
DetailAttributeGrid owns definition-row layout for both desktop and mobile details: label, value and action on one 44 px line. The action column is always reserved, so every value starts at the same position; nested messages keep their own layout. MapHeadTitle and MapFeedStallBanner keep mobile freshness and notices clear of the heading without changing announcement priority.
MapFreshness labels report age independently of current feed availability. A failed vehicle feed keeps the report timestamp and caution state; MapFeedStallBanner names the unavailable positions. A partial stall uses “Data may be out of date” / “Les données peuvent ne plus être à jour.” Whole-feed outage wording requires every active family to be stale; a loading family cannot certify that outage. Last-update timestamps remain visible.
vehicleSprites owns marker geometry and vehicleLayer applies its scaled offsets. The bus stays at the reported coordinate; the heading clears its silhouette, and status/stale badges share a row below it. Geometry changes need bearing, zoom, badge-pair and picking checks.
createVehicleMotion decides what the overlay draws; the map runtime owns the overlay's lifetime. It animates only when the preference is smooth, reduced motion is off and the map is ready. The UI calls the result Estimated positions, with Estimated and Reported labels.
An estimated position moves the vehicle along its route shape by reported speed × fix age, with the age capped at the horizon. A position is held at the vehicle's next stop only when that stop is more than 5 m ahead along the shape. Timing comes from liveTimingFor, reading the manifest's ttl_s and source_lag_s:
| Value | Formula | STM (TTL 20 s, lag 0) | OC Transpo (TTL 20 s, lag 60 s) |
|---|---|---|---|
| Not reporting cutoff | 150 s + lag; a fix at or past it is Not reporting | 150 s | 210 s |
| Projection horizon | min(150 s + lag, 2 × TTL + 30 s + lag) | 70 s | 130 s |
The motion controller blends from the previous drawn position over 1.5 s with an ease-out, renders at most 30 frames per second and stops its frame loop when nothing moves, waking for the next change. Its held-frame expiry and wake timer use a fixed 150 s (see Decisions and limitations).
createMapCamera and initialCamera own camera intents and recovery; the initial zoom is 11. mapCameraFraming.ts fits the provider's fit_bounds, falling back to the manifest bbox. mapViewportOptions keeps provider framing centered on the screen and moves it only enough to clear an open panel; when that zoom cannot clear both edges it fits into the uncovered area. measureMapFocusInset in mapFocusInset.ts measures the chrome that covers the map.
| Surface | Behavior | Source |
|---|---|---|
| Selection panel | Vehicle, stop and route detail. The header shows a kind label over the entity name, with a lede for the route tag, stop code or route name. A toolbar groups history (back, forward) and same-line stepping (previous, next, position) for vehicles and stops; routes have no stepper. Section tabs use TransitTabList. Stop code, Approaching and Departures are separate fact rows |
MapSelectionDetail.svelte, MapDetailNavigation.svelte, detail/ |
| Hover card | A glance: route tag and name, status, next stop, and one clamped alert line with a count of the rest. Trip, crowding and the mode badge stay in the panel. peekRepeatsDetail hides the card when the open desktop panel already shows the same selection |
mapHoverPeek.ts |
| Bottom sheet (phones) | Detents: peek 168 px, half 50% (the default) and full, measured to stop below the site header so navigation and the provider control stay usable (90% until measured) | BottomSheet.svelte |
| Provider pill | Shows city and operator, such as “Montréal · STM”; phones show only the operator. Its accessible name is “Transit network: …” / “Réseau de transport: …” | NavPill.svelte |
Each provider has its own Protomaps extract at public.basemap_url, cut to public.basemap_bounds (see Providers). refresh-basemap.yml runs at 06:00 UTC on the first of each month and on dispatch (optional provider and planet_build inputs). It extracts each provider's area from a Protomaps daily build to zoom 15, requires more than 100,000 bytes, and replaces the stable R2 object after writing a backup and reading it back. The map background uses the palette's land color, the same as the earth layer (basemap.ts). Production observation, November 10, 2026: the published archives come from the 2026-11-08 build (STM 176,104,928 bytes, OC Transpo 187,632,265 bytes); this records object sizes only, not rendering.
Run the map tests from apps/web with bun run test src/lib/features/map. Browser review also needs both desktop panels independently and together, touch controls, selection, empty positions, feed failure, renderer retry and reduced motion. Panel changes must leave the map canvas dimensions unchanged.