From ceaaa0bb92f25875d4394de426afe0b7066a97b5 Mon Sep 17 00:00:00 2001 From: Roman Priesol Date: Mon, 7 Sep 2026 23:12:20 +0200 Subject: [PATCH] Apply the NPS-inspired palette to the online map too ONLINE_STYLE_PATCH mirrors OFFLINE_THEME's color choices onto OpenFreeMap Liberty's OpenMapTiles layer ids (a separate hand-built table -- the two schemas share no layer names). loadOnlineStyle fetches Liberty's style.json once, patches it in place, and the result replaces the plain URL everywhere ONLINE_STYLE is used -- MapLibre accepts a style object anywhere it accepts a style URL, so applyStyle needed no change. Runs before the map (and the rest of the page) is constructed, so there's no flash of Liberty's own colors first -- but with a 3s timeout and a catch-all fallback to the plain URL, since this app is meant to keep working offline in the field and blocking startup on a remote style server would undermine that. Verified in a real browser: online map now visually matches the offline theme (cream ground, brown/khaki land cover, green parks, muted roads), no console errors, Online/Offline toggle still works. --- docs/architecture.adoc | 25 ++++++++++ docs/nps-resources.adoc | 4 +- public/js/map.js | 108 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 134 insertions(+), 3 deletions(-) diff --git a/docs/architecture.adoc b/docs/architecture.adoc index fdc8202..361bc60 100644 --- a/docs/architecture.adoc +++ b/docs/architecture.adoc @@ -121,6 +121,31 @@ 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. + == Map permalink The map's `hash: "map"` option (`public/js/map.js`) turns on MapLibre diff --git a/docs/nps-resources.adoc b/docs/nps-resources.adoc index 1e39774..6d4cbd7 100644 --- a/docs/nps-resources.adoc +++ b/docs/nps-resources.adoc @@ -24,8 +24,8 @@ checking here before searching from zero next time. | MIT (per `package.json`); the whole `npmap5-plugins` repo is separately public domain as a US government work -| Park Tiles color palette (offline basemap theme) -| `public/js/map.js`'s `OFFLINE_THEME` +| Park Tiles color palette (offline + online basemap themes) +| `public/js/map.js`'s `OFFLINE_THEME` and `ONLINE_STYLE_PATCH` | Colors only — see <> for why the actual tile service isn't used | `overview` (top-right minimap/inset control) diff --git a/public/js/map.js b/public/js/map.js index 6b8f215..a8b01e4 100644 --- a/public/js/map.js +++ b/public/js/map.js @@ -19,7 +19,113 @@ import * as maplibregl from "/vendor/maplibre-gl/maplibre-gl.mjs"; const CENTER = [17.1077, 48.1486]; const ZOOM = 13; -const ONLINE_STYLE = "https://tiles.openfreemap.org/styles/liberty"; +const ONLINE_STYLE_URL = "https://tiles.openfreemap.org/styles/liberty"; + +// Recolors OpenFreeMap Liberty to the same NPS-inspired palette as +// OFFLINE_THEME below — warm cream ground, brown/khaki land cover, green +// reserved for parks, muted roads/boundaries — mapped onto Liberty's +// OpenMapTiles layer ids instead of protomaps' theme roles (the two +// schemas don't share layer names, so this is a separate hand-built +// table, not a shared object). Only the layers that dominate the map's +// overall color read are touched; labels, halos, rail, and minor +// overlays (cemetery/hospital/school/pitch/track shading, building +// outlines) are left at Liberty's own defaults — same "not a full +// recolor" scope as OFFLINE_THEME. Road casings have no protomaps-role +// equivalent to reuse (offline roads don't get a separate outline), so +// #c9a66b there is reasoned to fit the family rather than sourced from +// anywhere real. +const ONLINE_STYLE_PATCH = { + background: { "background-color": "#f9f6ef" }, + landcover_wood: { "fill-color": "#c9bd9c" }, + landcover_grass: { "fill-color": "#d9cfb0" }, + landcover_sand: { "fill-color": "#f0e4c4" }, + water: { "fill-color": "#a3cedf" }, + park: { "fill-color": "#b3e378" }, + park_outline: { "line-color": "#546619" }, + building: { "fill-color": "#ded7cc" }, + boundary_2: { "line-color": "#546619" }, + boundary_3: { "line-color": "#546619" }, + + // Highway (motorway/trunk/primary), major (secondary/tertiary/link), + // minor (residential/service/track), pedestrian — each repeated for its + // tunnel_/bridge_ counterpart so a road doesn't change color when it + // dips into a tunnel or crosses a bridge. Rail is left alone. + road_motorway: { "line-color": "#fdefbe" }, + road_trunk_primary: { "line-color": "#fdefbe" }, + road_secondary_tertiary: { "line-color": "#fef7dd" }, + road_link: { "line-color": "#fef7dd" }, + road_motorway_link: { "line-color": "#fef7dd" }, + road_minor: { "line-color": "#fffdf8" }, + road_service_track: { "line-color": "#fffdf8" }, + road_path_pedestrian: { "line-color": "#e9e2d8" }, + road_motorway_casing: { "line-color": "#c9a66b" }, + road_trunk_primary_casing: { "line-color": "#c9a66b" }, + road_secondary_tertiary_casing: { "line-color": "#c9a66b" }, + road_link_casing: { "line-color": "#c9a66b" }, + road_motorway_link_casing: { "line-color": "#c9a66b" }, + road_minor_casing: { "line-color": "#c9a66b" }, + road_service_track_casing: { "line-color": "#c9a66b" }, + + tunnel_motorway: { "line-color": "#fdefbe" }, + tunnel_trunk_primary: { "line-color": "#fdefbe" }, + tunnel_secondary_tertiary: { "line-color": "#fef7dd" }, + tunnel_link: { "line-color": "#fef7dd" }, + tunnel_motorway_link: { "line-color": "#fef7dd" }, + tunnel_minor: { "line-color": "#fffdf8" }, + tunnel_service_track: { "line-color": "#fffdf8" }, + tunnel_path_pedestrian: { "line-color": "#e9e2d8" }, + tunnel_motorway_casing: { "line-color": "#c9a66b" }, + tunnel_trunk_primary_casing: { "line-color": "#c9a66b" }, + tunnel_secondary_tertiary_casing: { "line-color": "#c9a66b" }, + tunnel_link_casing: { "line-color": "#c9a66b" }, + tunnel_motorway_link_casing: { "line-color": "#c9a66b" }, + tunnel_street_casing: { "line-color": "#c9a66b" }, + + bridge_motorway: { "line-color": "#fdefbe" }, + bridge_trunk_primary: { "line-color": "#fdefbe" }, + bridge_secondary_tertiary: { "line-color": "#fef7dd" }, + bridge_link: { "line-color": "#fef7dd" }, + bridge_motorway_link: { "line-color": "#fef7dd" }, + bridge_street: { "line-color": "#fffdf8" }, + bridge_service_track: { "line-color": "#fffdf8" }, + bridge_path_pedestrian: { "line-color": "#e9e2d8" }, + bridge_motorway_casing: { "line-color": "#c9a66b" }, + bridge_trunk_primary_casing: { "line-color": "#c9a66b" }, + bridge_secondary_tertiary_casing: { "line-color": "#c9a66b" }, + bridge_link_casing: { "line-color": "#c9a66b" }, + bridge_motorway_link_casing: { "line-color": "#c9a66b" }, + bridge_street_casing: { "line-color": "#c9a66b" }, + bridge_path_pedestrian_casing: { "line-color": "#c9a66b" }, +}; + +function patchOnlineStyle(style) { + for (const layer of style.layers) { + const patch = ONLINE_STYLE_PATCH[layer.id]; + if (patch) Object.assign((layer.paint ??= {}), patch); + } + return style; +} + +// Fetched (not just referenced by URL) so its colors can be patched before +// the map ever sees it — MapLibre accepts a style object anywhere it'd +// accept a style URL, so every other use of ONLINE_STYLE (applyStyle's +// map.setStyle) needs no change. This runs before the map — and the rest +// of the page's setup — is constructed (see the top-level await below), so +// it falls back to the plain URL, letting MapLibre fetch and render it +// unpatched, both when the fetch fails outright and when it's just slow +// (a 3s timeout): this app is meant to work offline in the field, and +// blocking the entire page on a remote style server would undermine that +// even though this particular fetch is only for the online map. +async function loadOnlineStyle() { + try { + const response = await fetch(ONLINE_STYLE_URL, { signal: AbortSignal.timeout(3000) }); + return patchOnlineStyle(await response.json()); + } catch (err) { + return ONLINE_STYLE_URL; + } +} + +const ONLINE_STYLE = await loadOnlineStyle(); const protocol = new pmtiles.Protocol(); addProtocol("pmtiles", protocol.tile);