Files
homepage/tile-proxy/README.md
T
Alexander 94c8212078 feat(tile-proxy): Thunderforest Outdoors as foreign karte upstream
OpenTopoMap's hypsometric tint reads "red mountains / green flats" and
looks nothing like the Swisstopo Pixelkarte the proxy hands out
in-region — produced a jarring visual seam right at the CH/LI border.
Thunderforest Outdoors has a muted topo palette + subtle hillshade
that matches the swisstopo tile aesthetic much more closely, so use
it as the abroad `karte` upstream when an API key is available.

- `tile-proxy/build.rs`: reads `tile-proxy/.env` (gitignored) at build
  time and forwards each `KEY=VAL` line to rustc as `--env`, so the
  key is baked into the binary via `option_env!` and never touched at
  runtime. A shell env var of the same name wins over the .env entry
  (dotenv precedence). `cargo:rerun-if-changed=.env` +
  `cargo:rerun-if-env-changed` force a recompile whenever the value
  changes — no stale key cached in the binary.
- `main.rs`: `THUNDERFOREST_API_KEY` read via `option_env!`; foreign
  `karte` is Thunderforest Outdoors when set, OpenTopoMap fallback
  when absent. Behaviour unchanged for keyless builds.
- `mapTiles.ts`: page-footer attribution credits Thunderforest + OSM
  alongside the existing swisstopo / OpenTopoMap / Esri lines so the
  attribution stays correct regardless of which build is deployed.
- `.gitignore`: tile-proxy build artefacts (binary, `target/`, `.env`)
  moved to the root gitignore with fully-qualified paths so the
  source tree isn't hidden by a nested gitignore quirk; the per-dir
  `tile-proxy/.gitignore` is removed.
- README + systemd service: documentation refreshed for the new
  build-time key flow.
2026-05-26 22:56:32 +02:00

115 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# tile-proxy
A tiny region-switching map-tile service for the hikes maps. It exposes one
canonical XYZ scheme and, **per tile**, serves **swisstopo inside Switzerland
& Liechtenstein** and a **global provider elsewhere** — so a hike anywhere in
the world gets a good schematic + satellite basemap, while CH/LI hikes keep
swisstopo quality.
```
browser / build script
│ https://maps.bocken.org/{layer}/{z}/{x}/{y}
nginx ── TLS termination + on-disk tile cache (proxy_cache)
▼ http://$TILE_PROXY_ADDR (default 127.0.0.1:8765)
tile-proxy (this crate) ── z/x/y → point-in-polygon → pick + fetch upstream
swisstopo │ OpenTopoMap │ Esri World Imagery
```
The listen address is set entirely by the **`TILE_PROXY_ADDR`** env var
(`host:port`); pick whatever port you like. nginx, the systemd unit and this
binary all reference that one value.
Caching/TLS/rate-limiting live in **nginx** (Arch's stock nginx has no njs, and
we want a real geometry test anyway). This binary only does routing + fetch and
is stateless.
## Layers & providers
| `{layer}` | swisstopo region (CH + LI) | elsewhere |
|------------|----------------------------------|----------------------------------------|
| `karte` | `ch.swisstopo.pixelkarte-farbe` | Thunderforest Outdoors (or OpenTopoMap)|
| `luftbild` | `ch.swisstopo.swissimage` | Esri World Imagery |
| `dufour` | `ch.swisstopo.hiks-dufour` | — (CH/LI-only) |
The `karte` upstream abroad is **Thunderforest Outdoors** when a
`THUNDERFOREST_API_KEY` is available at **build** time — it's baked into the
binary via `option_env!`. The key is read from `tile-proxy/.env` (gitignored)
by `build.rs`, or from a shell env var of the same name; both are watched so
the binary recompiles whenever the value changes. Without a key, the build
falls back to **OpenTopoMap** (no key needed, but its hypsometric tint reads
as "red mountains / green flats", which is why Thunderforest is preferred
when available).
```sh
# tile-proxy/.env (gitignored)
THUNDERFOREST_API_KEY=your-key-here
```
The swisstopo region is **Switzerland + Liechtenstein** (swisstopo has
high-quality data for both). A tile is served by swisstopo when it **overlaps**
that region at all — the tile's lat/lng rectangle is intersected against the
polygons (`src/regions.in`, simplified + a **2 km outward buffer**), so *any*
tile touching CH/LI gets swisstopo, at every zoom. (A swisstopo tile partly
outside its data just renders white at the edges, which beats a foreign
provider drawing over covered ground.) For `karte`/`luftbild`, a swisstopo tile
that 404s near the edge falls back to the global provider automatically.
## Build & run
```sh
# Optional: drop a Thunderforest key in tile-proxy/.env for nicer abroad
# `karte` tiles; the build falls back to OpenTopoMap when the file is absent.
echo 'THUNDERFOREST_API_KEY=your-key-here' > .env
cargo build --release
TILE_PROXY_ADDR=127.0.0.1:8765 ./target/release/tile-proxy
# smoke test
curl -s -o /dev/null -w '%{http_code} %{content_type}\n' \
http://127.0.0.1:8765/karte/9/266/180 # Bern → swisstopo (jpeg)
curl -s -o /dev/null -w '%{http_code} %{content_type}\n' \
http://127.0.0.1:8765/karte/9/255/171 # London → Thunderforest / OpenTopoMap (png)
```
`TILE_PROXY_ADDR` defaults to `127.0.0.1:8765` but should be set explicitly.
`GET /healthz` returns `ok`.
Run it as a service with [`deploy/tile-proxy.service`](deploy/tile-proxy.service)
and put nginx in front with [`deploy/nginx.conf.example`](deploy/nginx.conf.example).
## Regenerating the region polygons
`src/regions.in` is generated — don't hand-edit it. To refresh (or change the
buffer / simplification, or add a region):
```sh
make regions # CH + LI, default 2 km buffer
BUFFER_KM=2 SIMPLIFY_DEG=0.004 node scripts/gen-regions.mjs
node scripts/gen-regions.mjs ./ch.geojson ./li.geojson # local files
```
It takes each source's largest exterior ring, DouglasPeuckersimplifies it,
then pushes every vertex outward by `BUFFER_KM`. Rebuild the binary afterwards
(the ring is baked in via `include!`). If you move the boundary, purge the nginx
cache so old tiles aren't served by the previous provider.
## Attribution (required)
All providers require credit — keep these on the page (the hikes pages show
them in the footer):
- **© swisstopo** — Swiss tiles.
- **Maps © Thunderforest, Data © OpenStreetMap contributors** — world schematic (when the key is baked in).
- **© OpenStreetMap contributors, SRTM | © OpenTopoMap (CC-BY-SA)** — world schematic (fallback build).
- **© Esri, Maxar, Earthstar Geographics** — world satellite.
## Notes
- **Max zoom** differs by provider — clamp the client: OpenTopoMap 17,
Esri imagery ~19, swisstopo 19/20.
- **OpenTopoMap fair-use:** it's a small volunteer project; the long nginx
cache + a descriptive `User-Agent` is the mitigation. If traffic grows,
self-host a renderer.
- **Dufour** has no world equivalent — the client hides that layer when the
view leaves Switzerland.