Île-de-France & France multimodal transit — journey planning, live network map, and estimated vehicle positions, built entirely from raw open data. No Navitia, Hove.io, or other commercial journey APIs.
▶ Watch demo (2½ min) · raw MP4
- Multimodal routing — RAPTOR over GTFS static schedules with walk transfers, hub links, and optional OSRM street geometry
- Realtime overlay — trip delays, cancellations, alerts, and vehicle positions from GTFS-RT and PRIM SIRI Lite (StopMonitoring, EstimatedTimetable, GeneralMessage)
- Position estimation — when GPS is unavailable, vehicles are interpolated along GTFS shapes using schedule + realtime progress
- GraphQL API — stops, departures, trip detail, itineraries, live vehicles, subscriptions
- Web map (WASM + Leaflet) — search stops, plan journeys, view polylines and live vehicles on an IDFM-style map
Data sources include Île-de-France Mobilités (GTFS + PRIM SIRI), SNCF open data, and the transport.data.gouv.fr PAN. Transit data remains under each producer's licence (often ODbL); application code is MIT.
Everything is built from first principles on public feeds — no third-party routing SaaS.
Open data feeds Processing pipeline Clients
┌─────────────────┐ ┌──────────────────────────┐ ┌─────────────┐
│ IDFM GTFS │──download──► │ Parse & pack static │ │ GraphQL API │
│ SNCF GTFS │ │ epoch (ArcSwap swap) │──RAPTOR──►│ (port 8080) │
│ PRIM SIRI Lite │──poll──────► │ Realtime overlay │ │ │
│ GTFS-RT (opt.) │ │ Vehicle estimation │ │ WASM map UI │
└─────────────────┘ └──────────────────────────┘ │ (port 8088) │
└─────────────┘
- Download / parse GTFS — conditional fetch (ETag/hash), streaming parse, horizon filter for large feeds
- Pack static epoch — stops, trips, stop times, shapes, pathways, hub links (grid spatial index)
- RAPTOR routing — multimodal legs with walk access/egress and transfer graph
- Realtime overlay — concurrent Tokio pollers merge SIRI StopMonitoring / EstimatedTimetable / TripUpdates into the live view
- Vehicle estimation — shape-aware interpolation when positions are missing
- GraphQL + WASM frontend — API serves queries; Trunk-built map UI talks to the same backend
See also docs/IDFM_DAILY.md, docs/REDIS_CACHE.md, and docs/IDFM_VEHICLE_MONITORING.md.
| Requirement | Notes |
|---|---|
| Rust 1.75+ | rustup recommended |
| RAM 8–16 GB+ | IDFM + SNCF together is memory-heavy |
| Disk ~2 GB+ | GTFS zips cached under ./data/ |
IDFM_PRIM_API_KEY |
PRIM marketplace key for IDFM realtime SIRI Lite (prim.iledefrance-mobilites.fr); apikey header |
PRIM_DATASET_KEY |
Separate PRIM key for GTFS zip downloads (make sync-idfm, daily_sync.sh); X-API-KEY header — not the same value as IDFM_PRIM_API_KEY |
| Redis (optional) | Response cache for search / vehicles / itineraries |
| OSRM (optional) | Street-level walk geometry; public demo URL in config |
git clone https://github.com/princefr/transit.git
cd transit
make setup # wasm target, scripts, .env from .env.exampleEdit .env and set API keys (see .env.example):
# PRIM realtime key — SIRI Lite (apikey header)
IDFM_PRIM_API_KEY=your_prim_realtime_api_key_here
# Separate PRIM dataset key for GTFS downloads (X-API-KEY header)
PRIM_DATASET_KEY=your_prim_dataset_api_key_herePRIM issues separate marketplace keys for realtime and dataset access. This repo uses two env vars because callers send different headers (apikey vs X-API-KEY) and the keys are not interchangeable.
| Variable | Used for | Header | Where to get it |
|---|---|---|---|
IDFM_PRIM_API_KEY |
PRIM SIRI Lite + optional GTFS-RT | apikey |
prim.iledefrance-mobilites.fr marketplace |
PRIM_DATASET_KEY |
GTFS zip via make sync-idfm / scripts/daily_sync.sh |
X-API-KEY |
PRIM Datahub / dataset product (separate key) |
DATASETS_API_KEY |
Legacy fallback for PRIM_DATASET_KEY |
X-API-KEY |
Deprecated alias |
DATAGOUV_API_KEY |
Legacy fallback for PRIM_DATASET_KEY |
X-API-KEY |
Deprecated alias |
The IDFM zip is large (~170 MB). Download before first boot:
make sync-idfm # → data/idfm/current.zipmake release # or: cargo build --release
make run # GraphQL API → http://127.0.0.1:8080First start also fetches SNCF GTFS into ./data/sncf/. Wait until GET /health reports stopCount > 0.
In a second terminal:
make web # WASM UI → http://127.0.0.1:8088Open http://127.0.0.1:8088 — search two stops, plan a journey, explore the live map.
make smoke # probes /health and GraphQL against BASE_URL| URL | Purpose |
|---|---|
http://127.0.0.1:8080/health |
Process & feed status (JSON) |
http://127.0.0.1:8080/graphql |
GraphQL queries |
http://127.0.0.1:8080/graphiql |
GraphiQL playground |
ws://127.0.0.1:8080/ws |
GraphQL subscriptions |
http://127.0.0.1:8088 |
Map UI (Trunk dev server) |
- File:
config/default.toml - Override:
TRANSIT_CONFIG=/path/to.toml - Secrets:
.env(loaded bymake run,scripts/run_server.sh, systemd) — never commit - Env overrides:
TRANSIT__SERVER__BIND=127.0.0.1:8080(figment__nesting)
Key sections: [server], [routing], [graphql], [prim], [[feeds]], optional [redis] and [ban].
Feed downloads can also use per-feed auth in TOML (auth_header + auth_env); see comments in config/default.toml for IDFM_PRIM_API_KEY and PRIM_DATASET_KEY.
# Optional: address autocomplete (Base Adresse Nationale, Île-de-France)
make ban-index- Journeys with scheduled + estimated departure/arrival times and delay badges
- Live network — vehicles on map, occupancy, trip status
- French stop search — accent-insensitive, station-biased ranking
- IDFM loading UX — progress while the large feed parses
- Multi-feed — namespaced ids (
idfm:…,sncf:…), configurable[[feeds]] - Docker —
docker compose up --build(mounts./datafor GTFS cache)
make test # unit + integration tests
make gate # fmt + lib tests + wasm check
make dev # print two-terminal run instructions
make help # all make targetsdocker compose up --build -d
BASE_URL=http://127.0.0.1:8080 make smokeBest-effort open data — not an official IDFM or SNCF journey planner. No ticketing or fares. Results are indicative; verify before travel.
MIT — see LICENSE. Transit data remains under each producer's licence.