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

+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 };