A MapLibre GL JS plugin for searching and adding USGS National Map web services to a map. It ships as a standard MapLibre IControl with a React wrapper, and also builds as a GeoLibre Desktop plugin bundle.
- Service Catalog - Browse 20+ USGS National Map services grouped by collapsible categories (Basemaps, Hydrography, Elevation, Cartography, Indexes)
- Search - Filter services by name, title, description, or category; matching categories expand automatically
- Layer Insertion Point - An "Insert before" selector in the panel (or the
beforeIdoption) inserts added layers beneath an existing layer (e.g. labels); changing it re-anchors layers already added - Resizable Panel - Drag the panel edge to resize; works from left and right corner placements
- Live Catalog Refresh - Fetches the latest service listings from the National Map ArcGIS REST endpoints at runtime, with a built-in static catalog as an instant-render fallback (works offline)
- Layer Management - Toggle visibility, adjust opacity, and remove added layers from an "Active layers" section
- Light/Dark Theme - Follows
prefers-color-schemeby default, with athemeoption to force light or dark - Small-Screen Friendly - The panel constrains itself to the map height and scrolls vertically
- Collapsible Control - 29x29 toggle button matching MapLibre's navigation control, with a floating panel
- TypeScript Support - All public types exported from the package root
- React Integration - React wrapper component and custom hook
- GeoLibre Bundle Output - Builds a zip with root
plugin.json, bundled ESM, and CSS for GeoLibre Desktop
The catalog mirrors the map services listed at apps.nationalmap.gov/services. All endpoints are CORS-enabled ArcGIS REST services and need no API key:
| Category | Services | Rendering |
|---|---|---|
| Basemaps | USGS Topo, Imagery Only, Imagery Topo, Shaded Relief, Hydro Cached | Cached XYZ tiles |
| Hydrography | 3DHP, NHD, NHDPlus HR, Watershed Boundary Dataset | Dynamic map export |
| Elevation | 3DEP Elevation (hillshade) | ImageServer export |
| Imagery | NAIP Plus, NAIP False Color Imagery, NAIP NDVI | ImageServer export |
| Cartography | Contours, Geographic Names, Governmental Units, Map Indices, Selectable Polygons, Structures, Transportation, USGS Trails | Dynamic map export |
| Hazards | FEMA National Flood Hazard Layer | Dynamic map export |
| Other Data | Scanned USA Topo Maps, BLM PLSS, FWS National Wetlands Inventory | Cached tiles / map export |
| Indexes | 3DEP Elevation Index, NHDPlus HR Index, Seamless 1m DEM Index, NAIP Imagery Index, US Topo Availability, Special Edition 250K Maps, 3DEP Acquisition Grid | Dynamic map export |
A few services from the page are intentionally excluded: WFS/WCS endpoints and FeatureServers (not raster-displayable), NLCD land cover (WMS landing page only), and partner endpoints that were unreachable at testing time (ScienceBase geology, USGS ecosystems, GAP land cover, earthquake faults, mine symbols, NPS boundaries).
Note: layers added later render on top of earlier ones (or beneath the beforeId layer when configured). Remove and re-add layers to change stacking.
npm install maplibre-gl-national-mapimport maplibregl from "maplibre-gl";
import { NationalMapControl } from "maplibre-gl-national-map";
import "maplibre-gl-national-map/style.css";
const map = new maplibregl.Map({
container: "map",
style: "https://tiles.openfreemap.org/styles/positron",
center: [-98.5, 39.8],
zoom: 4,
});
map.on("load", () => {
const control = new NationalMapControl({
title: "USGS National Map",
collapsed: false,
theme: "auto",
});
map.addControl(control, "top-right");
// Programmatic layer management
control.addService("basemap/USGSTopo");
console.log(control.getActiveLayers());
});import { useEffect, useRef, useState } from "react";
import maplibregl, { Map } from "maplibre-gl";
import {
NationalMapControlReact,
useNationalMapState,
} from "maplibre-gl-national-map/react";
import "maplibre-gl-national-map/style.css";
function App() {
const mapContainer = useRef<HTMLDivElement>(null);
const [map, setMap] = useState<Map | null>(null);
const { state, toggle } = useNationalMapState();
useEffect(() => {
if (!mapContainer.current) return;
const mapInstance = new maplibregl.Map({
container: mapContainer.current,
style: "https://tiles.openfreemap.org/styles/positron",
center: [-98.5, 39.8],
zoom: 4,
});
mapInstance.on("load", () => setMap(mapInstance));
return () => mapInstance.remove();
}, []);
return (
<div style={{ width: "100%", height: "100vh" }}>
<div ref={mapContainer} style={{ width: "100%", height: "100%" }} />
{map && (
<NationalMapControlReact
map={map}
title="National Map"
collapsed={state.collapsed}
theme="auto"
onStateChange={(newState) => console.log(newState)}
/>
)}
</div>
);
}The main control class implementing MapLibre's IControl interface.
| Option | Type | Default | Description |
|---|---|---|---|
collapsed |
boolean |
true |
Whether the panel starts collapsed (showing only the 29x29 toggle button) |
position |
string |
'top-right' |
Control position on the map |
title |
string |
'USGS National Map' |
Title displayed in the header |
panelWidth |
number |
320 |
Initial width of the dropdown panel in pixels (user-resizable by dragging the panel edge) |
className |
string |
'' |
Custom CSS class name |
theme |
string |
'auto' |
Color theme: 'light', 'dark', or 'auto' (follows prefers-color-scheme) |
beforeId |
string |
undefined |
Existing layer id to insert added layers before, so services render underneath it (ignored if the layer does not exist). Also adjustable at runtime via the panel's "Insert before" selector |
toggle()- Toggle the collapsed stateexpand()- Expand the panelcollapse()- Collapse the panelgetState()- Get the current state (includesactiveLayerIds)setState(state)- Update the stateaddService(serviceId)- Add a catalog service to the map (e.g."basemap/USGSTopo")removeService(serviceId)- Remove a previously added servicesetServiceOpacity(serviceId, opacity)- Set the opacity (0–1) of an active servicesetServiceVisibility(serviceId, visible)- Show or hide an active servicegetActiveLayers()- Get the services currently added to the mapsetTheme(theme)- Change the theme at runtimeon(event, handler)- Register an event handleroff(event, handler)- Remove an event handlergetMap()- Get the map instancegetContainer()- Get the container element
collapse- Fired when the panel is collapsedexpand- Fired when the panel is expandedstatechange- Fired when the state changeslayeradd- Fired when a service is added to the map (event.serviceis the service)layerremove- Fired when a service is removed from the map
React wrapper component for NationalMapControl.
All NationalMapControl options plus:
| Prop | Type | Description |
|---|---|---|
map |
Map |
MapLibre GL map instance (required) |
onStateChange |
function |
Callback fired when state changes |
Custom React hook for managing control state.
const {
state, // Current state
setState, // Replace the state
setCollapsed, // Set collapsed state
setPanelWidth, // Set panel width
setData, // Merge custom data
reset, // Reset to defaults
toggle, // Toggle collapsed
} = useNationalMapState({ collapsed: false });The catalog and spec builders are exported for advanced use without the UI control:
import {
STATIC_CATALOG, // Built-in service catalog
fetchCatalog, // Live catalog fetch with static fallback
buildLayerSpec, // NationalMapService -> MapLibre source/layer specs
filterServices, // Search the catalog
groupByCategory, // Group services for display
LayerManager, // Add/remove/visibility/opacity on a map
} from "maplibre-gl-national-map";
const spec = buildLayerSpec(STATIC_CATALOG[0]);
map.addSource(spec.sourceId, spec.source);
map.addLayer(spec.layer);Exported types include NationalMapService, NationalMapControlOptions, NationalMapState, NationalMapTheme, NationalMapHost, NationalMapCategory, ServiceType, ServiceRenderMode, ActiveLayer, LayerSpec, and CategoryGroup.
The control uses CSS custom properties scoped to .national-map and .national-map-panel. With theme: 'auto' (default), dark colors apply when the OS is in dark mode. Use theme: 'light' or theme: 'dark' to force a theme, or override the --nm-* variables in your own CSS:
.national-map-panel {
--nm-accent: #2e7d32;
--nm-accent-hover: #1b5e20;
}Run locally:
npm install
npm run dev- Basic: http://localhost:5173/examples/basic/
- React (with theme switcher): http://localhost:5173/examples/react/
GeoLibre Desktop loads external plugins from an app data plugins/ directory. The zip must contain plugin.json at the root, plus a bundled ESM entry and optional CSS file.
npm install
npm run package:geolibreThis creates:
geolibre-plugin/maplibre-gl-national-map-0.1.0.zip
Copy the zip into GeoLibre Desktop's app data plugins/ directory and restart GeoLibre. On Linux with the default app identifier, that directory is usually:
~/.local/share/org.geolibre.desktop/plugins/
For the GeoLibre web app, serve the unpacked plugin with CORS enabled:
npm run package:geolibre
npm run serve:geolibre -- 8000Then add this manifest URL in GeoLibre Settings > Plugins:
http://localhost:8000/plugin.json
npm install # Install dependencies
npm run dev # Start the dev server
npm test # Run the test suite
npm run lint # Lint
npm run build # Build the library and GeoLibre bundledocker build -t maplibre-gl-national-map .
docker run -p 8080:80 maplibre-gl-national-map
# Open http://localhost:8080/maplibre-gl-national-map/MIT