feat(train): the instance's own map tiles, drawn by Train's style (ADR 0030, D50)

Protomaps' basemap cut for Europe and served by pmtiles beside Train's server
(compose.maps.yaml), drawn by Train's own style, light and dark, which the
server serves. Only people signed in get tiles: map.source hands a key that
Caddy has the server check before each tile; nothing logs a tile.

Also fixes .dockerignore, which left apps/train/ out of the build context, so
Train's server's and BRouter's images couldn't have been built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-29 16:04:05 +01:00
commit 190c3872f4
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -1191,6 +1191,74 @@ Strava. What was found, 27 Oct:
one moves it a quarter of the way (`measuredWheelMm`); one typed on the Record sheet
(1000 to 3000 mm) stays until cleared, which measures again. Auto-pause still follows
GPS alone on a trainer.
- **D50 The instance's own map and searching for places** (recommended, the developer's
but the search's region; 29 Oct; step 12, what D22, D23 and D44 left for later):
- **Tiles** (12a): Protomaps' basemap (schema v4, zooms 0 to 15, ODbL, OpenStreetMap's
credit) cut for Europe (the BRouter region's box) from Protomaps' daily build with
`pmtiles extract` and served as ordinary vector tiles by `pmtiles serve` (go-pmtiles
v1.31.2, BSD-3-Clause, its release checked by SHA-256), both in an image of the
deploy's (`deploy/maps/`), a sidecar switched on in `.env` as Train is
(`compose.maps.yaml`); the cut renewed monthly, the new file written beside the old
and moved over it. The planet's build was 139 GB on 29 Oct; Europe's cut is to be
measured on the server (some 45 GB estimated), twice that while a new one is cut.
Over OpenFreeMap, which stays the source where the sidecar is off: no one outside
the instance sees the areas viewed.
- **Train's own style** (the developer's, over Protomaps' basemaps style, which needs
Node to make, its sprites hosted, and uses expressions vector_map_tiles' renderer
may not draw): written in Dart in Train's server (`train_style.dart`), light and
dark (the app's theme choosing), for running and riding: land, woods, parks, water,
roads by class with paths, tracks and cycleways set apart, rail, buildings at high
zooms, boundaries, and the names of places, roads and water in the app's language
where the tiles have it; no points of interest' icons at first. The server serves
it (`/map/style/light.json`, `dark.json`, public: nothing in it is secret), and a
test holds it to what vector_tile_renderer's reader takes without a warning.
- **For people signed in alone** (the developer's, over public tiles, which anyone
could spend the server's bandwidth on): `map.source` hands a key with the source
(`MapSource.tileKey`, a day count and an HMAC of it under the instance's
`mapKeySecret`, valid 30 days, naming no one, so that a tile asked for points at no
person), which the app sends as `Authorization: Bearer` with each tile; Caddy asks
Train's web server about each tile (`forward_auth`, `/map/tile-key`) before passing
it on. Neither logs a tile: what was asked shows the area viewed.
- **Searching** (12b): a Photon sidecar (v1.3.0, Apache-2.0, Java 21) on GraphHopper's
weekly database of **Europe** (the developer's choice, over Germany alone,
recommended for its 7.5 GB, and over no search yet): a 32.6 GB download on 29 Oct,
unpacked larger (to be measured), renewed monthly, its memory several GB, the
server's disk and memory the developer's to check before switching it on. As
BRouter: no site, reached by Train's server alone (a `search` endpoint, for people
signed in, limited, nothing kept or logged), on a network without a way out.
- **In the planner** (the developer's): a search field over the map; results nearest
the map's centre first, in the app's language; one chosen becomes the next waypoint
and the map moves to it (over moving the map alone).
- Built: 12a. Train's server: `MAP_TILES_URL` (set by `compose.maps.yaml`) makes the
instance's own tiles the source, before `MAP_STYLE_URL`; `MapSource` gained
`darkStyleUrl` and `tileKey` (both null for other sources); `InstanceMap`
(`lib/src/map/instance_map.dart`) hands the key with `map.source` (`MapKeys`,
`m1.<day>.<HMAC>`, the same all day, refused once its day has passed or changed in
a byte, compared in constant time), serves the style at `/map/style/light.json` and
`dark.json` (`?lang=en|de`) and answers Caddy's check at `/map/tile-key` (204 or
401; never where the tiles aren't the instance's own); without a `mapKeySecret` of
32 characters it doesn't start where it serves its own tiles. The style
(`train_style.dart`): 28 layers, expressions vector_tile_renderer reads (`match`, `coalesce`,
`interpolate`, `geometry-type`), tracks brown and dashed, footpaths dotted in rust,
cycleways teal; a test keeps copies in the app's fixtures, which the app's tests
read with vector_tile_renderer and no warning. The app picks the dark style in a
dark theme, adds the language to the instance's own, and sends the key with each
tile and with a kept route's (`map_style.dart`); kept styles are now one folder each
(`style/<hash>/`), so a style kept before this is read anew once online. The page
names the instance's own map as keeping nothing of the area shown. The deploy:
`deploy/maps/` (go-pmtiles on Alpine, `tiles.sh` cutting Europe's box,
`-25,35,45,75`, from the newest build each month), `compose.maps.yaml` (the tiles
beside Caddy alone on an internal network, unlogged; the cut on the downloads
network), `sidecars/train-maps.caddy` imported into Train's site; the operator's
steps in `deploy/sidecars/README.md`, *Train's map*. Found on the way:
`.dockerignore` left all of `apps/train/` out of the build's context, so Train's
server's image and BRouter's (which copies Train's running profile) couldn't have
been built on the server; now only Train's app and client are left out, and
`test/deploy/images_test.dart` checks Train's image and the sidecars' copied
files. BRouter's segments and the tiles are kept out of git and the context. Not
run: any of the deploy (no Docker access here), Caddy's `forward_auth` and its
`import` of an empty folder, whether `pmtiles serve` reads a new cut moved over the
old one, the style drawn on a phone.
## Consequences