# spatial — PostGIS source of truth Native spatial queries for the trip planner (DESIGN.md §Data: "PostGIS (Postgres) — import PBF via osm2pgsql; gives SQL spatial queries (radius, nearest, bbox)"). Replaces the flat-file/Overpass approximations. ## Stack | Piece | What it is | |---|---| | `docker-compose.yml` | `postgis/postgis:16-3.4` (DB, port 5432) + one-shot `osm2pgsql:16` importer + `spatiald` (query service, port 5005) | | `schema.sql` | runs on first boot: `poi` table (named POIs as points, GIST index), `spatial_extract` bookkeeping, `refresh_poi()` | | `import.sh` | loads a PBF from `../osm/` via osm2pgsql, refreshes `poi` | | `main.go` (spatiald) | JSON query service over `poi` | | `Dockerfile` | builds spatiald | ## Prerequisites * Docker with the current user in the `docker` group (on this box: one-time `sudo usermod -aG docker $USER`, then re-login). * PBF extracts in `~/trips/osm` (already present: `nh.osm.pbf`, `colombia.osm.pbf`, US state extracts; override with `OSM_DIR`). ## Usage ```bash docker compose up -d postgis # first: runs schema.sql ./import.sh ~/trips/osm/nh.osm.pbf nh # load New England (default) ./import.sh ~/trips/osm/colombia.osm.pbf colombia docker compose up -d spatiald # query service on :5005 ``` The mock server (`mock/server.js`) proxies it: `GET /spatial/*` → `:5005`, plus `GET /spatial-status` for the UI badge. When the service is up, the chat assistant gains the `poi_near` tool automatically. ## spatiald endpoints All return `{"count":N,"results":[{extract,osm_id,name,kind,opening_hours, fee,website,addr_city,dist_m},…]}`. Common filters: * `extract=nh` — one loaded extract * `kind=amenity=restaurant` — exact kind, or `kind=restaurant` (any family) * `name=café` — ILIKE substring * `limit=20` (max 200) | Endpoint | Query | SQL core | |---|---|---| | `GET /health` | — | extract bookkeeping | | `GET /near` | `lat,lng,r(m; default 500)` | `ST_DWithin(geography, …, r)` + KNN ordering | | `GET /nearest` | `lat,lng` | `ORDER BY geom <-> point` | | `GET /corridor` | `points=lng,lat;…`, `r` | `ST_DWithin(geom::geography, ST_Buffer(line::geography, r))` | Examples: ```bash curl 'localhost:5005/near?lat=42.35&lng=-71.06&r=1000&kind=restaurant' curl 'localhost:5005/nearest?lat=10.40&lng=-75.54&kind=tourism&limit=5' curl 'localhost:5005/corridor?r=300&kind=fuel&points=-71.06,42.35;-71.07,42.36' ``` ## Data notes * `poi` covers every **named** node (or polygon way, via centroid) with an `amenity`/`tourism`/`shop`/`leisure`/`historic`/`place` tag — the kinds the planner asks about. Unnamed amenities (e.g. a nameless kiosk) are out of scope for v1. * `opening_hours`, `fee`, `website`, `addr_city` are carried straight from the OSM tags; the live web enrichment (SearXNG, `mock/app.js`) fills gaps the tags don't have (the 3-source blend). * Multiple extracts coexist in one DB, disambiguated by `poi.extract` (set automatically by `import.sh`). * pgvector (semantic POI index, later milestone) gets its own init script + embedding importer — the base image doesn't ship the `vector` package. ## Roadmap hooks * `bbox` queries: trivial addition (`ST_Contains(ST_MakeEnvelope,…)`). * Routing-graph join: OSRM/GraphHopper geometries can be loaded into the same DB (`route_geom` table) for true along-route analytics instead of the current buffer-over-polyline.