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

MethodPathResponse
GET/locationsJSON 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)

VariableDefaultMeaning
PORT8080Port to listen on
RIDERS300Number of riders in the cluster (100–1000)
SPEED_KMH15Cluster 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 localhost automatically.
  • Real device: replace localhost with your Mac's LAN IP in the .local configuration (iOS/AppConfiguration.swift), and make sure both are on the same network.