Realtime google maps demo #54
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Reference
gtfs.zone/deploy-gtfs-rt#54
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
We need to make a super simple static site that demonstrates the GTFS RT from a feed. Lets have
This will be a new repo, lets call it test-track
Lets follow the tooling and styling of coloring-book
I created a simple one. Two problems:
Summary
Build a single-page static site at
viz.rt.gtfs.zonethat lets users load a GTFS static feed and point at a GTFS-RT endpoint to see live vehicle positions on a map, tap stops for trip updates, and browse service alerts. Follow the coloring-book toolchain (Vite + Tailwind v4 + DaisyUI v5 + MapLibre) but keep it dramatically simpler — no editing, no undo, no tab management.Relevant Context
cafe-car API (public, no auth) — base URL
https://rt.gtfs.zone:GET /{feed_name}/vehicle_positions.pb— live vehicle positions (GTFS-RT protobuf)GET /{feed_name}/trip_updates.pb— trip delay data (GTFS-RT protobuf)GET /{feed_name}/service_alerts.pb— service alerts (GTFS-RT protobuf)RT feed format: Standard GTFS-RT protobuf (
FeedMessage). Parse withgtfs-realtime-bindings.GTFS static: ZIP with
stops.txt,routes.txt,shapes.txt,trips.txt,stop_times.txt. Parse withjszip+papaparse.coloring-book files to copy/adapt:
package.json→ trim editing deps, addgtfs-realtime-bindingsvite.config.js→ simplify (no version tracking needed initially)tailwind.config.js→ copy as-ispostcss.config.js→ copy as-istsconfig.json→ copy as-issrc/index.htmlnavbar shell (lines 38–65, 91–116 for theme toggle) → adapt with "viz.rt.gtfs.zone" branding<dialog>+modal-box) → reuse for alerts and feed configCurrent repo state: Zola template — no src files yet.
build.ymluseszola build; must be replaced withnpm run build.Phase 1: Project scaffold
Replace Zola with Vite + npm, matching the coloring-book toolchain.
Tasks
package.jsonbased on coloring-book's, stripping editor-specific deps (clusterize.js,idb,zod, heavy scripts) and addinggtfs-realtime-bindingsvite.config.js(simplified: no git-describe versioning, same root/publicDir/build structure)tailwind.config.js— copy from coloring-book exactlypostcss.config.js— copy from coloring-book exactlytsconfig.json— copy from coloring-book exactlysrc/styles/main.csswith@import "tailwindcss"and@plugin "daisyui"public/dir with placeholderlogo.svg(can reuse coloring-book's).forgejo/workflows/build.yml: replacezola buildwithnpm ci && npm run build; deploydist/instead ofpublic/.gitignoreto addnode_modules/,dist/npm installto generatepackage-lock.jsonGotchas
@import "tailwindcss"not@tailwinddirectives; DaisyUI v5 uses@plugin "daisyui"— don't use v3 syntax.forgejo/workflows/build.ymlusesalpine-3.23which has Node.js available viaapk add nodejs npmgtfs-realtime-bindingslatest is1.1.1(not1.1.2as originally noted); pinned to^1.1.1Phase 2: Static HTML shell
Build the full page layout with DaisyUI components. No JS logic yet — just the skeleton.
Layout
Tasks
src/index.html:<html data-theme="dark">withfont-sans h-screen overflow-hidden<span class="text-primary">viz</span>.rt.gtfs.zone, theme toggle (copy coloring-book lines 91–116), alerts button<div id="map" class="w-full h-full">absolute z-40): static GTFS section (URL input + file upload button) and RT feed section (3 URL inputs: vehicle positions, trip updates, service alerts) + "Load" button; wrapped in<details>for collapsibility<div>that slides up on stop click, showing stop name, trip updates list, relevant alerts<dialog>modal with list of all service alertssrc/index.tsas empty entry point (export {})Gotchas
<details>/<summary>collapsible so it doesn't eat map space once loadedtranslate-ytogglePhase 3: GTFS static parsing + map rendering
Load and display the static GTFS feed on the map.
Tasks
src/gtfs-static.ts—GTFSStaticclass:loadFromFile(file: File)andloadFromUrl(url: string)→ both call internalparse(zip: JSZip)stops.txt→Map<string, Stop>(id, name, lat, lon)routes.txt→Map<string, Route>(id, short_name, long_name, color, text_color, type)shapes.txt→Map<string, [lon,lat][]>(shape_id → coordinate array)trips.txt→Map<string, Trip>(trip_id → route_id, shape_id, headsign)stop_times.txt→Map<string, string[]>(stop_id → trip_ids that serve it)src/map-controller.ts—MapControllerclass:initialize(container: string)— init MapLibre with a good default style (e.g. MapTiler or OpenFreeMap)loadStaticFeed(feed: GTFSStatic)— add sources/layers for shapes (lines) and stops (circles)route_color; stops as small circles, larger on hoveronStopClick(callback: (stopId: string) => void)— fire when a stop is clickedshowVehicles(positions: VehiclePosition[])— add/update avehiclesGeoJSON source with bearing arrowsclearVehicles()/clearStaticFeed()Gotchas
stop_times.txtcan be huge; parse lazily or skip if not needed for the initial load (only needed for stop→trip lookups)https://tiles.openfreemap.org/styles/liberty(no API key needed)#; prepend before using in MapLibre paintPhase 4: GTFS-RT polling + display
Fetch and render live data, wire up the stop tap sheet and alerts modal.
Tasks
src/gtfs-rt.ts—GTFSRealtimeclass:constructor(vehicleUrl: string, tripUpdatesUrl: string, alertsUrl: string)start(intervalMs = 15000)/stop()— poll all three endpointsgtfs-realtime-bindingsto decode protobuf responses'vehicles','tripUpdates','alerts'with typed payloadssrc/index.ts— wire everything together:'vehicles'event →mapController.showVehicles()'alerts'event → update alerts modal list + badge count on navbar button<div class="alert">card with header, description, cause/effect badges, affected routes/stopsGotchas
gtfs-realtime-bindingsships ESM; import asimport { transit_realtime } from 'gtfs-realtime-bindings'response.arrayBuffer()not.json()rt.gtfs.zonemust allow theviz.rt.gtfs.zoneorigin, or the user can enter a CORS proxy URL. Call this out in the UI with a note.entity.vehicle.position.bearingto rotate a directional arrow markertripUpdatesand matchstop_time_update.stop_sequenceagainst the staticstop_times.txtor match bytrip.trip_idagainststop_timesfor that stopITripDescriptordoes not have atripHeadsignfield; headsign comes only fromGTFSStatic.trips@protobufjs/inquiretriggers a Vite eval warning — benign, no action neededGTFSRealtimeextendsEventTarget; useCustomEvent<T>for typed payloadskinda working now but kinda garbo