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

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