trips/spatial/README.md
Greg Pomerantz c367eba5cb Live web enrichment (SearXNG) + PostGIS spatial stack
Milestone 1 — live web search:
- mock/server.js: /search + /search-status proxy to the self-hosted
  SearXNG (third leg of the 3-source blend; engine endpoint stays
  server-side), /spatial + /spatial-status proxy to the spatial service
- mock/app.js: background enrichment of suggestion cards — each
  candidate gets a web element (discovery cards + "you might like"
  rows) that is checked via SearXNG in the background (one in-flight
  query per place, 1 h TTL, failures retry in ~5 min) and APPENDS
  sourced facts with provenance URLs; never reorders/rewrites the plan.
  New LLM tools: web_search (cite URLs) and poi_near (PostGIS). Status
  badge gains 'web' / 'spatial'.
- styles.css: .cc-web live-facts blocks, t-web verified chip

Milestone 2 — PostGIS spatial DB (code complete; needs docker access
to run — see spatial/README.md):
- spatial/: docker-compose (postgis/postgis:16-3.4 + osm2pgsql:16
  one-shot importer + spatiald), schema.sql (poi table w/ GIST index,
  spatial_extract bookkeeping, refresh_poi()), import.sh for the PBF
  extracts in ../osm, spatiald (Go, lib/pq): /health, /near
  (ST_DWithin), /nearest (KNN), /corridor (ST_Buffer along a route),
  with kind/name/extract/limit filters
2026-09-10 09:43:30 -04:00

82 lines
3.3 KiB
Markdown

# 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 `../osm/` (already present: `nh.osm.pbf`,
`colombia.osm.pbf`, US state extracts).
## Usage
```bash
docker compose up -d postgis # first: runs schema.sql
./import.sh ../osm/nh.osm.pbf nh # load New England (default)
./import.sh ../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.