diff --git a/Dockerfile b/Dockerfile index 89e6fea..447d04f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,9 +3,14 @@ FROM ruby:4.0.6-slim-bookworm # build-essential: puma has a native (C) extension; harmless if the # platform-specific gem in Gemfile.lock already has a precompiled binary # matching this base image, but keeps the build working if it doesn't. -# ca-certificates/curl: needed for the go-pmtiles download below (also -# handy for anyone debugging inside a running container). -RUN apt-get update && apt-get install --no-install-recommends -y build-essential ca-certificates curl \ +# ca-certificates/curl: needed for the go-pmtiles/planetiler.jar downloads +# below (also handy for anyone debugging inside a running container). +# default-jre-headless: Planetiler is a JVM tool (see PLANETILER_VERSION +# below) — headless since this never touches a display. +# osmium-tool: merges multiple countries' OSM extracts into one file before +# handing them to Planetiler (lib/maptool/pmtiles_builder.rb) — a normal +# Debian package, no separate download/pinning needed like the other two. +RUN apt-get update && apt-get install --no-install-recommends -y build-essential ca-certificates curl default-jre-headless osmium-tool \ && rm -rf /var/lib/apt/lists/* # The `pmtiles` CLI (lib/maptool/pmtiles_builder.rb's "Regenerate for @@ -26,6 +31,15 @@ RUN set -eux; \ curl -fsSL "https://github.com/protomaps/go-pmtiles/releases/download/v${PMTILES_VERSION}/go-pmtiles_${PMTILES_VERSION}_Linux_${pmtiles_arch}.tar.gz" \ | tar xz -C /usr/local/bin pmtiles +# Planetiler (lib/maptool/pmtiles_builder.rb's offline.pmtiles build, +# replacing an earlier Protomaps-CDN-based pipeline — see +# docs/development.adoc) — a single JVM fat jar, no per-arch variant needed +# unlike go-pmtiles above. Default PLANETILER_JAR path (pmtiles_builder.rb) +# expects it here. +ARG PLANETILER_VERSION=0.10.2 +RUN curl -fsSL "https://github.com/onthegomap/planetiler/releases/download/v${PLANETILER_VERSION}/planetiler.jar" \ + -o /usr/local/lib/planetiler.jar + WORKDIR /app COPY Gemfile Gemfile.lock ./ diff --git a/TODO.adoc b/TODO.adoc index abf11fc..7d9a18e 100644 --- a/TODO.adoc +++ b/TODO.adoc @@ -8,7 +8,7 @@ independence from connectivity. == Offline data and cartography -* [ ] Offline vector tiles (PMTiles/MBTiles) for Europe/world via Planetiler or MapTiler +* [x] Offline vector tiles (PMTiles/MBTiles) for Europe/world via Planetiler or MapTiler — Geofabrik per-country extracts, `osmium merge`, Planetiler's default OpenMapTiles profile straight to PMTiles (`lib/maptool/pmtiles_builder.rb`); offline now shares the exact same style/palette as online (`ONLINE_STYLE_PATCH`) instead of a separate protomaps-schema theme * [x] Shaded relief / DEM layer — hillshade toggle in the sidebar, sourced from public Terrarium tiles online; reads `data/terrain.pmtiles` for offline if one's been generated (see docs/development.adoc), otherwise the toggle is online-only for now ** [ ] Terrain slope readout for parking (numeric, not just visual shading) * [ ] Contour lines from OSM for hikes and mountain passes diff --git a/app.rb b/app.rb index 2a2b678..5bcf475 100644 --- a/app.rb +++ b/app.rb @@ -260,10 +260,9 @@ module Maptool end # Regenerates offline.pmtiles for the currently selected regions (see - # POST /api/offline-regions above) by extracting them from Protomaps' - # own daily full-planet basemap build — see - # lib/maptool/pmtiles_builder.rb. Runs in the background; poll - # build-status below for progress. + # POST /api/offline-regions above) by downloading each from Geofabrik + # and tiling them with Planetiler — see lib/maptool/pmtiles_builder.rb. + # Runs in the background; poll build-status below for progress. post "/api/offline-tiles/build" do require_login! begin diff --git a/docker-compose.yaml b/docker-compose.yaml index d656209..d86f454 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -60,7 +60,11 @@ services: # docs/development.adoc for how the deploy job treats this # directory). A private plugin (app.rb's PLUGINS_DIRS) also belongs # under here, at data/plugins// — nothing further to mount, - # this one bind already covers the whole data/ tree. + # this one bind already covers the whole data/ tree. An offline.pmtiles + # build's own scratch work (Dir.mktmpdir, lib/maptool/pmtiles_builder.rb) + # happens outside this mount, in the container's own filesystem — a + # large country's .osm.pbf plus Planetiler's working set can be + # several GB, worth knowing if the host is ever short on space there. - ./data:/app/data # Fakes a GNSS fix (a loop around Bratislava, see diff --git a/docs/architecture.adoc b/docs/architecture.adoc index 622be2c..3b3b665 100644 --- a/docs/architecture.adoc +++ b/docs/architecture.adoc @@ -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 diff --git a/docs/development.adoc b/docs/development.adoc index 7d49a2a..894eef3 100644 --- a/docs/development.adoc +++ b/docs/development.adoc @@ -23,40 +23,31 @@ explicit cache headers, so an edit to `public/` (or `views/`) can appear not to have taken effect on the next reload. Force a real reload (e.g. Ctrl/Cmd+Shift+R) if that happens. -`/theme_preview.html` is a standalone debug page (not linked from the -app itself) for comparing offline base-map themes side by side: a bare -`protomaps-themes-base` theme, an iframe of the real app's offline -style, and one of its online style, with pan/zoom kept in sync across -all three via the same URL-hash mechanism `map.js` already uses for -shareable links. It's a plain static file, so nothing gates it to -development — it's reachable on a production instance too, same as any -other file under `public/`. Harmless if it stays that way (it shows -nothing `/` doesn't already), but worth knowing if that's ever not the -case. - == Offline map data To enable the offline style, place a PMTiles file at `data/offline.pmtiles` (see `docs/architecture.adoc` for how it is -served and styled). Logged-in users can generate this from the app -itself — see "In-app regeneration" below — or it can be built by hand: +served and styled — the same OpenMapTiles schema/style the online map +uses, just from local data). Logged-in users can generate this from +the app itself — see "In-app regeneration" below — or it can be built +by hand: -* Extract a region from Protomaps' own daily full-planet basemap build - (https://build-metadata.protomaps.dev/builds.json[build index], - files at `https://build.protomaps.com/`, ~120GB, ODbL/OSM - attribution) with the - https://github.com/protomaps/go-pmtiles[go-pmtiles] CLI's `extract` - subcommand, which pulls only the byte ranges covering a bbox over - HTTP range requests rather than the whole file: - `pmtiles extract https://build.protomaps.com/ data/offline.pmtiles --bbox=west,south,east,north --maxzoom=15`. - (The old `app.protomaps.com` extract-builder UI this section used to - point at has since redirected elsewhere — this is the same - underlying mechanism, just the CLI instead of a web UI.) -* To convert some other vector tile source (e.g. an `.mbtiles` file) - into PMTiles instead, use the same CLI's `convert` subcommand: - `pmtiles convert in.mbtiles data/offline.pmtiles`. Its schema is - what `protomaps-themes-base` (the styling library used for the - offline style) expects. +* Download one or more country extracts from + https://download.geofabrik.de[Geofabrik] (`.osm.pbf`, ODbL/OSM + attribution) — e.g. + `curl -O https://download.geofabrik.de/europe/slovakia-latest.osm.pbf`. +* If more than one country, merge them with + https://github.com/osmcode/osmium-tool[osmium-tool]: + `osmium merge slovakia-latest.osm.pbf austria-latest.osm.pbf -o merged.osm.pbf` + (each Geofabrik file is already clipped to that country's real + border, so no separate bbox/polygon clip step is needed either + before or after — skip this step entirely for a single country). +* Tile it with https://github.com/onthegomap/planetiler[Planetiler]'s + default profile, which *is* OpenMapTiles — no `--profile` flag + needed — writing PMTiles directly: + `java -jar planetiler.jar --osm-path=merged.osm.pbf --output=data/offline.pmtiles` + (needs Java 21+; `planetiler.jar` from + https://github.com/onthegomap/planetiler/releases/latest[the latest release]). Once the file is in place, `GET /api/offline-tiles` reports `available: true` and the map defaults to the offline style. @@ -113,14 +104,15 @@ just what changed — fine given extraction is fast (byte-range requests against remote archives, not per-tile fetches), but worth revisiting if someone selects half of Europe at once and finds it slow. -This wraps exactly the manual `go-pmtiles` workflows documented above -(`lib/maptool/pmtiles_builder.rb`) — the server needs the -https://github.com/protomaps/go-pmtiles[go-pmtiles] CLI installed and -on `PATH` (override with the `PMTILES_BIN` env var if it lives -somewhere else), using `--region=` (a `FeatureCollection` of -the selected countries — go-pmtiles treats multiple features as their -union already, no union/dissolve math needed on this side) rather than -`--bbox=` for precise, per-country extraction. terrain.pmtiles extracts +This wraps exactly the manual workflows documented above +(`lib/maptool/pmtiles_builder.rb`). offline.pmtiles downloads each +selected country's Geofabrik extract, `osmium merge`s them if there's +more than one, then tiles the result with Planetiler — the server +needs `osmium` and `java` + `planetiler.jar` installed and on `PATH` +(override with the `OSMIUM_BIN`/`JAVA_BIN`/`PLANETILER_JAR` env vars if +they live somewhere else; the Docker image installs all three, see +`Dockerfile`). terrain.pmtiles still uses `go-pmtiles` +(`PMTILES_BIN`) against Mapterhorn, unrelated to this — it extracts `planet.pmtiles` (always) plus whichever Mapterhorn regional chunks intersect the selection, `merge`s them if there was more than one — always safe regardless of how many/which countries are selected, since @@ -139,18 +131,20 @@ starting a second while one's running gets a 409, as does starting either with nothing selected yet. Each successful build also writes a small sidecar (`offline.pmtiles. -meta.json` / `terrain.pmtiles.meta.json` — the Protomaps build date used, -or the Mapterhorn source names/md5sums) recording what was actually -built, since the `.pmtiles` file itself carries no such record. `GET -/api/offline-tiles`/`/api/terrain-tiles` include it as `meta`, shown in -the panel next to the existing size/updated line. `GET .../check-update` -compares that recorded build against the live upstream index/manifest -(without rebuilding anything) and reports `update_available: true/false/ -null` — `null` means "don't know" (no local build yet, or the upstream -check itself failed), kept as its own route rather than folded into the -plain status endpoints above since those are on the page-load hot path -(the topbar's offline-status dot) and this one reaches an external -service, only worth doing when the Offline data panel is actually open. +meta.json` — each source country's code/Geofabrik slug/Last-Modified; +`terrain.pmtiles.meta.json` — the Mapterhorn source names/md5sums) +recording what was actually built, since the `.pmtiles` file itself +carries no such record. `GET /api/offline-tiles`/`/api/terrain-tiles` +include it as `meta`, shown in the panel next to the existing +size/updated line. `GET .../check-update` compares that recorded build +against each source's live upstream state (a Geofabrik `HEAD` per +country for offline, the Mapterhorn manifest for terrain — without +rebuilding anything) and reports `update_available: true/false/null` — +`null` means "don't know" (no local build yet, or a check itself +failed), kept as its own route rather than folded into the plain +status endpoints above since those are on the page-load hot path (the +topbar's offline-status dot) and this one reaches an external service, +only worth doing when the Offline data panel is actually open. == Camper-relevant POIs (Overpass) diff --git a/docs/nps-resources.adoc b/docs/nps-resources.adoc index 12f3a5b..227bb1c 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 + online basemap themes) -| `public/js/map.js`'s `OFFLINE_THEME` and `ONLINE_STYLE_PATCH` +| Park Tiles color palette (shared offline + online basemap theme) +| `public/js/map.js`'s `ONLINE_STYLE_PATCH` | Colors only — see <> for why the actual tile service isn't used | `overview` (top-right minimap/inset control) @@ -187,7 +187,7 @@ why this app uses OpenFreeMap Liberty as its actual basemap, not Park Tiles — see `docs/architecture.adoc`'s "Offline maps" section. Recreating the *color choices* is a different matter — not the kind of -thing a data license restricts — and is what `OFFLINE_THEME` in +thing a data license restricts — and is what `ONLINE_STYLE_PATCH` in `public/js/map.js` actually does (see `docs/architecture.adoc`). === Public style JSON (source for the real colors we did pull) @@ -219,11 +219,11 @@ embedded credentials, even when technically reachable: |=== Colors actually pulled from the Imagery style JSON and used in -`OFFLINE_THEME`: background (`hsl(40, 37%, 97%)`, a warm cream), the +`ONLINE_STYLE_PATCH`: background (`hsl(40, 37%, 97%)`, a warm cream), the park/protected-area green (`#b3e378`), the two road-importance tiers (`#fdefbe`/`#fef7dd`), and the park-boundary line color (`#546619`, dark -olive). The rest of `OFFLINE_THEME` extends that same warm/olive family -by eye rather than from a sourced value. +olive). The rest of `ONLINE_STYLE_PATCH` extends that same warm/olive +family by eye rather than from a sourced value. === Legend / cartographic hierarchy diff --git a/docs/third-party-notices.adoc b/docs/third-party-notices.adoc index d6d589e..776ff84 100644 --- a/docs/third-party-notices.adoc +++ b/docs/third-party-notices.adoc @@ -18,10 +18,6 @@ BSD-3-Clause licensed. | 4.5.0 | https://github.com/protomaps/PMTiles -| protomaps-themes-base -| 4.5.0 -| https://github.com/protomaps/basemaps - | maplibre-contour (Overlays panel's "Contour lines", `public/js/contour.js`) | 0.1.0 | https://github.com/onthegomap/maplibre-contour @@ -91,38 +87,62 @@ for free use under the SIL Open Font License" per the site; no `OFL.txt` ships in its own download zips, unlike Noto Sans above |=== -The online map style (not vendored, fetched at runtime when the -"Online" layer is selected) is -https://tiles.openfreemap.org/styles/liberty[OpenFreeMap "liberty"], -built from https://www.openstreetmap.org[OpenStreetMap] data. +The map style — https://tiles.openfreemap.org/styles/liberty[OpenFreeMap +"liberty"], built from https://www.openstreetmap.org[OpenStreetMap] +data — is used two ways (see `docs/architecture.adoc`): fetched live +at runtime for the "Online" layer, and as a one-time vendored snapshot +(`public/vendor/liberty-style/liberty.json`) that the "Offline" layer +repoints at local data instead. Its icon sprite is vendored the same +way, under `public/vendor/liberty-sprite/` (`ofm.json`/`ofm.png` + +`@2x` variants, from `tiles.openfreemap.org/sprites/`). Neither has +been re-checked against a specific upstream version pin the way the +table above does — re-fetch both from `tiles.openfreemap.org` if +Liberty's own style/sprite ever visibly drifts from what's vendored. -`terrain.pmtiles` (`lib/maptool/pmtiles_builder.rb`'s in-app +`data/offline.pmtiles` (`lib/maptool/pmtiles_builder.rb`'s in-app regeneration, or the manual recipe in `docs/development.adoc`) is -extracted at build time, not vendored, from -https://mapterhorn.com[Mapterhorn] — a Protomaps-affiliated project -publishing pre-built Terrarium-encoded terrain as PMTiles. Per-region -source licenses vary (CC-BY-4.0 and similar); see +built at build time, not vendored, from +https://download.geofabrik.de[Geofabrik]'s per-country OpenStreetMap +extracts (ODbL). + +`terrain.pmtiles` (same regeneration mechanism) is extracted at build +time, not vendored, from https://mapterhorn.com[Mapterhorn] — a +Protomaps-affiliated project publishing pre-built Terrarium-encoded +terrain as PMTiles. Per-region source licenses vary (CC-BY-4.0 and +similar); see https://download.mapterhorn.com/attribution.json[their own attribution list] for the full provenance. Not a frontend asset like the above, but also baked into this app's own Docker image (not a separate container the way kiwix-serve/gpsd are — -see `docs/deployment.adoc`/`docs/development.adoc`): the `pmtiles` CLI -(`go-pmtiles`), installed as a static binary in the `Dockerfile` for -`lib/maptool/pmtiles_builder.rb`'s in-app offline.pmtiles/terrain.pmtiles -regeneration. BSD-3-Clause, same as pmtiles.js/MapLibre GL above. +see `docs/deployment.adoc`/`docs/development.adoc`): three CLI tools +`lib/maptool/pmtiles_builder.rb` shells out to for offline.pmtiles/ +terrain.pmtiles regeneration. -[cols="1,1,2"] +[cols="1,1,1,2"] |=== -| Tool | Version | Source +| Tool | Version | License | Source | go-pmtiles (`pmtiles` CLI) | 1.31.2 +| BSD-3-Clause | https://github.com/protomaps/go-pmtiles + +| Planetiler (`planetiler.jar`) +| 0.10.2 +| Apache-2.0 +| https://github.com/onthegomap/planetiler + +| osmium-tool (`osmium`, installed via the `osmium-tool` Debian package) +| (Debian bookworm's packaged version) +| GPL-3.0 +| https://github.com/osmcode/osmium-tool |=== To update a vendored library: download its package (e.g. `npm pack @`), replace the corresponding files under `public/vendor//`, and update the version in this file. To -update go-pmtiles, bump `PMTILES_VERSION` in the `Dockerfile` and the -version here. +update go-pmtiles or Planetiler, bump `PMTILES_VERSION`/ +`PLANETILER_VERSION` in the `Dockerfile` and the version here — +osmium-tool tracks whatever version Debian bookworm currently packages, +nothing to pin on this side. diff --git a/lib/maptool/pmtiles_builder.rb b/lib/maptool/pmtiles_builder.rb index 6b6cc17..9aa5319 100644 --- a/lib/maptool/pmtiles_builder.rb +++ b/lib/maptool/pmtiles_builder.rb @@ -9,12 +9,10 @@ module Maptool # Regenerates data/offline.pmtiles and data/terrain.pmtiles for the # currently *selected* regions (data/offline_regions.json, an array of # Natural Earth iso_a2 country codes — see available_regions/ - # selected_regions/set_selected_regions below) by shelling out to - # go-pmtiles — the same tool docs/development.adoc already documents - # running by hand. No job queue: each build runs on its own background - # Thread and reports into a Mutex-guarded status Hash a route can poll, - # the same shape app.rb's own GNSS poller (GNSS_LATEST/GNSS_LATEST_MUTEX) - # already uses. + # selected_regions/set_selected_regions below). No job queue: each build + # runs on its own background Thread and reports into a Mutex-guarded + # status Hash a route can poll, the same shape app.rb's own GNSS poller + # (GNSS_LATEST/GNSS_LATEST_MUTEX) already uses. module PmtilesBuilder class BuildError < StandardError; end @@ -22,24 +20,71 @@ module Maptool # countries, filtered to Europe and trimmed to name/iso_a2/iso_a3 — # see the plan this was built from for the exact filter. 110m is # deliberately coarse (a km or so of border precision, not - # survey-grade) — fine for "which tiles to grab," a same-schema swap + # survey-grade) — fine for "which country to grab," a same-schema swap # to Natural Earth's 50m/10m cut if that ever needs to be tighter. REGIONS_FILE = File.join(__dir__, "data", "europe_countries.geojson") - # Protomaps' own daily full-planet basemap build index — extract_offline - # below only pulls the HTTP byte ranges covering the requested region - # (go-pmtiles's `extract` supports a remote source over range requests), - # never the whole ~120GB file. - PROTOMAPS_BUILDS_INDEX = "https://build-metadata.protomaps.dev/builds.json" - PROTOMAPS_BUILD_BASE = "https://build.protomaps.com" + # Every iso_a2 code in REGIONS_FILE mapped to its Geofabrik download + # slug (https://download.geofabrik.de/-latest.osm.pbf) — Geofabrik + # names regions, not ISO codes, and doesn't always split the way a + # country border does, so this table exists rather than deriving the + # slug from the country name. Two known quirks worth knowing about, + # not worth working around: + # - RU has no "europe/" prefix (Russia is its own top-level index, + # and unlike every other entry here is a single ~4GB file covering + # the whole country rather than a European-only slice). + # - GB's "great-britain" slug is England/Scotland/Wales only — + # Northern Ireland lives in "ireland-and-northern-ireland" instead + # (bundled with IE, not GB). Selecting GB alone won't include NI. + GEOFABRIK_SLUGS = { + "AL" => "europe/albania", + "AT" => "europe/austria", + "BY" => "europe/belarus", + "BE" => "europe/belgium", + "BA" => "europe/bosnia-herzegovina", + "BG" => "europe/bulgaria", + "HR" => "europe/croatia", + "CZ" => "europe/czech-republic", + "DK" => "europe/denmark", + "EE" => "europe/estonia", + "FI" => "europe/finland", + "FR" => "europe/france", + "DE" => "europe/germany", + "GR" => "europe/greece", + "HU" => "europe/hungary", + "IS" => "europe/iceland", + "IE" => "europe/ireland-and-northern-ireland", + "IT" => "europe/italy", + "XK" => "europe/kosovo", + "LV" => "europe/latvia", + "LT" => "europe/lithuania", + "LU" => "europe/luxembourg", + "MD" => "europe/moldova", + "ME" => "europe/montenegro", + "NL" => "europe/netherlands", + "MK" => "europe/macedonia", + "NO" => "europe/norway", + "PL" => "europe/poland", + "PT" => "europe/portugal", + "RO" => "europe/romania", + "RU" => "russia", + "RS" => "europe/serbia", + "SK" => "europe/slovakia", + "SI" => "europe/slovenia", + "ES" => "europe/spain", + "SE" => "europe/sweden", + "CH" => "europe/switzerland", + "UA" => "europe/ukraine", + "GB" => "europe/great-britain", + }.freeze # Mapterhorn (a Protomaps-affiliated project) publishes Terrarium- # encoded terrain as pre-built PMTiles archives: one global # planet.pmtiles (z0-12) plus ~450 regional chunks (z13 up to a - # per-region max, 13-18) — same "extract only the region's byte ranges - # from a remote archive" story as offline.pmtiles above, so no more - # fetching individual tiles or building an MBTiles file ourselves the - # way an earlier version of this file did. + # per-region max, 13-18) — extract_offline below no longer touches + # Protomaps at all (see GEOFABRIK_SLUGS above), but terrain stays on + # this same "extract only the region's byte ranges from a remote + # archive" story via go-pmtiles, unrelated to the basemap swap. MAPTERHORN_MANIFEST_URL = "https://download.mapterhorn.com/download_urls.json" MAPTERHORN_PLANET_NAME = "planet.pmtiles" # Matches the online hillshade layer's own cap (public/js/overlays.js's @@ -102,28 +147,22 @@ module Maptool end private_class_method :region_features - # Writes the FeatureCollection of just the selected countries into - # `dir` — this *is* the file go-pmtiles' `--region=` reads. No - # union/dissolve needed: go-pmtiles treats a FeatureCollection's - # features as one combined MultiPolygon on its own (confirmed against - # its own source, pmtiles/region.go's UnmarshalRegion). Also returns a - # plain aggregate [south,west,north,east] (min/max over every ring - # coordinate, no geometry library) used only for the Mapterhorn - # chunk-intersection check below — approximate is fine there, the - # `--region=` file is what does the real precise selection. - def self.region_geojson_and_bbox(codes, dir) + # Still used by terrain (below) to compute the aggregate bbox Mapterhorn + # chunk intersection needs — offline.pmtiles no longer needs this at + # all, since each Geofabrik country file is already clipped to that + # country's real border (more accurate than this 110m polygon, not + # less), so there's no separate "--region=" precision step for it + # anymore. + def self.region_bbox(codes) selected = region_features.select { |f| codes.include?(f["properties"]["iso_a2"]) } raise BuildError, "no matching regions for #{codes.join(", ")}" if selected.empty? - path = File.join(dir, "region.geojson") - File.write(path, JSON.generate(type: "FeatureCollection", features: selected)) - coords = selected.flat_map { |f| ring_coordinates(f["geometry"]) } lons = coords.map { |lng, _lat| lng } lats = coords.map { |_lng, lat| lat } - [path, [lats.min, lons.min, lats.max, lons.max]] + [lats.min, lons.min, lats.max, lons.max] end - private_class_method :region_geojson_and_bbox + private_class_method :region_bbox def self.ring_coordinates(geometry) case geometry["type"] @@ -140,21 +179,21 @@ module Maptool # finishes — raises BuildError (the caller turns this into an error # response) if nothing's selected yet, or one's already running. # - # fetch_build_url exists as an injectable seam so tests can exercise the - # rest of this (temp path, go-pmtiles invocation, atomic rename) without - # a real network call — same reasoning test_helper.rb already gives for - # keeping /api/plugins route tests off the real Overpass API. - def self.build_offline(data_dir:, fetch_build_url: method(:latest_protomaps_build_url)) + # download_extract exists as an injectable seam so tests can exercise + # the rest of this (scratch dir, osmium/planetiler invocation, atomic + # rename) without a real network call — same reasoning test_helper.rb + # already gives for keeping /api/plugins route tests off the real + # Overpass API. + def self.build_offline(data_dir:, download_extract: method(:download_extract)) codes = selected_regions(data_dir) raise BuildError, "select at least one region first" if codes.empty? begin_run!(OFFLINE_MUTEX, OFFLINE_STATUS) - Thread.new { run_job(OFFLINE_MUTEX, OFFLINE_STATUS) { extract_offline(codes, data_dir, fetch_build_url) } } + Thread.new { run_job(OFFLINE_MUTEX, OFFLINE_STATUS) { extract_offline(codes, data_dir, download_extract) } } end - # fetch_manifest: same injectable-seam reasoning as fetch_build_url - # above, for the one Mapterhorn manifest lookup this needs instead of - # the Protomaps build index. + # fetch_manifest: same injectable-seam reasoning as download_extract + # above, for the one Mapterhorn manifest lookup this needs. def self.build_terrain(data_dir:, fetch_manifest: method(:fetch_mapterhorn_manifest)) codes = selected_regions(data_dir) raise BuildError, "select at least one region first" if codes.empty? @@ -181,56 +220,103 @@ module Maptool # --- offline.pmtiles --- - def self.extract_offline(codes, data_dir, fetch_build_url) - source = fetch_build_url.call + # One Geofabrik .osm.pbf per selected country (already clipped to that + # country's real border), osmium-merged into one file if more than one + # was selected (skipped entirely for a single country — nothing to + # merge), then tiled by Planetiler using its default OpenMapTiles + # profile straight to PMTiles — the same vector schema + # tiles.openfreemap.org's Liberty style already expects, so + # public/js/map.js's offlineStyle() can reuse ONLINE_STYLE_PATCH + # unchanged instead of a separate protomaps-themes-base palette. + def self.extract_offline(codes, data_dir, download_extract) scratch_dir = Dir.mktmpdir("maptool-offline") - region_path, = region_geojson_and_bbox(codes, scratch_dir) - tmp_path = File.join(data_dir, "offline.pmtiles.tmp") + sources = codes.map do |code| + slug = GEOFABRIK_SLUGS.fetch(code) { raise BuildError, "no Geofabrik source known for region #{code}" } + dest = File.join(scratch_dir, "#{code}.osm.pbf") + last_modified = download_extract.call(geofabrik_url(slug), dest) + { "code" => code, "slug" => slug, "last_modified" => last_modified, "path" => dest } + end - run_pmtiles!("extract", source, tmp_path, "--region=#{region_path}", "--maxzoom=15") + osm_path = + if sources.size == 1 + sources.first["path"] + else + merged_path = File.join(scratch_dir, "merged.osm.pbf") + run_osmium!("merge", *sources.map { |s| s["path"] }, "-o", merged_path) + merged_path + end + + tmp_path = File.join(data_dir, "offline.pmtiles.tmp") + run_planetiler!("--osm-path=#{osm_path}", "--output=#{tmp_path}") File.rename(tmp_path, File.join(data_dir, "offline.pmtiles")) - File.write(offline_meta_path(data_dir), JSON.generate(source: File.basename(URI(source).path))) + File.write(offline_meta_path(data_dir), JSON.generate(sources: sources.map { |s| s.slice("code", "slug", "last_modified") })) ensure FileUtils.remove_entry(scratch_dir) if scratch_dir && Dir.exist?(scratch_dir) File.delete(tmp_path) if tmp_path && File.exist?(tmp_path) end private_class_method :extract_offline - def self.latest_protomaps_build_url - response = Net::HTTP.get_response(URI(PROTOMAPS_BUILDS_INDEX)) - raise BuildError, "could not reach #{PROTOMAPS_BUILDS_INDEX}" unless response.is_a?(Net::HTTPSuccess) - - builds = JSON.parse(response.body) - raise BuildError, "protomaps build index was empty" if builds.empty? - - "#{PROTOMAPS_BUILD_BASE}/#{builds.last.fetch("key")}" - rescue JSON::ParserError - raise BuildError, "protomaps build index was not valid JSON" + def self.geofabrik_url(slug) + "https://download.geofabrik.de/#{slug}-latest.osm.pbf" end - private_class_method :latest_protomaps_build_url + private_class_method :geofabrik_url - # Sidecar recording which Protomaps build (a date-stamped filename, e.g. - # "20260910.pmtiles") the on-disk offline.pmtiles was actually extracted - # from — the file itself carries no such record. nil if never built (or - # built before this existed). + # Real network implementation of the download_extract seam above — + # streams the extract to dest and returns its Last-Modified header + # (free off the same GET, no separate HEAD needed) for the sidecar + # meta / update-check below. + def self.download_extract(url, dest) + uri = URI(url) + response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.get(uri) } + raise BuildError, "could not download #{url} (HTTP #{response.code})" unless response.is_a?(Net::HTTPSuccess) + + File.binwrite(dest, response.body) + response["last-modified"] + end + + # Sidecar recording which Geofabrik sources (country code, slug, and + # that request's Last-Modified) the on-disk offline.pmtiles was + # actually built from — the file itself carries no such record. nil if + # never built (or built before this existed). def self.offline_meta(data_dir) read_json(offline_meta_path(data_dir)) end - # Compares the persisted build against the current latest without - # rebuilding anything — nil (not true/false) if there's nothing to - # compare yet (no local build) or the upstream index couldn't be - # reached, so the caller can tell "don't know" apart from "no". - def self.offline_update_available?(data_dir, fetch_build_url: method(:latest_protomaps_build_url)) + # Compares each currently-selected country's live Geofabrik + # Last-Modified against what's recorded, without downloading anything + # — nil (not true/false) if there's nothing to compare yet (no local + # build, nothing selected) or a check couldn't be completed, so the + # caller can tell "don't know" apart from "no". Also naturally reports + # "yes" if the selection changed since the last build (a country now + # relevant that wasn't recorded, or vice versa) — same reasoning + # terrain_update_available? below already uses. + def self.offline_update_available?(data_dir, fetch_last_modified: method(:fetch_geofabrik_last_modified)) meta = offline_meta(data_dir) return nil unless meta - latest = File.basename(URI(fetch_build_url.call).path) - latest != meta["source"] + codes = selected_regions(data_dir) + return nil if codes.empty? + + current = codes.map do |code| + slug = GEOFABRIK_SLUGS[code] + return nil unless slug + + { "code" => code, "last_modified" => fetch_last_modified.call(geofabrik_url(slug)) } + end.sort_by { |s| s["code"] } + recorded = (meta["sources"] || []).map { |s| s.slice("code", "last_modified") }.sort_by { |s| s["code"] } + current != recorded rescue BuildError nil end + def self.fetch_geofabrik_last_modified(url) + uri = URI(url) + response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.head(uri) } + raise BuildError, "could not reach #{url}" unless response.is_a?(Net::HTTPSuccess) + + response["last-modified"] + end + def self.offline_meta_path(data_dir) File.join(data_dir, "offline.pmtiles.meta.json") end @@ -258,8 +344,10 @@ module Maptool raise BuildError, "mapterhorn manifest is missing #{MAPTERHORN_PLANET_NAME}" unless planet scratch_dir = Dir.mktmpdir("maptool-terrain") - region_path, bbox = region_geojson_and_bbox(codes, scratch_dir) - south, west, north, east = bbox + south, west, north, east = region_bbox(codes) + region_path = File.join(scratch_dir, "region.geojson") + selected = region_features.select { |f| codes.include?(f["properties"]["iso_a2"]) } + File.write(region_path, JSON.generate(type: "FeatureCollection", features: selected)) chunks = items.select { |item| item["name"] != MAPTERHORN_PLANET_NAME && bbox_intersects?(item, south, west, north, east) } @@ -322,13 +410,10 @@ module Maptool planet = items.find { |item| item["name"] == MAPTERHORN_PLANET_NAME } return nil unless planet - Dir.mktmpdir do |dir| - _region_path, bbox = region_geojson_and_bbox(codes, dir) - south, west, north, east = bbox - chunks = items.select { |item| item["name"] != MAPTERHORN_PLANET_NAME && bbox_intersects?(item, south, west, north, east) } - current = [planet, *chunks].map { |i| { "name" => i["name"], "md5sum" => i["md5sum"] } }.sort_by { |s| s["name"] } - current != (meta["sources"] || []).sort_by { |s| s["name"] } - end + south, west, north, east = region_bbox(codes) + chunks = items.select { |item| item["name"] != MAPTERHORN_PLANET_NAME && bbox_intersects?(item, south, west, north, east) } + current = [planet, *chunks].map { |i| { "name" => i["name"], "md5sum" => i["md5sum"] } }.sort_by { |s| s["name"] } + current != (meta["sources"] || []).sort_by { |s| s["name"] } rescue BuildError nil end @@ -347,12 +432,24 @@ module Maptool end private_class_method :read_json - # ENV read lazily (not a load-time constant) so tests can point this at - # a fake script without a require-order dance. + # ENV read lazily (not load-time constants) so tests can point these at + # fake binaries without a require-order dance. def self.pmtiles_bin ENV.fetch("PMTILES_BIN", "pmtiles") end + def self.osmium_bin + ENV.fetch("OSMIUM_BIN", "osmium") + end + + def self.java_bin + ENV.fetch("JAVA_BIN", "java") + end + + def self.planetiler_jar + ENV.fetch("PLANETILER_JAR", "/usr/local/lib/planetiler.jar") + end + def self.run_pmtiles!(*args) _stdout, stderr, status = Open3.capture3(pmtiles_bin, *args) raise BuildError, "pmtiles exited with status #{status.exitstatus}: #{stderr.strip}" unless status.success? @@ -360,5 +457,21 @@ module Maptool raise BuildError, "could not run '#{pmtiles_bin}' (#{e.message}) — is go-pmtiles installed and on PATH? See docs/development.adoc." end private_class_method :run_pmtiles! + + def self.run_osmium!(*args) + _stdout, stderr, status = Open3.capture3(osmium_bin, *args) + raise BuildError, "osmium exited with status #{status.exitstatus}: #{stderr.strip}" unless status.success? + rescue Errno::ENOENT, Errno::EACCES => e + raise BuildError, "could not run '#{osmium_bin}' (#{e.message}) — is osmium-tool installed and on PATH? See docs/development.adoc." + end + private_class_method :run_osmium! + + def self.run_planetiler!(*args) + _stdout, stderr, status = Open3.capture3(java_bin, "-jar", planetiler_jar, *args) + raise BuildError, "planetiler exited with status #{status.exitstatus}: #{stderr.strip}" unless status.success? + rescue Errno::ENOENT, Errno::EACCES => e + raise BuildError, "could not run '#{java_bin} -jar #{planetiler_jar}' (#{e.message}) — is Java and planetiler.jar installed? See docs/development.adoc." + end + private_class_method :run_planetiler! end end diff --git a/public/js/map.js b/public/js/map.js index 7a189d7..dce4127 100644 --- a/public/js/map.js +++ b/public/js/map.js @@ -28,29 +28,46 @@ const CENTER = [17.1077, 48.1486]; const ZOOM = 13; 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, 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. +// Recolors OpenFreeMap Liberty to a warm, NPS-Park-Tiles-inspired palette — +// cream ground, muted roads/boundaries — mapped onto Liberty's OpenMapTiles +// layer ids. Applied identically regardless of which PMTiles data actually +// backs the "openmaptiles" source (see offlineStyle() below): offline tiles +// are built by Planetiler's own OpenMapTiles profile (docs/development.adoc), +// the same schema Liberty already expects, so one patch serves both — +// no separate offline theme/palette to keep in sync. 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. // -// wood/grass are deliberately left unpatched — Liberty's own green reads -// better than this app's earlier brown/khaki recolor, and OFFLINE_THEME's -// wood_a/scrub_a below were changed to match Liberty's green instead. +// wood/grass are deliberately left unpatched — Liberty's own green (a +// translucent landcover_wood/landcover_grass wash) already reads well over +// the patched cream background. const ONLINE_STYLE_PATCH = { background: { "background-color": "#f9f6ef" }, + // Liberty washes every built-up area in a translucent gray-tan below + // z12 (landuse_residential, maxzoom 12 — gone by this app's default + // z13) that the offline schema has no equivalent of; protomaps just + // leaves that ground as plain background/earth. Matched to background + // here rather than left alone, so a zoomed-out view doesn't read as + // visibly dingier online than offline. + landuse_residential: { "fill-color": "#f9f6ef" }, landcover_sand: { "fill-color": "#f0e4c4" }, water: { "fill-color": "#a3cedf" }, - park: { "fill-color": "#b3e378" }, - park_outline: { "line-color": "#546619" }, - building: { "fill-color": "#ded7cc" }, + // fill-outline-color isn't touched by a plain fill-color patch — Liberty + // draws it separately, defaulting to a bright green that reads as a + // distinct border around named conservation areas (e.g. the Malé + // Karpaty CHKO). Matched to the fill itself, and park_outline's own + // dashed line hidden below, so a reserve blends in as ordinary forest — + // offline has no equivalent stroke at all (see park_a/park_b below), + // and the point was never to call reserves out as visibly different, + // on either side. + park: { "fill-color": "#b3e378", "fill-outline-color": "#b3e378" }, + park_outline: { "line-opacity": 0 }, + // fill-opacity: 0.5 matches the offline theme's own "buildings" layer — + // without it, Liberty's dense individual building footprints (each with + // its own outline) paint fully opaque and read much heavier/grayer than + // offline's softer, half-transparent fill over the same cream ground. + building: { "fill-color": "#ded7cc", "fill-opacity": 0.5 }, boundary_2: { "line-color": "#546619" }, boundary_3: { "line-color": "#546619" }, @@ -58,19 +75,30 @@ const ONLINE_STYLE_PATCH = { // 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" }, + // + // line-width below is a flat number, not Liberty's own zoom-interpolated + // expression — picked to match the offline theme's actual rendered width + // at this app's default zoom (13; verified live via queryRenderedFeatures + // on both sides), since without it Liberty's own curve draws surface + // roads roughly twice as thick as offline at street level, reading as a + // much bolder road network online. + // ponytail: tuned for z13 only, not a matching curve across all zooms — + // will drift back apart at very different zoom levels. Upgrade path: + // sample offline's width at a few more zooms and replace with a matching + // ["interpolate", ["linear"], ["zoom"], ...] expression. + road_motorway: { "line-color": "#fdefbe", "line-width": 2.26 }, + road_trunk_primary: { "line-color": "#fdefbe", "line-width": 2.26 }, + road_secondary_tertiary: { "line-color": "#fef7dd", "line-width": 1.87 }, + road_link: { "line-color": "#fef7dd", "line-width": 1 }, + road_motorway_link: { "line-color": "#fef7dd", "line-width": 1 }, 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_motorway_casing: { "line-color": "#c9a66b", "line-width": 1.48 }, + road_trunk_primary_casing: { "line-color": "#c9a66b", "line-width": 1.48 }, + road_secondary_tertiary_casing: { "line-color": "#c9a66b", "line-width": 1 }, + road_link_casing: { "line-color": "#c9a66b", "line-width": 1.5 }, + road_motorway_link_casing: { "line-color": "#c9a66b", "line-width": 1.5 }, road_minor_casing: { "line-color": "#c9a66b" }, road_service_track_casing: { "line-color": "#c9a66b" }, @@ -161,95 +189,45 @@ async function loadOnlineStyle() { const ONLINE_STYLE = await loadOnlineStyle(); +// A vendored copy of the same Liberty style ONLINE_STYLE fetches live +// (public/vendor/liberty-style/liberty.json — a point-in-time snapshot, not +// auto-updated) that offlineStyle() below patches+repoints the exact same +// way. Offline mode can't depend on ONLINE_STYLE_URL actually being +// reachable — that fetch failing/timing out is the normal case in the +// field, not an edge case — so this is a local, same-origin fetch instead +// (fast and always available, since it's served by this app itself). +const OFFLINE_STYLE_TEMPLATE = await fetch("/vendor/liberty-style/liberty.json").then((response) => response.json()); + const protocol = new pmtiles.Protocol(); addProtocol("pmtiles", protocol.tile); -// Nudged 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. Not their actual map: a handful of -// values below (marked) 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 their four styles served from a plain, -// unauthenticated URL; "Standard"/"Slate"/"Light" resolve through 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). -// The rest are reasoned to match that same family (warm cream ground, olive -// greens, muted blue) rather than sourced. Not a full recolor: labels, -// casings, and roles with no clean real-color anchor are left at theme -// defaults. +// Builds the offline style from OFFLINE_STYLE_TEMPLATE — the identical +// Liberty JSON + ONLINE_STYLE_PATCH recolor as the online map, just +// repointed at a local PMTiles source instead of tiles.openfreemap.org. +// This works because data/offline.pmtiles is now built by Planetiler's own +// OpenMapTiles profile (see docs/development.adoc), the same vector schema +// Liberty expects — before that, offline tiles used a different schema +// (protomaps' own basemaps) needing a completely separate hand-tuned +// theme (protomaps-themes-base + OFFLINE_THEME) that could never be made +// to match ONLINE_STYLE_PATCH's look exactly, no matter how much either +// side got tuned. // -// Recreating the color choices is legitimate — that's not the kind of thing -// a data license restricts (see docs/architecture.adoc's "Offline maps" -// section) — what stays off-limits is their actual tile *service*: its -// OSM/terrain layers are licensed through Mapbox Atlas, a commercial, -// non-sublicensable data service scoped to NPS. -const OFFLINE_THEME = { - ...protomaps_themes_base.namedTheme("light"), - background: "#f9f6ef", // real: Park Tiles' own background, hsl(40, 37%, 97%) - earth: "#f9f6ef", - water: "#a3cedf", - // Matches OpenFreeMap Liberty's own wood/grass green (its landcover_wood - // hsla(98,61%,72%,0.7) and landcover_grass rgba(176,213,154,1) at 0.3 - // opacity, blended over the cream background) — the online map's actual - // look whenever ONLINE_STYLE_PATCH's brown/khaki recolor fails to apply - // (see loadOnlineStyle's fetch fallback) turned out to be the look this - // app's users actually want, so offline was changed to match it instead - // of chasing the patch's own reliability. - wood_a: "#c3e8ab", - wood_b: "#a8d888", - scrub_a: "#dcecc9", - scrub_b: "#cfe3b8", - // Close to wood_b, not the more saturated Park Tiles green: protomaps' - // theme routes "forest"/"nature_reserve"/"protected_area" (e.g. the Malé - // Karpaty CHKO boundary) through this same park_a/park_b pair as actual - // named parks — Liberty's online style doesn't make that reserve-boundary - // distinction at all, so a visibly different green here reads as an - // offline-only blotch rather than a deliberate distinction. - park_a: "#c3e8ab", - park_b: "#a8d888", - sand: "#f0e4c4", - beach: "#f0e4c4", - buildings: "#ded7cc", - pedestrian: "#e9e2d8", - highway: "#fdefbe", // real: Park Tiles' primary/highway road color - major: "#fef7dd", // real: Park Tiles' secondary road color - // "minor" (singular) is never actually read by protomaps' own paint - // expressions — only the zoom-interpolated minor_a/minor_b pair is, so a - // plain "minor" key here was dead: ordinary streets were silently - // falling back to the base theme's default pale yellow instead of this - // app's cream. - minor_a: "#fffdf8", - minor_b: "#fffdf8", - link: "#fef7dd", - // Distinct brown, not cream: protomaps' "other"/"path" kind is what a - // camper actually needs called out — unpaved tracks and footpaths, - // separate from paved minor_road/service (minor_service, left at the - // base theme's default since it's real driveways/parking aisles, not - // unpaved). Already rendered dashed by the theme itself; this just picks - // a real "dirt" tone instead of unstyled white. - other: "#c9a66b", - boundaries: "#546619", // real: Park Tiles' park-boundary line color -}; - +// glyphs/sprite point at vendored local copies (public/vendor/fonts, +// public/vendor/liberty-sprite) rather than tiles.openfreemap.org's — +// glyphs are Latin-only (see docs/architecture.adoc: labels in other +// scripts won't render offline), sprite is a one-time snapshot like +// OFFLINE_STYLE_TEMPLATE itself. ne2_shaded (Liberty's low-zoom shaded- +// relief backdrop, z0-6 only) is left pointing at its remote URL +// unchanged — offline usage is regional, not whole-earth zoomed out, so +// this only ever silently fails to load at a zoom level offline users +// aren't in, the same graceful-degradation story as this app's other +// online-only extras (Wikipedia, live radar). function offlineStyle() { - const pmtilesUrl = `${window.location.origin}/tiles/offline.pmtiles`; - - return { - version: 8, - glyphs: `${window.location.origin}/vendor/fonts/{fontstack}/{range}.pbf`, - sources: { - offline: { - type: "vector", - url: `pmtiles://${pmtilesUrl}`, - attribution: "© OpenStreetMap contributors", - }, - }, - // lang: "en" pulls in labels_layers (see docs/architecture.adoc) — only - // the Latin-script glyph ranges are vendored, so labels using other - // scripts won't render offline. - layers: protomaps_themes_base.layers("offline", OFFLINE_THEME, { lang: "en" }), - }; + const style = patchOnlineStyle(structuredClone(OFFLINE_STYLE_TEMPLATE)); + style.sources.openmaptiles.url = `pmtiles://${window.location.origin}/tiles/offline.pmtiles`; + style.glyphs = `${window.location.origin}/vendor/fonts/{fontstack}/{range}.pbf`; + style.sprite = `${window.location.origin}/vendor/liberty-sprite/ofm`; + return style; } // A sidebar icon that opens a full-height side panel (like the GPX one) diff --git a/public/js/offline_data.js b/public/js/offline_data.js index 91c5e6d..5cee5da 100644 --- a/public/js/offline_data.js +++ b/public/js/offline_data.js @@ -37,13 +37,13 @@ function flagIconUrl(code) { return `/vendor/flag-icons/1x1/${code.toLowerCase()}.svg`; } -// meta.source is a Protomaps build filename like "20260910.pmtiles" — the -// leading 8 digits are its build date. +// meta.sources is one entry per country the current build covers (see +// lib/maptool/pmtiles_builder.rb) — listing the codes is more useful here +// than a count, since they're exactly what RegionPicker's checklist above +// already shows. function formatOfflineMeta(meta) { if (!meta) return ""; - const match = meta.source.match(/^(\d{4})(\d{2})(\d{2})/); - const date = match ? `${match[1]}-${match[2]}-${match[3]}` : meta.source; - return ` · Protomaps ${date}`; + return ` · ${meta.sources.map((s) => s.code).join(", ")}`; } function formatTerrainMeta(meta) { @@ -172,9 +172,9 @@ class DataFileRow { } // Separate from _refreshStatus's own fetch (and only called once, right - // after — not polled): this one reaches an external service (Protomaps' - // build index or Mapterhorn's manifest, see - // lib/maptool/pmtiles_builder.rb), so it's kept off the hot status path + // after — not polled): this one reaches an external service (Geofabrik + // or Mapterhorn's manifest, see lib/maptool/pmtiles_builder.rb), so it's + // kept off the hot status path // and just silently skipped if it fails, same as everywhere else this // app degrades gracefully when an optional network check doesn't answer. async _checkUpdate() { diff --git a/public/theme_preview.html b/public/theme_preview.html deleted file mode 100644 index c5eac67..0000000 --- a/public/theme_preview.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - -Offline theme preview - - - - -
-
-
light (bare base theme)
-
-
-
-
offline (current app)
- -
-
-
online (current app)
- -
-
- - - - - diff --git a/public/vendor/liberty-sprite/ofm.json b/public/vendor/liberty-sprite/ofm.json new file mode 100644 index 0000000..99fd5bc --- /dev/null +++ b/public/vendor/liberty-sprite/ofm.json @@ -0,0 +1,1850 @@ +{ + "aerialway": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 147, + "y": 190 + }, + "aerialway_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 252, + "y": 169 + }, + "airfield": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 166, + "y": 190 + }, + "airfield_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 267, + "y": 169 + }, + "airport": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 185, + "y": 190 + }, + "airport_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 438, + "y": 211 + }, + "alcohol_shop": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 64, + "y": 0 + }, + "alcohol_shop_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 455, + "y": 211 + }, + "american_football": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 85, + "y": 0 + }, + "american_football_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 472, + "y": 211 + }, + "amusement_park": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 106, + "y": 0 + }, + "amusement_park_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 489, + "y": 211 + }, + "aquarium": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 0, + "y": 64 + }, + "aquarium_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 246, + "y": 230 + }, + "arrow": { + "height": 7, + "pixelRatio": 1, + "width": 10, + "x": 72, + "y": 249 + }, + "art_gallery": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 21, + "y": 64 + }, + "art_gallery_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 263, + "y": 230 + }, + "attraction": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 42, + "y": 64 + }, + "attraction_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 280, + "y": 230 + }, + "bakery": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 63, + "y": 64 + }, + "bakery_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 297, + "y": 230 + }, + "bank": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 84, + "y": 64 + }, + "bank_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 314, + "y": 230 + }, + "bar": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 105, + "y": 64 + }, + "bar_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 331, + "y": 230 + }, + "baseball": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 0, + "y": 85 + }, + "baseball_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 348, + "y": 230 + }, + "basketball": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 21, + "y": 85 + }, + "basketball_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 365, + "y": 230 + }, + "beer": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 42, + "y": 85 + }, + "beer_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 382, + "y": 230 + }, + "bicycle": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 63, + "y": 85 + }, + "bicycle_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 399, + "y": 230 + }, + "bicycle_rental": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 84, + "y": 85 + }, + "bicycle_rental_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 416, + "y": 230 + }, + "building": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 204, + "y": 190 + }, + "building_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 282, + "y": 169 + }, + "bus": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 223, + "y": 190 + }, + "bus_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 297, + "y": 169 + }, + "butcher": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 105, + "y": 85 + }, + "butcher_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 433, + "y": 230 + }, + "cafe": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 0, + "y": 106 + }, + "cafe_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 450, + "y": 230 + }, + "campsite": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 21, + "y": 106 + }, + "campsite_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 467, + "y": 230 + }, + "car": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 0, + "y": 211 + }, + "car_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 312, + "y": 169 + }, + "castle": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 42, + "y": 106 + }, + "castle_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 484, + "y": 230 + }, + "cemetery": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 63, + "y": 106 + }, + "cemetery_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 252, + "y": 64 + }, + "cinema": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 84, + "y": 106 + }, + "cinema_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 269, + "y": 64 + }, + "circle": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 19, + "y": 211 + }, + "circle_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 327, + "y": 169 + }, + "circle_11_black": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 286, + "y": 64 + }, + "circle_stroked": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 38, + "y": 211 + }, + "circle_stroked_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 342, + "y": 169 + }, + "clothing_store": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 105, + "y": 106 + }, + "clothing_store_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 303, + "y": 64 + }, + "college": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 126, + "y": 64 + }, + "college_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 320, + "y": 64 + }, + "commercial": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 57, + "y": 211 + }, + "commercial_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 357, + "y": 169 + }, + "cricket": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 147, + "y": 64 + }, + "cricket_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 337, + "y": 64 + }, + "cross": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 76, + "y": 211 + }, + "cross_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 372, + "y": 169 + }, + "dam": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 95, + "y": 211 + }, + "dam_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 387, + "y": 169 + }, + "danger": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 168, + "y": 64 + }, + "danger_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 354, + "y": 64 + }, + "default_1": { + "height": 18, + "pixelRatio": 1, + "width": 18, + "x": 228, + "y": 230 + }, + "default_2": { + "height": 18, + "pixelRatio": 1, + "width": 25, + "x": 247, + "y": 211 + }, + "default_3": { + "height": 18, + "pixelRatio": 1, + "width": 32, + "x": 272, + "y": 211 + }, + "default_4": { + "height": 18, + "pixelRatio": 1, + "width": 39, + "x": 304, + "y": 211 + }, + "default_5": { + "height": 18, + "pixelRatio": 1, + "width": 45, + "x": 343, + "y": 211 + }, + "default_6": { + "height": 18, + "pixelRatio": 1, + "width": 50, + "x": 388, + "y": 211 + }, + "dentist": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 189, + "y": 64 + }, + "dentist_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 371, + "y": 64 + }, + "doctors": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 210, + "y": 64 + }, + "doctors_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 388, + "y": 64 + }, + "dog_park": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 231, + "y": 64 + }, + "dog_park_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 405, + "y": 64 + }, + "dot_10": { + "height": 10, + "pixelRatio": 1, + "width": 10, + "x": 53, + "y": 249 + }, + "dot_11": { + "height": 11, + "pixelRatio": 1, + "width": 11, + "x": 42, + "y": 249 + }, + "dot_9": { + "height": 9, + "pixelRatio": 1, + "width": 9, + "x": 63, + "y": 249 + }, + "drinking_water": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 126, + "y": 85 + }, + "drinking_water_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 422, + "y": 64 + }, + "embassy": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 147, + "y": 85 + }, + "embassy_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 439, + "y": 64 + }, + "entrance": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 114, + "y": 211 + }, + "entrance_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 402, + "y": 169 + }, + "fast_food": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 168, + "y": 85 + }, + "fast_food_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 456, + "y": 64 + }, + "ferry": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 133, + "y": 211 + }, + "ferry_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 417, + "y": 169 + }, + "fire_station": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 189, + "y": 85 + }, + "fire_station_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 473, + "y": 64 + }, + "florist": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 210, + "y": 85 + }, + "fuel": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 152, + "y": 211 + }, + "fuel_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 432, + "y": 169 + }, + "furniture": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 231, + "y": 85 + }, + "garden": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 126, + "y": 106 + }, + "garden_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 490, + "y": 64 + }, + "gift": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 147, + "y": 106 + }, + "gift_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 252, + "y": 85 + }, + "golf": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 168, + "y": 106 + }, + "golf_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 269, + "y": 85 + }, + "grocery": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 189, + "y": 106 + }, + "grocery_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 286, + "y": 85 + }, + "hairdresser": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 210, + "y": 106 + }, + "hairdresser_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 303, + "y": 85 + }, + "harbor": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 171, + "y": 211 + }, + "harbor_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 447, + "y": 169 + }, + "heart": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 231, + "y": 106 + }, + "heart_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 320, + "y": 85 + }, + "heliport": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 190, + "y": 211 + }, + "heliport_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 462, + "y": 169 + }, + "hospital": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 127, + "y": 0 + }, + "hospital_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 337, + "y": 85 + }, + "ice_cream": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 148, + "y": 0 + }, + "ice_cream_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 354, + "y": 85 + }, + "industry": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 209, + "y": 211 + }, + "industry_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 477, + "y": 169 + }, + "information": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 169, + "y": 0 + }, + "information_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 371, + "y": 85 + }, + "laundry": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 190, + "y": 0 + }, + "laundry_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 388, + "y": 85 + }, + "library": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 211, + "y": 0 + }, + "library_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 405, + "y": 85 + }, + "lighthouse": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 232, + "y": 0 + }, + "lighthouse_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 422, + "y": 85 + }, + "lodging": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 0, + "y": 127 + }, + "lodging_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 439, + "y": 85 + }, + "marker": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 228, + "y": 211 + }, + "marker_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 492, + "y": 169 + }, + "monument": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 21, + "y": 127 + }, + "monument_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 456, + "y": 85 + }, + "mountain": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 42, + "y": 127 + }, + "mountain_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 473, + "y": 85 + }, + "museum": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 63, + "y": 127 + }, + "museum_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 490, + "y": 85 + }, + "music": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 84, + "y": 127 + }, + "music_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 252, + "y": 106 + }, + "oneway": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 105, + "y": 127 + }, + "park": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 126, + "y": 127 + }, + "park_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 269, + "y": 106 + }, + "parking": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 0, + "y": 230 + }, + "parking_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 242, + "y": 190 + }, + "parking_garage": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 19, + "y": 230 + }, + "parking_garage_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 257, + "y": 190 + }, + "pedestrian_polygon": { + "height": 64, + "pixelRatio": 1, + "width": 64, + "x": 0, + "y": 0 + }, + "pharmacy": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 147, + "y": 127 + }, + "pharmacy_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 286, + "y": 106 + }, + "picnic_site": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 168, + "y": 127 + }, + "picnic_site_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 303, + "y": 106 + }, + "pitch": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 189, + "y": 127 + }, + "pitch_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 320, + "y": 106 + }, + "place_of_worship": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 210, + "y": 127 + }, + "place_of_worship_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 337, + "y": 106 + }, + "playground": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 231, + "y": 127 + }, + "playground_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 354, + "y": 106 + }, + "police": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 0, + "y": 148 + }, + "police_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 371, + "y": 106 + }, + "post": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 21, + "y": 148 + }, + "post_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 388, + "y": 106 + }, + "prison": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 42, + "y": 148 + }, + "prison_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 405, + "y": 106 + }, + "railway": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 38, + "y": 230 + }, + "railway_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 272, + "y": 190 + }, + "railway_light": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 57, + "y": 230 + }, + "railway_light_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 287, + "y": 190 + }, + "railway_metro": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 76, + "y": 230 + }, + "railway_metro_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 302, + "y": 190 + }, + "ranger_station": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 63, + "y": 148 + }, + "ranger_station_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 422, + "y": 106 + }, + "religious_christian": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 84, + "y": 148 + }, + "religious_christian_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 439, + "y": 106 + }, + "religious_jewish": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 105, + "y": 148 + }, + "religious_jewish_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 456, + "y": 106 + }, + "religious_muslim": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 126, + "y": 148 + }, + "religious_muslim_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 473, + "y": 106 + }, + "restaurant": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 147, + "y": 148 + }, + "restaurant_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 490, + "y": 106 + }, + "road_1": { + "height": 14, + "pixelRatio": 1, + "width": 14, + "x": 498, + "y": 148 + }, + "road_2": { + "height": 14, + "pixelRatio": 1, + "width": 20, + "x": 437, + "y": 190 + }, + "road_3": { + "height": 14, + "pixelRatio": 1, + "width": 25, + "x": 457, + "y": 190 + }, + "road_4": { + "height": 14, + "pixelRatio": 1, + "width": 31, + "x": 253, + "y": 0 + }, + "road_5": { + "height": 14, + "pixelRatio": 1, + "width": 36, + "x": 284, + "y": 0 + }, + "road_6": { + "height": 14, + "pixelRatio": 1, + "width": 40, + "x": 320, + "y": 0 + }, + "roadblock": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 168, + "y": 148 + }, + "roadblock_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 252, + "y": 127 + }, + "rocket": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 189, + "y": 148 + }, + "rocket_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 269, + "y": 127 + }, + "school": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 210, + "y": 148 + }, + "school_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 286, + "y": 127 + }, + "shelter": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 231, + "y": 148 + }, + "shelter_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 303, + "y": 127 + }, + "shop": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 0, + "y": 169 + }, + "shop_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 320, + "y": 127 + }, + "skiing": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 21, + "y": 169 + }, + "skiing_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 337, + "y": 127 + }, + "soccer": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 42, + "y": 169 + }, + "soccer_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 354, + "y": 127 + }, + "square": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 95, + "y": 230 + }, + "square_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 317, + "y": 190 + }, + "square_stroked": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 114, + "y": 230 + }, + "square_stroked_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 332, + "y": 190 + }, + "stadium": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 63, + "y": 169 + }, + "stadium_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 371, + "y": 127 + }, + "star": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 133, + "y": 230 + }, + "star_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 347, + "y": 190 + }, + "star_stroked": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 152, + "y": 230 + }, + "star_stroked_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 362, + "y": 190 + }, + "suitcase": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 84, + "y": 169 + }, + "suitcase_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 388, + "y": 127 + }, + "sushi": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 105, + "y": 169 + }, + "sushi_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 405, + "y": 127 + }, + "swimming": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 126, + "y": 169 + }, + "swimming_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 422, + "y": 127 + }, + "telephone": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 147, + "y": 169 + }, + "telephone_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 439, + "y": 127 + }, + "tennis": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 168, + "y": 169 + }, + "tennis_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 456, + "y": 127 + }, + "theatre": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 189, + "y": 169 + }, + "theatre_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 473, + "y": 127 + }, + "toilets": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 210, + "y": 169 + }, + "toilets_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 490, + "y": 127 + }, + "town_hall": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 231, + "y": 169 + }, + "town_hall_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 252, + "y": 148 + }, + "triangle": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 171, + "y": 230 + }, + "triangle_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 377, + "y": 190 + }, + "triangle_stroked": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 190, + "y": 230 + }, + "triangle_stroked_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 392, + "y": 190 + }, + "us-highway_1": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 269, + "y": 148 + }, + "us-highway_2": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 286, + "y": 148 + }, + "us-highway_3": { + "height": 17, + "pixelRatio": 1, + "width": 21, + "x": 303, + "y": 148 + }, + "us-interstate_1": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 324, + "y": 148 + }, + "us-interstate_2": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 341, + "y": 148 + }, + "us-interstate_3": { + "height": 17, + "pixelRatio": 1, + "width": 21, + "x": 358, + "y": 148 + }, + "us-state_1": { + "height": 14, + "pixelRatio": 1, + "width": 17, + "x": 482, + "y": 190 + }, + "us-state_2": { + "height": 14, + "pixelRatio": 1, + "width": 22, + "x": 360, + "y": 0 + }, + "us-state_3": { + "height": 14, + "pixelRatio": 1, + "width": 27, + "x": 382, + "y": 0 + }, + "us-state_4": { + "height": 14, + "pixelRatio": 1, + "width": 32, + "x": 409, + "y": 0 + }, + "us-state_5": { + "height": 14, + "pixelRatio": 1, + "width": 37, + "x": 441, + "y": 0 + }, + "us-state_6": { + "height": 14, + "pixelRatio": 1, + "width": 42, + "x": 0, + "y": 249 + }, + "veterinary": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 0, + "y": 190 + }, + "veterinary_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 379, + "y": 148 + }, + "volcano": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 21, + "y": 190 + }, + "volcano_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 396, + "y": 148 + }, + "warehouse": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 209, + "y": 230 + }, + "warehouse_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 407, + "y": 190 + }, + "waste_basket": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 42, + "y": 190 + }, + "waste_basket_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 413, + "y": 148 + }, + "water": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 63, + "y": 190 + }, + "water_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 430, + "y": 148 + }, + "wetland": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 84, + "y": 190 + }, + "wetland_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 447, + "y": 148 + }, + "wetland_bg_11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 422, + "y": 190 + }, + "wheelchair": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 105, + "y": 190 + }, + "wheelchair_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 464, + "y": 148 + }, + "zoo": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 126, + "y": 190 + }, + "zoo_11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 481, + "y": 148 + } +} \ No newline at end of file diff --git a/public/vendor/liberty-sprite/ofm.png b/public/vendor/liberty-sprite/ofm.png new file mode 100644 index 0000000..8ac87bc Binary files /dev/null and b/public/vendor/liberty-sprite/ofm.png differ diff --git a/public/vendor/liberty-sprite/ofm@2x.json b/public/vendor/liberty-sprite/ofm@2x.json new file mode 100644 index 0000000..15b71ed --- /dev/null +++ b/public/vendor/liberty-sprite/ofm@2x.json @@ -0,0 +1,1850 @@ +{ + "aerialway": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 294, + "y": 380 + }, + "aerialway_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 504, + "y": 338 + }, + "airfield": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 332, + "y": 380 + }, + "airfield_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 534, + "y": 338 + }, + "airport": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 370, + "y": 380 + }, + "airport_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 876, + "y": 422 + }, + "alcohol_shop": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 128, + "y": 0 + }, + "alcohol_shop_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 910, + "y": 422 + }, + "american_football": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 170, + "y": 0 + }, + "american_football_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 944, + "y": 422 + }, + "amusement_park": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 212, + "y": 0 + }, + "amusement_park_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 978, + "y": 422 + }, + "aquarium": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 0, + "y": 128 + }, + "aquarium_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 492, + "y": 460 + }, + "arrow": { + "height": 14, + "pixelRatio": 2, + "width": 20, + "x": 144, + "y": 498 + }, + "art_gallery": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 42, + "y": 128 + }, + "art_gallery_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 526, + "y": 460 + }, + "attraction": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 84, + "y": 128 + }, + "attraction_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 560, + "y": 460 + }, + "bakery": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 126, + "y": 128 + }, + "bakery_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 594, + "y": 460 + }, + "bank": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 168, + "y": 128 + }, + "bank_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 628, + "y": 460 + }, + "bar": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 210, + "y": 128 + }, + "bar_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 662, + "y": 460 + }, + "baseball": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 0, + "y": 170 + }, + "baseball_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 696, + "y": 460 + }, + "basketball": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 42, + "y": 170 + }, + "basketball_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 730, + "y": 460 + }, + "beer": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 84, + "y": 170 + }, + "beer_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 764, + "y": 460 + }, + "bicycle": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 126, + "y": 170 + }, + "bicycle_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 798, + "y": 460 + }, + "bicycle_rental": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 168, + "y": 170 + }, + "bicycle_rental_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 832, + "y": 460 + }, + "building": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 408, + "y": 380 + }, + "building_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 564, + "y": 338 + }, + "bus": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 446, + "y": 380 + }, + "bus_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 594, + "y": 338 + }, + "butcher": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 210, + "y": 170 + }, + "butcher_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 866, + "y": 460 + }, + "cafe": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 0, + "y": 212 + }, + "cafe_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 900, + "y": 460 + }, + "campsite": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 42, + "y": 212 + }, + "campsite_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 934, + "y": 460 + }, + "car": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 0, + "y": 422 + }, + "car_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 624, + "y": 338 + }, + "castle": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 84, + "y": 212 + }, + "castle_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 968, + "y": 460 + }, + "cemetery": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 126, + "y": 212 + }, + "cemetery_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 504, + "y": 128 + }, + "cinema": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 168, + "y": 212 + }, + "cinema_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 538, + "y": 128 + }, + "circle": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 38, + "y": 422 + }, + "circle_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 654, + "y": 338 + }, + "circle_11_black": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 572, + "y": 128 + }, + "circle_stroked": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 76, + "y": 422 + }, + "circle_stroked_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 684, + "y": 338 + }, + "clothing_store": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 210, + "y": 212 + }, + "clothing_store_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 606, + "y": 128 + }, + "college": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 252, + "y": 128 + }, + "college_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 640, + "y": 128 + }, + "commercial": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 114, + "y": 422 + }, + "commercial_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 714, + "y": 338 + }, + "cricket": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 294, + "y": 128 + }, + "cricket_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 674, + "y": 128 + }, + "cross": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 152, + "y": 422 + }, + "cross_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 744, + "y": 338 + }, + "dam": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 190, + "y": 422 + }, + "dam_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 774, + "y": 338 + }, + "danger": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 336, + "y": 128 + }, + "danger_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 708, + "y": 128 + }, + "default_1": { + "height": 36, + "pixelRatio": 2, + "width": 36, + "x": 456, + "y": 460 + }, + "default_2": { + "height": 36, + "pixelRatio": 2, + "width": 50, + "x": 494, + "y": 422 + }, + "default_3": { + "height": 36, + "pixelRatio": 2, + "width": 64, + "x": 544, + "y": 422 + }, + "default_4": { + "height": 36, + "pixelRatio": 2, + "width": 78, + "x": 608, + "y": 422 + }, + "default_5": { + "height": 36, + "pixelRatio": 2, + "width": 90, + "x": 686, + "y": 422 + }, + "default_6": { + "height": 36, + "pixelRatio": 2, + "width": 100, + "x": 776, + "y": 422 + }, + "dentist": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 378, + "y": 128 + }, + "dentist_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 742, + "y": 128 + }, + "doctors": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 420, + "y": 128 + }, + "doctors_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 776, + "y": 128 + }, + "dog_park": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 462, + "y": 128 + }, + "dog_park_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 810, + "y": 128 + }, + "dot_10": { + "height": 20, + "pixelRatio": 2, + "width": 20, + "x": 106, + "y": 498 + }, + "dot_11": { + "height": 22, + "pixelRatio": 2, + "width": 22, + "x": 84, + "y": 498 + }, + "dot_9": { + "height": 18, + "pixelRatio": 2, + "width": 18, + "x": 126, + "y": 498 + }, + "drinking_water": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 252, + "y": 170 + }, + "drinking_water_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 844, + "y": 128 + }, + "embassy": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 294, + "y": 170 + }, + "embassy_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 878, + "y": 128 + }, + "entrance": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 228, + "y": 422 + }, + "entrance_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 804, + "y": 338 + }, + "fast_food": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 336, + "y": 170 + }, + "fast_food_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 912, + "y": 128 + }, + "ferry": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 266, + "y": 422 + }, + "ferry_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 834, + "y": 338 + }, + "fire_station": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 378, + "y": 170 + }, + "fire_station_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 946, + "y": 128 + }, + "florist": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 420, + "y": 170 + }, + "fuel": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 304, + "y": 422 + }, + "fuel_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 864, + "y": 338 + }, + "furniture": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 462, + "y": 170 + }, + "garden": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 252, + "y": 212 + }, + "garden_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 980, + "y": 128 + }, + "gift": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 294, + "y": 212 + }, + "gift_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 504, + "y": 170 + }, + "golf": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 336, + "y": 212 + }, + "golf_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 538, + "y": 170 + }, + "grocery": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 378, + "y": 212 + }, + "grocery_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 572, + "y": 170 + }, + "hairdresser": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 420, + "y": 212 + }, + "hairdresser_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 606, + "y": 170 + }, + "harbor": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 342, + "y": 422 + }, + "harbor_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 894, + "y": 338 + }, + "heart": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 462, + "y": 212 + }, + "heart_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 640, + "y": 170 + }, + "heliport": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 380, + "y": 422 + }, + "heliport_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 924, + "y": 338 + }, + "hospital": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 254, + "y": 0 + }, + "hospital_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 674, + "y": 170 + }, + "ice_cream": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 296, + "y": 0 + }, + "ice_cream_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 708, + "y": 170 + }, + "industry": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 418, + "y": 422 + }, + "industry_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 954, + "y": 338 + }, + "information": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 338, + "y": 0 + }, + "information_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 742, + "y": 170 + }, + "laundry": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 380, + "y": 0 + }, + "laundry_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 776, + "y": 170 + }, + "library": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 422, + "y": 0 + }, + "library_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 810, + "y": 170 + }, + "lighthouse": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 464, + "y": 0 + }, + "lighthouse_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 844, + "y": 170 + }, + "lodging": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 0, + "y": 254 + }, + "lodging_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 878, + "y": 170 + }, + "marker": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 456, + "y": 422 + }, + "marker_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 984, + "y": 338 + }, + "monument": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 42, + "y": 254 + }, + "monument_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 912, + "y": 170 + }, + "mountain": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 84, + "y": 254 + }, + "mountain_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 946, + "y": 170 + }, + "museum": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 126, + "y": 254 + }, + "museum_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 980, + "y": 170 + }, + "music": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 168, + "y": 254 + }, + "music_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 504, + "y": 212 + }, + "oneway": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 210, + "y": 254 + }, + "park": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 252, + "y": 254 + }, + "park_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 538, + "y": 212 + }, + "parking": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 0, + "y": 460 + }, + "parking_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 484, + "y": 380 + }, + "parking_garage": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 38, + "y": 460 + }, + "parking_garage_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 514, + "y": 380 + }, + "pedestrian_polygon": { + "height": 128, + "pixelRatio": 2, + "width": 128, + "x": 0, + "y": 0 + }, + "pharmacy": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 294, + "y": 254 + }, + "pharmacy_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 572, + "y": 212 + }, + "picnic_site": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 336, + "y": 254 + }, + "picnic_site_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 606, + "y": 212 + }, + "pitch": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 378, + "y": 254 + }, + "pitch_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 640, + "y": 212 + }, + "place_of_worship": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 420, + "y": 254 + }, + "place_of_worship_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 674, + "y": 212 + }, + "playground": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 462, + "y": 254 + }, + "playground_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 708, + "y": 212 + }, + "police": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 0, + "y": 296 + }, + "police_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 742, + "y": 212 + }, + "post": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 42, + "y": 296 + }, + "post_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 776, + "y": 212 + }, + "prison": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 84, + "y": 296 + }, + "prison_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 810, + "y": 212 + }, + "railway": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 76, + "y": 460 + }, + "railway_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 544, + "y": 380 + }, + "railway_light": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 114, + "y": 460 + }, + "railway_light_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 574, + "y": 380 + }, + "railway_metro": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 152, + "y": 460 + }, + "railway_metro_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 604, + "y": 380 + }, + "ranger_station": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 126, + "y": 296 + }, + "ranger_station_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 844, + "y": 212 + }, + "religious_christian": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 168, + "y": 296 + }, + "religious_christian_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 878, + "y": 212 + }, + "religious_jewish": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 210, + "y": 296 + }, + "religious_jewish_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 912, + "y": 212 + }, + "religious_muslim": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 252, + "y": 296 + }, + "religious_muslim_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 946, + "y": 212 + }, + "restaurant": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 294, + "y": 296 + }, + "restaurant_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 980, + "y": 212 + }, + "road_1": { + "height": 28, + "pixelRatio": 2, + "width": 28, + "x": 996, + "y": 296 + }, + "road_2": { + "height": 28, + "pixelRatio": 2, + "width": 40, + "x": 874, + "y": 380 + }, + "road_3": { + "height": 28, + "pixelRatio": 2, + "width": 51, + "x": 914, + "y": 380 + }, + "road_4": { + "height": 28, + "pixelRatio": 2, + "width": 62, + "x": 506, + "y": 0 + }, + "road_5": { + "height": 28, + "pixelRatio": 2, + "width": 72, + "x": 568, + "y": 0 + }, + "road_6": { + "height": 28, + "pixelRatio": 2, + "width": 80, + "x": 640, + "y": 0 + }, + "roadblock": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 336, + "y": 296 + }, + "roadblock_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 504, + "y": 254 + }, + "rocket": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 378, + "y": 296 + }, + "rocket_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 538, + "y": 254 + }, + "school": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 420, + "y": 296 + }, + "school_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 572, + "y": 254 + }, + "shelter": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 462, + "y": 296 + }, + "shelter_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 606, + "y": 254 + }, + "shop": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 0, + "y": 338 + }, + "shop_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 640, + "y": 254 + }, + "skiing": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 42, + "y": 338 + }, + "skiing_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 674, + "y": 254 + }, + "soccer": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 84, + "y": 338 + }, + "soccer_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 708, + "y": 254 + }, + "square": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 190, + "y": 460 + }, + "square_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 634, + "y": 380 + }, + "square_stroked": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 228, + "y": 460 + }, + "square_stroked_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 664, + "y": 380 + }, + "stadium": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 126, + "y": 338 + }, + "stadium_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 742, + "y": 254 + }, + "star": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 266, + "y": 460 + }, + "star_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 694, + "y": 380 + }, + "star_stroked": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 304, + "y": 460 + }, + "star_stroked_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 724, + "y": 380 + }, + "suitcase": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 168, + "y": 338 + }, + "suitcase_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 776, + "y": 254 + }, + "sushi": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 210, + "y": 338 + }, + "sushi_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 810, + "y": 254 + }, + "swimming": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 252, + "y": 338 + }, + "swimming_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 844, + "y": 254 + }, + "telephone": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 294, + "y": 338 + }, + "telephone_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 878, + "y": 254 + }, + "tennis": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 336, + "y": 338 + }, + "tennis_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 912, + "y": 254 + }, + "theatre": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 378, + "y": 338 + }, + "theatre_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 946, + "y": 254 + }, + "toilets": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 420, + "y": 338 + }, + "toilets_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 980, + "y": 254 + }, + "town_hall": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 462, + "y": 338 + }, + "town_hall_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 504, + "y": 296 + }, + "triangle": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 342, + "y": 460 + }, + "triangle_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 754, + "y": 380 + }, + "triangle_stroked": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 380, + "y": 460 + }, + "triangle_stroked_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 784, + "y": 380 + }, + "us-highway_1": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 538, + "y": 296 + }, + "us-highway_2": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 572, + "y": 296 + }, + "us-highway_3": { + "height": 34, + "pixelRatio": 2, + "width": 42, + "x": 606, + "y": 296 + }, + "us-interstate_1": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 648, + "y": 296 + }, + "us-interstate_2": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 682, + "y": 296 + }, + "us-interstate_3": { + "height": 34, + "pixelRatio": 2, + "width": 42, + "x": 716, + "y": 296 + }, + "us-state_1": { + "height": 28, + "pixelRatio": 2, + "width": 34, + "x": 965, + "y": 380 + }, + "us-state_2": { + "height": 28, + "pixelRatio": 2, + "width": 44, + "x": 720, + "y": 0 + }, + "us-state_3": { + "height": 28, + "pixelRatio": 2, + "width": 54, + "x": 764, + "y": 0 + }, + "us-state_4": { + "height": 28, + "pixelRatio": 2, + "width": 64, + "x": 818, + "y": 0 + }, + "us-state_5": { + "height": 28, + "pixelRatio": 2, + "width": 74, + "x": 882, + "y": 0 + }, + "us-state_6": { + "height": 28, + "pixelRatio": 2, + "width": 84, + "x": 0, + "y": 498 + }, + "veterinary": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 0, + "y": 380 + }, + "veterinary_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 758, + "y": 296 + }, + "volcano": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 42, + "y": 380 + }, + "volcano_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 792, + "y": 296 + }, + "warehouse": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 418, + "y": 460 + }, + "warehouse_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 814, + "y": 380 + }, + "waste_basket": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 84, + "y": 380 + }, + "waste_basket_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 826, + "y": 296 + }, + "water": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 126, + "y": 380 + }, + "water_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 860, + "y": 296 + }, + "wetland": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 168, + "y": 380 + }, + "wetland_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 894, + "y": 296 + }, + "wetland_bg_11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 844, + "y": 380 + }, + "wheelchair": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 210, + "y": 380 + }, + "wheelchair_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 928, + "y": 296 + }, + "zoo": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 252, + "y": 380 + }, + "zoo_11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 962, + "y": 296 + } +} \ No newline at end of file diff --git a/public/vendor/liberty-sprite/ofm@2x.png b/public/vendor/liberty-sprite/ofm@2x.png new file mode 100644 index 0000000..224898b Binary files /dev/null and b/public/vendor/liberty-sprite/ofm@2x.png differ diff --git a/public/vendor/liberty-style/liberty.json b/public/vendor/liberty-style/liberty.json new file mode 100644 index 0000000..0210811 --- /dev/null +++ b/public/vendor/liberty-style/liberty.json @@ -0,0 +1 @@ +{"version":8,"sources":{"ne2_shaded":{"maxzoom":6,"tileSize":256,"tiles":["https://tiles.openfreemap.org/natural_earth/ne2sr/{z}/{x}/{y}.png"],"type":"raster"},"openmaptiles":{"type":"vector","url":"https://tiles.openfreemap.org/planet"}},"sprite":"https://tiles.openfreemap.org/sprites/ofm_f384/ofm","glyphs":"https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf","layers":[{"id":"background","type":"background","paint":{"background-color":"#f8f4f0"}},{"id":"natural_earth","type":"raster","source":"ne2_shaded","maxzoom":7,"paint":{"raster-opacity":["interpolate",["exponential",1.5],["zoom"],0,0.6,6,0.1]}},{"id":"park","type":"fill","source":"openmaptiles","source-layer":"park","paint":{"fill-color":"#d8e8c8","fill-opacity":0.7,"fill-outline-color":"rgba(95, 208, 100, 1)"}},{"id":"park_outline","type":"line","source":"openmaptiles","source-layer":"park","paint":{"line-color":"rgba(228, 241, 215, 1)","line-dasharray":[1,1.5]}},{"id":"landuse_residential","type":"fill","source":"openmaptiles","source-layer":"landuse","maxzoom":12,"filter":["==",["get","class"],"residential"],"paint":{"fill-color":["interpolate",["linear"],["zoom"],9,"hsla(0,3%,85%,0.84)",12,"hsla(35,57%,88%,0.49)"]}},{"id":"landcover_wood","type":"fill","source":"openmaptiles","source-layer":"landcover","filter":["==",["get","class"],"wood"],"paint":{"fill-antialias":false,"fill-color":"hsla(98,61%,72%,0.7)","fill-opacity":0.4}},{"id":"landcover_grass","type":"fill","source":"openmaptiles","source-layer":"landcover","filter":["==",["get","class"],"grass"],"paint":{"fill-antialias":false,"fill-color":"rgba(176, 213, 154, 1)","fill-opacity":0.3}},{"id":"landcover_ice","type":"fill","source":"openmaptiles","source-layer":"landcover","filter":["==",["get","class"],"ice"],"paint":{"fill-antialias":false,"fill-color":"rgba(224, 236, 236, 1)","fill-opacity":0.8}},{"id":"landcover_wetland","type":"fill","source":"openmaptiles","source-layer":"landcover","minzoom":12,"filter":["==",["get","class"],"wetland"],"paint":{"fill-antialias":true,"fill-opacity":0.8,"fill-pattern":"wetland_bg_11","fill-translate-anchor":"map"}},{"id":"landuse_pitch","type":"fill","source":"openmaptiles","source-layer":"landuse","filter":["==",["get","class"],"pitch"],"paint":{"fill-color":"#DEE3CD"}},{"id":"landuse_track","type":"fill","source":"openmaptiles","source-layer":"landuse","filter":["==",["get","class"],"track"],"paint":{"fill-color":"#DEE3CD"}},{"id":"landuse_cemetery","type":"fill","source":"openmaptiles","source-layer":"landuse","filter":["==",["get","class"],"cemetery"],"paint":{"fill-color":"hsl(75,37%,81%)"}},{"id":"landuse_hospital","type":"fill","source":"openmaptiles","source-layer":"landuse","filter":["==",["get","class"],"hospital"],"paint":{"fill-color":"#fde"}},{"id":"landuse_school","type":"fill","source":"openmaptiles","source-layer":"landuse","filter":["==",["get","class"],"school"],"paint":{"fill-color":"rgb(236,238,204)"}},{"id":"waterway_tunnel","type":"line","source":"openmaptiles","source-layer":"waterway","filter":["==",["get","brunnel"],"tunnel"],"paint":{"line-color":"#a0c8f0","line-dasharray":[3,3],"line-gap-width":["interpolate",["linear"],["zoom"],12,0,20,6],"line-opacity":1,"line-width":["interpolate",["exponential",1.4],["zoom"],8,1,20,2]}},{"id":"waterway_river","type":"line","source":"openmaptiles","source-layer":"waterway","filter":["all",["==",["get","class"],"river"],["!=",["get","brunnel"],"tunnel"]],"layout":{"line-cap":"round"},"paint":{"line-color":"#a0c8f0","line-width":["interpolate",["exponential",1.2],["zoom"],11,0.5,20,6]}},{"id":"waterway_other","type":"line","source":"openmaptiles","source-layer":"waterway","filter":["all",["!=",["get","class"],"river"],["!=",["get","brunnel"],"tunnel"]],"layout":{"line-cap":"round"},"paint":{"line-color":"#a0c8f0","line-width":["interpolate",["exponential",1.3],["zoom"],13,0.5,20,6]}},{"id":"water","type":"fill","source":"openmaptiles","source-layer":"water","filter":["!=",["get","brunnel"],"tunnel"],"paint":{"fill-color":"rgb(158,189,255)"}},{"id":"landcover_sand","type":"fill","source":"openmaptiles","source-layer":"landcover","filter":["==",["get","class"],"sand"],"paint":{"fill-color":"rgba(247, 239, 195, 1)"}},{"id":"aeroway_fill","type":"fill","source":"openmaptiles","source-layer":"aeroway","minzoom":11,"filter":["match",["geometry-type"],["MultiPolygon","Polygon"],true,false],"paint":{"fill-color":"rgba(229, 228, 224, 1)","fill-opacity":0.7}},{"id":"aeroway_runway","type":"line","source":"openmaptiles","source-layer":"aeroway","minzoom":11,"filter":["all",["match",["geometry-type"],["LineString","MultiLineString"],true,false],["==",["get","class"],"runway"]],"paint":{"line-color":"#f0ede9","line-width":["interpolate",["exponential",1.2],["zoom"],11,3,20,16]}},{"id":"aeroway_taxiway","type":"line","source":"openmaptiles","source-layer":"aeroway","minzoom":11,"filter":["all",["match",["geometry-type"],["LineString","MultiLineString"],true,false],["==",["get","class"],"taxiway"]],"paint":{"line-color":"#f0ede9","line-width":["interpolate",["exponential",1.2],["zoom"],11,0.5,20,6]}},{"id":"tunnel_motorway_link_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"motorway"],["==",["get","ramp"],1],["==",["get","brunnel"],"tunnel"]],"layout":{"line-join":"round"},"paint":{"line-color":"#e9ac77","line-dasharray":[0.5,0.25],"line-width":["interpolate",["exponential",1.2],["zoom"],12,1,13,3,14,4,20,15]}},{"id":"tunnel_service_track_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"tunnel"],["match",["get","class"],["service","track"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#cfcdca","line-dasharray":[0.5,0.25],"line-width":["interpolate",["exponential",1.2],["zoom"],15,1,16,4,20,11]}},{"id":"tunnel_link_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","ramp"],1],["==",["get","brunnel"],"tunnel"]],"layout":{"line-join":"round"},"paint":{"line-color":"#e9ac77","line-width":["interpolate",["exponential",1.2],["zoom"],12,1,13,3,14,4,20,15]}},{"id":"tunnel_street_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"tunnel"],["match",["get","class"],["street","street_limited"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#cfcdca","line-opacity":["interpolate",["linear"],["zoom"],12,0,12.5,1],"line-width":["interpolate",["exponential",1.2],["zoom"],12,0.5,13,1,14,4,20,15]}},{"id":"tunnel_secondary_tertiary_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"tunnel"],["match",["get","class"],["secondary","tertiary"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#e9ac77","line-width":["interpolate",["exponential",1.2],["zoom"],8,1.5,20,17]}},{"id":"tunnel_trunk_primary_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"tunnel"],["match",["get","class"],["primary","trunk"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#e9ac77","line-width":["interpolate",["exponential",1.2],["zoom"],5,0.4,6,0.7,7,1.75,20,22]}},{"id":"tunnel_motorway_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"motorway"],["!=",["get","ramp"],1],["==",["get","brunnel"],"tunnel"]],"layout":{"line-join":"round"},"paint":{"line-color":"#e9ac77","line-dasharray":[0.5,0.25],"line-width":["interpolate",["exponential",1.2],["zoom"],5,0.4,6,0.7,7,1.75,20,22]}},{"id":"tunnel_path_pedestrian","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["geometry-type"],["LineString","MultiLineString"],true,false],["==",["get","brunnel"],"tunnel"],["match",["get","class"],["path","pedestrian"],true,false]],"paint":{"line-color":"hsl(0,0%,100%)","line-dasharray":[1,0.75],"line-width":["interpolate",["exponential",1.2],["zoom"],14,0.5,20,10]}},{"id":"tunnel_motorway_link","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"motorway"],["==",["get","ramp"],1],["==",["get","brunnel"],"tunnel"]],"layout":{"line-join":"round"},"paint":{"line-color":"#fc8","line-width":["interpolate",["exponential",1.2],["zoom"],12.5,0,13,1.5,14,2.5,20,11.5]}},{"id":"tunnel_service_track","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"tunnel"],["match",["get","class"],["service","track"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#fff","line-width":["interpolate",["exponential",1.2],["zoom"],15.5,0,16,2,20,7.5]}},{"id":"tunnel_link","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","ramp"],1],["==",["get","brunnel"],"tunnel"]],"layout":{"line-join":"round"},"paint":{"line-color":"#fff4c6","line-width":["interpolate",["exponential",1.2],["zoom"],12.5,0,13,1.5,14,2.5,20,11.5]}},{"id":"tunnel_minor","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"tunnel"],["match",["get","class"],["minor"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#fff","line-width":["interpolate",["exponential",1.2],["zoom"],13.5,0,14,2.5,20,11.5]}},{"id":"tunnel_secondary_tertiary","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"tunnel"],["match",["get","class"],["secondary","tertiary"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#fff4c6","line-width":["interpolate",["exponential",1.2],["zoom"],6.5,0,7,0.5,20,10]}},{"id":"tunnel_trunk_primary","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"tunnel"],["match",["get","class"],["primary","trunk"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#fff4c6","line-width":["interpolate",["exponential",1.2],["zoom"],5,0,7,1,20,18]}},{"id":"tunnel_motorway","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"motorway"],["!=",["get","ramp"],1],["==",["get","brunnel"],"tunnel"]],"layout":{"line-join":"round"},"paint":{"line-color":"#ffdaa6","line-width":["interpolate",["exponential",1.2],["zoom"],5,0,7,1,20,18]}},{"id":"tunnel_major_rail","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"tunnel"],["match",["get","class"],["rail"],true,false]],"paint":{"line-color":"#bbb","line-width":["interpolate",["exponential",1.4],["zoom"],14,0.4,15,0.75,20,2]}},{"id":"tunnel_major_rail_hatching","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"tunnel"],["==",["get","class"],"rail"]],"paint":{"line-color":"#bbb","line-dasharray":[0.2,8],"line-width":["interpolate",["exponential",1.4],["zoom"],14.5,0,15,3,20,8]}},{"id":"tunnel_transit_rail","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"tunnel"],["match",["get","class"],["transit"],true,false]],"paint":{"line-color":"#bbb","line-width":["interpolate",["exponential",1.4],["zoom"],14,0.4,15,0.75,20,2]}},{"id":"tunnel_transit_rail_hatching","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"tunnel"],["==",["get","class"],"transit"]],"paint":{"line-color":"#bbb","line-dasharray":[0.2,8],"line-width":["interpolate",["exponential",1.4],["zoom"],14.5,0,15,3,20,8]}},{"id":"road_area_pattern","type":"fill","source":"openmaptiles","source-layer":"transportation","filter":["match",["geometry-type"],["MultiPolygon","Polygon"],true,false],"paint":{"fill-pattern":"pedestrian_polygon"}},{"id":"road_motorway_link_casing","type":"line","source":"openmaptiles","source-layer":"transportation","minzoom":12,"filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["==",["get","class"],"motorway"],["==",["get","ramp"],1]],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"#e9ac77","line-width":["interpolate",["exponential",1.2],["zoom"],12,1,13,3,14,4,20,15]}},{"id":"road_service_track_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["match",["get","class"],["service","track"],true,false]],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"#cfcdca","line-width":["interpolate",["exponential",1.2],["zoom"],15,1,16,4,20,11]}},{"id":"road_link_casing","type":"line","source":"openmaptiles","source-layer":"transportation","minzoom":13,"filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["match",["get","class"],["motorway","path","pedestrian","service","track"],false,true],["==",["get","ramp"],1]],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"#e9ac77","line-width":["interpolate",["exponential",1.2],["zoom"],12,1,13,3,14,4,20,15]}},{"id":"road_minor_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["geometry-type"],["LineString","MultiLineString"],true,false],["match",["get","brunnel"],["bridge","tunnel"],false,true],["match",["get","class"],["minor"],true,false],["!=",["get","ramp"],1]],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"#cfcdca","line-opacity":["interpolate",["linear"],["zoom"],12,0,12.5,1],"line-width":["interpolate",["exponential",1.2],["zoom"],12,0.5,13,1,14,4,20,20]}},{"id":"road_secondary_tertiary_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["match",["get","class"],["secondary","tertiary"],true,false],["!=",["get","ramp"],1]],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"#e9ac77","line-width":["interpolate",["exponential",1.2],["zoom"],8,1.5,20,17]}},{"id":"road_trunk_primary_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["match",["get","class"],["primary","trunk"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#e9ac77","line-width":["interpolate",["exponential",1.2],["zoom"],5,0.4,6,0.7,7,1.75,20,22]}},{"id":"road_motorway_casing","type":"line","source":"openmaptiles","source-layer":"transportation","minzoom":5,"filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["==",["get","class"],"motorway"],["!=",["get","ramp"],1]],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"#e9ac77","line-width":["interpolate",["exponential",1.2],["zoom"],5,0.4,6,0.7,7,1.75,20,22]}},{"id":"road_path_pedestrian","type":"line","source":"openmaptiles","source-layer":"transportation","minzoom":14,"filter":["all",["match",["geometry-type"],["LineString","MultiLineString"],true,false],["match",["get","brunnel"],["bridge","tunnel"],false,true],["match",["get","class"],["path","pedestrian"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"hsl(0,0%,100%)","line-dasharray":[1,0.7],"line-width":["interpolate",["exponential",1.2],["zoom"],14,1,20,10]}},{"id":"road_motorway_link","type":"line","source":"openmaptiles","source-layer":"transportation","minzoom":12,"filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["==",["get","class"],"motorway"],["==",["get","ramp"],1]],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"#fc8","line-width":["interpolate",["exponential",1.2],["zoom"],12.5,0,13,1.5,14,2.5,20,11.5]}},{"id":"road_service_track","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["match",["get","class"],["service","track"],true,false]],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"#fff","line-width":["interpolate",["exponential",1.2],["zoom"],15.5,0,16,2,20,7.5]}},{"id":"road_link","type":"line","source":"openmaptiles","source-layer":"transportation","minzoom":13,"filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["==",["get","ramp"],1],["match",["get","class"],["motorway","path","pedestrian","service","track"],false,true]],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"#fea","line-width":["interpolate",["exponential",1.2],["zoom"],12.5,0,13,1.5,14,2.5,20,11.5]}},{"id":"road_minor","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["geometry-type"],["LineString","MultiLineString"],true,false],["match",["get","brunnel"],["bridge","tunnel"],false,true],["match",["get","class"],["minor"],true,false]],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"#fff","line-width":["interpolate",["exponential",1.2],["zoom"],13.5,0,14,2.5,20,18]}},{"id":"road_secondary_tertiary","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["match",["get","class"],["secondary","tertiary"],true,false]],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"#fea","line-width":["interpolate",["exponential",1.2],["zoom"],6.5,0,8,0.5,20,13]}},{"id":"road_trunk_primary","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["match",["get","class"],["primary","trunk"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#fea","line-width":["interpolate",["exponential",1.2],["zoom"],5,0,7,1,20,18]}},{"id":"road_motorway","type":"line","source":"openmaptiles","source-layer":"transportation","minzoom":5,"filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["==",["get","class"],"motorway"],["!=",["get","ramp"],1]],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":["interpolate",["linear"],["zoom"],5,"hsl(26,87%,62%)",6,"#fc8"],"line-width":["interpolate",["exponential",1.2],["zoom"],5,0,7,1,20,18]}},{"id":"road_major_rail","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["==",["get","class"],"rail"]],"paint":{"line-color":"#bbb","line-width":["interpolate",["exponential",1.4],["zoom"],14,0.4,15,0.75,20,2]}},{"id":"road_major_rail_hatching","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["==",["get","class"],"rail"]],"paint":{"line-color":"#bbb","line-dasharray":[0.2,8],"line-width":["interpolate",["exponential",1.4],["zoom"],14.5,0,15,3,20,8]}},{"id":"road_transit_rail","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["==",["get","class"],"transit"]],"paint":{"line-color":"#bbb","line-width":["interpolate",["exponential",1.4],["zoom"],14,0.4,15,0.75,20,2]}},{"id":"road_transit_rail_hatching","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["get","brunnel"],["bridge","tunnel"],false,true],["==",["get","class"],"transit"]],"paint":{"line-color":"#bbb","line-dasharray":[0.2,8],"line-width":["interpolate",["exponential",1.4],["zoom"],14.5,0,15,3,20,8]}},{"id":"road_one_way_arrow","type":"symbol","source":"openmaptiles","source-layer":"transportation","minzoom":16,"filter":["==",["get","oneway"],1],"layout":{"icon-image":"arrow","symbol-placement":"line"}},{"id":"road_one_way_arrow_opposite","type":"symbol","source":"openmaptiles","source-layer":"transportation","minzoom":16,"filter":["==",["get","oneway"],-1],"layout":{"icon-image":"arrow","icon-rotate":180,"symbol-placement":"line"}},{"id":"bridge_motorway_link_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"motorway"],["==",["get","ramp"],1],["==",["get","brunnel"],"bridge"]],"layout":{"line-join":"round"},"paint":{"line-color":"#e9ac77","line-width":["interpolate",["exponential",1.2],["zoom"],12,1,13,3,14,4,20,15]}},{"id":"bridge_service_track_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"bridge"],["match",["get","class"],["service","track"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#cfcdca","line-width":["interpolate",["exponential",1.2],["zoom"],15,1,16,4,20,11]}},{"id":"bridge_link_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"link"],["==",["get","brunnel"],"bridge"]],"layout":{"line-join":"round"},"paint":{"line-color":"#e9ac77","line-width":["interpolate",["exponential",1.2],["zoom"],12,1,13,3,14,4,20,15]}},{"id":"bridge_street_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"bridge"],["match",["get","class"],["street","street_limited"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"hsl(36,6%,74%)","line-opacity":["interpolate",["linear"],["zoom"],12,0,12.5,1],"line-width":["interpolate",["exponential",1.2],["zoom"],12,0.5,13,1,14,4,20,25]}},{"id":"bridge_path_pedestrian_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["geometry-type"],["LineString","MultiLineString"],true,false],["==",["get","brunnel"],"bridge"],["match",["get","class"],["path","pedestrian"],true,false]],"paint":{"line-color":"hsl(35,6%,80%)","line-dasharray":[1,0],"line-width":["interpolate",["exponential",1.2],["zoom"],14,1.5,20,18]}},{"id":"bridge_secondary_tertiary_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"bridge"],["match",["get","class"],["secondary","tertiary"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#e9ac77","line-width":["interpolate",["exponential",1.2],["zoom"],8,1.5,20,17]}},{"id":"bridge_trunk_primary_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"bridge"],["match",["get","class"],["primary","trunk"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#e9ac77","line-width":["interpolate",["exponential",1.2],["zoom"],5,0.4,6,0.7,7,1.75,20,22]}},{"id":"bridge_motorway_casing","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"motorway"],["!=",["get","ramp"],1],["==",["get","brunnel"],"bridge"]],"layout":{"line-join":"round"},"paint":{"line-color":"#e9ac77","line-width":["interpolate",["exponential",1.2],["zoom"],5,0.4,6,0.7,7,1.75,20,22]}},{"id":"bridge_path_pedestrian","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["match",["geometry-type"],["LineString","MultiLineString"],true,false],["==",["get","brunnel"],"bridge"],["match",["get","class"],["path","pedestrian"],true,false]],"paint":{"line-color":"hsl(0,0%,100%)","line-dasharray":[1,0.3],"line-width":["interpolate",["exponential",1.2],["zoom"],14,0.5,20,10]}},{"id":"bridge_motorway_link","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"motorway"],["==",["get","ramp"],1],["==",["get","brunnel"],"bridge"]],"layout":{"line-join":"round"},"paint":{"line-color":"#fc8","line-width":["interpolate",["exponential",1.2],["zoom"],12.5,0,13,1.5,14,2.5,20,11.5]}},{"id":"bridge_service_track","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"bridge"],["match",["get","class"],["service","track"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#fff","line-width":["interpolate",["exponential",1.2],["zoom"],15.5,0,16,2,20,7.5]}},{"id":"bridge_link","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"link"],["==",["get","brunnel"],"bridge"]],"layout":{"line-join":"round"},"paint":{"line-color":"#fea","line-width":["interpolate",["exponential",1.2],["zoom"],12.5,0,13,1.5,14,2.5,20,11.5]}},{"id":"bridge_street","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"bridge"],["match",["get","class"],["minor"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#fff","line-width":["interpolate",["exponential",1.2],["zoom"],13.5,0,14,2.5,20,18]}},{"id":"bridge_secondary_tertiary","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"bridge"],["match",["get","class"],["secondary","tertiary"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#fea","line-width":["interpolate",["exponential",1.2],["zoom"],6.5,0,7,0.5,20,10]}},{"id":"bridge_trunk_primary","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","brunnel"],"bridge"],["match",["get","class"],["primary","trunk"],true,false]],"layout":{"line-join":"round"},"paint":{"line-color":"#fea","line-width":["interpolate",["exponential",1.2],["zoom"],5,0,7,1,20,18]}},{"id":"bridge_motorway","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"motorway"],["!=",["get","ramp"],1],["==",["get","brunnel"],"bridge"]],"layout":{"line-join":"round"},"paint":{"line-color":"#fc8","line-width":["interpolate",["exponential",1.2],["zoom"],5,0,7,1,20,18]}},{"id":"bridge_major_rail","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"rail"],["==",["get","brunnel"],"bridge"]],"paint":{"line-color":"#bbb","line-width":["interpolate",["exponential",1.4],["zoom"],14,0.4,15,0.75,20,2]}},{"id":"bridge_major_rail_hatching","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"rail"],["==",["get","brunnel"],"bridge"]],"paint":{"line-color":"#bbb","line-dasharray":[0.2,8],"line-width":["interpolate",["exponential",1.4],["zoom"],14.5,0,15,3,20,8]}},{"id":"bridge_transit_rail","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"transit"],["==",["get","brunnel"],"bridge"]],"paint":{"line-color":"#bbb","line-width":["interpolate",["exponential",1.4],["zoom"],14,0.4,15,0.75,20,2]}},{"id":"bridge_transit_rail_hatching","type":"line","source":"openmaptiles","source-layer":"transportation","filter":["all",["==",["get","class"],"transit"],["==",["get","brunnel"],"bridge"]],"paint":{"line-color":"#bbb","line-dasharray":[0.2,8],"line-width":["interpolate",["exponential",1.4],["zoom"],14.5,0,15,3,20,8]}},{"id":"building","type":"fill","source":"openmaptiles","source-layer":"building","minzoom":13,"maxzoom":14,"paint":{"fill-color":"hsl(35,8%,85%)","fill-outline-color":["interpolate",["linear"],["zoom"],13,"hsla(35,6%,79%,0.32)",14,"hsl(35,6%,79%)"]}},{"id":"building-3d","type":"fill-extrusion","source":"openmaptiles","source-layer":"building","minzoom":14,"paint":{"fill-extrusion-base":["get","render_min_height"],"fill-extrusion-color":"hsl(35,8%,85%)","fill-extrusion-height":["get","render_height"],"fill-extrusion-opacity":0.8}},{"id":"boundary_3","type":"line","source":"openmaptiles","source-layer":"boundary","minzoom":5,"filter":["all",[">=",["get","admin_level"],3],["<=",["get","admin_level"],6],["!=",["get","maritime"],1],["!=",["get","disputed"],1],["!",["has","claimed_by"]]],"paint":{"line-color":"hsl(0,0%,70%)","line-dasharray":[1,1],"line-width":["interpolate",["linear",1],["zoom"],7,1,11,2]}},{"id":"boundary_2","type":"line","source":"openmaptiles","source-layer":"boundary","filter":["all",["==",["get","admin_level"],2],["!=",["get","maritime"],1],["!=",["get","disputed"],1],["!",["has","claimed_by"]]],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"hsl(248,1%,41%)","line-opacity":["interpolate",["linear"],["zoom"],0,0.4,4,1],"line-width":["interpolate",["linear"],["zoom"],3,1,5,1.2,12,3]}},{"id":"boundary_disputed","type":"line","source":"openmaptiles","source-layer":"boundary","filter":["all",["!=",["get","maritime"],1],["==",["get","disputed"],1]],"paint":{"line-color":"hsl(248,1%,41%)","line-dasharray":[1,2],"line-width":["interpolate",["linear"],["zoom"],3,1,5,1.2,12,3]}},{"id":"waterway_line_label","type":"symbol","source":"openmaptiles","source-layer":"waterway","minzoom":10,"filter":["match",["geometry-type"],["LineString","MultiLineString"],true,false],"layout":{"symbol-placement":"line","symbol-spacing":350,"text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"]," ",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Italic"],"text-letter-spacing":0.2,"text-max-width":5,"text-size":14},"paint":{"text-color":"#74aee9","text-halo-color":"rgba(255,255,255,0.7)","text-halo-width":1.5}},{"id":"water_name_point_label","type":"symbol","source":"openmaptiles","source-layer":"water_name","filter":["match",["geometry-type"],["MultiPoint","Point"],true,false],"layout":{"text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Italic"],"text-letter-spacing":0.2,"text-max-width":5,"text-size":["interpolate",["linear"],["zoom"],0,10,8,14]},"paint":{"text-color":"#495e91","text-halo-color":"rgba(255,255,255,0.7)","text-halo-width":1.5}},{"id":"water_name_line_label","type":"symbol","source":"openmaptiles","source-layer":"water_name","filter":["match",["geometry-type"],["LineString","MultiLineString"],true,false],"layout":{"symbol-placement":"line","symbol-spacing":350,"text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"]," ",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Italic"],"text-letter-spacing":0.2,"text-max-width":5,"text-size":14},"paint":{"text-color":"#495e91","text-halo-color":"rgba(255,255,255,0.7)","text-halo-width":1.5}},{"id":"poi_r20","type":"symbol","source":"openmaptiles","source-layer":"poi","minzoom":17,"filter":["all",["match",["geometry-type"],["MultiPoint","Point"],true,false],[">=",["get","rank"],20]],"layout":{"icon-image":["match",["get","subclass"],["florist","furniture"],["get","subclass"],["get","class"]],"text-anchor":"top","text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Italic"],"text-max-width":9,"text-offset":[0,0.6],"text-size":12},"paint":{"text-color":"#666","text-halo-blur":0.5,"text-halo-color":"#ffffff","text-halo-width":1}},{"id":"poi_r7","type":"symbol","source":"openmaptiles","source-layer":"poi","minzoom":16,"filter":["all",["match",["geometry-type"],["MultiPoint","Point"],true,false],[">=",["get","rank"],7],["<",["get","rank"],20]],"layout":{"icon-image":["match",["get","subclass"],["florist","furniture"],["get","subclass"],["get","class"]],"text-anchor":"top","text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Italic"],"text-max-width":9,"text-offset":[0,0.6],"text-size":12},"paint":{"text-color":"#666","text-halo-blur":0.5,"text-halo-color":"#ffffff","text-halo-width":1}},{"id":"poi_r1","type":"symbol","source":"openmaptiles","source-layer":"poi","minzoom":15,"filter":["all",["match",["geometry-type"],["MultiPoint","Point"],true,false],[">=",["get","rank"],1],["<",["get","rank"],7]],"layout":{"icon-image":["match",["get","subclass"],["florist","furniture"],["get","subclass"],["get","class"]],"text-anchor":"top","text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Italic"],"text-max-width":9,"text-offset":[0,0.6],"text-size":12},"paint":{"text-color":"#666","text-halo-blur":0.5,"text-halo-color":"#ffffff","text-halo-width":1}},{"id":"poi_transit","type":"symbol","source":"openmaptiles","source-layer":"poi","filter":["match",["get","class"],["airport","bus","rail"],true,false],"layout":{"icon-image":["to-string",["get","class"]],"icon-size":0.7,"text-anchor":"left","text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Italic"],"text-max-width":9,"text-offset":[0.9,0],"text-size":12},"paint":{"text-color":"#2e5a80","text-halo-blur":0.5,"text-halo-color":"#ffffff","text-halo-width":1}},{"id":"highway-name-path","type":"symbol","source":"openmaptiles","source-layer":"transportation_name","minzoom":15.5,"filter":["==",["get","class"],"path"],"layout":{"symbol-placement":"line","text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"]," ",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Regular"],"text-rotation-alignment":"map","text-size":["interpolate",["linear"],["zoom"],13,12,14,13]},"paint":{"text-color":"hsl(30,23%,62%)","text-halo-color":"#f8f4f0","text-halo-width":0.5}},{"id":"highway-name-minor","type":"symbol","source":"openmaptiles","source-layer":"transportation_name","minzoom":15,"filter":["all",["match",["geometry-type"],["LineString","MultiLineString"],true,false],["match",["get","class"],["minor","service","track"],true,false]],"layout":{"symbol-placement":"line","text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"]," ",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Regular"],"text-rotation-alignment":"map","text-size":["interpolate",["linear"],["zoom"],13,12,14,13]},"paint":{"text-color":"#666","text-halo-blur":0.5,"text-halo-width":1}},{"id":"highway-name-major","type":"symbol","source":"openmaptiles","source-layer":"transportation_name","minzoom":12.2,"filter":["match",["get","class"],["primary","secondary","tertiary","trunk"],true,false],"layout":{"symbol-placement":"line","text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"]," ",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Regular"],"text-rotation-alignment":"map","text-size":["interpolate",["linear"],["zoom"],13,12,14,13]},"paint":{"text-color":"#666","text-halo-blur":0.5,"text-halo-width":1}},{"id":"highway-shield-non-us","type":"symbol","source":"openmaptiles","source-layer":"transportation_name","minzoom":8,"filter":["all",["<=",["get","ref_length"],6],["match",["geometry-type"],["LineString","MultiLineString"],true,false],["match",["get","network"],["us-highway","us-interstate","us-state"],false,true]],"layout":{"icon-image":["concat","road_",["get","ref_length"]],"icon-rotation-alignment":"viewport","icon-size":1,"symbol-placement":["step",["zoom"],"point",11,"line"],"symbol-spacing":200,"text-field":["to-string",["get","ref"]],"text-font":["Noto Sans Regular"],"text-rotation-alignment":"viewport","text-size":10}},{"id":"highway-shield-us-interstate","type":"symbol","source":"openmaptiles","source-layer":"transportation_name","minzoom":7,"filter":["all",["<=",["get","ref_length"],6],["match",["geometry-type"],["LineString","MultiLineString"],true,false],["match",["get","network"],["us-interstate"],true,false]],"layout":{"icon-image":["concat",["get","network"],"_",["get","ref_length"]],"icon-rotation-alignment":"viewport","icon-size":1,"symbol-placement":["step",["zoom"],"point",7,"line",8,"line"],"symbol-spacing":200,"text-field":["to-string",["get","ref"]],"text-font":["Noto Sans Regular"],"text-rotation-alignment":"viewport","text-size":10}},{"id":"road_shield_us","type":"symbol","source":"openmaptiles","source-layer":"transportation_name","minzoom":9,"filter":["all",["<=",["get","ref_length"],6],["match",["geometry-type"],["LineString","MultiLineString"],true,false],["match",["get","network"],["us-highway","us-state"],true,false]],"layout":{"icon-image":["concat",["get","network"],"_",["get","ref_length"]],"icon-rotation-alignment":"viewport","icon-size":1,"symbol-placement":["step",["zoom"],"point",11,"line"],"symbol-spacing":200,"text-field":["to-string",["get","ref"]],"text-font":["Noto Sans Regular"],"text-rotation-alignment":"viewport","text-size":10}},{"id":"airport","type":"symbol","source":"openmaptiles","source-layer":"aerodrome_label","minzoom":10,"filter":["all",["has","iata"]],"layout":{"icon-image":"airport_11","icon-size":1,"text-anchor":"top","text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Regular"],"text-max-width":9,"text-offset":[0,0.6],"text-optional":true,"text-padding":2,"text-size":12},"paint":{"text-color":"#666","text-halo-blur":0.5,"text-halo-color":"#ffffff","text-halo-width":1}},{"id":"label_other","type":"symbol","source":"openmaptiles","source-layer":"place","minzoom":8,"filter":["match",["get","class"],["city","continent","country","state","town","village"],false,true],"layout":{"text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Italic"],"text-letter-spacing":0.1,"text-max-width":9,"text-size":["interpolate",["linear"],["zoom"],8,9,12,10],"text-transform":"uppercase"},"paint":{"text-color":"#333","text-halo-blur":1,"text-halo-color":"#fff","text-halo-width":1}},{"id":"label_village","type":"symbol","source":"openmaptiles","source-layer":"place","minzoom":9,"filter":["==",["get","class"],"village"],"layout":{"icon-allow-overlap":true,"icon-image":["step",["zoom"],"circle_11_black",10,""],"icon-optional":false,"icon-size":0.2,"text-anchor":"bottom","text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Regular"],"text-max-width":8,"text-size":["interpolate",["exponential",1.2],["zoom"],7,10,11,12]},"paint":{"text-color":"#000","text-halo-blur":1,"text-halo-color":"#fff","text-halo-width":1}},{"id":"label_town","type":"symbol","source":"openmaptiles","source-layer":"place","minzoom":6,"filter":["==",["get","class"],"town"],"layout":{"icon-allow-overlap":true,"icon-image":["step",["zoom"],"circle_11_black",10,""],"icon-optional":false,"icon-size":0.2,"text-anchor":"bottom","text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Regular"],"text-max-width":8,"text-size":["interpolate",["exponential",1.2],["zoom"],7,12,11,14]},"paint":{"text-color":"#000","text-halo-blur":1,"text-halo-color":"#fff","text-halo-width":1}},{"id":"label_state","type":"symbol","source":"openmaptiles","source-layer":"place","minzoom":5,"maxzoom":8,"filter":["==",["get","class"],"state"],"layout":{"text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Italic"],"text-letter-spacing":0.2,"text-max-width":9,"text-size":["interpolate",["linear"],["zoom"],5,10,8,14],"text-transform":"uppercase"},"paint":{"text-color":"#333","text-halo-blur":1,"text-halo-color":"#fff","text-halo-width":1}},{"id":"label_city","type":"symbol","source":"openmaptiles","source-layer":"place","minzoom":3,"filter":["all",["==",["get","class"],"city"],["!=",["get","capital"],2]],"layout":{"icon-allow-overlap":true,"icon-image":["step",["zoom"],"circle_11_black",9,""],"icon-optional":false,"icon-size":0.4,"text-anchor":"bottom","text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Regular"],"text-max-width":8,"text-offset":[0,-0.1],"text-size":["interpolate",["exponential",1.2],["zoom"],4,11,7,13,11,18]},"paint":{"text-color":"#000","text-halo-blur":1,"text-halo-color":"#fff","text-halo-width":1}},{"id":"label_city_capital","type":"symbol","source":"openmaptiles","source-layer":"place","minzoom":3,"filter":["all",["==",["get","class"],"city"],["==",["get","capital"],2]],"layout":{"icon-allow-overlap":true,"icon-image":["step",["zoom"],"circle_11_black",9,""],"icon-optional":false,"icon-size":0.5,"text-anchor":"bottom","text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Bold"],"text-max-width":8,"text-offset":[0,-0.2],"text-size":["interpolate",["exponential",1.2],["zoom"],4,12,7,14,11,20]},"paint":{"text-color":"#000","text-halo-blur":1,"text-halo-color":"#fff","text-halo-width":1}},{"id":"label_country_3","type":"symbol","source":"openmaptiles","source-layer":"place","minzoom":2,"maxzoom":9,"filter":["all",["==",["get","class"],"country"],[">=",["get","rank"],3]],"layout":{"text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Bold"],"text-max-width":6.25,"text-size":["interpolate",["linear"],["zoom"],3,9,7,17]},"paint":{"text-color":"#000","text-halo-blur":1,"text-halo-color":"#fff","text-halo-width":1}},{"id":"label_country_2","type":"symbol","source":"openmaptiles","source-layer":"place","maxzoom":9,"filter":["all",["==",["get","class"],"country"],["==",["get","rank"],2]],"layout":{"text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Bold"],"text-max-width":6.25,"text-size":["interpolate",["linear"],["zoom"],2,9,5,17]},"paint":{"text-color":"#000","text-halo-blur":1,"text-halo-color":"#fff","text-halo-width":1}},{"id":"label_country_1","type":"symbol","source":"openmaptiles","source-layer":"place","maxzoom":9,"filter":["all",["==",["get","class"],"country"],["==",["get","rank"],1]],"layout":{"text-field":["case",["has","name:nonlatin"],["concat",["get","name:latin"],"\n",["get","name:nonlatin"]],["coalesce",["get","name_en"],["get","name"]]],"text-font":["Noto Sans Bold"],"text-max-width":6.25,"text-size":["interpolate",["linear"],["zoom"],1,9,4,17]},"paint":{"text-color":"#000","text-halo-blur":1,"text-halo-color":"#fff","text-halo-width":1}}]} diff --git a/public/vendor/protomaps-themes-base/protomaps-themes-base.js b/public/vendor/protomaps-themes-base/protomaps-themes-base.js deleted file mode 100644 index 10cf583..0000000 --- a/public/vendor/protomaps-themes-base/protomaps-themes-base.js +++ /dev/null @@ -1,16 +0,0 @@ -"use strict";var protomaps_themes_base=(()=>{var f=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames,x=Object.getOwnPropertySymbols;var j=Object.prototype.hasOwnProperty,$=Object.prototype.propertyIsEnumerable;var z=(a,e,i)=>e in a?f(a,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):a[e]=i,b=(a,e)=>{for(var i in e||(e={}))j.call(e,i)&&z(a,i,e[i]);if(x)for(var i of x(e))$.call(e,i)&&z(a,i,e[i]);return a};var l=(a,e)=>f(a,"name",{value:e,configurable:!0});var C=(a,e)=>{for(var i in e)f(a,i,{get:e[i],enumerable:!0})},P=(a,e,i,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of T(e))!j.call(a,r)&&r!==i&&f(a,r,{get:()=>e[r],enumerable:!(n=D(e,r))||n.enumerable});return a};var A=a=>P(f({},"__esModule",{value:!0}),a);var W={};C(W,{default:()=>S,labels:()=>I,labelsWithCustomTheme:()=>K,language_script_pairs:()=>m,layers:()=>B,layersWithCustomTheme:()=>V,layersWithPartialCustomTheme:()=>F,namedTheme:()=>H,noLabels:()=>E,noLabelsWithCustomTheme:()=>G});function t(a,e){let i="script";return a==="name"?i="script":a==="name2"?i="script2":a==="name3"&&(i="script3"),[["coalesce",["get",`pgf:${a}`],["get",a]],{"text-font":["case",["==",["get",i],"Devanagari"],["literal",["Noto Sans Devanagari Regular v1"]],["literal",[e||"Noto Sans Regular"]]]}]}l(t,"get_name_block");function c(a,e,i){let n="name";return i==="name"?n="":i==="name2"?n="2":i==="name3"&&(n="3"),e==="Latin"?["has",`script${n}`]:a==="ja"?["all",["!=",["get",`script${n}`],"Han"],["!=",["get",`script${n}`],"Hiragana"],["!=",["get",`script${n}`],"Katakana"],["!=",["get",`script${n}`],"Mixed-Japanese"]]:["!=",["get",`script${n}`],e]}l(c,"is_not_in_target_script");function o(a){return a==="Devanagari"?{"text-font":["literal",["Noto Sans Devanagari Regular v1"]]}:{}}l(o,"get_font_formatting");function v(a){let e=m.find(i=>i.lang===a);return e===void 0?"Latin":e.script}l(v,"get_default_script");function L(a,e){let i=e||v(a),n;return i==="Devanagari"?n="pgf:":n="",["format",["coalesce",["get",`${n}name:${a}`],["get","name:en"]],o(i)]}l(L,"get_country_name");function s(a,e,i){let n=e||v(a),r;return n==="Devanagari"?r="pgf:":r="",["case",["all",["any",["has","name"],["has","pgf:name"]],["!",["any",["has","name2"],["has","pgf:name2"]]],["!",["any",["has","name3"],["has","pgf:name3"]]]],["case",c(a,n,"name"),["case",["any",["is-supported-script",["get","name"]],["has","pgf:name"]],["format",["coalesce",["get",`${r}name:${a}`],["get","name:en"]],o(n),` -`,{},["case",["all",["!",["has",`${r}name:${a}`]],["has","name:en"],["!",["has","script"]]],"",["coalesce",["get","pgf:name"],["get","name"]]],{"text-font":["case",["==",["get","script"],"Devanagari"],["literal",["Noto Sans Devanagari Regular v1"]],["literal",[i||"Noto Sans Regular"]]]}],["get","name:en"]],["format",["coalesce",["get",`${r}name:${a}`],["get","pgf:name"],["get","name"]],o(n)]],["all",["any",["has","name"],["has","pgf:name"]],["any",["has","name2"],["has","pgf:name2"]],["!",["any",["has","name3"],["has","pgf:name3"]]]],["case",["all",c(a,n,"name"),c(a,n,"name2")],["format",["get",`${r}name:${a}`],o(n),` -`,{},...t("name"),` -`,{},...t("name2")],["case",c(a,n,"name2"),["format",["coalesce",["get",`${r}name:${a}`],["get","pgf:name"],["get","name"]],o(n),` -`,{},...t("name2")],["format",["coalesce",["get",`${r}name:${a}`],["get","pgf:name2"],["get","name2"]],o(n),` -`,{},...t("name")]]],["case",["all",c(a,n,"name"),c(a,n,"name2"),c(a,n,"name3")],["format",["get",`${r}name:${a}`],o(n),` -`,{},...t("name"),` -`,{},...t("name2"),` -`,{},...t("name3")],["case",["!",c(a,n,"name")],["format",["coalesce",["get",`${r}name:${a}`],["get","pgf:name"],["get","name"]],o(n),` -`,{},...t("name2"),` -`,{},...t("name3")],["!",c(a,n,"name2")],["format",["coalesce",["get",`${r}name:${a}`],["get","pgf:name2"],["get","name2"]],o(n),` -`,{},...t("name"),` -`,{},...t("name3")],["format",["coalesce",["get",`${r}name:${a}`],["get","pgf:name3"],["get","name3"]],o(n),` -`,{},...t("name"),` -`,{},...t("name2")]]]]}l(s,"get_multiline_name");var m=[{lang:"ar",full_name:"Arabic",script:"Arabic"},{lang:"cs",full_name:"Czech",script:"Latin"},{lang:"bg",full_name:"Bulgarian",script:"Cyrillic"},{lang:"da",full_name:"Danish",script:"Latin"},{lang:"de",full_name:"German",script:"Latin"},{lang:"el",full_name:"Greek",script:"Greek"},{lang:"en",full_name:"English",script:"Latin"},{lang:"es",full_name:"Spanish",script:"Latin"},{lang:"et",full_name:"Estonian",script:"Latin"},{lang:"fa",full_name:"Persian",script:"Arabic"},{lang:"fi",full_name:"Finnish",script:"Latin"},{lang:"fr",full_name:"French",script:"Latin"},{lang:"ga",full_name:"Irish",script:"Latin"},{lang:"he",full_name:"Hebrew",script:"Hebrew"},{lang:"hi",full_name:"Hindi",script:"Devanagari"},{lang:"hr",full_name:"Croatian",script:"Latin"},{lang:"hu",full_name:"Hungarian",script:"Latin"},{lang:"id",full_name:"Indonesian",script:"Latin"},{lang:"it",full_name:"Italian",script:"Latin"},{lang:"ja",full_name:"Japanese",script:""},{lang:"ko",full_name:"Korean",script:"Hangul"},{lang:"lt",full_name:"Lithuanian",script:"Latin"},{lang:"lv",full_name:"Latvian",script:"Latin"},{lang:"ne",full_name:"Nepali",script:"Devanagari"},{lang:"nl",full_name:"Dutch",script:"Latin"},{lang:"no",full_name:"Norwegian",script:"Latin"},{lang:"mr",full_name:"Marathi",script:"Devanagari"},{lang:"mt",full_name:"Maltese",script:"Latin"},{lang:"pl",full_name:"Polish",script:"Latin"},{lang:"pt",full_name:"Portuguese",script:"Latin"},{lang:"ro",full_name:"Romanian",script:"Latin"},{lang:"ru",full_name:"Russian",script:"Cyrillic"},{lang:"sk",full_name:"Slovak",script:"Latin"},{lang:"sl",full_name:"Slovenian",script:"Latin"},{lang:"sv",full_name:"Swedish",script:"Latin"},{lang:"tr",full_name:"Turkish",script:"Latin"},{lang:"uk",full_name:"Ukrainian",script:"Cyrillic"},{lang:"ur",full_name:"Urdu",script:"Arabic"},{lang:"vi",full_name:"Vietnamese",script:"Latin"},{lang:"zh-Hans",full_name:"Chinese (Simplified)",script:"Han"},{lang:"zh-Hant",full_name:"Chinese (Traditional)",script:"Han"}];function _(a,e){return[{id:"background",type:"background",paint:{"background-color":e.background}},{id:"earth",type:"fill",filter:["==","$type","Polygon"],source:a,"source-layer":"earth",paint:{"fill-color":e.earth}},...e.landcover?[{id:"landcover",type:"fill",source:a,"source-layer":"landcover",paint:{"fill-color":["match",["get","kind"],"grassland",e.landcover.grassland,"barren",e.landcover.barren,"urban_area",e.landcover.urban_area,"farmland",e.landcover.farmland,"glacier",e.landcover.glacier,"scrub",e.landcover.scrub,e.landcover.forest],"fill-opacity":["interpolate",["linear"],["zoom"],5,1,7,0]}}]:[],{id:"landuse_park",type:"fill",source:a,"source-layer":"landuse",filter:["in","kind","national_park","park","cemetery","protected_area","nature_reserve","forest","golf_course","wood","nature_reserve","forest","scrub","grassland","grass","military","naval_base","airfield"],paint:{"fill-opacity":["interpolate",["linear"],["zoom"],6,0,11,1],"fill-color":["case",["in",["get","kind"],["literal",["national_park","park","cemetery","protected_area","nature_reserve","forest","golf_course"]]],e.park_b,["in",["get","kind"],["literal",["wood","nature_reserve","forest"]]],e.wood_b,["in",["get","kind"],["literal",["scrub","grassland","grass"]]],e.scrub_b,["in",["get","kind"],["literal",["glacier"]]],e.glacier,["in",["get","kind"],["literal",["sand"]]],e.sand,["in",["get","kind"],["literal",["military","naval_base","airfield"]]],e.zoo,e.earth]}},{id:"landuse_urban_green",type:"fill",source:a,"source-layer":"landuse",filter:["in","kind","allotments","village_green","playground"],paint:{"fill-color":e.park_b,"fill-opacity":.7}},{id:"landuse_hospital",type:"fill",source:a,"source-layer":"landuse",filter:["==","kind","hospital"],paint:{"fill-color":e.hospital}},{id:"landuse_industrial",type:"fill",source:a,"source-layer":"landuse",filter:["==","kind","industrial"],paint:{"fill-color":e.industrial}},{id:"landuse_school",type:"fill",source:a,"source-layer":"landuse",filter:["in","kind","school","university","college"],paint:{"fill-color":e.school}},{id:"landuse_beach",type:"fill",source:a,"source-layer":"landuse",filter:["in","kind","beach"],paint:{"fill-color":e.beach}},{id:"landuse_zoo",type:"fill",source:a,"source-layer":"landuse",filter:["in","kind","zoo"],paint:{"fill-color":e.zoo}},{id:"landuse_aerodrome",type:"fill",source:a,"source-layer":"landuse",filter:["in","kind","aerodrome"],paint:{"fill-color":e.aerodrome}},{id:"roads_runway",type:"line",source:a,"source-layer":"roads",filter:["==","kind_detail","runway"],paint:{"line-color":e.runway,"line-width":["interpolate",["exponential",1.6],["zoom"],10,0,12,4,18,30]}},{id:"roads_taxiway",type:"line",source:a,"source-layer":"roads",minzoom:13,filter:["==","kind_detail","taxiway"],paint:{"line-color":e.runway,"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,15,6]}},{id:"landuse_runway",type:"fill",source:a,"source-layer":"landuse",filter:["any",["in","kind","runway","taxiway"]],paint:{"fill-color":e.runway}},{id:"water",type:"fill",filter:["==","$type","Polygon"],source:a,"source-layer":"water",paint:{"fill-color":e.water}},{id:"water_stream",type:"line",source:a,"source-layer":"water",minzoom:14,filter:["in","kind","stream"],paint:{"line-color":e.water,"line-width":.5}},{id:"water_river",type:"line",source:a,"source-layer":"water",minzoom:9,filter:["in","kind","river"],paint:{"line-color":e.water,"line-width":["interpolate",["exponential",1.6],["zoom"],9,0,9.5,1,18,12]}},{id:"landuse_pedestrian",type:"fill",source:a,"source-layer":"landuse",filter:["==","kind","pedestrian"],paint:{"fill-color":e.pedestrian}},{id:"landuse_pier",type:"fill",source:a,"source-layer":"landuse",filter:["==","kind","pier"],paint:{"fill-color":e.pier}},{id:"roads_tunnels_other_casing",type:"line",source:a,"source-layer":"roads",filter:["all",["has","is_tunnel"],["in","kind","other","path"]],paint:{"line-color":e.tunnel_other_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],14,0,20,7]}},{id:"roads_tunnels_minor_casing",type:"line",source:a,"source-layer":"roads",filter:["all",["has","is_tunnel"],["==","kind","minor_road"]],paint:{"line-color":e.tunnel_minor_casing,"line-dasharray":[3,2],"line-gap-width":["interpolate",["exponential",1.6],["zoom"],11,0,12.5,.5,15,2,18,11],"line-width":["interpolate",["exponential",1.6],["zoom"],12,0,12.5,1]}},{id:"roads_tunnels_link_casing",type:"line",source:a,"source-layer":"roads",filter:["all",["has","is_tunnel"],["has","is_link"]],paint:{"line-color":e.tunnel_link_casing,"line-dasharray":[3,2],"line-gap-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,18,11],"line-width":["interpolate",["exponential",1.6],["zoom"],12,0,12.5,1]}},{id:"roads_tunnels_major_casing",type:"line",source:a,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","major_road"]],paint:{"line-color":e.tunnel_major_casing,"line-dasharray":[3,2],"line-gap-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,.5,18,13],"line-width":["interpolate",["exponential",1.6],["zoom"],9,0,9.5,1]}},{id:"roads_tunnels_highway_casing",type:"line",source:a,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","highway"],["!has","is_link"]],paint:{"line-color":e.tunnel_highway_casing,"line-dasharray":[6,.5],"line-gap-width":["interpolate",["exponential",1.6],["zoom"],3,0,3.5,.5,18,15],"line-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,1,20,15]}},{id:"roads_tunnels_other",type:"line",source:a,"source-layer":"roads",filter:["all",["has","is_tunnel"],["in","kind","other","path"]],paint:{"line-color":e.tunnel_other,"line-dasharray":[4.5,.5],"line-width":["interpolate",["exponential",1.6],["zoom"],14,0,20,7]}},{id:"roads_tunnels_minor",type:"line",source:a,"source-layer":"roads",filter:["all",["has","is_tunnel"],["==","kind","minor_road"]],paint:{"line-color":e.tunnel_minor,"line-width":["interpolate",["exponential",1.6],["zoom"],11,0,12.5,.5,15,2,18,11]}},{id:"roads_tunnels_link",type:"line",source:a,"source-layer":"roads",filter:["all",["has","is_tunnel"],["has","is_link"]],paint:{"line-color":e.tunnel_minor,"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,18,11]}},{id:"roads_tunnels_major",type:"line",source:a,"source-layer":"roads",filter:["all",["has","is_tunnel"],["==","kind","major_road"]],paint:{"line-color":e.tunnel_major,"line-width":["interpolate",["exponential",1.6],["zoom"],6,0,12,1.6,15,3,18,13]}},{id:"roads_tunnels_highway",type:"line",source:a,"source-layer":"roads",filter:["all",["has","is_tunnel"],["==",["get","kind"],"highway"],["!",["has","is_link"]]],paint:{"line-color":e.tunnel_highway,"line-width":["interpolate",["exponential",1.6],["zoom"],3,0,6,1.1,12,1.6,15,5,18,15]}},{id:"buildings",type:"fill",source:a,"source-layer":"buildings",filter:["in","kind","building","building_part"],paint:{"fill-color":e.buildings,"fill-opacity":.5}},{id:"roads_pier",type:"line",source:a,"source-layer":"roads",filter:["==","kind_detail","pier"],paint:{"line-color":e.pier,"line-width":["interpolate",["exponential",1.6],["zoom"],12,0,12.5,.5,20,16]}},{id:"roads_minor_service_casing",type:"line",source:a,"source-layer":"roads",minzoom:13,filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","minor_road"],["==","kind_detail","service"]],paint:{"line-color":e.minor_service_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],13,0,18,8],"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,.8]}},{id:"roads_minor_casing",type:"line",source:a,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","minor_road"],["!=","kind_detail","service"]],paint:{"line-color":e.minor_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],11,0,12.5,.5,15,2,18,11],"line-width":["interpolate",["exponential",1.6],["zoom"],12,0,12.5,1]}},{id:"roads_link_casing",type:"line",source:a,"source-layer":"roads",minzoom:13,filter:["has","is_link"],paint:{"line-color":e.minor_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,18,11],"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1.5]}},{id:"roads_major_casing_late",type:"line",source:a,"source-layer":"roads",minzoom:12,filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","major_road"]],paint:{"line-color":e.major_casing_late,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],6,0,12,1.6,15,3,18,13],"line-width":["interpolate",["exponential",1.6],["zoom"],9,0,9.5,1]}},{id:"roads_highway_casing_late",type:"line",source:a,"source-layer":"roads",minzoom:12,filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","highway"],["!has","is_link"]],paint:{"line-color":e.highway_casing_late,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],3,0,3.5,.5,18,15],"line-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,1,20,15]}},{id:"roads_other",type:"line",source:a,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["in","kind","other","path"],["!=","kind_detail","pier"]],paint:{"line-color":e.other,"line-dasharray":[3,1],"line-width":["interpolate",["exponential",1.6],["zoom"],14,0,20,7]}},{id:"roads_link",type:"line",source:a,"source-layer":"roads",filter:["has","is_link"],paint:{"line-color":e.link,"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,18,11]}},{id:"roads_minor_service",type:"line",source:a,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","minor_road"],["==","kind_detail","service"]],paint:{"line-color":e.minor_service,"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,18,8]}},{id:"roads_minor",type:"line",source:a,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","minor_road"],["!=","kind_detail","service"]],paint:{"line-color":["interpolate",["exponential",1.6],["zoom"],11,e.minor_a,16,e.minor_b],"line-width":["interpolate",["exponential",1.6],["zoom"],11,0,12.5,.5,15,2,18,11]}},{id:"roads_major_casing_early",type:"line",source:a,"source-layer":"roads",maxzoom:12,filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","major_road"]],paint:{"line-color":e.major_casing_early,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,.5,18,13],"line-width":["interpolate",["exponential",1.6],["zoom"],9,0,9.5,1]}},{id:"roads_major",type:"line",source:a,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","major_road"]],paint:{"line-color":e.major,"line-width":["interpolate",["exponential",1.6],["zoom"],6,0,12,1.6,15,3,18,13]}},{id:"roads_highway_casing_early",type:"line",source:a,"source-layer":"roads",maxzoom:12,filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","highway"],["!has","is_link"]],paint:{"line-color":e.highway_casing_early,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],3,0,3.5,.5,18,15],"line-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,1]}},{id:"roads_highway",type:"line",source:a,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","highway"],["!has","is_link"]],paint:{"line-color":e.highway,"line-width":["interpolate",["exponential",1.6],["zoom"],3,0,6,1.1,12,1.6,15,5,18,15]}},{id:"roads_rail",type:"line",source:a,"source-layer":"roads",filter:["==","kind","rail"],paint:{"line-dasharray":[.3,.75],"line-opacity":.5,"line-color":e.railway,"line-width":["interpolate",["exponential",1.6],["zoom"],3,0,6,.15,18,9]}},{id:"boundaries_country",type:"line",source:a,"source-layer":"boundaries",filter:["<=","kind_detail",2],paint:{"line-color":e.boundaries,"line-width":.7,"line-dasharray":["step",["zoom"],["literal",[2]],4,["literal",[2,1]]]}},{id:"boundaries",type:"line",source:a,"source-layer":"boundaries",filter:[">","kind_detail",2],paint:{"line-color":e.boundaries,"line-width":.4,"line-dasharray":["step",["zoom"],["literal",[2]],4,["literal",[2,1]]]}},{id:"roads_bridges_other_casing",type:"line",source:a,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["in","kind","other","path"]],paint:{"line-color":e.bridges_other_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],14,0,20,7]}},{id:"roads_bridges_link_casing",type:"line",source:a,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["has","is_link"]],paint:{"line-color":e.bridges_minor_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,18,11],"line-width":["interpolate",["exponential",1.6],["zoom"],12,0,12.5,1.5]}},{id:"roads_bridges_minor_casing",type:"line",source:a,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["==","kind","minor_road"]],paint:{"line-color":e.bridges_minor_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],11,0,12.5,.5,15,2,18,11],"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,.8]}},{id:"roads_bridges_major_casing",type:"line",source:a,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["==","kind","major_road"]],paint:{"line-color":e.bridges_major_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,.5,18,10],"line-width":["interpolate",["exponential",1.6],["zoom"],9,0,9.5,1.5]}},{id:"roads_bridges_other",type:"line",source:a,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["in","kind","other","path"]],paint:{"line-color":e.bridges_other,"line-dasharray":[2,1],"line-width":["interpolate",["exponential",1.6],["zoom"],14,0,20,7]}},{id:"roads_bridges_minor",type:"line",source:a,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["==","kind","minor_road"]],paint:{"line-color":e.bridges_minor,"line-width":["interpolate",["exponential",1.6],["zoom"],11,0,12.5,.5,15,2,18,11]}},{id:"roads_bridges_link",type:"line",source:a,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["has","is_link"]],paint:{"line-color":e.bridges_minor,"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,18,11]}},{id:"roads_bridges_major",type:"line",source:a,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["==","kind","major_road"]],paint:{"line-color":e.bridges_major,"line-width":["interpolate",["exponential",1.6],["zoom"],6,0,12,1.6,15,3,18,13]}},{id:"roads_bridges_highway_casing",type:"line",source:a,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["==","kind","highway"],["!has","is_link"]],paint:{"line-color":e.bridges_highway_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],3,0,3.5,.5,18,15],"line-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,1,20,15]}},{id:"roads_bridges_highway",type:"line",source:a,"source-layer":"roads",filter:["all",["has","is_bridge"],["==","kind","highway"],["!has","is_link"]],paint:{"line-color":e.bridges_highway,"line-width":["interpolate",["exponential",1.6],["zoom"],3,0,6,1.1,12,1.6,15,5,18,15]}}]}l(_,"nolabels_layers");function d(a,e,i,n){return[{id:"address_label",type:"symbol",source:a,"source-layer":"buildings",minzoom:18,filter:["==","kind","address"],layout:{"symbol-placement":"point","text-font":[e.italic||"Noto Sans Italic"],"text-field":["get","addr_housenumber"],"text-size":12},paint:{"text-color":e.address_label,"text-halo-color":e.address_label_halo,"text-halo-width":1}},{id:"water_waterway_label",type:"symbol",source:a,"source-layer":"water",minzoom:13,filter:["in","kind","river","stream"],layout:{"symbol-placement":"line","text-font":[e.italic||"Noto Sans Italic"],"text-field":s(i,n,e.regular),"text-size":12,"text-letter-spacing":.2},paint:{"text-color":e.ocean_label,"text-halo-color":e.water,"text-halo-width":1}},{id:"roads_labels_minor",type:"symbol",source:a,"source-layer":"roads",minzoom:15,filter:["in","kind","minor_road","other","path"],layout:{"symbol-sort-key":["get","min_zoom"],"symbol-placement":"line","text-font":[e.regular||"Noto Sans Regular"],"text-field":s(i,n,e.regular),"text-size":12},paint:{"text-color":e.roads_label_minor,"text-halo-color":e.roads_label_minor_halo,"text-halo-width":1}},{id:"water_label_ocean",type:"symbol",source:a,"source-layer":"water",filter:["in","kind","sea","ocean","bay","strait","fjord"],layout:{"text-font":[e.italic||"Noto Sans Italic"],"text-field":s(i,n,e.regular),"text-size":["interpolate",["linear"],["zoom"],3,10,10,12],"text-letter-spacing":.1,"text-max-width":9,"text-transform":"uppercase"},paint:{"text-color":e.ocean_label,"text-halo-width":1,"text-halo-color":e.water}},{id:"water_label_lakes",type:"symbol",source:a,"source-layer":"water",filter:["in","kind","lake","water"],layout:{"text-font":[e.italic||"Noto Sans Italic"],"text-field":s(i,n,e.regular),"text-size":["interpolate",["linear"],["zoom"],3,10,6,12,10,12],"text-letter-spacing":.1,"text-max-width":9},paint:{"text-color":e.ocean_label,"text-halo-color":e.water,"text-halo-width":1}},{id:"roads_labels_major",type:"symbol",source:a,"source-layer":"roads",minzoom:11,filter:["in","kind","highway","major_road"],layout:{"symbol-sort-key":["get","min_zoom"],"symbol-placement":"line","text-font":[e.regular||"Noto Sans Regular"],"text-field":s(i,n,e.regular),"text-size":12},paint:{"text-color":e.roads_label_major,"text-halo-color":e.roads_label_major_halo,"text-halo-width":1}},...e.pois?[{id:"pois",type:"symbol",source:a,"source-layer":"pois",filter:["all",["in",["get","kind"],["literal",["beach","forest","marina","park","peak","zoo","garden","bench","aerodrome","station","bus_stop","ferry_terminal","stadium","university","library","school","animal","toilets","drinking_water"]]],[">=",["zoom"],["+",["get","min_zoom"],0]]],layout:{"icon-image":["match",["get","kind"],"station","train_station",["get","kind"]],"text-font":[e.regular||"Noto Sans Regular"],"text-justify":"auto","text-field":s(i,n,e.regular),"text-size":["interpolate",["linear"],["zoom"],17,10,19,16],"text-max-width":8,"text-offset":[1.1,0],"text-variable-anchor":["left","right"]},paint:{"text-color":["case",["in",["get","kind"],["literal",["beach","forest","marina","park","peak","zoo","garden","bench"]]],e.pois.green,["in",["get","kind"],["literal",["aerodrome","station","bus_stop","ferry_terminal"]]],e.pois.lapis,["in",["get","kind"],["literal",["stadium","university","library","school","animal","toilets","drinking_water"]]],e.pois.slategray,e.earth],"text-halo-color":e.earth,"text-halo-width":1}}]:[],{id:"places_subplace",type:"symbol",source:a,"source-layer":"places",filter:["==","kind","neighbourhood"],layout:{"symbol-sort-key":["get","min_zoom"],"text-field":s(i,n,e.regular),"text-font":[e.regular||"Noto Sans Regular"],"text-max-width":7,"text-letter-spacing":.1,"text-padding":["interpolate",["linear"],["zoom"],5,2,8,4,12,18,15,20],"text-size":["interpolate",["exponential",1.2],["zoom"],11,8,14,14,18,24],"text-transform":"uppercase"},paint:{"text-color":e.subplace_label,"text-halo-color":e.subplace_label_halo,"text-halo-width":1}},{id:"places_locality",type:"symbol",source:a,"source-layer":"places",filter:["==","kind","locality"],layout:{"icon-image":["step",["zoom"],"townspot",8,""],"icon-size":.7,"text-field":s(i,n,e.regular),"text-font":["case",["<=",["get","min_zoom"],5],["literal",[e.bold||"Noto Sans Medium"]],["literal",[e.regular||"Noto Sans Regular"]]],"text-padding":["interpolate",["linear"],["zoom"],5,3,8,7,12,11],"text-size":["interpolate",["linear"],["zoom"],2,["case",["<",["get","population_rank"],13],8,[">=",["get","population_rank"],13],13,0],4,["case",["<",["get","population_rank"],13],10,[">=",["get","population_rank"],13],15,0],6,["case",["<",["get","population_rank"],12],11,[">=",["get","population_rank"],12],17,0],8,["case",["<",["get","population_rank"],11],11,[">=",["get","population_rank"],11],18,0],10,["case",["<",["get","population_rank"],9],12,[">=",["get","population_rank"],9],20,0],15,["case",["<",["get","population_rank"],8],12,[">=",["get","population_rank"],8],22,0]],"icon-padding":["interpolate",["linear"],["zoom"],0,0,8,4,10,8,12,6,22,2],"text-justify":"auto","text-anchor":["step",["zoom"],"left",8,"center"],"text-radial-offset":.4},paint:{"text-color":e.city_label,"text-halo-color":e.city_label_halo,"text-halo-width":1}},{id:"places_region",type:"symbol",source:a,"source-layer":"places",filter:["==","kind","region"],layout:{"symbol-sort-key":["get","min_zoom"],"text-field":["step",["zoom"],["get","name:short"],6,s(i,n,e.regular)],"text-font":[e.regular||"Noto Sans Regular"],"text-size":["interpolate",["linear"],["zoom"],3,11,7,16],"text-radial-offset":.2,"text-anchor":"center","text-transform":"uppercase"},paint:{"text-color":e.state_label,"text-halo-color":e.state_label_halo,"text-halo-width":1}},{id:"places_country",type:"symbol",source:a,"source-layer":"places",filter:["==","kind","country"],layout:{"symbol-sort-key":["get","min_zoom"],"text-field":L(i,n),"text-font":[e.bold||"Noto Sans Medium"],"text-size":["interpolate",["linear"],["zoom"],2,["case",["<",["get","population_rank"],10],8,[">=",["get","population_rank"],10],12,0],6,["case",["<",["get","population_rank"],8],10,[">=",["get","population_rank"],8],18,0],8,["case",["<",["get","population_rank"],7],11,[">=",["get","population_rank"],7],20,0]],"icon-padding":["interpolate",["linear"],["zoom"],0,2,14,2,16,20,17,2,22,2],"text-transform":"uppercase"},paint:{"text-color":e.country_label,"text-halo-color":e.earth,"text-halo-width":1}}]}l(d,"labels_layers");var N={background:"#dddddd",earth:"#ededed",park_a:"#bfc99c",park_b:"#bfc99c",hospital:"#ffeae8",industrial:"#f8ffed",school:"#f2fef9",wood_a:"#bfc99c",wood_b:"#bfc99c",pedestrian:"#eef0f0",scrub_a:"#bfc99c",scrub_b:"#bfc99c",glacier:"#ffffff",sand:"#ebe7da",beach:"#ebe7da",aerodrome:"#dbe7e7",runway:"#d1d9d9",water:"#84b7cf",zoo:"#ebe6ed",military:"#ebe6ed",tunnel_other_casing:"#ffffff",tunnel_minor_casing:"#e2e2e2",tunnel_link_casing:"#e2e2e2",tunnel_major_casing:"#e3cfd3",tunnel_highway_casing:"#ebcea2",tunnel_other:"#f7f7f7",tunnel_minor:"#ebebeb",tunnel_link:"#ebebeb",tunnel_major:"#ebebeb",tunnel_highway:"#ebebeb",pier:"#ededed",buildings:"#cbcece",minor_service_casing:"#e2e2e2",minor_casing:"#e2e2e2",link_casing:"#e2e2e2",major_casing_late:"#e3cfd3",highway_casing_late:"#ebcea2",other:"#ffffff",minor_service:"#ffffff",minor_a:"#fff2bb",minor_b:"#fff2bb",link:"#fff2bb",major_casing_early:"#e3cfd3",major:"#ffdf59",highway_casing_early:"#ebcea2",highway:"#e9ac77",railway:"#b3bcc9",boundaries:"#5c4a6b",waterway_label:"#a4cae1",bridges_other_casing:"#ffffff",bridges_minor_casing:"#e2e2e2",bridges_link_casing:"#e1e1e1",bridges_major_casing:"#e3cfd3",bridges_highway_casing:"#ebcea2",bridges_other:"#ffffff",bridges_minor:"#ffffff",bridges_link:"#e1e1e1",bridges_major:"#ffffff",bridges_highway:"#fefffc",roads_label_minor:"#91888b",roads_label_minor_halo:"#ffffff",roads_label_major:"#91888b",roads_label_major_halo:"#ffffff",ocean_label:"#ffffff",peak_label:"#61bb5b",subplace_label:"#757d91",subplace_label_halo:"#ffffff",city_label:"#3c3c3c",city_label_halo:"#ffffff",state_label:"#777777",state_label_halo:"#ffffff",country_label:"#9590aa",address_label:"#91888b",address_label_halo:"#ffffff"},h={background:"#cccccc",earth:"#e2dfda",park_a:"#cfddd5",park_b:"#9cd3b4",hospital:"#e4dad9",industrial:"#d1dde1",school:"#e4ded7",wood_a:"#d0ded0",wood_b:"#a0d9a0",pedestrian:"#e3e0d4",scrub_a:"#cedcd7",scrub_b:"#99d2bb",glacier:"#e7e7e7",sand:"#e2e0d7",beach:"#e8e4d0",aerodrome:"#dadbdf",runway:"#e9e9ed",water:"#80deea",zoo:"#c6dcdc",military:"#dcdcdc",tunnel_other_casing:"#e0e0e0",tunnel_minor_casing:"#e0e0e0",tunnel_link_casing:"#e0e0e0",tunnel_major_casing:"#e0e0e0",tunnel_highway_casing:"#e0e0e0",tunnel_other:"#d5d5d5",tunnel_minor:"#d5d5d5",tunnel_link:"#d5d5d5",tunnel_major:"#d5d5d5",tunnel_highway:"#d5d5d5",pier:"#e0e0e0",buildings:"#cccccc",minor_service_casing:"#e0e0e0",minor_casing:"#e0e0e0",link_casing:"#e0e0e0",major_casing_late:"#e0e0e0",highway_casing_late:"#e0e0e0",other:"#ebebeb",minor_service:"#ebebeb",minor_a:"#ebebeb",minor_b:"#ffffff",link:"#ffffff",major_casing_early:"#e0e0e0",major:"#ffffff",highway_casing_early:"#e0e0e0",highway:"#ffffff",railway:"#a7b1b3",boundaries:"#adadad",waterway_label:"#ffffff",bridges_other_casing:"#e0e0e0",bridges_minor_casing:"#e0e0e0",bridges_link_casing:"#e0e0e0",bridges_major_casing:"#e0e0e0",bridges_highway_casing:"#e0e0e0",bridges_other:"#ebebeb",bridges_minor:"#ffffff",bridges_link:"#ffffff",bridges_major:"#f5f5f5",bridges_highway:"#ffffff",roads_label_minor:"#91888b",roads_label_minor_halo:"#ffffff",roads_label_major:"#938a8d",roads_label_major_halo:"#ffffff",ocean_label:"#728dd4",peak_label:"#7e9aa0",subplace_label:"#8f8f8f",subplace_label_halo:"#e0e0e0",city_label:"#5c5c5c",city_label_halo:"#e0e0e0",state_label:"#b3b3b3",state_label_halo:"#e0e0e0",country_label:"#a3a3a3",address_label:"#91888b",address_label_halo:"#ffffff",pois:{blue:"#1A8CBD",green:"#20834D",lapis:"#315BCF",pink:"#EF56BA",red:"#F2567A",slategray:"#6A5B8F",tangerine:"#CB6704",turquoise:"#00C3D4"},landcover:{grassland:"rgba(210, 239, 207, 1)",barren:"rgba(255, 243, 215, 1)",urban_area:"rgba(230, 230, 230, 1)",farmland:"rgba(216, 239, 210, 1)",glacier:"rgba(255, 255, 255, 1)",scrub:"rgba(234, 239, 210, 1)",forest:"rgba(196, 231, 210, 1)"}},u={background:"#34373d",earth:"#1f1f1f",park_a:"#1c2421",park_b:"#192a24",hospital:"#252424",industrial:"#222222",school:"#262323",wood_a:"#202121",wood_b:"#202121",pedestrian:"#1e1e1e",scrub_a:"#222323",scrub_b:"#222323",glacier:"#1c1c1c",sand:"#212123",beach:"#28282a",aerodrome:"#1e1e1e",runway:"#333333",water:"#31353f",zoo:"#222323",military:"#242323",tunnel_other_casing:"#141414",tunnel_minor_casing:"#141414",tunnel_link_casing:"#141414",tunnel_major_casing:"#141414",tunnel_highway_casing:"#141414",tunnel_other:"#292929",tunnel_minor:"#292929",tunnel_link:"#292929",tunnel_major:"#292929",tunnel_highway:"#292929",pier:"#333333",buildings:"#111111",minor_service_casing:"#1f1f1f",minor_casing:"#1f1f1f",link_casing:"#1f1f1f",major_casing_late:"#1f1f1f",highway_casing_late:"#1f1f1f",other:"#333333",minor_service:"#333333",minor_a:"#3d3d3d",minor_b:"#333333",link:"#3d3d3d",major_casing_early:"#1f1f1f",major:"#3d3d3d",highway_casing_early:"#1f1f1f",highway:"#474747",railway:"#000000",boundaries:"#5b6374",waterway_label:"#717784",bridges_other_casing:"#2b2b2b",bridges_minor_casing:"#1f1f1f",bridges_link_casing:"#1f1f1f",bridges_major_casing:"#1f1f1f",bridges_highway_casing:"#1f1f1f",bridges_other:"#333333",bridges_minor:"#333333",bridges_link:"#3d3d3d",bridges_major:"#3d3d3d",bridges_highway:"#474747",roads_label_minor:"#525252",roads_label_minor_halo:"#1f1f1f",roads_label_major:"#666666",roads_label_major_halo:"#1f1f1f",ocean_label:"#717784",peak_label:"#898080",subplace_label:"#525252",subplace_label_halo:"#1f1f1f",city_label:"#7a7a7a",city_label_halo:"#212121",state_label:"#3d3d3d",state_label_halo:"#1f1f1f",country_label:"#5c5c5c",address_label:"#525252",address_label_halo:"#1f1f1f",pois:{blue:"#4299BB",green:"#30C573",lapis:"#2B5CEA",pink:"#EF56BA",red:"#F2567A",slategray:"#93939F",tangerine:"#F19B6E",turquoise:"#00C3D4"},landcover:{grassland:"rgba(30, 41, 31, 1)",barren:"rgba(38, 38, 36, 1)",urban_area:"rgba(28, 28, 28, 1)",farmland:"rgba(31, 36, 32, 1)",glacier:"rgba(43, 43, 43, 1)",scrub:"rgba(34, 36, 30, 1)",forest:"rgba(28, 41, 37, 1)"}},y={background:"#ffffff",earth:"#ffffff",park_a:"#fcfcfc",park_b:"#fcfcfc",hospital:"#f8f8f8",industrial:"#fcfcfc",school:"#f8f8f8",wood_a:"#fafafa",wood_b:"#fafafa",pedestrian:"#fdfdfd",scrub_a:"#fafafa",scrub_b:"#fafafa",glacier:"#fcfcfc",sand:"#fafafa",beach:"#f6f6f6",aerodrome:"#fdfdfd",runway:"#efefef",water:"#dcdcdc",zoo:"#f7f7f7",military:"#fcfcfc",tunnel_other_casing:"#d6d6d6",tunnel_minor_casing:"#fcfcfc",tunnel_link_casing:"#fcfcfc",tunnel_major_casing:"#fcfcfc",tunnel_highway_casing:"#fcfcfc",tunnel_other:"#d6d6d6",tunnel_minor:"#d6d6d6",tunnel_link:"#d6d6d6",tunnel_major:"#d6d6d6",tunnel_highway:"#d6d6d6",pier:"#efefef",buildings:"#efefef",minor_service_casing:"#ffffff",minor_casing:"#ffffff",link_casing:"#ffffff",major_casing_late:"#ffffff",highway_casing_late:"#ffffff",other:"#f5f5f5",minor_service:"#f5f5f5",minor_a:"#ebebeb",minor_b:"#f5f5f5",link:"#ebebeb",major_casing_early:"#ffffff",major:"#ebebeb",highway_casing_early:"#ffffff",highway:"#ebebeb",railway:"#d6d6d6",boundaries:"#adadad",waterway_label:"#adadad",bridges_other_casing:"#ffffff",bridges_minor_casing:"#ffffff",bridges_link_casing:"#ffffff",bridges_major_casing:"#ffffff",bridges_highway_casing:"#ffffff",bridges_other:"#f5f5f5",bridges_minor:"#f5f5f5",bridges_link:"#ebebeb",bridges_major:"#ebebeb",bridges_highway:"#ebebeb",roads_label_minor:"#adadad",roads_label_minor_halo:"#ffffff",roads_label_major:"#999999",roads_label_major_halo:"#ffffff",ocean_label:"#adadad",peak_label:"#adadad",subplace_label:"#8f8f8f",subplace_label_halo:"#ffffff",city_label:"#5c5c5c",city_label_halo:"#ffffff",state_label:"#b3b3b3",state_label_halo:"#ffffff",country_label:"#b8b8b8",address_label:"#adadad",address_label_halo:"#ffffff"},w={background:"#a3a3a3",earth:"#cccccc",park_a:"#c2c2c2",park_b:"#c2c2c2",hospital:"#d0d0d0",industrial:"#c6c6c6",school:"#d0d0d0",wood_a:"#c2c2c2",wood_b:"#c2c2c2",pedestrian:"#c4c4c4",scrub_a:"#c2c2c2",scrub_b:"#c2c2c2",glacier:"#d2d2d2",sand:"#d2d2d2",beach:"#d2d2d2",aerodrome:"#c9c9c9",runway:"#f5f5f5",water:"#a3a3a3",zoo:"#c7c7c7",military:"#bfbfbf",tunnel_other_casing:"#b8b8b8",tunnel_minor_casing:"#b8b8b8",tunnel_link_casing:"#b8b8b8",tunnel_major_casing:"#b8b8b8",tunnel_highway_casing:"#b8b8b8",tunnel_other:"#d6d6d6",tunnel_minor:"#d6d6d6",tunnel_link:"#d6d6d6",tunnel_major:"#d6d6d6",tunnel_highway:"#d6d6d6",pier:"#b8b8b8",buildings:"#e0e0e0",minor_service_casing:"#cccccc",minor_casing:"#cccccc",link_casing:"#cccccc",major_casing_late:"#cccccc",highway_casing_late:"#cccccc",other:"#e0e0e0",minor_service:"#e0e0e0",minor_a:"#ebebeb",minor_b:"#e0e0e0",link:"#ebebeb",major_casing_early:"#cccccc",major:"#ebebeb",highway_casing_early:"#cccccc",highway:"#ebebeb",railway:"#f5f5f5",boundaries:"#5c5c5c",waterway_label:"#7a7a7a",bridges_other_casing:"#cccccc",bridges_minor_casing:"#cccccc",bridges_link_casing:"#cccccc",bridges_major_casing:"#cccccc",bridges_highway_casing:"#cccccc",bridges_other:"#e0e0e0",bridges_minor:"#e0e0e0",bridges_link:"#ebebeb",bridges_major:"#ebebeb",bridges_highway:"#ebebeb",roads_label_minor:"#999999",roads_label_minor_halo:"#e0e0e0",roads_label_major:"#8f8f8f",roads_label_major_halo:"#ebebeb",ocean_label:"#7a7a7a",peak_label:"#5c5c5c",subplace_label:"#7a7a7a",subplace_label_halo:"#cccccc",city_label:"#474747",city_label_halo:"#cccccc",state_label:"#999999",state_label_halo:"#cccccc",country_label:"#858585",address_label:"#999999",address_label_halo:"#e0e0e0"},k={background:"#2b2b2b",earth:"#141414",park_a:"#181818",park_b:"#181818",hospital:"#1d1d1d",industrial:"#101010",school:"#111111",wood_a:"#1a1a1a",wood_b:"#1a1a1a",pedestrian:"#191919",scrub_a:"#1c1c1c",scrub_b:"#1c1c1c",glacier:"#191919",sand:"#161616",beach:"#1f1f1f",aerodrome:"#191919",runway:"#323232",water:"#333333",zoo:"#191919",military:"#121212",tunnel_other_casing:"#101010",tunnel_minor_casing:"#101010",tunnel_link_casing:"#101010",tunnel_major_casing:"#101010",tunnel_highway_casing:"#101010",tunnel_other:"#292929",tunnel_minor:"#292929",tunnel_link:"#292929",tunnel_major:"#292929",tunnel_highway:"#292929",pier:"#0a0a0a",buildings:"#0a0a0a",minor_service_casing:"#141414",minor_casing:"#141414",link_casing:"#141414",major_casing_late:"#141414",highway_casing_late:"#141414",other:"#1f1f1f",minor_service:"#1f1f1f",minor_a:"#292929",minor_b:"#1f1f1f",link:"#1f1f1f",major_casing_early:"#141414",major:"#292929",highway_casing_early:"#141414",highway:"#292929",railway:"#292929",boundaries:"#707070",waterway_label:"#707070",bridges_other_casing:"#141414",bridges_minor_casing:"#141414",bridges_link_casing:"#141414",bridges_major_casing:"#141414",bridges_highway_casing:"#141414",bridges_other:"#1f1f1f",bridges_minor:"#1f1f1f",bridges_link:"#292929",bridges_major:"#292929",bridges_highway:"#292929",roads_label_minor:"#525252",roads_label_minor_halo:"#141414",roads_label_major:"#5c5c5c",roads_label_major_halo:"#141414",ocean_label:"#707070",peak_label:"#707070",subplace_label:"#5c5c5c",subplace_label_halo:"#141414",city_label:"#999999",city_label_halo:"#141414",state_label:"#3d3d3d",state_label_halo:"#141414",country_label:"#707070",address_label:"#525252",address_label_halo:"#141414"},R={light:h,dark:u,white:y,grayscale:w,black:k,contrast:N},g=R;function H(a){switch(a){case"light":return h;case"dark":return u;case"white":return y;case"grayscale":return w;case"black":return k}throw new Error("Theme not found")}l(H,"namedTheme");function B(a,e,i){let n=[];return i!=null&&i.labelsOnly||(n=_(a,e)),i!=null&&i.lang&&(n=n.concat(d(a,e,i.lang))),n}l(B,"layers");function S(a,e,i,n){let r=g[e];return _(a,r).concat(d(a,r,i,n))}l(S,"default");function E(a,e){let i=g[e];return _(a,i)}l(E,"noLabels");function I(a,e,i,n){let r=g[e];return d(a,r,i,n)}l(I,"labels");function V(a,e,i,n){return _(a,e).concat(d(a,e,i,n))}l(V,"layersWithCustomTheme");function F(a,e,i,n,r){let p=b(b({},g[e]),i);return _(a,p).concat(d(a,p,n,r))}l(F,"layersWithPartialCustomTheme");function G(a,e){return _(a,e)}l(G,"noLabelsWithCustomTheme");function K(a,e,i,n){return d(a,e,i,n)}l(K,"labelsWithCustomTheme");return A(W);})(); -//# sourceMappingURL=protomaps-themes-base.js.map \ No newline at end of file diff --git a/test/test_app.rb b/test/test_app.rb index 41d0a31..972e242 100644 --- a/test/test_app.rb +++ b/test/test_app.rb @@ -402,13 +402,13 @@ class AppTest < Minitest::Test assert_equal 409, response.status end - # fetch_build_url blocks on a Queue, called directly (not through the + # download_extract blocks on a Queue, called directly (not through the # route, which has no way to inject a fake) so the build is # deterministically still "running" when the route call below happens. def test_offline_tiles_build_is_409_when_one_is_already_running Maptool::PmtilesBuilder.set_selected_regions(Maptool::App::DATA_DIR, ["SK"]) gate = Queue.new - thread = Maptool::PmtilesBuilder.build_offline(data_dir: Maptool::App::DATA_DIR, fetch_build_url: -> { gate.pop }) + thread = Maptool::PmtilesBuilder.build_offline(data_dir: Maptool::App::DATA_DIR, download_extract: ->(_url, _dest) { gate.pop }) response = request.post("/api/offline-tiles/build", **logged_in_env) assert_equal 409, response.status diff --git a/test/test_pmtiles_builder.rb b/test/test_pmtiles_builder.rb index 8ed619d..7c45401 100644 --- a/test/test_pmtiles_builder.rb +++ b/test/test_pmtiles_builder.rb @@ -3,14 +3,15 @@ require "maptool/pmtiles_builder" require "tmpdir" require "fileutils" -# Network calls (the protomaps build index, the mapterhorn manifest) are +# Network calls (Geofabrik downloads/HEADs, the mapterhorn manifest) are # never exercised here — same "keep this offline/deterministic" reasoning # test_helper.rb already gives for /api/plugins staying off the real -# Overpass API — build_offline/build_terrain take fetch_build_url/ -# fetch_manifest precisely so tests can swap those out. A fake `pmtiles` -# CLI (PMTILES_BIN) stands in for the real go-pmtiles binary, logging each -# invocation's subcommand + first arg to PMTILES_LOG so tests can assert on -# what actually got called (e.g. "was merge invoked, and with what"). +# Overpass API — build_offline/build_terrain take download_extract/ +# fetch_manifest precisely so tests can swap those out. Fake `pmtiles`, +# `osmium`, and `java` binaries (PMTILES_BIN/OSMIUM_BIN/JAVA_BIN) stand in +# for the real tools, logging each invocation's subcommand/first arg to +# PMTILES_LOG so tests can assert on what actually got called (e.g. "was +# merge invoked, and with what"). # # Country codes below are real ones from the vendored # lib/maptool/data/europe_countries.geojson (Slovakia, Iceland, Spain) — @@ -46,8 +47,45 @@ class PmtilesBuilderTest < Minitest::Test File.write(@pmtiles_fail, "#!/bin/sh\necho 'boom' >&2\nexit 1\n") File.chmod(0o755, @pmtiles_fail) + # osmium merge PBF1 PBF2 ... -o OUTPUT — output is always "-o"'s next + # arg, unlike pmtiles' varying-position output above. + @osmium_ok = File.join(@bin_dir, "osmium-ok") + File.write(@osmium_ok, <<~SH) + #!/bin/sh + echo "osmium $1" >> "$PMTILES_LOG" + prev="" + for arg; do + if [ "$prev" = "-o" ]; then echo fake > "$arg"; fi + prev="$arg" + done + SH + File.chmod(0o755, @osmium_ok) + + # Stands in for `java -jar planetiler.jar --osm-path=... --output=OUT` + # — PLANETILER_JAR itself is irrelevant here (this fake ignores -jar's + # argument entirely), only JAVA_BIN needs pointing at it. + @java_ok = File.join(@bin_dir, "java-ok") + File.write(@java_ok, <<~SH) + #!/bin/sh + echo "planetiler ${3%%=*}" >> "$PMTILES_LOG" + for arg; do + case "$arg" in + --output=*) echo fake > "${arg#--output=}" ;; + esac + done + SH + File.chmod(0o755, @java_ok) + + @java_fail = File.join(@bin_dir, "java-fail") + File.write(@java_fail, "#!/bin/sh\necho 'boom' >&2\nexit 1\n") + File.chmod(0o755, @java_fail) + @original_pmtiles_bin = ENV["PMTILES_BIN"] + @original_osmium_bin = ENV["OSMIUM_BIN"] + @original_java_bin = ENV["JAVA_BIN"] ENV["PMTILES_LOG"] = @log + ENV["OSMIUM_BIN"] = @osmium_ok + ENV["JAVA_BIN"] = @java_ok Maptool::PmtilesBuilder.reset! end @@ -55,10 +93,22 @@ class PmtilesBuilderTest < Minitest::Test FileUtils.remove_entry(@data_dir) FileUtils.remove_entry(@bin_dir) ENV["PMTILES_BIN"] = @original_pmtiles_bin + ENV["OSMIUM_BIN"] = @original_osmium_bin + ENV["JAVA_BIN"] = @original_java_bin ENV.delete("PMTILES_LOG") Maptool::PmtilesBuilder.reset! end + # download_extract stub: writes a marker file (same "fake" content the + # pmtiles/osmium/java fixtures use) instead of hitting Geofabrik, and + # returns a fixed Last-Modified so sidecar-meta assertions are stable. + def fake_download(last_modified: "Wed, 10 Sep 2026 08:00:01 GMT") + ->(_url, dest) { + File.write(dest, "fake") + last_modified + } + end + def log_lines File.exist?(@log) ? File.readlines(@log, chomp: true) : [] end @@ -107,21 +157,36 @@ class PmtilesBuilderTest < Minitest::Test def test_build_offline_raises_with_no_selection assert_raises(Maptool::PmtilesBuilder::BuildError) do - Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, fetch_build_url: -> { "unused" }) + Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) end end def test_build_offline_writes_the_file_and_reports_done select_regions("SK") - ENV["PMTILES_BIN"] = @pmtiles_ok - thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, fetch_build_url: -> { "https://example.com/fake.pmtiles" }) + thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) thread.join assert_path_exists File.join(@data_dir, "offline.pmtiles") refute_path_exists File.join(@data_dir, "offline.pmtiles.tmp") assert_equal "done", Maptool::PmtilesBuilder.offline_status[:state] - assert_equal ["extract https://example.com/fake.pmtiles"], log_lines - assert_equal({ "source" => "fake.pmtiles" }, Maptool::PmtilesBuilder.offline_meta(@data_dir)) + assert_equal ["planetiler --osm-path"], log_lines + assert_equal( + { "sources" => [{ "code" => "SK", "slug" => "europe/slovakia", "last_modified" => "Wed, 10 Sep 2026 08:00:01 GMT" }] }, + Maptool::PmtilesBuilder.offline_meta(@data_dir), + ) + end + + # Two countries selected — osmium merge should run before planetiler, + # each downloaded source recorded in the sidecar meta. + def test_build_offline_merges_multiple_selected_countries + select_regions("SK", "AT") + thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) + thread.join + + assert_equal "done", Maptool::PmtilesBuilder.offline_status[:state] + assert_equal ["osmium merge", "planetiler --osm-path"], log_lines + codes = Maptool::PmtilesBuilder.offline_meta(@data_dir)["sources"].map { |s| s["code"] } + assert_equal %w[SK AT], codes end def test_offline_meta_is_nil_before_any_build @@ -129,38 +194,45 @@ class PmtilesBuilderTest < Minitest::Test end def test_offline_update_available_is_nil_before_any_build - assert_nil Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_build_url: -> { "https://example.com/fake.pmtiles" }) + assert_nil Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_last_modified: ->(_url) { "unused" }) end def test_offline_update_available_is_false_when_unchanged select_regions("SK") - ENV["PMTILES_BIN"] = @pmtiles_ok - Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, fetch_build_url: -> { "https://example.com/fake.pmtiles" }).join + Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download).join - refute Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_build_url: -> { "https://example.com/fake.pmtiles" }) + refute Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_last_modified: ->(_url) { "Wed, 10 Sep 2026 08:00:01 GMT" }) end - def test_offline_update_available_is_true_when_a_newer_build_exists + def test_offline_update_available_is_true_when_a_newer_extract_exists select_regions("SK") - ENV["PMTILES_BIN"] = @pmtiles_ok - Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, fetch_build_url: -> { "https://example.com/old.pmtiles" }).join + Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download).join - assert Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_build_url: -> { "https://example.com/new.pmtiles" }) + assert Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_last_modified: ->(_url) { "Thu, 11 Sep 2026 08:00:01 GMT" }) end - def test_offline_update_available_is_nil_when_the_index_is_unreachable + # Same recorded source, but the selection has since grown — recorded + # sources no longer match what the current selection would need. + def test_offline_update_available_is_true_when_the_selection_grew select_regions("SK") - ENV["PMTILES_BIN"] = @pmtiles_ok - Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, fetch_build_url: -> { "https://example.com/fake.pmtiles" }).join + Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download).join - fetch_build_url = -> { raise Maptool::PmtilesBuilder::BuildError, "could not reach index" } - assert_nil Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_build_url: fetch_build_url) + select_regions("SK", "AT") + assert Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_last_modified: ->(_url) { "Wed, 10 Sep 2026 08:00:01 GMT" }) + end + + def test_offline_update_available_is_nil_when_geofabrik_is_unreachable + select_regions("SK") + Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download).join + + fetch_last_modified = ->(_url) { raise Maptool::PmtilesBuilder::BuildError, "could not reach geofabrik" } + assert_nil Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_last_modified: fetch_last_modified) end def test_build_offline_reports_an_error_status_on_non_zero_exit select_regions("SK") - ENV["PMTILES_BIN"] = @pmtiles_fail - thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, fetch_build_url: -> { "https://example.com/fake.pmtiles" }) + ENV["JAVA_BIN"] = @java_fail + thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) thread.join refute_path_exists File.join(@data_dir, "offline.pmtiles") @@ -169,31 +241,30 @@ class PmtilesBuilderTest < Minitest::Test assert_match(/exited with status/, status[:message]) end - def test_build_offline_reports_a_clear_error_when_the_binary_is_missing + def test_build_offline_reports_a_clear_error_when_planetiler_is_missing select_regions("SK") - ENV["PMTILES_BIN"] = File.join(@bin_dir, "does-not-exist") - thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, fetch_build_url: -> { "https://example.com/fake.pmtiles" }) + ENV["JAVA_BIN"] = File.join(@bin_dir, "does-not-exist") + thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) thread.join status = Maptool::PmtilesBuilder.offline_status assert_equal "error", status[:state] - assert_match(/go-pmtiles installed/, status[:message]) + assert_match(/Java and planetiler\.jar installed/, status[:message]) end - # fetch_build_url blocks on a Queue so the build is deterministically + # download_extract blocks on a Queue so the build is deterministically # still "running" when the second call happens, rather than racing a real # one to see which state wins. def test_build_offline_raises_when_one_is_already_running select_regions("SK") - ENV["PMTILES_BIN"] = @pmtiles_ok gate = Queue.new - thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, fetch_build_url: -> { gate.pop }) + thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: ->(_url, _dest) { gate.pop }) assert_raises(Maptool::PmtilesBuilder::BuildError) do - Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, fetch_build_url: -> { "unused" }) + Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) end ensure - gate.push("https://example.com/fake.pmtiles") + gate.push("Wed, 10 Sep 2026 08:00:01 GMT") thread&.join end diff --git a/views/layout.erb b/views/layout.erb index 8a9304e..76e4089 100644 --- a/views/layout.erb +++ b/views/layout.erb @@ -20,7 +20,6 @@ } - <%# Vendored libs above don't change between deploys, so they're left unversioned; app.js's own modules do, and map.js statically imports