Real-time South East Queensland (SEQ) transit map, Progressive Web App (PWA), and GTFS Static + Realtime API organized as a Turborepo monorepo.
| App | Path | Stack | Port |
|---|---|---|---|
| Web | apps/web |
Next.js 16, React 19, MapLibre GL, TanStack Query, PWA | 3000 |
| API | apps/api |
TypeScript, Hono 4, Node 22 (node:sqlite, node:zlib), GTFS |
8000 |
@translink/api: Indexes Translink's static GTFS schedule (SEQ_GTFS.zip— routes, official colors, headsigns, stop sequences, andshapes.txtpolylines) into a local SQLite index (node:sqlite) and merges all three 30-second GTFS-Realtime feeds (VehiclePositions,TripUpdates,Alerts), computing live vehicle bearings and speeds while serving pre-compressed Brotli/Gzip incremental delta syncs.@translink/web: Installable mobile/desktop PWA rendering 100% of active buses, trains, ferries, and trams with GPU-rotated directional markers, route polylines, Inbound/Outbound direction switching, live stop arrival predictions, and low-data delta polling (~9 KB per 30s poll).
- Node.js 22.5+ (uses built-in
node:sqlite) - npm 10+
- Docker (optional, for container builds / production)
# Install dependencies
npm install
# Optional: copy .example.env to .env.local (works out of the box without keys)
cp .example.env .env.local
# Start both apps in dev mode
npm run devDefault URLs:
- Web: http://localhost:3000
- API: http://localhost:8000
Create .env.local in the repo root (see .example.env):
| Variable | Scope | Required | Description |
|---|---|---|---|
NEXT_PUBLIC_MAPTILER_KEY |
Web (public) | No | MapTiler API key for vector dataviz-dark tiles. Falls back automatically to CARTO Dark Matter @2x if unset. |
TRANSIT_API_URL |
Web (server only) | No | Upstream API origin (http://localhost:8000 by default, or derived automatically from VEHICLE_API_URL). |
VEHICLE_API_URL |
Web (server only) | No | Legacy-compatible vehicle URL (http://localhost:8000/v1/seq/vehicle_positions by default). |
PORT |
API | No | API server port (8000 by default). |
All active SEQ vehicles (~650–1,000+ at peak) remain loaded simultaneously on the map without dropping or viewport-culling vehicles:
- Dictionary + Quantized Tuple Encoding: Route metadata (
[shortName, longName, modeCode, color, textColor]) and trip headsigns are interned into append-only dictionaries instead of repeating strings per vehicle. Coordinates use1e5-scaled integer tuples (~1.1 m precision). - 30-Second Incremental Delta Sync (
GET /v1/seq/vehicles/sync?since=<seq>&epoch=<epoch>): Subsequent polls transmit only coordinate deltas ([id, dLatE5, dLonE5, bearing, speedKmh, statusCode, delaySec, ageSec]) plus newly added or removed vehicles. - Pre-Compressed Brotli (
br) &ETag(304 Not Modified):- Initial full snapshot (~670 vehicles): ~20–22 KB over the wire (Brotli) vs ~180+ KB uncompressed verbose JSON.
- 30-second delta update: ~8–9.5 KB over the wire (Brotli).
- Unchanged feed tick:
304 Not Modified(0 Bbody).
- Visibility-Aware Polling & Tile Caching: Background tabs / locked phone screens pause polling automatically (
document.visibilityState) and catch up immediately on wake; the PWA service worker (public/sw.js) caches app shell assets and basemap tiles.
| Endpoint | Description |
|---|---|
GET / |
Service metadata, endpoint directory, and feed health summary. |
GET /v1/health |
Realtime feed ages, active counts by mode, static GTFS feed validity dates, and wire-byte telemetry. |
GET /v1/seq/vehicles/sync |
Compact full snapshot or incremental delta (?since=<seq>&epoch=<epoch>), ETag + Brotli/Gzip. |
GET /v1/seq/vehicle_positions |
Enriched JSON vehicle list (?route=, ?mode=, ?direction=, ?bbox=). |
GET /v1/seq/vehicles/:id |
Single vehicle telemetry, exact trip GeoJSON LineString shape, stop ETA sequence, and route alerts. |
GET /v1/seq/routes |
Searchable GTFS route catalog (?q=, ?mode=, ?activeOnly=1) with active counts per direction. |
GET /v1/seq/routes/:routeKey |
Full route details with both directions (directionId 0 & 1, shapes, stops, active vehicles). |
GET /v1/seq/stops |
Stop search by name, stop code, ID, or proximity (?q=, ?lat=&lon=&radius=). |
GET /v1/seq/stops/:stopId |
Stop & child station platforms with live predicted departures for the next 90 minutes. |
GET /v1/seq/alerts |
Active GTFS-Realtime service alerts filtered by ?route=, ?stopId=, ?tripId=, or ?mode=. |
Run from the repo root:
| Command | Description |
|---|---|
npm run dev |
Start both apps in dev mode (Turbo) |
npm run build |
Build all apps (Turbo) |
npm run lint |
Lint all apps (Turbo) |
npm run format |
Format all files with Prettier |
npm run format:check |
Check formatting without writing |
docker compose config
docker compose up --build- API (
apps/api/Dockerfile): Multi-stagenode:22-alpinebuild withtranslink-api-datavolume storingseq_gtfs.sqlite,SEQ_GTFS.zip, and cached.pbrealtime feeds, plus/v1/healthcontainer healthcheck. - Web (
apps/web/Dockerfile): Multi-stage Next.js standalone runner includingpublic/PWA icons and service worker (sw.js).
CC BY-NC 4.0 — see package.json.