Replace offline basemap pipeline with Planetiler, unify online/offline styling
Offline tiles were built from Protomaps' own basemap schema, needing a completely separate hand-tuned theme (protomaps-themes-base + OFFLINE_THEME) from the online Liberty style's ONLINE_STYLE_PATCH — the two could never be made to match exactly no matter how much either side got tuned (today's session found and fixed several mismatches: building opacity, park boundaries, road casings, road widths). Offline.pmtiles is now built with Planetiler's default OpenMapTiles profile against Geofabrik per-country OSM extracts (osmium-merged when more than one country is selected), the same vector schema Liberty already expects. offlineStyle() now clones the same Liberty JSON (vendored as a local snapshot for reliability with no internet) and applies the identical ONLINE_STYLE_PATCH, just repointed at local PMTiles/glyphs/sprite — one palette instead of two, correct on both sides by construction. Also vendors Liberty's icon sprite for the first time (offline had none before), adds Java/osmium-tool/planetiler.jar to the Docker image, and removes the now-unused protomaps-themes-base dependency and the theme-comparison debug page that found today's bugs.
This commit is contained in:
1 parent
0874e8f8ed
commit
d08ed41185
21 files changed
+4286
-512
No files matched your search
+70
-62
@@ -44,18 +44,33 @@ opened in a browser.
|
||||
|
||||
Map data is vector, not raster: the app ships
|
||||
https://maplibre.org[MapLibre GL JS] (vendored, see below) and the map
|
||||
view (`public/js/map.js`) offers two styles:
|
||||
view (`public/js/map.js`) offers two styles, both **the same
|
||||
OpenMapTiles-schema Liberty style** (https://tiles.openfreemap.org/styles/liberty[OpenFreeMap
|
||||
"liberty"]), just pointed at different vector sources:
|
||||
|
||||
* *Online* — a full hosted vector style,
|
||||
https://tiles.openfreemap.org/styles/liberty[OpenFreeMap "liberty"],
|
||||
* *Online* — Liberty's own hosted tiles, `tiles.openfreemap.org`,
|
||||
requires internet access.
|
||||
* *Offline* — built at runtime from a local
|
||||
https://github.com/protomaps/PMTiles[PMTiles] archive at
|
||||
`data/offline.pmtiles`, served as-is (see below) at
|
||||
`/tiles/offline.pmtiles`, decoded client-side by
|
||||
* *Offline* — a local https://github.com/protomaps/PMTiles[PMTiles]
|
||||
archive at `data/offline.pmtiles`, built by
|
||||
https://github.com/onthegomap/planetiler[Planetiler] from an OSM
|
||||
extract (see `docs/development.adoc`), served as-is at
|
||||
`/tiles/offline.pmtiles` and decoded client-side by
|
||||
https://github.com/protomaps/PMTiles[pmtiles.js] registered as a
|
||||
`pmtiles://` protocol handler, and styled with base layers from
|
||||
https://github.com/protomaps/basemaps[protomaps-themes-base].
|
||||
`pmtiles://` protocol handler.
|
||||
|
||||
Using the same schema both ways is deliberate: an earlier version of
|
||||
this app built offline tiles from Protomaps' own basemap schema
|
||||
instead, which needed a *separate* hand-tuned theme
|
||||
(`protomaps-themes-base` + a since-removed `OFFLINE_THEME`) to
|
||||
approximate the online look — two independently-maintained palettes
|
||||
that drifted out of sync (building opacity, road casings, park
|
||||
boundaries) every time one side changed. Planetiler's default profile
|
||||
*is* OpenMapTiles, so `offlineStyle()` now just clones the same parsed
|
||||
Liberty JSON `ONLINE_STYLE` already fetched, repoints its `sources`,
|
||||
`glyphs`, and `sprite` at local copies, and applies the exact same
|
||||
`ONLINE_STYLE_PATCH` recolor (below) — one palette, one place to tune
|
||||
it, correct on both sides by construction rather than by re-checking
|
||||
every time something changes.
|
||||
|
||||
`GET /api/offline-tiles` reports whether `data/offline.pmtiles` is
|
||||
present (`{"available": true|false}`); the frontend uses it once, on
|
||||
@@ -72,28 +87,35 @@ support) just returns byte ranges of the file; pmtiles.js in the
|
||||
browser reads the archive's own directory and requests only the bytes
|
||||
it needs for the tiles on screen.
|
||||
|
||||
=== Offline text labels and color palette
|
||||
No PMTiles file ships with the repository — it is built locally or
|
||||
in-app (see `docs/development.adoc`) and stays gitignored because such
|
||||
extracts are typically large binary files. Geofabrik's per-country
|
||||
extracts are OSM data, ODbL-licensed, same attribution
|
||||
(`© OpenStreetMap contributors`) the map already shows either way — no
|
||||
separate licensing story the way the old Protomaps-basemap pipeline's
|
||||
NPS-Park-Tiles color research needed.
|
||||
|
||||
=== Offline text labels, sprite, and color palette
|
||||
|
||||
Label rendering in MapLibre needs a "glyphs" server serving font PBF
|
||||
ranges (256 Unicode code points each); `data/offline.pmtiles`'s fields
|
||||
(`name`, `name:en`, `script`, ...) match what
|
||||
`protomaps-themes-base`'s `layers(source, theme, { lang: "en" })`
|
||||
expects for its label layers (verified against the actual PMTiles
|
||||
vector layer schema), so passing that option turns labels on. The
|
||||
glyphs themselves are vendored under `public/vendor/fonts/` — Noto
|
||||
Sans Regular/Italic/Medium, ranges `0-255` and `256-511`
|
||||
(Basic Latin + Latin-1 Supplement + Latin Extended-A, SIL OFL
|
||||
licensed, from
|
||||
ranges (256 Unicode code points each). The glyphs Liberty's style
|
||||
references are vendored under `public/vendor/fonts/` — Noto Sans
|
||||
Regular/Italic/Medium, ranges `0-255` and `256-511` (Basic Latin +
|
||||
Latin-1 Supplement + Latin Extended-A, SIL OFL licensed, from
|
||||
https://github.com/protomaps/basemaps-assets[protomaps/basemaps-assets]),
|
||||
covering accented Latin place names (Slovak, Czech, Polish, German,
|
||||
...). Labels in other scripts (Cyrillic, Greek, CJK, ...) won't render
|
||||
offline, since those glyph ranges aren't vendored.
|
||||
offline, since those glyph ranges aren't vendored. Liberty's icon
|
||||
sprite (shields, POI icons) is likewise vendored as a one-time
|
||||
snapshot under `public/vendor/liberty-sprite/` — both `offlineStyle()`
|
||||
and the online style otherwise share identical `glyphs`/`sprite`
|
||||
roles, just resolved to local vs. `tiles.openfreemap.org` URLs.
|
||||
|
||||
`public/js/map.js`'s `OFFLINE_THEME` nudges a couple dozen color roles
|
||||
from `protomaps-themes-base`'s stock "light" theme towards the
|
||||
National Park Service's own "Park Tiles" cartography — a warm,
|
||||
Harpers-Ferry-Center-derived look. A few of these values are the
|
||||
genuine paint colors pulled from a public Park Tiles style JSON
|
||||
`public/js/map.js`'s `ONLINE_STYLE_PATCH` — applied to whichever
|
||||
source is loaded, online or offline (see above) — nudges Liberty's
|
||||
OpenMapTiles layer ids towards a warm, National Park Service
|
||||
"Park Tiles"-inspired look. A few of these values are the genuine
|
||||
paint colors pulled from a public Park Tiles style JSON
|
||||
(`nps.gov/lib/npmap.js/style/ck72fwp2642dv07o7tbqinvz4.json`, the
|
||||
"Imagery" variant — the only one of NPS's four styles served from a
|
||||
plain, unauthenticated URL; "Standard"/"Slate"/"Light" resolve through
|
||||
@@ -101,50 +123,36 @@ Mapbox Studio's private API with an access token, which isn't
|
||||
something to piggyback on even though it's technically visible in
|
||||
their own JS bundle) — background, the park/protected-area green, the
|
||||
two road-importance tiers, and the park-boundary line color. The rest
|
||||
are reasoned to match that same warm family (olive greens, muted blue)
|
||||
rather than sourced. This is deliberately partial — labels, casings,
|
||||
and roles with no clean real-color anchor are left at theme
|
||||
defaults — not the kind of full per-layer recolor a previous iteration
|
||||
of this app did (and removed, along with any dependency on OsmAnd's
|
||||
own tile server, which this app cannot use per OsmAnd's terms
|
||||
restricting tile use to "personal, non-commercial use within OsmAnd
|
||||
applications and platforms").
|
||||
are reasoned to match that same warm family (olive greens, muted blue,
|
||||
brown road casings) rather than sourced. This is deliberately
|
||||
partial — labels, halos, rail, and minor overlays (cemetery/hospital/
|
||||
school/pitch/track shading) are left at Liberty's own defaults.
|
||||
+
|
||||
Recreating the *color choices* is fine — that's not the kind of thing
|
||||
a data license restricts — but the actual Park Tiles *service* stays
|
||||
off-limits: its OSM/terrain layers are licensed through Mapbox Atlas
|
||||
(a commercial, non-sublicensable data service scoped to NPS), and it's
|
||||
built for park visitors specifically (park-unit and trailhead
|
||||
labeling prioritized), not general-purpose worldwide cartography.
|
||||
labeling prioritized), not general-purpose worldwide cartography —
|
||||
irrelevant to this app either way now, since it never talks to NPS's
|
||||
tile service, only ever OpenFreeMap/Geofabrik.
|
||||
|
||||
No PMTiles file ships with the repository — it is expected to be
|
||||
supplied locally (see `docs/development.adoc`) and stays gitignored
|
||||
because such extracts are typically large binary files.
|
||||
|
||||
The Online map (OpenFreeMap Liberty) gets the same treatment via
|
||||
`ONLINE_STYLE_PATCH`, a separate hand-built table — Liberty is an
|
||||
OpenMapTiles-schema style, with its own layer ids (`landcover_wood`,
|
||||
`road_secondary_tertiary`, `boundary_2`, ...) that don't correspond to
|
||||
protomaps' theme roles, so the two palettes can't share one object,
|
||||
only the same underlying color choices. `loadOnlineStyle`
|
||||
(`public/js/map.js`) fetches Liberty's `style.json` once, patches it
|
||||
in place, and uses the resulting object everywhere `ONLINE_STYLE` used
|
||||
to be a plain URL string — MapLibre accepts a style object anywhere it
|
||||
accepts a style URL, so `applyStyle`'s `map.setStyle(... : ONLINE_STYLE)`
|
||||
needed no change. That fetch runs before the map (and the rest of the
|
||||
page) is constructed — a top-level `await` — so there's no flash of
|
||||
Liberty's own colors before the patch lands; a 3s timeout and a
|
||||
catch-all fall back to the plain URL (letting MapLibre fetch and
|
||||
render it unpatched) rather than block the whole page's startup on a
|
||||
remote style server, since this app is meant to keep working offline
|
||||
in the field, independent of whether the online map itself loads.
|
||||
Only the layers that dominate the map's overall color read are
|
||||
patched (land cover, water, parks, roads, boundaries, buildings) —
|
||||
same deliberately-partial scope as `OFFLINE_THEME`, not a full
|
||||
per-layer recolor. Road casings are the one addition with no
|
||||
protomaps-role equivalent to reuse, since offline roads don't get a
|
||||
separate outline color there; `#c9a66b` is reasoned to fit the family
|
||||
rather than sourced from anywhere real.
|
||||
`loadOnlineStyle` (`public/js/map.js`) fetches Liberty's live
|
||||
`style.json` once, patches it in place, and uses the resulting object
|
||||
everywhere `ONLINE_STYLE` used to be a plain URL string — MapLibre
|
||||
accepts a style object anywhere it accepts a style URL, so
|
||||
`applyStyle`'s `map.setStyle(... : ONLINE_STYLE)` needed no change.
|
||||
That fetch runs before the map (and the rest of the page) is
|
||||
constructed — a top-level `await` — so there's no flash of Liberty's
|
||||
own colors before the patch lands; a 3s timeout and a catch-all fall
|
||||
back to the plain URL (letting MapLibre fetch and render it unpatched)
|
||||
rather than block the whole page's startup on a remote style server.
|
||||
Offline mode can't rely on that same live fetch succeeding — the field
|
||||
usage this app is built for is exactly when it won't — so
|
||||
`offlineStyle()` instead clones a vendored, point-in-time snapshot of
|
||||
the same Liberty JSON (`public/vendor/liberty-style/liberty.json`),
|
||||
patches it the same way, and repoints it at the local PMTiles/glyphs/
|
||||
sprite described above.
|
||||
|
||||
== Map permalink
|
||||
|
||||
|
||||
Reference in new issue
Block a user