Skip to content

feat(api): add OpenStreetMap as a 2nd open station source - #53

Merged
AayushCharde merged 1 commit into
mainfrom
feat/osm-source
Jun 17, 2026
Merged

feat(api): add OpenStreetMap as a 2nd open station source#53
AayushCharde merged 1 commit into
mainfrom
feat/osm-source

Conversation

@AayushCharde

Copy link
Copy Markdown
Owner

What (PR-1 of "more stations")

Adds OpenStreetMap (ODbL) as a second open station source alongside Open Charge Map. OSM is fetched per-viewport via the Overpass API and merged into the same stations table, so coverage grows everywhere OSM has data. Location-only chargers are first-class (still render; missing fields handled).

How

  • app/ingest.py (new) — source-neutral StationRow (now with source), shared UPSERT (writes source), upsert_stations, and osm_station_id() that namespaces OSM ids into a BIGINT range disjoint from OCM + between node/way/relation → single-column PK + reports FK untouched.
  • app/osm.py (new) — fetch_osm_elements (Overpass GET + User-Agent) + pure map_osm_element mapping OSM tags into the same OCM-compatible connectors JSON (socket:*→titles, kW parsing); source="osm".
  • app/ocm.pymap_poi sets source="ocm"; uses shared StationRow.
  • app/stations.py — generic guarded _sync_source per source (independent tile guards); cold viewport gathers OCM+OSM then re-queries, warm refreshes both in background; source added to query + StationOut.
  • geo.tile_key(source), config OSM_OVERPASS_URL/OSM_SYNC_TTL_SECONDS, migration 004_station_source.sql.
  • Frontend: Station.source + detail panel shows the source. ATTRIBUTION.md: OSM/ODbL.

Verification

  • ruff + ruff format + mypy app (strict) + pure tests (test_osm, test_ocm) ✓. Frontend lint/test (27)/build/svelte-check ✓.
  • End-to-end against Supabase: migration 004 applied; ingested central London → 9 OCM + 40 OSM = 49 stations (was OCM-only). Real OSM chargers ("Source London", "Juice").

Rollout

Migration 004 already applied to Supabase ✓. On merge, Render auto-deploys the multi-source backend. (No cross-source dedup yet → possible duplicate markers; tracked as a follow-up.)

Multi-source ingest. OSM (ODbL) is fetched per-viewport via Overpass and
merged into the same stations table alongside Open Charge Map, increasing
coverage everywhere OSM has data (location-only chargers are first-class).

- app/ingest.py: source-neutral StationRow (now with `source`), shared UPSERT
  (writes source), upsert_stations, and osm_station_id() namespacing OSM ids
  into a BIGINT range disjoint from OCM (+ between node/way/relation) so the
  single-column PK + reports FK are untouched.
- app/ocm.py: map_poi sets source="ocm"; imports shared StationRow.
- app/osm.py: fetch_osm_elements (Overpass GET + User-Agent) + pure
  map_osm_element → OCM-compatible connectors JSON (socket:* → titles, power
  parsing); source="osm".
- app/stations.py: generic guarded _sync_source per source (independent tile
  guards); cold viewport gathers OCM+OSM, warm refreshes both in background;
  `source` added to the query + StationOut.
- geo.tile_key(source); config OSM_OVERPASS_URL / OSM_SYNC_TTL_SECONDS.
- migration 004_station_source.sql (source column).
- frontend: Station.source; StationDetail shows the data source.
- ATTRIBUTION: OSM station data (ODbL, © OpenStreetMap contributors).
- tests: map_osm_element + osm_station_id; map_poi source assertion.

Verified: ruff + mypy strict + pure tests; frontend lint/test/build/svelte-check.
End-to-end against Supabase: central London now 9 OCM + 40 OSM stations.
@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
plugpulse Ready Ready Preview, Comment Jun 17, 2026 7:29am

@AayushCharde
AayushCharde merged commit 3dde772 into main Jun 17, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant