Metadata-Version: 2.4
Name: tracelines
Version: 0.2.0
Summary: Extract continuous street-level 'blue line' coverage (Google/Mapillary/KartaView) as GeoJSON — official car Street View only, never photospheres.
Author: Prekzursil
License-Expression: AGPL-3.0-or-later
Project-URL: Homepage, https://prekzursil.github.io/tracelines/
Project-URL: Documentation, https://prekzursil.github.io/tracelines/docs/
Project-URL: Repository, https://github.com/Prekzursil/tracelines
Project-URL: Issues, https://github.com/Prekzursil/tracelines/issues
Keywords: street-view,streetlevel,mapillary,kartaview,coverage,geojson,gis,openstreetmap,panorama,reverse-engineering
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: THIRD_PARTY_LICENSES.md
Requires-Dist: shapely>=2.0
Requires-Dist: networkx>=3.0
Requires-Dist: aiohttp>=3.9
Requires-Dist: streetlevel<0.13,>=0.12
Requires-Dist: diskcache>=5.6
Provides-Extra: mapillary
Requires-Dist: mapbox-vector-tile>=2.0; extra == "mapillary"
Provides-Extra: snap
Requires-Dist: osmnx>=1.9; extra == "snap"
Requires-Dist: geopandas>=0.14; extra == "snap"
Requires-Dist: pyproj>=3.6; extra == "snap"
Provides-Extra: server
Requires-Dist: fastapi>=0.110; extra == "server"
Requires-Dist: uvicorn[standard]>=0.29; extra == "server"
Provides-Extra: all
Requires-Dist: mapbox-vector-tile>=2.0; extra == "all"
Requires-Dist: osmnx>=1.9; extra == "all"
Requires-Dist: geopandas>=0.14; extra == "all"
Requires-Dist: pyproj>=3.6; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: mercantile>=1.2; extra == "dev"
Requires-Dist: mapbox-vector-tile>=2.0; extra == "dev"
Requires-Dist: mkdocs-material>=9.5; extra == "dev"
Requires-Dist: mkdocstrings[python]>=0.25; extra == "dev"
Requires-Dist: pymdown-extensions>=10; extra == "dev"
Dynamic: license-file

<div align="center">

# 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](https://github.com/sk-zk/streetlevel)),
**Mapillary**, and **KartaView**, fused onto one map.

[![CI](https://github.com/Prekzursil/tracelines/actions/workflows/ci.yml/badge.svg)](https://github.com/Prekzursil/tracelines/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/tracelines.svg)](https://pypi.org/project/tracelines/)
[![Python](https://img.shields.io/pypi/pyversions/tracelines.svg)](https://pypi.org/project/tracelines/)
[![License: AGPL v3](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE)
[![Docs](https://img.shields.io/badge/docs-mkdocs-blue.svg)](https://prekzursil.github.io/tracelines/docs/)
[![GUI](https://img.shields.io/badge/GUI-GitHub%20Pages-brightgreen.svg)](https://prekzursil.github.io/tracelines/)

**[🗺️ Live GUI](https://prekzursil.github.io/tracelines/) · [📖 Documentation](https://prekzursil.github.io/tracelines/docs/) · [🔬 How the HARD RULE works](https://prekzursil.github.io/tracelines/docs/methodology/) · [✅ Reproduce the verification](https://prekzursil.github.io/tracelines/docs/verification/)**

</div>

---

> **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](https://prekzursil.github.io/tracelines/docs/methodology/) and
[verification](https://prekzursil.github.io/tracelines/docs/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

```bash
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

```bash
# 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
```

```python
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](https://prekzursil.github.io/tracelines/)** — 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](https://tracelines-proxy-ttfjfqlcwq-ew.a.run.app/health) — or [run your own free](#deploy-your-own-proxy-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 | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/Prekzursil/tracelines) — one click; uses [`render.yaml`](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/`](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](DATA_LICENSES.md)) |
| 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](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](https://prekzursil.github.io/tracelines/docs/architecture/)**.

## Documentation

- **[Methodology / Filter contract](https://prekzursil.github.io/tracelines/docs/methodology/)** — the HARD RULE as a normative (RFC-2119) contract.
- **[Verification](https://prekzursil.github.io/tracelines/docs/verification/)** — reproduce the 7,438-pano sweep yourself.
- **[Data provenance & licensing](https://prekzursil.github.io/tracelines/docs/data-provenance/)**.
- **[Output schema](https://prekzursil.github.io/tracelines/docs/output-schema/)** — the GeoJSON contract.
- **[API reference](https://prekzursil.github.io/tracelines/docs/api/)**.

## Development

```bash
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](https://github.com/sk-zk/streetlevel)** by **sk-zk** — the MIT library this stands on; it does the actual Google coverage-tile walking.
- **[matdoes.dev](https://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](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](THIRD_PARTY_LICENSES.md).
