- planner dashboard: replace Overpass panels with POI index freshness + serving panels (age, rows/category, import status, API request rate/errors) - alerts: replace overpass-upstream-unhealthy with poi-index-stale (>6 weeks) - architecture.md: POI data flow now the self-hosted index - privacy manifest (DE+EN): Overpass is Journal-surface-backfill-only; Planner POIs served same-origin from /api/pois - self-host-overpass README + roadmap: superseded for POIs by poi-index - poi-extract README: self-hoster story (optional pipeline, regional extract, graceful empty index) - map-core tsconfig: exclude *.sync.test.ts from tsc to keep it zero-dep Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| gen-osmium-filters.ts | ||
| osmium-filters.txt | ||
| poi-extract.service | ||
| poi-extract.sh | ||
| poi-extract.timer | ||
| README.md | ||
| to-ndjson.py | ||
POI extract pipeline (BRouter host)
Monthly job that turns an OSM PBF into the compact POI artifact the flagship's
planner.pois index is built from. This is the "filter where the disk is"
half of the poi-index change — it
replaces the third-party Overpass dependency for the Planner's POI overlays.
Runs on the dedicated BRouter host (ullrich.is, the box with 1.8 TB free)
under the non-root trails user. The flagship import job
(infrastructure/scripts/poi-import.sh) pulls the result over the vSwitch.
Files
| File | Role |
|---|---|
poi-extract.sh |
Download PBF → osmium tags-filter → osmium export → to-ndjson.py → gzip + manifest |
to-ndjson.py |
GeoJSONSeq → NDJSON, reducing ways/relations to their bbox centre (Overpass out center equivalent) |
osmium-filters.txt |
osmium tag filters, generated from @trails-cool/map-core POI selectors |
gen-osmium-filters.ts |
Regenerates osmium-filters.txt (run after changing categories) |
poi-extract.service / .timer |
systemd units (monthly) |
Single source of truth
osmium-filters.txt is derived from poiCategories in
packages/map-core/src/poi.ts. After changing categories, regenerate it:
npx tsx infrastructure/brouter-host/poi-extract/gen-osmium-filters.ts
packages/map-core/src/osmium-filters.sync.test.ts fails CI if the committed
file drifts from the selectors, so the host never needs a Node runtime.
Artifact contract
poi-extract.sh writes to the publish dir (default ./publish, mounted into
the Caddy sidecar at /srv/poi):
pois.ndjson.gz— one POI per line:{osm_type, osm_id, name, lat, lon, tags}. Category is not stored here; the flagship importer classifies each element from itstagsusing the same map-core selectors (so the selector logic is never duplicated on this host).manifest.json—{artifact, sha256, row_count, generated_at, source}.
Caddy serves both under /poi/* on the existing vSwitch-only :17777
listener, gated by the same X-BRouter-Auth shared secret as BRouter — so the
artifact is unreachable from the public internet.
Prerequisites
Install on the host: osmium-tool, python3, curl, gzip, coreutils
(sha256sum). On Debian/Ubuntu: apt install osmium-tool python3.
Install the timer
Deployed under ~trails/brouter/poi-extract/. As a user unit with lingering:
loginctl enable-linger trails
mkdir -p ~/.config/systemd/user
cp poi-extract.service poi-extract.timer ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now poi-extract.timer
systemctl --user list-timers poi-extract.timer
Trigger a run by hand (e.g. the first bootstrap): systemctl --user start poi-extract.service then journalctl --user -u poi-extract.service -f.
Footprint
- Bandwidth: the planet PBF is ~80 GB downloaded per run (monthly). Point
POI_PBF_URLat a Geofabrik mirror or a regional/continental extract to cut this — e.g.europe-latest.osm.pbf(~30 GB) orgermany-latest.osm.pbf(~4 GB) for a smaller instance. - Disk: the planet PBF + filtered PBF live transiently in
./workand are removed on exit (even on failure) via a trap. Peak transient usage ≈ size of the PBF. The published artifact is a few hundred MB gzipped. - CPU/time: a full planet filter is IO-bound and can take a few hours; the
unit runs at
Nice=10/ idle IO priority so it doesn't disturb BRouter.
Self-hosting
The pipeline is optional. A fresh instance works with an empty or absent
planner.pois table: /api/pois returns an empty result / 503 and the map's
POI panel shows "unavailable" while every other overlay and tile keeps working
— nothing breaks.
When you do want POIs, you don't need a second host or the planet. Both halves
run on one box, and POI_PBF_URL can point at any Geofabrik extract sized to
your region:
# Extract just your region (much smaller download, seconds not hours):
POI_PBF_URL=https://download.geofabrik.de/europe/germany-latest.osm.pbf \
./poi-extract.sh
# Then import it (first time: --bootstrap, since the live table is empty):
POI_ARTIFACT_BASE_URL=file-or-vswitch-url \
../../scripts/poi-import.sh --bootstrap
On a single-host instance, publish the artifact however is convenient (local
path, a static file server) and set POI_ARTIFACT_BASE_URL accordingly; the
two-host vSwitch topology is a flagship detail, not a requirement.
Dry run first
Before the first planet run, validate the whole chain on a small extract:
POI_PBF_URL=https://download.geofabrik.de/europe/germany-latest.osm.pbf \
./poi-extract.sh
cat publish/manifest.json # row_count sane? sha256 present?
zcat publish/pois.ndjson.gz | head # records well-formed?
zcat publish/pois.ndjson.gz | wc -l