Add a Home button and fill icon gaps from NPS's newer symbol library
CI / check (push) Successful in 42s
CI / deploy (push) Successful in 18s

Two things pulled in from github.com/nationalparkservice/npmap5-plugins:

- A "Home" button (public/vendor/npmap-home-button/, NPS's own MapLibre
  control) pinned to the app's CENTER/ZOOM, sitting top-left on the map. Now
  that the URL hash permalink can open the map at any view, "current view"
  and "the default view" aren't the same thing anymore, so a quick way back
  to the default is worth having.

- npmap5-plugins bundles a newer, actively-maintained version of the same
  NPS symbol-library already used for POI_ICONS (confirmed by its
  maintainer to be the designated replacement for the older standalone
  repo: nationalparkservice/npmap5-plugins#4), with a much larger icon
  vocabulary and a simpler CC0 (public domain) license. All 25 icons
  already in use came back byte-identical from the new source, so this
  just repoints their attribution — but it also has icons the old repo
  didn't: `forest` (POI_ICONS) is now the official "wilderness" pictogram
  instead of hand-drawn, and three of the five previously icon-less
  Services/Activities tags (wifi, mobile data coverage, winter/snow
  camping) now have real icons too. Only bakery and vehicle wash still
  have no NPS equivalent.
This commit is contained in:
randy committed 2026-09-07 19:45:19 +02:00
1 parent 481233c232
commit 170af80cac
6 files changed
+235 -39

No files matched your search

+20 -10
View File
@@ -117,6 +117,14 @@ Maps use, so copying the URL bar shares the exact spot someone's
looking at, and reloading the page doesn't reset the view back to the looking at, and reloading the page doesn't reset the view back to the
default. default.
Now that any view can be the starting one (via that permalink), a way
back to the app's actual default is worth having: a "Home" button
(`public/vendor/npmap-home-button/`, the National Park Service's own
`home-button` MapLibre control — see `docs/third-party-notices.adoc`)
sits top-left on the map, pinned to `CENTER`/`ZOOM` explicitly rather
than its own fallback (the view at construction time, which would just
be wherever a loaded permalink happened to point).
== Weather radar overlay == Weather radar overlay
`public/js/radar.js` adds a "Radar" map control that overlays recent `public/js/radar.js` adds a "Radar" map control that overlays recent
@@ -252,13 +260,14 @@ categories p4n2gpx actually uses, not an exhaustive OsmAnd icon set)
draws a white glyph on top of the badge for any waypoint whose `icon` draws a white glyph on top of the badge for any waypoint whose `icon`
matches a known key (filtered out entirely for anything else, so an matches a known key (filtered out entirely for anything else, so an
unrecognized icon key just falls back to a plain colored badge rather unrecognized icon key just falls back to a plain colored badge rather
than a broken/missing image warning). Six of the seven glyphs are the than a broken/missing image warning). All seven glyphs are the National
National Park Service's own map symbols Park Service's own map symbols — pulled from
(`github.com/nationalparkservice/symbol-library`, BSD-licensed, `github.com/nationalparkservice/npmap5-plugins`'s bundled
originally forked from Mapbox's Maki project) — a professional, `symbol-library` (CC0-licensed, an actively-maintained monorepo that's
the designated replacement for the same org's older standalone
`symbol-library` repo, per its maintainer) — a professional,
purpose-built icon set beats hand-drawing "fuel pump"/"picnic table" purpose-built icon set beats hand-drawing "fuel pump"/"picnic table"
glyphs from scratch; `forest` has no NPS equivalent (that set covers glyphs from scratch.
facilities, not land cover) and stays hand-drawn.
+ +
Both the badge and the glyphs are rasterized from their source SVGs Both the badge and the glyphs are rasterized from their source SVGs
(an `<img>` → `<canvas>` → `ImageData` round trip, since `map.addImage` (an `<img>` → `<canvas>` → `ImageData` round trip, since `map.addImage`
@@ -366,10 +375,11 @@ instead. That table covers the codes p4n2gpx's exports actually use
(not park4night's whole API), with a human label always and an icon (not park4night's whole API), with a human label always and an icon
only where the icon set below has a reasonable match — most of the only where the icon set below has a reasonable match — most of the
icons are National Park Service symbols again (same source/license as icons are National Park Service symbols again (same source/license as
`POI_ICONS`), a few (bakery, wifi, vehicle wash, ...) have no NPS `POI_ICONS`); a couple (bakery, vehicle wash) have no NPS equivalent
equivalent and get a label with no icon. A code this table doesn't even in that larger set and get a label with no icon. A code this
cover at all just renders as its own raw text, same as before this table doesn't cover at all just renders as its own raw text, same as
existed — nothing p4n2gpx might add later is silently dropped. before this existed — nothing p4n2gpx might add later is silently
dropped.
Track properties:: Every track feature (both formats) also carries Track properties:: Every track feature (both formats) also carries
`distance_meters`, `elevation_gain_meters`, `duration_secs`, `distance_meters`, `elevation_gain_meters`, `duration_secs`,
+15
View File
@@ -23,6 +23,21 @@ BSD-3-Clause licensed.
| https://github.com/protomaps/basemaps | https://github.com/protomaps/basemaps
|=== |===
Also vendored under `public/vendor/`, but not BSD-3-Clause like the
above — MIT-licensed per its own `package.json` (the monorepo's
top-level `LICENSE.md` separately places the whole repo in the public
domain as a work of the US government; either way, no attribution is
required):
[cols="1,1,2"]
|===
| Library | Version | Source
| NPS Home Button (`home-button`)
| 0.0.1-d
| https://github.com/nationalparkservice/npmap5-plugins/tree/main/home-button
|===
Offline map text labels need font glyphs too (see Offline map text labels need font glyphs too (see
`docs/architecture.adoc`), vendored under `public/vendor/fonts/` — `docs/architecture.adoc`), vendored under `public/vendor/fonts/` —
SIL Open Font License, not BSD-3-Clause like the above: SIL Open Font License, not BSD-3-Clause like the above:
+40 -29
View File
@@ -75,25 +75,25 @@ export const MAP_MARKER_ICON = `
// the categories park4night actually uses (see waypoint_style in // the categories park4night actually uses (see waypoint_style in
// lib/maptool/gpx.rb), not an exhaustive OsmAnd icon set. // lib/maptool/gpx.rb), not an exhaustive OsmAnd icon set.
// //
// Six of these are the National Park Service's own map pictograms // These are the National Park Service's own map pictograms, not
// (github.com/nationalparkservice/symbol-library, src/standalone/*-black-22), // hand-drawn — a professional, widely-recognized icon set built for
// not hand-drawn — a professional, widely-recognized icon set built for
// exactly this job (small monochrome map markers) beats reinventing "fuel // exactly this job (small monochrome map markers) beats reinventing "fuel
// pump"/"picnic table" glyphs from scratch. Used under that repo's license: // pump"/"picnic table" glyphs from scratch. Pulled from
// github.com/nationalparkservice/npmap5-plugins's bundled `symbol-library`
// (src/<name>-22.svg) — an actively-maintained monorepo that's the
// designated replacement for the same org's older standalone
// `symbol-library` repo (confirmed by its maintainer:
// github.com/nationalparkservice/npmap5-plugins/issues/4), with a much
// larger icon vocabulary. CC0-licensed (public domain) — no attribution
// required.
// //
// BSD License. Copyright (c) 2013, Mapbox, LLC. All rights reserved. // All filled shapes — these render at ~14px on the map, where a hairline
// Redistribution and use in source and binary forms, with or without // stroke disappears — and rasterized (non-SDF; a plain colored raster, not
// modification, are permitted provided that the copyright notice and // tinted at render time, see gpx.js's _registerCategoryGlyphs) so only the
// this list of conditions are retained. // alpha shape matters, not the actual `currentColor` value.
//
// `forest` has no NPS equivalent (that set covers facilities, not land
// cover) and stays hand-drawn. All filled shapes — these render at ~14px on
// the map, where a hairline stroke disappears — and rasterized/used as SDF
// map icons (see gpx.js), so only the alpha shape matters, not the actual
// `currentColor` value.
// src/standalone/bicycle-trail-black-22.svg — pulled out to its own constant // src/bicycle-trail-22.svg — pulled out to its own constant since it's also
// since it's also used unrasterized, inline, as PARK4NIGHT_TAGS.vtt below. // used unrasterized, inline, as PARK4NIGHT_TAGS.vtt below.
const BICYCLE_TRAIL_ICON = ` const BICYCLE_TRAIL_ICON = `
<svg viewBox="0 0 22 22" fill="currentColor"> <svg viewBox="0 0 22 22" fill="currentColor">
<circle cx="13.9" cy="3" r="2"/> <circle cx="13.9" cy="3" r="2"/>
@@ -107,12 +107,12 @@ const BICYCLE_TRAIL_ICON = `
</svg>`; </svg>`;
export const POI_ICONS = { export const POI_ICONS = {
// src/standalone/parking-black-22.svg // src/parking-22.svg
amenity_parking: ` amenity_parking: `
<svg viewBox="0 0 22 22"><path fill="currentColor" d="M11.9,11c1.8,0,3.5-1.8,3.5-3.6S13.7,4,11.9,4h-5v7H11.9z M12.1,0C16.2,0,20,3.3,20,7.5c0,4.1-3.9,7.5-8.1,7.5h-5v7H3V0 <svg viewBox="0 0 22 22"><path fill="currentColor" d="M11.9,11c1.8,0,3.5-1.8,3.5-3.6S13.7,4,11.9,4h-5v7H11.9z M12.1,0C16.2,0,20,3.3,20,7.5c0,4.1-3.9,7.5-8.1,7.5h-5v7H3V0
H12.1z"/></svg>`, H12.1z"/></svg>`,
// src/standalone/campground-black-22.svg // src/campground-22.svg
tourism_camp_site: ` tourism_camp_site: `
<svg viewBox="0 0 22 22" fill="currentColor"> <svg viewBox="0 0 22 22" fill="currentColor">
<path d="M17,21L9.4,5.8L2,21H0v1h19v-1H17z M5,21l4.5-8.5L14,21H5z"/> <path d="M17,21L9.4,5.8L2,21H0v1h19v-1H17z M5,21l4.5-8.5L14,21H5z"/>
@@ -120,18 +120,18 @@ export const POI_ICONS = {
<polygon points="15.5,0 14.7,1.7 22,16 22,13 "/> <polygon points="15.5,0 14.7,1.7 22,16 22,13 "/>
</svg>`, </svg>`,
// src/standalone/picnic-area-black-22.svg // src/picnic-area-22.svg
leisure_picnic_table: ` leisure_picnic_table: `
<svg viewBox="0 0 22 22"><path fill="currentColor" d="M22,11h-6l-2-5h4V4H4v2h4l-2,5H0v2h5l-2,5h2l2-5h8l2,5h2l-2-5h5V11z M8,11l2-5h2l2,5H8z"/></svg>`, <svg viewBox="0 0 22 22"><path fill="currentColor" d="M22,11h-6l-2-5h4V4H4v2h4l-2,5H0v2h5l-2,5h2l2-5h8l2,5h2l-2-5h5V11z M8,11l2-5h2l2,5H8z"/></svg>`,
// src/standalone/gas-station-black-22.svg // src/gas-station-22.svg
amenity_fuel: ` amenity_fuel: `
<svg viewBox="0 0 22 22"><path fill="currentColor" d="M19,14.4L19,14.4v-1.5V6.2c0-0.7-0.4-1.3-0.9-1.8L15,1.5c-0.2,0-0.8,0.4-0.8,0.4c0,0.2,1.5,1.8,1.5,1.8v2.6 <svg viewBox="0 0 22 22"><path fill="currentColor" d="M19,14.4L19,14.4v-1.5V6.2c0-0.7-0.4-1.3-0.9-1.8L15,1.5c-0.2,0-0.8,0.4-0.8,0.4c0,0.2,1.5,1.8,1.5,1.8v2.6
c0,0.9,0.8,1.8,1.7,1.8L18,7.9v5v1.7l1,5.1c0,0.8-0.9,1.3-1.5,1.3C17,21,16,20.5,16,20v-8.9C16,10,15.4,9,14.4,9H12V1h-1V0H3v1H2 c0,0.9,0.8,1.8,1.7,1.8L18,7.9v5v1.7l1,5.1c0,0.8-0.9,1.3-1.5,1.3C17,21,16,20.5,16,20v-8.9C16,10,15.4,9,14.4,9H12V1h-1V0H3v1H2
v21h10V10.4h2.4c0.4,0,0.6,0.3,0.6,0.7V20c0,1.2,1.2,2,2.5,2c1.2,0,2.5-0.8,2.5-2C20,19.7,19,14.4,19,14.4z M18,7h-1V5h1V7z v21h10V10.4h2.4c0.4,0,0.6,0.3,0.6,0.7V20c0,1.2,1.2,2,2.5,2c1.2,0,2.5-0.8,2.5-2C20,19.7,19,14.4,19,14.4z M18,7h-1V5h1V7z
M10.5,5.8L9.6,6.6H4.4L3.5,5.8V2.2l0.9-0.9h5.2l0.9,0.9V5.8z"/></svg>`, M10.5,5.8L9.6,6.6H4.4L3.5,5.8V2.2l0.9-0.9h5.2l0.9,0.9V5.8z"/></svg>`,
// src/standalone/lodging-black-22.svg // src/lodging-22.svg
tourism_hotel: ` tourism_hotel: `
<svg viewBox="0 0 22 22" fill="currentColor"> <svg viewBox="0 0 22 22" fill="currentColor">
<polygon points="0,4 2,4 2,12 22,12 22,17 20,17 20,14 2,14 2,17 0,17 "/> <polygon points="0,4 2,4 2,12 22,12 22,17 20,17 20,14 2,14 2,17 0,17 "/>
@@ -139,13 +139,11 @@ export const POI_ICONS = {
<circle cx="4.5" cy="6.5" r="1.5"/> <circle cx="4.5" cy="6.5" r="1.5"/>
</svg>`, </svg>`,
// src/wilderness-22.svg
forest: ` forest: `
<svg viewBox="0 0 24 24"> <svg viewBox="0 0 22 22" fill="currentColor"><polygon points="18,18 13,15 17,15 13,11 16,11 12,7 15,7 12,4 14,4 11,0 8,4 10,4 7,7 10,7 6,11 9,11 5,15 9,15 4,18 10,18 10,22 12,22 12,18 "/></svg>`,
<path d="M12 2 18 12 15 12 19 19 5 19 9 12 6 12Z" fill="currentColor"/>
<rect x="10.5" y="19" width="3" height="3" fill="currentColor"/>
</svg>`,
// src/standalone/bicycle-trail-black-22.svg // src/bicycle-trail-22.svg
sport_cycling: BICYCLE_TRAIL_ICON, sport_cycling: BICYCLE_TRAIL_ICON,
}; };
@@ -213,6 +211,19 @@ const GUIDED_TOURS_ICON = `
const WIND_SURFING_ICON = ` const WIND_SURFING_ICON = `
<svg viewBox="0 0 22 22" fill="currentColor"><polygon points="21,20 19,19 18,19 16,20 15,20 13,19 12,19 10,20 9,20 8,20 8,19.5 7,19 6,19 4,20 3,20 1,19 1,21 3,22 4,22 6,21 7,21 9,22 10,22 12,21 13,21 15,22 16,22 18,21 19,21 21,22 22,22 22,20 "/><path d="M10.7,17.5l-2.5-2.4C8.1,15,7.9,15,7.7,15H5l-0.4-1H4v-1h0.2L4,12.4h1.4c0,0,0.3,0,0.4-0.1l2.3-1c0.2-0.1,0.3-0.3,0.3-0.5 c0-0.4-0.3-0.7-0.6-0.7c-0.1,0-0.2,0-0.2,0l-2.1,1h-3c-0.1,0-0.2,0-0.4,0.1V12h-1C1,12.1,1,12.2,1,12.4c0,0.1,0,0.2,0,0.3l1.4,3.2 c0.2,0.5,0.7,0.8,1.2,0.8h3.7l2.2,2.1c0.3,0.3,0.9,0.3,1.2,0C11.1,18.4,11.1,17.8,10.7,17.5z"/><path d="M7,0l7.1,18l7.9-4.9C21.5,5.7,13.4,0.8,7,0z M18,10c-1.5,0-6,0-6,0V9c0,0,4.5,0,6,0c1.8,0,3,2,3,2v1h-1C20,12,18.8,10,18,10 z"/><circle cx="1.5" cy="9.5" r="1.5"/></svg>`; <svg viewBox="0 0 22 22" fill="currentColor"><polygon points="21,20 19,19 18,19 16,20 15,20 13,19 12,19 10,20 9,20 8,20 8,19.5 7,19 6,19 4,20 3,20 1,19 1,21 3,22 4,22 6,21 7,21 9,22 10,22 12,21 13,21 15,22 16,22 18,21 19,21 21,22 22,22 22,20 "/><path d="M10.7,17.5l-2.5-2.4C8.1,15,7.9,15,7.7,15H5l-0.4-1H4v-1h0.2L4,12.4h1.4c0,0,0.3,0,0.4-0.1l2.3-1c0.2-0.1,0.3-0.3,0.3-0.5 c0-0.4-0.3-0.7-0.6-0.7c-0.1,0-0.2,0-0.2,0l-2.1,1h-3c-0.1,0-0.2,0-0.4,0.1V12h-1C1,12.1,1,12.2,1,12.4c0,0.1,0,0.2,0,0.3l1.4,3.2 c0.2,0.5,0.7,0.8,1.2,0.8h3.7l2.2,2.1c0.3,0.3,0.9,0.3,1.2,0C11.1,18.4,11.1,17.8,10.7,17.5z"/><path d="M7,0l7.1,18l7.9-4.9C21.5,5.7,13.4,0.8,7,0z M18,10c-1.5,0-6,0-6,0V9c0,0,4.5,0,6,0c1.8,0,3,2,3,2v1h-1C20,12,18.8,10,18,10 z"/><circle cx="1.5" cy="9.5" r="1.5"/></svg>`;
// These three have no equivalent in the older standalone symbol-library
// repo (see POI_ICONS above) — pulled from npmap5-plugins' bundled,
// actively-maintained symbol-library instead (CC0-licensed), which has a
// much larger vocabulary.
const WIFI_ICON = `
<svg viewBox="0 0 22 22" fill="currentColor"><g><path d="M11,11.9c-1.9,0-3.6,0.8-4.8,2l1.6,1.6c0.8-0.8,1.9-1.3,3.1-1.3c1.2,0,2.3,0.5,3.1,1.3l1.6-1.6 C14.6,12.7,12.9,11.9,11,11.9z"/><path d="M11,7c-3.1,0-5.9,1.3-7.9,3.3l1.6,1.6c1.6-1.6,3.8-2.6,6.2-2.6c2.4,0,4.6,1,6.2,2.6l1.6-1.6C16.9,8.3,14.1,7,11,7z"/><path d="M11,4.5c3.7,0,7,1.5,9.4,3.9L22,6.7c-2.8-2.8-6.7-4.6-11-4.6c-4.3,0-8.2,1.7-11,4.6l1.6,1.6C4,6,7.3,4.5,11,4.5z"/><circle cx="11" cy="18.3" r="1.6"/></g></svg>`;
const CELLULAR_SIGNAL_ICON = `
<svg viewBox="0 0 22 22" fill="currentColor"><path d="M18.8,3.2l-1.4,1.4C19,6.3,20,8.5,20,11s-1,4.7-2.6,6.4l1.4,1.4c2-2,3.2-4.7,3.2-7.8C22,8,20.8,5.2,18.8,3.2 z"/><path d="M2,11c0-2.5,1-4.7,2.6-6.4L3.2,3.2C1.2,5.2,0,8,0,11c0,3,1.2,5.8,3.2,7.8l1.4-1.4C3,15.7,2,13.5,2,11z"/><path d="M6.1,15.9l1.4-1.4C6.6,13.6,6,12.4,6,11c0-1.4,0.6-2.6,1.5-3.5L6.1,6.1C4.8,7.3,4,9.1,4,11 S4.8,14.7,6.1,15.9z"/><path d="M16,11c0,1.4-0.6,2.6-1.5,3.5l1.4,1.4c1.3-1.3,2.1-3,2.1-4.9c0-1.9-0.8-3.7-2.1-4.9l-1.4,1.4 C15.4,8.4,16,9.6,16,11z"/><path d="M12,13.8c1.2-0.4,2-1.5,2-2.8c0-1.7-1.3-3-3-3s-3,1.3-3,3c0,1.3,0.8,2.4,2,2.8L8.9,20H8v2h0.6h1h2.9h1H14v-2 h-0.9L12,13.8z M9.9,20l1.1-6c0,0,0,0,0,0s0,0,0,0l1.1,6H9.9z"/></svg>`;
const WINTER_RECREATION_AREA_ICON = `
<svg viewBox="0 0 22 22" fill="currentColor"><path d="M10.1,21.4v-2.9l-1.6,0.9l-0.8-0.5v-1l2.4-1.4v-1.9l-1.7,1.1v-2l-1.7,1v2.7L5.7,18l-0.8-0.5v-1.8l-2.5,1.4l-1.1-0.5v-1.2 L4,14l-1.7-0.8v-1.1l0.9-0.5L5.6,13l1.7-1l0,0l-1.7-1l1.6-1l-1.7-1l-2.4,1.4L2.3,10V9l1.6-0.9L1.3,6.7V5.5l1-0.5l2.5,1.5V4.6 l0.8-0.5l0.9,0.5l0,2.8l1.7,1l0-2l1.8,1V5.5L7.6,4.1v-1l0.8-0.5l1.6,0.9V0.6L11,0l0.9,0.6v2.9l1.7-0.9l0.8,0.6V4L12,5.5v1.9l1.7-1 v2.1l1.8-1v-3l0.8-0.4l0.9,0.4v1.9L19.8,5l0.9,0.6v1.1l-2.6,1.5l1.6,0.9v1l-0.8,0.5L16.5,9l-1.7,1.1l1.6,1l-1.7,1l1.7,0.9l2.5-1.4 l0.8,0.6v1L18.2,14l2.5,1.5v1.1l-1,0.5l-2.4-1.4v1.8L16.3,18l-0.8-0.5v-2.8l-1.8-1v1.9l-1.7-1v1.9l2.4,1.4V19l-0.8,0.4l-1.6-0.9v2.9 L11,22L10.1,21.4z M9.4,11.1l0.9,1.3h1.6l0.7-1.3l-0.8-1.4h-1.6L9.4,11.1z"/></svg>`;
// park4night's fixed vocabulary of Services/Activities codes (the ones // park4night's fixed vocabulary of Services/Activities codes (the ones
// p4n2gpx exports actually use — not necessarily park4night's entire API), // p4n2gpx exports actually use — not necessarily park4night's entire API),
// each with a human label (always shown) and an icon where the NPS set has // each with a human label (always shown) and an icon where the NPS set has
@@ -223,8 +234,8 @@ export const PARK4NIGHT_TAGS = {
// Services // Services
animaux: { icon: PETS_ON_LEASH_ICON, label: "Pets allowed" }, animaux: { icon: PETS_ON_LEASH_ICON, label: "Pets allowed" },
boulangerie: { label: "Bakery nearby" }, boulangerie: { label: "Bakery nearby" },
caravaneige: { label: "Winter/snow camping" }, caravaneige: { icon: WINTER_RECREATION_AREA_ICON, label: "Winter/snow camping" },
donnees_mobile: { label: "Mobile data coverage" }, donnees_mobile: { icon: CELLULAR_SIGNAL_ICON, label: "Mobile data coverage" },
douche: { icon: SHOWERS_ICON, label: "Showers" }, douche: { icon: SHOWERS_ICON, label: "Showers" },
eau_noire: { icon: SANITARY_DISPOSAL_ICON, label: "Black water disposal" }, eau_noire: { icon: SANITARY_DISPOSAL_ICON, label: "Black water disposal" },
eau_usee: { icon: SANITARY_DISPOSAL_ICON, label: "Grey water disposal" }, eau_usee: { icon: SANITARY_DISPOSAL_ICON, label: "Grey water disposal" },
@@ -237,7 +248,7 @@ export const PARK4NIGHT_TAGS = {
point_eau: { icon: DRINKING_WATER_ICON, label: "Drinking water" }, point_eau: { icon: DRINKING_WATER_ICON, label: "Drinking water" },
poubelle: { icon: TRASH_DUMPSTER_ICON, label: "Trash disposal" }, poubelle: { icon: TRASH_DUMPSTER_ICON, label: "Trash disposal" },
wc_public: { icon: RESTROOMS_ICON, label: "Public toilet" }, wc_public: { icon: RESTROOMS_ICON, label: "Public toilet" },
wifi: { label: "Wi-Fi" }, wifi: { icon: WIFI_ICON, label: "Wi-Fi" },
// Activities // Activities
baignade: { icon: SWIMMING_ICON, label: "Swimming" }, baignade: { icon: SWIMMING_ICON, label: "Swimming" },
+8
View File
@@ -11,6 +11,7 @@ import { LiveTrackPanelControl } from "/js/livetrack.js";
import { TrackProfilePanel } from "/js/track_profile.js"; import { TrackProfilePanel } from "/js/track_profile.js";
import { LAYERS_ICON } from "/js/icons.js"; import { LAYERS_ICON } from "/js/icons.js";
import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js"; import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
import HomeButton from "/vendor/npmap-home-button/home-button.esm.js";
const CENTER = [17.1077, 48.1486]; const CENTER = [17.1077, 48.1486];
const ZOOM = 13; const ZOOM = 13;
@@ -133,6 +134,13 @@ map.addControl(
}), }),
"bottom-right", "bottom-right",
); );
// homePosition/zoom pinned to the app's own default rather than left to the
// button's own fallback (current center/zoom at construction) — now that
// the URL hash can restore any view on load (see the Hash option above),
// "current view" and "the default view" are no longer the same thing, and
// a home button that resets to wherever you happened to load the page
// wouldn't be a home button.
map.addControl(new HomeButton({ homePosition: CENTER, zoom: ZOOM }), "top-left");
const sidebar = document.getElementById("sidebar"); const sidebar = document.getElementById("sidebar");
+5
View File
@@ -0,0 +1,5 @@
### LICENSE.md
This project constitutes a work of the United States Government and is not subject to domestic copyright protection under [17 USC § 105](https://www.govinfo.gov/app/details/USCODE-2010-title17/USCODE-2010-title17-chap1-sec105).
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+147
View File
@@ -0,0 +1,147 @@
class HomeButton {
static createSvgElement() {
// Create the SVG element
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 576 512");
svg.setAttribute("fill", "#333");
svg.setAttribute("stroke", "currentColor");
svg.setAttribute("stroke-width", "2");
svg.setAttribute("stroke-linecap", "round");
svg.setAttribute("stroke-linejoin", "round");
svg.setAttribute("class", "feather feather-home");
svg.setAttribute("width", '80%');
// Create the path for the house
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", "M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z");
// Append the path and polyline to the SVG
svg.appendChild(path);
return svg;
}
/**
* Class constructor which calls the `setOptions` function to merge
* the default options and user-provided options.
*
* @param {homeButtonOptions} options - The options provided by the user
*/
constructor(options = {}) {
this.defaultOptions = HomeButton.DefaultOptions;
this._options = this.setOptions(options);
}
/**
* Sets the button options by merging the default options with user-provided options.
* Overrides the default `fitBoundsOptions` if any are provided by the user.
*
* @param {homeButtonOptions} options - The options provided by the user
* @returns {homeButtonOptions} - The merged options
*/
setOptions(options) {
// Merge fitBoundsOptions from default and user-provided options
const fitBoundsOptions = {
...this.defaultOptions.fitBoundsOptions,
...options.fitBoundsOptions
};
// Merge all options
this._options = {
...this.defaultOptions,
...options,
fitBoundsOptions
};
return this._options;
}
/**
* Setter for the `options` property.
*
* @param {homeButtonOptions} options - The options provided by the user
*/
set options(options) {
this.setOptions(options);
}
/**
* Creates a DOM div element with a child button element.
* The div is assigned CSS classes for styling, and the button has ARIA attributes for accessibility.
* The button also has an event listener to prevent the context menu from appearing on right click.
*
* @returns {HTMLDivElement} The created div element with its child button.
*/
createDiv() {
const div = document.createElement("div");
div.className = "maplibregl-ctrl maplibregl-ctrl-group";
const button = document.createElement("button");
// Add ARIA attributes to the button for accessibility
button.setAttribute('aria-label', 'Pan/zoom to initial extent'); // For screen readers
button.alt = 'Pan/zoom to initial extent'; // For legacy screen readers
button.appendChild(this._options.icon);
div.appendChild(button);
div.addEventListener("contextmenu", (e) => e.preventDefault());
this.div = div;
return this.div;
}
/**
* Adds the created div to the map and sets up its click behavior.
* If the `bounds` option is set, the map will fit to these bounds when the div is clicked.
* If the `bounds` option is not set, the map will fly to the `homePosition` at the specified `zoom` level.
* If the `homePosition` and `zoom` level are not set, it defaults to the current map center and zoom level.
*
* @param {MapLibraryMap} map - The map to which the div is being added.
* @returns {HTMLDivElement} The div that has been added to the map.
*/
onAdd(map) {
// Create the div
const div = this.createDiv();
// Destructure the options for cleaner code
let { bounds, fitBoundsOptions } = this._options;
// If the homePosition and zoom options are not set, use the current map center and zoom level
const center = this._options.homePosition || map.getCenter();
const zoom = this._options.zoom || map.getZoom();
// Add a click event listener to the div
div.addEventListener("click", () => {
// If the bounds option is set, fit the map to these bounds
if (bounds) {
map.fitBounds(bounds, fitBoundsOptions);
}
// If bounds option is not set, fly the map to the home position or current map center at the specified zoom level
else {
map.flyTo({
center,
zoom,
// Merge in the fitBoundsOptions, but exclude 'linear' and 'maxZoom' properties as they are not compatible with flyTo
...{
...fitBoundsOptions,
linear: undefined,
maxZoom: undefined
}
});
}
});
// Return the div to be added to the map
return div;
}
/**
* Removes the div from the DOM and resets the `div` property.
* If `div` does not exist, the function simply exits.
*/
onRemove() {
// Check if this.div exists
if (this.div) {
// If it does, remove the div from the DOM
this.div.remove();
}
// Reset the div property to undefined
this.div = undefined;
}
getDefaultPosition() {
return 'top-left';
}
}
HomeButton.DefaultOptions = {
homePosition: null,
zoom: null,
bounds: null,
fitBoundsOptions: {
pitch: 0,
bearing: 0
},
icon: HomeButton.createSvgElement()
};
export { HomeButton as default };