Critical Maps Mock Server
June 4, 2026 · View on GitHub
A tiny local HTTP server (Swift + Hummingbird)
that plays back a fake Critical Mass ride for development. It emulates the
production api-cdn.criticalmaps.net locations endpoint so the app can show a
realistic moving cluster of riders without hitting the live API.
The ride is a cluster of ~300 riders cycling a ~2 km loop through central Berlin (around the Tiergarten / Straße des 17. Juni). Positions advance by elapsed wall-clock time, so the ride keeps moving regardless of how often the app polls.
Endpoints
| Method | Path | Response |
|---|---|---|
GET | /locations | JSON array of riders (device, latitude, longitude, timestamp) |
PUT | /locations | {"status":"ok"} (the app posts its own location here) |
latitude/longitude are integer microdegrees (decimal degrees × 1,000,000),
matching the app's decoder (Rider in CriticalMapsKit/.../SharedModels/Rider.swift).
Run
cd MockServer
swift run
Then verify:
curl -s http://localhost:8080/locations | python3 -m json.tool | head
Configuration (environment variables)
| Variable | Default | Meaning |
|---|---|---|
PORT | 8080 | Port to listen on |
RIDERS | 300 | Number of riders in the cluster (100–1000) |
SPEED_KMH | 15 | Cluster speed in km/h (15 ≈ realistic CM pace) |
Example — a faster, larger ride for a demo:
SPEED_KMH=40 RIDERS=800 swift run
Movement per update
The app refetches riders once per poll full cycle. At the dev poll interval of
10 s (configured in the app's Debug Local build) and the default 15 km/h, the
cluster advances ≈ 42 m per update — clearly visible against the ~250 m-wide
cluster while staying physically honest. Raising SPEED_KMH scales the per-update
step linearly.
Pointing the app at this server
Run the app with the Critical Maps (Local) scheme. That build sets a
DEBUG_LOCAL compile flag which makes the app inject a ServerConfiguration
targeting http://localhost:8080 with a fast poll interval. The normal
Critical Maps scheme is unaffected and still uses production.
- iOS Simulator: reaches the server via
localhostautomatically. - Real device: replace
localhostwith your Mac's LAN IP in the.localconfiguration (iOS/AppConfiguration.swift), and make sure both are on the same network.