River guide configs

One JSON file per river, rendered by python3 tools/build_river_guide.py <config>. Output: output/river-guides/<slug>-guide.html + output/pdf/<slug>-river-guide.pdf (PDF printed automatically via headless Chrome). A3 landscape; layout picks itself: map-on-top for wide rivers, map-left for tall ones (bbox aspect > 0.6).

Recipe for a new river (token-efficient)

  1. Data. Need an OSM extract in data/river-guides/<slug>-osm.json (Overpass: river ways + highways + landuse + waterways in the river bbox) and a centreline — either OSM way ids (centreline.type: "osm") or an FWA export with centreline.coordinates (type: "fwa"). Obstacles (falls/cascades/rapids/dams, FISS + FWA merged) come from data/river-guides/obstructions-kootenay.geojson automatically by proximity (150 m) — refresh with tools/build_obstructions.py --refresh.
  2. Config. Copy skookumchuck-creek.json (wide) or goat-river.json (tall). Fill points/zones/markers/labels/cards from the river's wiki page (wiki/rivers-and-lakes/<slug>.md: standfirst, regs, access, flies) — cards carry the fishing content; keep regs wording aligned with the wiki.
  3. Iterate on lint, not on renders. The builder prints LINT overlap: lines for colliding labels/markers (estimated boxes). Fix config coordinates until LINT: no label overlaps detected, then do ONE visual check of the PDF (sips -s format png + zoom crops of dense clusters). Lint does not know about icons, leader lines, map edges or the legend box — check those visually: labels near the bbox edge, leaders crossing icons, and the bottom-right legend overlay are the usual offenders.
  4. Zones/legend: every drawn symbol gets a legend chip automatically; zone legend text is per-config. Long marker labels in dense clusters: shorten the on-map text and keep the full name in the stops card.

Honesty rules (keep): falls/rapids positions are approximate and incomplete; road crossings are landmarks, not access rights; "not for navigation" note stays.