Extract continuous street-level 'blue line' coverage (Google/Mapillary/KartaView) as GeoJSON — official car Street View only, never photospheres.
Project description
tracelines
Extract the blue lines, never the circles.
Turn an area into GeoJSON polylines of continuous official street-level coverage — the solid blue Street View lines — while rigorously excluding user photospheres / photopets (the dots). Multi-source: Google (via streetlevel), Mapillary, and KartaView, fused onto one map.
🗺️ Live GUI · 📖 Documentation · 🔬 How the HARD RULE works · ✅ Reproduce the verification
Disclaimer. Research / educational tool, provided as-is, without warranty. The Google provider relies on undocumented internal endpoints (via streetlevel) that may break without notice. You are responsible for complying with each data source's Terms of Service and your local law. Respect rate limits — don't hammer the endpoints. Not affiliated with or endorsed by Google, Meta/Mapillary, or KartaView.
The HARD RULE
Output contains only genuine official Google car Street View coverage — never a user photosphere / photopet / photo-path, even a Google-hosted one.
Enforced in three defense-in-depth layers (each verified — see the filter contract and verification):
| Layer | Guard | Stops |
|---|---|---|
| 0 — Source | Nodes come only from the z17 get_coverage_tile layer; never search_third_party. |
Photospheres are structurally absent there. Verified: 7,433 / 7,433 tile panos official (2026-07-13 snapshot; the count drifts as Google updates coverage, the 0-leak invariant does not — re-check with scripts/verify_hardrule.py). |
| 1 — ID whitelist | is_official_panoid(): 22 chars, 16-byte decode, terminal ∈ {A,Q,g,w}, not CIHM/CIAB/AF1Q. |
Legacy 22-char photospheres + the new CIAB UGC scheme. Verified: 753/753 official accepted, 3/3 photospheres rejected. |
2 — Source allowlist (--precision) |
source == 'launch' (car), fetched per-pano. |
Trekker (scout), indoor (innerspace), every photos:* upload. |
Honest caveat: launch is the road-snapped layer Google draws as solid blue lines; Google's own
metadata says launch is "sometimes trekker" and scout is "sometimes car" — so no field
perfectly separates car from trekker. That fuzz is orthogonal to the photosphere exclusion,
which is airtight.
Install
pip install tracelines # Google-capable out of the box (no API key)
pip install "tracelines[mapillary]" # + Mapillary vector-tile source
pip install "tracelines[all]" # + Mapillary + OSM road-snapping
Optional env vars: MAPILLARY_TOKEN (free, for the Mapillary source); MAPS_API_TOKEN
(optional Google metadata enrichment — not required).
Quickstart
# Nearest official car coverage to a point (never a photosphere)
tracelines nearest 44.435072 26.050430
# Extract blue lines over an area -> GeoJSON
tracelines extract --area bucharest-city --sources google --out bucharest.geojson
tracelines extract --bbox 26.09,44.45,26.11,44.46 --sources google,mapillary --out out.geojson
# Pure car only (drops trekker/indoor; slower, one metadata call per pano)
tracelines extract --area bucharest-city --sources google --precision --out cars.geojson
# Density probe: covered km / segments / median year per source
tracelines probe --bbox 26.09,44.45,26.11,44.46 --sources google,mapillary
from tracelines import Settings, AREAS
from tracelines.pipeline import extract
from tracelines.export import write_geojson, summary_stats
segments, meta = extract(AREAS["bucharest-city"], ["google"], Settings())
write_geojson(segments, "bucharest.geojson")
print(summary_stats(segments))
The GUI
A static cockpit on GitHub Pages — because the Google extraction can't run in a browser (undocumented, CORS-blocked, Python-only), the page is a control surface, not the engine:
- View any GeoJSON the CLI produced (drag & drop) on an interactive map.
- Build a command — draw a bbox, copy the exact
tracelines extract …command. - Live Mapillary overlay (paste your own free token — stored only in your browser).
- Live Google works out-of-the-box via a shared hosted demo proxy — or run your own free.
- Diff sources — where does Google have coverage Mapillary lacks, and vice-versa.
Deploy your own proxy (free)
The GUI ships pointed at a shared hosted demo proxy (Google Cloud Run) so Live Google works
immediately. It's hardened (tiny bbox cap, per-IP rate limit) and may sleep or hit limits — for real
use, run your own. A public Google-extraction proxy is an open relay (CORS doesn't stop curl),
so keep the hardening env tight and treat it as best-effort, not a service.
| Host | Free? | How |
|---|---|---|
| Render (recommended) | ✅ no card | render.yaml |
| Docker anywhere | — | docker build -t tracelines-proxy . && docker run -p 8000:8000 -e TRACELINES_CORS_ORIGINS="https://<you>.github.io" tracelines-proxy |
| Google Cloud Run | ✅ free tier | gcloud run deploy tracelines-proxy --source . --allow-unauthenticated --max-instances 1 |
| Hugging Face Space | ⚠️ needs HF PRO now | files in spaces/ |
Then paste your URL into the GUI's Proxy tab. Hardening knobs: TRACELINES_MAX_BBOX_DEG2,
TRACELINES_RATE_PER_MIN, TRACELINES_CORS_ORIGINS, and the kill switch TRACELINES_DISABLED=1.
Sources & what you may do with the output
| Source | Live in browser? | Output license |
|---|---|---|
| Google (streetlevel) | ❌ (CLI or self-hosted proxy) | No redistribution license — generate for yourself (details) |
| Mapillary | ✅ (your token) | CC-BY-SA 4.0 (attribute + share-alike) |
| KartaView | ✅ best-effort | CC-BY-SA 4.0 (attribute + share-alike) |
See DATA_LICENSES.md — the AGPL license covers the code, not the data you extract.
How it works
bbox → z17 tile enumerate → async fetch (cached, concurrent)
→ Layer-1 official-id whitelist → panoid graph (links = edges)
→ [--precision] source=='launch' allowlist
→ shapely line_merge (splits at junctions) → CoverageSegments
Mapillary sequence tiles ─┐
KartaView bbox polylines ─┴→ fuse (union + provenance) → GeoJSON
Full internals: Architecture.
Documentation
- Methodology / Filter contract — the HARD RULE as a normative (RFC-2119) contract.
- Verification — reproduce the 7,438-pano sweep yourself.
- Data provenance & licensing.
- Output schema — the GeoJSON contract.
- API reference.
Development
pip install -e ".[dev]"
pytest # hermetic tests (no network)
pytest -m network # live tests (hit Google/Mapillary)
ruff check tracelines tests
python scripts/verify_hardrule.py # re-run the HARD-RULE sweep
Acknowledgements
- streetlevel by sk-zk — the MIT library this stands on; it does the actual Google coverage-tile walking.
- matdoes.dev Street View / pano-id write-ups — invaluable for the id taxonomy.
- Mapillary and KartaView for open, documented street-level APIs.
License
AGPL-3.0-or-later — see LICENSE. If you run a modified version as a network service (e.g. the proxy backend), the AGPL requires you to share your source. Third-party notices in THIRD_PARTY_LICENSES.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tracelines-0.2.0.tar.gz.
File metadata
- Download URL: tracelines-0.2.0.tar.gz
- Upload date:
- Size: 53.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0cf9fc381325d8bba9eb0e0a80c1aabb9a48149c937e237dca5b28ed41e293e9
|
|
| MD5 |
68d1ddced38b251a9636041b68961105
|
|
| BLAKE2b-256 |
1eec7ed5525d53d7716177eb68628195f398ed7cdcb8ee56c852685a7c9f4a8e
|
File details
Details for the file tracelines-0.2.0-py3-none-any.whl.
File metadata
- Download URL: tracelines-0.2.0-py3-none-any.whl
- Upload date:
- Size: 50.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3961bc7ae299f704008e8eb9f09b07ac6196aebe6e224b07f626542fe4e325c1
|
|
| MD5 |
5d6c17b0427a2705a0123f15ab02ebb4
|
|
| BLAKE2b-256 |
84eab3e07a506b30d9dea6c4387664fee539fc5dc61aeb6e0d0ce2fb1a45cd70
|