Metro transit detection
Where Is Tereza? auto-classifies every GPS point into a transport mode — walking, running, cycling, driving. Metro transit detection is the special-case layer that recognises when you've boarded an underground train and renders the segment as a dashed public-transport line instead of a straight gold line across the city.
Why it matters
Underground sections are GPS dead zones. Without detection, a metro ride from one station to another would either:
- Leave a long gap in the route (no points between stations), or
- Snap to whatever surface road happens to lie above the tunnel — which is rarely the actual line.
The detector recognises the pattern (last GPS point at a station
entrance, gap of 1–25 minutes, next point at a station entrance
0.5–15 km away) and labels the connecting segment as
public_transport, drawing it as a dashed line.
How it works
- Station database. A built-in list of station coordinates per
city (see Coverage below). Each entry is a
(latitude, longitude, name, line)tuple. - Bounding-box pre-check. For each candidate GPS pair we first check which city's footprint they could fall in. Stations in other cities are skipped — adding more cities scales linearly only inside the city the GPS point is actually in.
- Detection criteria. A segment qualifies as metro travel when:
- both endpoints are within 350 m of different stations;
- the time gap is 60 s – 1500 s (1–25 minutes);
- the straight-line distance is 0.5 km – 15 km;
- both stations belong to the same city (no inter-city teleport).
- Classification. Matching segments get
mode: public_transportand render as a dashed line on the public tracker map, distinct from the walking / cycling / driving polylines.
Coverage
Live as of build 0.2.1042. Numbers reflect the curated station
list shipped in backend/services/metro.py. The detector covers
the busiest central + interchange stations; less-used outer
terminals may be missing for now.
| City | Country | System | Stations | Lines | Bounding box (lat / lon) |
|---|---|---|---|---|---|
| Prague | CZ | Metro | 61 | A, B, C | 49.95–50.17 / 14.24–14.60 |
| London | UK | Underground | 67 | Bakerloo, Central, Circle, District, Jubilee, Northern, Piccadilly, Victoria | 51.41–51.58 / −0.28–0.03 |
| Paris | FR | Métro | 81 | L1, L2, L4, L6, L7, L9, L10, L11, L12, L13, L14 | 48.77–48.93 / 2.23–2.44 |
| Berlin | DE | U-Bahn | 66 | U1, U2, U3, U4, U6, U7, U8, U9 | 52.43–52.62 / 13.22–13.50 |
| Barcelona | ES | Metro | 48 | L1, L2, L3, L4, L5 | 41.32–41.47 / 2.09–2.26 |
| Madrid | ES | Metro | 67 | L1, L2, L3, L4, L5, L6, L8, L9, L10 | 40.34–40.54 / −3.78–−3.52 |
| Vienna | AT | U-Bahn | 77 | U1, U2, U3, U4, U6 | 48.06–48.38 / 16.26–16.53 |
| Total | 467 |
The bounding boxes are auto-derived from the stations themselves plus a 0.05° (~5 km) margin. If you walk just outside a city's bbox (e.g. the airport), the detector simply skips that city — no false positives.
Adding a city
Internally, extending the detector is a one-file change in
backend/services/metro.py:
- Add a
(lat, lon, name, line)list per city — one tuple per station entrance. - Append a
CITIESentry:{"name", "country", "system", "stations": YOUR_LIST}. - The bbox is auto-computed; no further config needed.
For end-users: spotted a city we don't cover? File a bug report from the docs footer with the name + a sample trip URL where the metro segments are missing — we add cities in priority order based on usage data.
What it gets right
- Single-line rides between adjacent stations.
- Multi-line rides with one or two transfers (each transfer surfaces briefly in the station mezzanine, generates new GPS in passing and re-anchors the detection).
- Trips where the user pulled their phone out at the platform and posted a photo — the photo location lands at the platform, the algorithm snaps the segment to that station.
What it gets wrong
- Brand-new stations whose coordinates aren't in our database yet — the segment falls back to a straight gold line.
- Surface-running suburban rail. S-Bahn, RER, London Overground etc. are surface trains; GPS works fine on those. We deliberately exclude them from the metro lists so they don't get mis-detected as underground when GPS dropped for an unrelated reason.
- Bus tunnels (Boston, San Francisco, Bratislava have a few) surface to street and are classified as driving / public_transport, not metro.
In your stats
Metro segments contribute to:
- Total distance as the straight-line distance between stations, not the actual underground tunnel length (we don't have tunnel geometry data — typical error 5–15 %).
- Active time is excluded from stop / rest detection. Metro rides DON'T count as a stop even though the user is sitting still.
- Naismith equivalent (terrain-adjusted distance) treats metro rides as flat ground, regardless of the actual underground gradient.
Plan availability
The detector itself runs on every plan today. Per-plan gating is on the roadmap — see the platform admin "Metro transit detection" feature flag.
Related
- Trip management — segments + per-trip transport override.
- Transport modes — how each mode is classified and rendered.
- Analytics glossary — how transport mode affects every metric.
Need help? Contact support · Where Is Tereza?