RunFlow
RunFlow plans running routes through Vienna with 72 % fewer traffic lights per kilometre – for 7 % more distance.
Data sheet
| Identification no. | JS-2026-01 |
|---|---|
| Period | 01–09/2026 |
| Role | Concept, evaluation and testing; code written with AI assistance |
| Tools | Python · FastAPI · networkx · OpenStreetMap · Leaflet · Docker |
| Key result | 3.63 → 1.00 traffic lights per km (−72 %) for 7 % more distance; 20 of 20 test loops within ±10 % of the target distance |
| Status | Live demo |
1Result
Zone BExample route A → B: 4 traffic lights over 2.51 km. Dashed: the routes at the other values.
- Lights / km
- 1.00
- Detour (median)
- +33 %
Measured on 40 random routes (1.5–4 km) in the inner districts, current network, 05/10/2026. A trade-off as in vehicle routing: every metre of detour has to pay for itself through less waiting.
Live demo
2Problem and motivation
Zone CRed lights break a runner's rhythm, and common running apps only optimise distance. In Vienna that means a signalised crossing every few hundred metres.
3Set-up
Zone D- 01Map data: OpenStreetMap
- 02Path network: 131,000 nodes, metres per path type
- 03Cost function: length × factor + 100 m per lightMy part
- 04Search: A*
- 05Loop calibration: ±8 %My part
The cost function is a trade-off just like in logistics: each traffic light costs 100 “virtual metres” (≈ 36 s of waiting at 6:00 min/km). Quiet paths count 1.0, sidewalks along main roads 1.2, stairs 1.5.
This is the real decision: what is a traffic light worth?4Verification
Zone EWhat went wrong
The first version looked finished and drew plausible routes. Only measuring 40 test routes revealed that, because of a detail in the networkx library, every edge got the same weight – the router counted street segments instead of metres and traffic lights. Re-measuring on the rebuilt network (Oct 2026) gave clearly higher values: the old network most likely lost traffic lights during simplification, so the old absolute figures were too optimistic.
| Metric | First version | Fixed |
|---|---|---|
| Traffic lights per km (A → B) | 1.58 | 0.38 |
| Route length vs. straight line (median) | 1.41× | 1.27× |
| Loops within ±10 % of target distance | 0 % | 95 % |
| Start-up time / memory | 15 s / 1.8 GB | 1.5 s / 0.4 GB |
5Insight and next iteration
Zone F- Code that works in a demo can still be wrong. A measurement with numbers catches what a glance at the map misses.
- Separate facts (stored in the network) from policy (applied at start-up): the weighting can change without downloading data again.
- Real data is messy: OpenStreetMap maps one crossing as 2–5 signal nodes, which had to be grouped first.
Next iteration
Movement-aware penalty: turning right at a signalised junction without crossing should not be penalised. That needs an edge-based graph.
Technical details: Cost function and search
w(u,v) = Σ length per path type × factor (+10 % on cobblestones) + 100 m when v enters a new signalised crossing.
All factors ≥ 1, so the straight-line distance never overestimates the remaining cost: A* returns the optimal route for this cost model.
Signal nodes joined by an edge ≤ 30 m count as one crossing (88 % of signal nodes have a neighbour within 30 m).
Technical details: Loops
Two helper points form a right-angled triangle with the start. Streets already used cost ten times as much (bridges twice) so the way back differs; dead-end spurs are removed.
If the length is off by more than 8 %, the triangle is rescaled (max. 5 attempts). Test on the current network: 20 of 20 loops (5 and 10 km) within ±10 %, median deviation 3.8 %.