@gisfun/maplibre-gl-components 0.15.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1960 -0
  3. package/dist/ControlGrid-DN5md8hp.cjs +19636 -0
  4. package/dist/ControlGrid-rVNG7B9O.js +170422 -0
  5. package/dist/DuckDBConverter-B98M0DFs.cjs +23 -0
  6. package/dist/DuckDBConverter-RPq48-t0.js +434 -0
  7. package/dist/ShapefileConverter-AjbEjEyq.cjs +1 -0
  8. package/dist/ShapefileConverter-trvt8J3z.js +125 -0
  9. package/dist/decoder-CLokFc0V.js +8 -0
  10. package/dist/decoder-D9LU4bUo.cjs +1 -0
  11. package/dist/deflate-BA1jZeSX.js +10 -0
  12. package/dist/deflate-DEdCz12a.cjs +1 -0
  13. package/dist/geojson-BQSVgKFt.cjs +1 -0
  14. package/dist/geojson-BSUuDj5k.js +2551 -0
  15. package/dist/geotiff-De1w1lBy.cjs +8 -0
  16. package/dist/geotiff-o_Fq1Na4.js +3108 -0
  17. package/dist/index-8ZZtuDTp.js +705 -0
  18. package/dist/index-B-Nr9y7J.cjs +84 -0
  19. package/dist/index-B5NoFrpY.js +8892 -0
  20. package/dist/index-Bi1MMPUx.js +4163 -0
  21. package/dist/index-C9fk_HKR.js +167 -0
  22. package/dist/index-CBBRBJvR.cjs +274 -0
  23. package/dist/index-CZxPF1qt.js +4 -0
  24. package/dist/index-CiDPSJ9T.cjs +1 -0
  25. package/dist/index-CjM_nbxd.cjs +107 -0
  26. package/dist/index-DQXdX5y1.js +4666 -0
  27. package/dist/index-Dh1kpCb6.cjs +1 -0
  28. package/dist/index-IrsIiQNM.cjs +4 -0
  29. package/dist/index.cjs +3819 -0
  30. package/dist/index.mjs +19580 -0
  31. package/dist/jpeg-CF9OGQg_.js +533 -0
  32. package/dist/jpeg-JSQOxGHy.cjs +1 -0
  33. package/dist/lerc-7LlQoT6u.js +1032 -0
  34. package/dist/lerc-BIsodce9.cjs +1 -0
  35. package/dist/lzw-BZniWIYG.js +84 -0
  36. package/dist/lzw-BhML-BvT.cjs +1 -0
  37. package/dist/main-dist-Bymiy5aM.cjs +2 -0
  38. package/dist/main-dist-Cv8AKwrY.js +629 -0
  39. package/dist/maplibre-geoman.es-Bxdg-2EU.cjs +129 -0
  40. package/dist/maplibre-geoman.es-CFgM2ajb.js +22827 -0
  41. package/dist/maplibre-gl-components.css +1 -0
  42. package/dist/packbits-B9b7gX2c.cjs +1 -0
  43. package/dist/packbits-DWY5O-FG.js +24 -0
  44. package/dist/pako.esm-Bx5X36Wo.js +1074 -0
  45. package/dist/pako.esm-DZC2QrbJ.cjs +1 -0
  46. package/dist/raw-C3ARbSFo.cjs +1 -0
  47. package/dist/raw-DUslI1gr.js +9 -0
  48. package/dist/react.cjs +1 -0
  49. package/dist/react.mjs +1306 -0
  50. package/dist/types/index.d.ts +44 -0
  51. package/dist/types/index.d.ts.map +1 -0
  52. package/dist/types/lib/adapters/AddVectorAdapter.d.ts +66 -0
  53. package/dist/types/lib/adapters/AddVectorAdapter.d.ts.map +1 -0
  54. package/dist/types/lib/adapters/CogLayerAdapter.d.ts +126 -0
  55. package/dist/types/lib/adapters/CogLayerAdapter.d.ts.map +1 -0
  56. package/dist/types/lib/adapters/PMTilesLayerAdapter.d.ts +78 -0
  57. package/dist/types/lib/adapters/PMTilesLayerAdapter.d.ts.map +1 -0
  58. package/dist/types/lib/adapters/StacLayerAdapter.d.ts +71 -0
  59. package/dist/types/lib/adapters/StacLayerAdapter.d.ts.map +1 -0
  60. package/dist/types/lib/adapters/ZarrLayerAdapter.d.ts +72 -0
  61. package/dist/types/lib/adapters/ZarrLayerAdapter.d.ts.map +1 -0
  62. package/dist/types/lib/adapters/index.d.ts +7 -0
  63. package/dist/types/lib/adapters/index.d.ts.map +1 -0
  64. package/dist/types/lib/addControlGrid.d.ts +80 -0
  65. package/dist/types/lib/addControlGrid.d.ts.map +1 -0
  66. package/dist/types/lib/colormaps/diverging.d.ts +30 -0
  67. package/dist/types/lib/colormaps/diverging.d.ts.map +1 -0
  68. package/dist/types/lib/colormaps/index.d.ts +32 -0
  69. package/dist/types/lib/colormaps/index.d.ts.map +1 -0
  70. package/dist/types/lib/colormaps/misc.d.ts +38 -0
  71. package/dist/types/lib/colormaps/misc.d.ts.map +1 -0
  72. package/dist/types/lib/colormaps/sequential.d.ts +22 -0
  73. package/dist/types/lib/colormaps/sequential.d.ts.map +1 -0
  74. package/dist/types/lib/converters/DuckDBConverter.d.ts +112 -0
  75. package/dist/types/lib/converters/DuckDBConverter.d.ts.map +1 -0
  76. package/dist/types/lib/converters/ShapefileConverter.d.ts +56 -0
  77. package/dist/types/lib/converters/ShapefileConverter.d.ts.map +1 -0
  78. package/dist/types/lib/converters/index.d.ts +8 -0
  79. package/dist/types/lib/converters/index.d.ts.map +1 -0
  80. package/dist/types/lib/converters/types.d.ts +75 -0
  81. package/dist/types/lib/converters/types.d.ts.map +1 -0
  82. package/dist/types/lib/core/AddVector.d.ts +116 -0
  83. package/dist/types/lib/core/AddVector.d.ts.map +1 -0
  84. package/dist/types/lib/core/Basemap.d.ts +206 -0
  85. package/dist/types/lib/core/Basemap.d.ts.map +1 -0
  86. package/dist/types/lib/core/BasemapReact.d.ts +32 -0
  87. package/dist/types/lib/core/BasemapReact.d.ts.map +1 -0
  88. package/dist/types/lib/core/BookmarkControl.d.ts +180 -0
  89. package/dist/types/lib/core/BookmarkControl.d.ts.map +1 -0
  90. package/dist/types/lib/core/ChoroplethControl.d.ts +105 -0
  91. package/dist/types/lib/core/ChoroplethControl.d.ts.map +1 -0
  92. package/dist/types/lib/core/CogLayer.d.ts +139 -0
  93. package/dist/types/lib/core/CogLayer.d.ts.map +1 -0
  94. package/dist/types/lib/core/CogLayerReact.d.ts +32 -0
  95. package/dist/types/lib/core/CogLayerReact.d.ts.map +1 -0
  96. package/dist/types/lib/core/Colorbar.d.ts +133 -0
  97. package/dist/types/lib/core/Colorbar.d.ts.map +1 -0
  98. package/dist/types/lib/core/ColorbarGuiControl.d.ts +79 -0
  99. package/dist/types/lib/core/ColorbarGuiControl.d.ts.map +1 -0
  100. package/dist/types/lib/core/ColorbarReact.d.ts +34 -0
  101. package/dist/types/lib/core/ColorbarReact.d.ts.map +1 -0
  102. package/dist/types/lib/core/ControlGrid.d.ts +125 -0
  103. package/dist/types/lib/core/ControlGrid.d.ts.map +1 -0
  104. package/dist/types/lib/core/ControlGridReact.d.ts +32 -0
  105. package/dist/types/lib/core/ControlGridReact.d.ts.map +1 -0
  106. package/dist/types/lib/core/HtmlControl.d.ts +140 -0
  107. package/dist/types/lib/core/HtmlControl.d.ts.map +1 -0
  108. package/dist/types/lib/core/HtmlControlReact.d.ts +32 -0
  109. package/dist/types/lib/core/HtmlControlReact.d.ts.map +1 -0
  110. package/dist/types/lib/core/HtmlGuiControl.d.ts +68 -0
  111. package/dist/types/lib/core/HtmlGuiControl.d.ts.map +1 -0
  112. package/dist/types/lib/core/InspectControl.d.ts +202 -0
  113. package/dist/types/lib/core/InspectControl.d.ts.map +1 -0
  114. package/dist/types/lib/core/InspectControlReact.d.ts +32 -0
  115. package/dist/types/lib/core/InspectControlReact.d.ts.map +1 -0
  116. package/dist/types/lib/core/Legend.d.ts +142 -0
  117. package/dist/types/lib/core/Legend.d.ts.map +1 -0
  118. package/dist/types/lib/core/LegendGuiControl.d.ts +69 -0
  119. package/dist/types/lib/core/LegendGuiControl.d.ts.map +1 -0
  120. package/dist/types/lib/core/LegendReact.d.ts +34 -0
  121. package/dist/types/lib/core/LegendReact.d.ts.map +1 -0
  122. package/dist/types/lib/core/MeasureControl.d.ts +211 -0
  123. package/dist/types/lib/core/MeasureControl.d.ts.map +1 -0
  124. package/dist/types/lib/core/MinimapControl.d.ts +77 -0
  125. package/dist/types/lib/core/MinimapControl.d.ts.map +1 -0
  126. package/dist/types/lib/core/MinimapControlReact.d.ts +32 -0
  127. package/dist/types/lib/core/MinimapControlReact.d.ts.map +1 -0
  128. package/dist/types/lib/core/PMTilesLayer.d.ts +119 -0
  129. package/dist/types/lib/core/PMTilesLayer.d.ts.map +1 -0
  130. package/dist/types/lib/core/PrintControl.d.ts +226 -0
  131. package/dist/types/lib/core/PrintControl.d.ts.map +1 -0
  132. package/dist/types/lib/core/SearchControl.d.ts +172 -0
  133. package/dist/types/lib/core/SearchControl.d.ts.map +1 -0
  134. package/dist/types/lib/core/SearchControlReact.d.ts +32 -0
  135. package/dist/types/lib/core/SearchControlReact.d.ts.map +1 -0
  136. package/dist/types/lib/core/StacLayer.d.ts +107 -0
  137. package/dist/types/lib/core/StacLayer.d.ts.map +1 -0
  138. package/dist/types/lib/core/StacSearch.d.ts +109 -0
  139. package/dist/types/lib/core/StacSearch.d.ts.map +1 -0
  140. package/dist/types/lib/core/Terrain.d.ts +165 -0
  141. package/dist/types/lib/core/Terrain.d.ts.map +1 -0
  142. package/dist/types/lib/core/TerrainReact.d.ts +32 -0
  143. package/dist/types/lib/core/TerrainReact.d.ts.map +1 -0
  144. package/dist/types/lib/core/VectorDataset.d.ts +228 -0
  145. package/dist/types/lib/core/VectorDataset.d.ts.map +1 -0
  146. package/dist/types/lib/core/VectorDatasetReact.d.ts +31 -0
  147. package/dist/types/lib/core/VectorDatasetReact.d.ts.map +1 -0
  148. package/dist/types/lib/core/ViewStateControl.d.ts +205 -0
  149. package/dist/types/lib/core/ViewStateControl.d.ts.map +1 -0
  150. package/dist/types/lib/core/ViewStateControlReact.d.ts +32 -0
  151. package/dist/types/lib/core/ViewStateControlReact.d.ts.map +1 -0
  152. package/dist/types/lib/core/ZarrLayer.d.ts +110 -0
  153. package/dist/types/lib/core/ZarrLayer.d.ts.map +1 -0
  154. package/dist/types/lib/core/types.d.ts +2793 -0
  155. package/dist/types/lib/core/types.d.ts.map +1 -0
  156. package/dist/types/lib/hooks/index.d.ts +13 -0
  157. package/dist/types/lib/hooks/index.d.ts.map +1 -0
  158. package/dist/types/lib/hooks/useBasemap.d.ts +43 -0
  159. package/dist/types/lib/hooks/useBasemap.d.ts.map +1 -0
  160. package/dist/types/lib/hooks/useCogLayer.d.ts +44 -0
  161. package/dist/types/lib/hooks/useCogLayer.d.ts.map +1 -0
  162. package/dist/types/lib/hooks/useColorbar.d.ts +36 -0
  163. package/dist/types/lib/hooks/useColorbar.d.ts.map +1 -0
  164. package/dist/types/lib/hooks/useControlGrid.d.ts +41 -0
  165. package/dist/types/lib/hooks/useControlGrid.d.ts.map +1 -0
  166. package/dist/types/lib/hooks/useHtmlControl.d.ts +39 -0
  167. package/dist/types/lib/hooks/useHtmlControl.d.ts.map +1 -0
  168. package/dist/types/lib/hooks/useInspectControl.d.ts +49 -0
  169. package/dist/types/lib/hooks/useInspectControl.d.ts.map +1 -0
  170. package/dist/types/lib/hooks/useLegend.d.ts +41 -0
  171. package/dist/types/lib/hooks/useLegend.d.ts.map +1 -0
  172. package/dist/types/lib/hooks/useMinimapControl.d.ts +35 -0
  173. package/dist/types/lib/hooks/useMinimapControl.d.ts.map +1 -0
  174. package/dist/types/lib/hooks/useSearchControl.d.ts +43 -0
  175. package/dist/types/lib/hooks/useSearchControl.d.ts.map +1 -0
  176. package/dist/types/lib/hooks/useTerrain.d.ts +43 -0
  177. package/dist/types/lib/hooks/useTerrain.d.ts.map +1 -0
  178. package/dist/types/lib/hooks/useVectorDataset.d.ts +35 -0
  179. package/dist/types/lib/hooks/useVectorDataset.d.ts.map +1 -0
  180. package/dist/types/lib/hooks/useViewState.d.ts +43 -0
  181. package/dist/types/lib/hooks/useViewState.d.ts.map +1 -0
  182. package/dist/types/lib/utils/color.d.ts +47 -0
  183. package/dist/types/lib/utils/color.d.ts.map +1 -0
  184. package/dist/types/lib/utils/fileHelpers.d.ts +207 -0
  185. package/dist/types/lib/utils/fileHelpers.d.ts.map +1 -0
  186. package/dist/types/lib/utils/helpers.d.ts +48 -0
  187. package/dist/types/lib/utils/helpers.d.ts.map +1 -0
  188. package/dist/types/lib/utils/index.d.ts +4 -0
  189. package/dist/types/lib/utils/index.d.ts.map +1 -0
  190. package/dist/types/lib/utils/providers.d.ts +46 -0
  191. package/dist/types/lib/utils/providers.d.ts.map +1 -0
  192. package/dist/types/react.d.ts +15 -0
  193. package/dist/types/react.d.ts.map +1 -0
  194. package/dist/webimage-CBRffWZD.cjs +1 -0
  195. package/dist/webimage-ibSPOLHJ.js +19 -0
  196. package/package.json +137 -0
package/README.md ADDED
@@ -0,0 +1,1960 @@
1
+ # maplibre-gl-components
2
+
3
+ Legend, colorbar, basemap switcher, terrain toggle, search, vector data loader, feature inspector, measurement tools, coordinate display, bookmarks, minimap, and more for MapLibre GL JS maps.
4
+
5
+ [![npm version](https://badge.fury.io/js/maplibre-gl-components.svg)](https://badge.fury.io/js/maplibre-gl-components)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?logo=codesandbox)](https://codesandbox.io/p/github/opengeos/maplibre-gl-components)
8
+ [![Open in StackBlitz](https://img.shields.io/badge/Open%20in-StackBlitz-blue?logo=stackblitz)](https://stackblitz.com/github/opengeos/maplibre-gl-components)
9
+
10
+ ## Features
11
+
12
+ - **Colorbar** - Continuous gradient legends with built-in matplotlib colormaps
13
+ - **Legend** - Categorical legends with color swatches and labels
14
+ - **BasemapControl** - Interactive basemap switcher with 100+ providers from xyzservices
15
+ - **TerrainControl** - Toggle 3D terrain on/off using free AWS Terrarium elevation tiles
16
+ - **SearchControl** - Collapsible place search with geocoding and fly-to functionality
17
+ - **VectorDatasetControl** - Load GeoJSON files via file upload or drag-and-drop
18
+ - **AddVectorControl** - Load vector data from URLs (GeoJSON, GeoParquet, FlatGeobuf) with styling options
19
+ - **InspectControl** - Click on features to view their properties/attributes
20
+ - **ViewStateControl** - Display live map state (center, bounds, zoom, pitch, bearing) with optional bbox drawing
21
+ - **HtmlControl** - Flexible HTML content control for custom info panels
22
+ - **CogLayerControl** - Load and visualize Cloud Optimized GeoTIFF (COG) files with colormaps
23
+ - **ZarrLayerControl** - Load and visualize multi-dimensional Zarr arrays with colormaps
24
+ - **StacLayerControl** - Load COG layers from STAC (SpatioTemporal Asset Catalog) items
25
+ - **StacSearchControl** - Search and visualize STAC items from public catalogs (Earth Search, Planetary Computer)
26
+ - **MeasureControl** - Measure distances and areas on the map with multiple unit options
27
+ - **BookmarkControl** - Save and restore map views with localStorage persistence
28
+ - **PrintControl** - Export the map as PNG, JPEG, or PDF with optional title overlay
29
+ - **MinimapControl** - Inset overview map showing the current viewport extent with optional click-to-navigate
30
+ - **ControlGrid** - Collapsible toolbar grid that hosts any combination of built-in and plugin controls
31
+ - **addControlGrid** - One-call convenience function to add all default controls with customization
32
+ - **Three.js Integration** - Re-exported helpers (`MapScene`, `SceneTransform`, `Sun`, `Creator`) from `@dvt3d/maplibre-three-plugin`
33
+ - **Zoom-based Visibility** - Show/hide components at specific zoom levels with `minzoom`/`maxzoom`
34
+ - **React Support** - First-class React components and hooks
35
+ - **TypeScript** - Full type definitions included
36
+ - **20+ Built-in Colormaps** - viridis, plasma, terrain, jet, and more
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ npm install maplibre-gl-components
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ ### Vanilla JavaScript/TypeScript
47
+
48
+ ```typescript
49
+ import maplibregl from "maplibre-gl";
50
+ import {
51
+ Colorbar,
52
+ Legend,
53
+ HtmlControl,
54
+ BasemapControl,
55
+ TerrainControl,
56
+ SearchControl,
57
+ VectorDatasetControl,
58
+ AddVectorControl,
59
+ ViewStateControl,
60
+ CogLayerControl,
61
+ ZarrLayerControl,
62
+ StacLayerControl,
63
+ MinimapControl,
64
+ } from "maplibre-gl-components";
65
+ import "maplibre-gl-components/style.css";
66
+
67
+ const map = new maplibregl.Map({
68
+ container: "map",
69
+ style: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
70
+ center: [-98, 38.5],
71
+ zoom: 4,
72
+ });
73
+
74
+ // Add a terrain toggle control
75
+ const terrainControl = new TerrainControl({
76
+ exaggeration: 1.5,
77
+ hillshade: true,
78
+ });
79
+ map.addControl(terrainControl, "top-right");
80
+
81
+ // Add a search control
82
+ const searchControl = new SearchControl({
83
+ placeholder: "Search for a place...",
84
+ flyToZoom: 14,
85
+ showMarker: true,
86
+ });
87
+ map.addControl(searchControl, "top-right");
88
+
89
+ // Add a vector dataset loader (file upload and drag-drop)
90
+ const vectorControl = new VectorDatasetControl({
91
+ fitBounds: true,
92
+ });
93
+ map.addControl(vectorControl, "top-left");
94
+
95
+ vectorControl.on("load", (event) => {
96
+ console.log("Loaded:", event.dataset?.filename);
97
+ });
98
+
99
+ // Add a view state control
100
+ const viewStateControl = new ViewStateControl({
101
+ collapsed: false,
102
+ enableBBox: true,
103
+ precision: 4,
104
+ });
105
+ map.addControl(viewStateControl, "bottom-left");
106
+
107
+ viewStateControl.on("bboxdraw", (event) => {
108
+ console.log("Drawn bbox:", event.bbox);
109
+ });
110
+
111
+ // Add a basemap switcher
112
+ const basemapControl = new BasemapControl({
113
+ defaultBasemap: "OpenStreetMap.Mapnik",
114
+ showSearch: true,
115
+ filterGroups: ["OpenStreetMap", "CartoDB", "Stadia", "Esri"],
116
+ });
117
+ map.addControl(basemapControl, "top-left");
118
+
119
+ // Add a colorbar
120
+ const colorbar = new Colorbar({
121
+ colormap: "viridis",
122
+ vmin: 0,
123
+ vmax: 100,
124
+ label: "Temperature",
125
+ units: "°C",
126
+ orientation: "vertical",
127
+ });
128
+ map.addControl(colorbar, "bottom-right");
129
+
130
+ // Add a legend
131
+ const legend = new Legend({
132
+ title: "Land Cover",
133
+ items: [
134
+ { label: "Forest", color: "#228B22" },
135
+ { label: "Water", color: "#4169E1" },
136
+ { label: "Urban", color: "#808080" },
137
+ ],
138
+ collapsible: true,
139
+ });
140
+ map.addControl(legend, "bottom-left");
141
+
142
+ // Add an HTML control
143
+ const htmlControl = new HtmlControl({
144
+ html: "<div><strong>Stats:</strong> 1,234 features</div>",
145
+ });
146
+ map.addControl(htmlControl, "top-left");
147
+
148
+ // Update HTML dynamically
149
+ htmlControl.setHtml("<div><strong>Stats:</strong> 5,678 features</div>");
150
+
151
+ // Add a COG layer control (auto-loads the layer)
152
+ const cogControl = new CogLayerControl({
153
+ defaultUrl: "https://example.com/dem.tif",
154
+ defaultColormap: "terrain",
155
+ defaultRescaleMin: 0,
156
+ defaultRescaleMax: 4000,
157
+ loadDefaultUrl: true, // Auto-load the layer when control is added
158
+ });
159
+ map.addControl(cogControl, "top-right");
160
+
161
+ cogControl.on("layeradd", (event) => {
162
+ console.log("COG layer added:", event.url);
163
+ });
164
+
165
+ // Add a Zarr layer control (auto-loads the layer)
166
+ const zarrControl = new ZarrLayerControl({
167
+ defaultUrl: "https://example.com/climate.zarr",
168
+ defaultVariable: "temperature",
169
+ defaultColormap: ["#440154", "#21918c", "#fde725"],
170
+ defaultClim: [0, 30],
171
+ loadDefaultUrl: true, // Auto-load the layer when control is added
172
+ });
173
+ map.addControl(zarrControl, "top-right");
174
+
175
+ zarrControl.on("layeradd", (event) => {
176
+ console.log("Zarr layer added:", event.url);
177
+ });
178
+
179
+ // Add a STAC layer control (loads COG from STAC items)
180
+ const stacControl = new StacLayerControl({
181
+ defaultUrl: "https://example.com/stac-item.json",
182
+ loadDefaultUrl: true,
183
+ defaultColormap: "viridis",
184
+ defaultRescaleMin: 0,
185
+ defaultRescaleMax: 255,
186
+ });
187
+ map.addControl(stacControl, "top-right");
188
+
189
+ stacControl.on("stacload", (event) => {
190
+ console.log("STAC item loaded:", event.url);
191
+ });
192
+
193
+ stacControl.on("layeradd", (event) => {
194
+ console.log("STAC layer added:", event.assetKey);
195
+ });
196
+ ```
197
+
198
+ ### React
199
+
200
+ ```tsx
201
+ import { useState, useEffect, useRef } from "react";
202
+ import maplibregl from "maplibre-gl";
203
+ import {
204
+ ColorbarReact,
205
+ LegendReact,
206
+ HtmlControlReact,
207
+ BasemapReact,
208
+ TerrainReact,
209
+ SearchControlReact,
210
+ VectorDatasetReact,
211
+ ViewStateControlReact,
212
+ } from "maplibre-gl-components/react";
213
+ import "maplibre-gl-components/style.css";
214
+
215
+ function MyMap() {
216
+ const mapContainer = useRef<HTMLDivElement>(null);
217
+ const [map, setMap] = useState<maplibregl.Map | null>(null);
218
+ const [stats, setStats] = useState("Loading...");
219
+
220
+ useEffect(() => {
221
+ if (!mapContainer.current) return;
222
+
223
+ const mapInstance = new maplibregl.Map({
224
+ container: mapContainer.current,
225
+ style: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
226
+ center: [-98, 38.5],
227
+ zoom: 4,
228
+ });
229
+
230
+ mapInstance.on("load", () => setMap(mapInstance));
231
+
232
+ return () => mapInstance.remove();
233
+ }, []);
234
+
235
+ return (
236
+ <div style={{ width: "100%", height: "100vh" }}>
237
+ <div ref={mapContainer} style={{ width: "100%", height: "100%" }} />
238
+
239
+ {map && (
240
+ <>
241
+ <VectorDatasetReact
242
+ map={map}
243
+ fitBounds
244
+ position="top-left"
245
+ onDatasetLoad={(dataset) =>
246
+ console.log("Loaded:", dataset.filename)
247
+ }
248
+ />
249
+
250
+ <ViewStateControlReact
251
+ map={map}
252
+ collapsed={false}
253
+ enableBBox
254
+ precision={4}
255
+ position="bottom-left"
256
+ onBBoxDraw={(bbox) => console.log("Drawn bbox:", bbox)}
257
+ />
258
+
259
+ <SearchControlReact
260
+ map={map}
261
+ placeholder="Search for a place..."
262
+ flyToZoom={14}
263
+ showMarker
264
+ position="top-right"
265
+ onResultSelect={(result) => console.log("Selected:", result.name)}
266
+ />
267
+
268
+ <TerrainReact
269
+ map={map}
270
+ exaggeration={1.5}
271
+ hillshade
272
+ position="top-right"
273
+ onTerrainChange={(enabled) => console.log("Terrain:", enabled)}
274
+ />
275
+
276
+ <BasemapReact
277
+ map={map}
278
+ defaultBasemap="OpenStreetMap.Mapnik"
279
+ showSearch
280
+ filterGroups={["OpenStreetMap", "CartoDB", "Stadia"]}
281
+ position="top-left"
282
+ />
283
+
284
+ <ColorbarReact
285
+ map={map}
286
+ colormap="viridis"
287
+ vmin={0}
288
+ vmax={100}
289
+ label="Temperature"
290
+ units="°C"
291
+ position="bottom-right"
292
+ />
293
+
294
+ <LegendReact
295
+ map={map}
296
+ title="Categories"
297
+ items={[
298
+ { label: "Low", color: "#2166ac" },
299
+ { label: "High", color: "#b2182b" },
300
+ ]}
301
+ position="bottom-left"
302
+ collapsible
303
+ />
304
+
305
+ <HtmlControlReact
306
+ map={map}
307
+ html={`<div><strong>Stats:</strong> ${stats}</div>`}
308
+ position="top-left"
309
+ />
310
+ </>
311
+ )}
312
+ </div>
313
+ );
314
+ }
315
+ ```
316
+
317
+ ## API Reference
318
+
319
+ ### Colorbar
320
+
321
+ A continuous gradient colorbar control.
322
+
323
+ ```typescript
324
+ interface ColorbarOptions {
325
+ colormap?: ColormapName | string[]; // Colormap name or custom colors
326
+ colorStops?: ColorStop[]; // Fine-grained color control
327
+ vmin?: number; // Minimum value (default: 0)
328
+ vmax?: number; // Maximum value (default: 1)
329
+ label?: string; // Title/label
330
+ units?: string; // Units suffix
331
+ orientation?: "horizontal" | "vertical";
332
+ position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
333
+ barThickness?: number; // Bar width/height in pixels
334
+ barLength?: number; // Bar length in pixels
335
+ ticks?: { count?: number; values?: number[]; format?: (v: number) => string };
336
+ visible?: boolean;
337
+ backgroundColor?: string;
338
+ opacity?: number;
339
+ fontSize?: number;
340
+ fontColor?: string;
341
+ minzoom?: number; // Min zoom level to show (default: 0)
342
+ maxzoom?: number; // Max zoom level to show (default: 24)
343
+ }
344
+
345
+ // Methods
346
+ colorbar.show();
347
+ colorbar.hide();
348
+ colorbar.update(options);
349
+ colorbar.getState();
350
+ colorbar.on(event, handler);
351
+ colorbar.off(event, handler);
352
+ ```
353
+
354
+ ### Legend
355
+
356
+ A categorical legend control.
357
+
358
+ ```typescript
359
+ interface LegendOptions {
360
+ title?: string;
361
+ items?: LegendItem[]; // { label, color, shape?, icon? }
362
+ position?: ControlPosition;
363
+ visible?: boolean;
364
+ collapsible?: boolean;
365
+ collapsed?: boolean;
366
+ width?: number;
367
+ maxHeight?: number;
368
+ swatchSize?: number;
369
+ backgroundColor?: string;
370
+ opacity?: number;
371
+ fontSize?: number;
372
+ fontColor?: string;
373
+ minzoom?: number; // Min zoom level to show (default: 0)
374
+ maxzoom?: number; // Max zoom level to show (default: 24)
375
+ }
376
+
377
+ interface LegendItem {
378
+ label: string;
379
+ color: string;
380
+ shape?: "square" | "circle" | "line";
381
+ strokeColor?: string;
382
+ icon?: string; // URL to icon image
383
+ }
384
+
385
+ // Methods
386
+ legend.show();
387
+ legend.hide();
388
+ legend.expand();
389
+ legend.collapse();
390
+ legend.toggle();
391
+ legend.setItems(items);
392
+ legend.addItem(item);
393
+ legend.removeItem(label);
394
+ legend.update(options);
395
+ legend.getState();
396
+ ```
397
+
398
+ ### HtmlControl
399
+
400
+ A flexible HTML content control.
401
+
402
+ ```typescript
403
+ interface HtmlControlOptions {
404
+ html?: string; // HTML content
405
+ element?: HTMLElement; // Or provide a DOM element
406
+ position?: ControlPosition;
407
+ visible?: boolean;
408
+ backgroundColor?: string;
409
+ padding?: number;
410
+ borderRadius?: number;
411
+ opacity?: number;
412
+ maxWidth?: number;
413
+ maxHeight?: number;
414
+ minzoom?: number; // Min zoom level to show (default: 0)
415
+ maxzoom?: number; // Max zoom level to show (default: 24)
416
+ }
417
+
418
+ // Methods
419
+ htmlControl.show();
420
+ htmlControl.hide();
421
+ htmlControl.setHtml(html); // Update HTML content
422
+ htmlControl.setElement(element); // Set DOM element
423
+ htmlControl.getElement(); // Get content container
424
+ htmlControl.update(options);
425
+ htmlControl.getState();
426
+ ```
427
+
428
+ ### BasemapControl
429
+
430
+ An interactive basemap switcher that loads providers from [xyzservices](https://github.com/geopandas/xyzservices).
431
+
432
+ ```typescript
433
+ interface BasemapControlOptions {
434
+ basemaps?: BasemapItem[]; // Custom basemaps array
435
+ providersUrl?: string; // URL to fetch providers.json (defaults to xyzservices)
436
+ defaultBasemap?: string; // Initial basemap ID (e.g., 'OpenStreetMap.Mapnik')
437
+ position?: ControlPosition;
438
+ visible?: boolean;
439
+ collapsible?: boolean; // Whether control is collapsible (default: true)
440
+ collapsed?: boolean; // Whether control starts collapsed (default: true)
441
+ displayMode?: "dropdown" | "gallery" | "list"; // UI mode (default: 'dropdown')
442
+ showSearch?: boolean; // Show search input (default: true)
443
+ filterGroups?: string[]; // Only include these provider groups
444
+ excludeGroups?: string[]; // Exclude these provider groups
445
+ excludeBroken?: boolean; // Exclude broken providers (default: true)
446
+ backgroundColor?: string;
447
+ maxWidth?: number;
448
+ maxHeight?: number;
449
+ fontSize?: number;
450
+ fontColor?: string;
451
+ minzoom?: number;
452
+ maxzoom?: number;
453
+ }
454
+
455
+ interface BasemapItem {
456
+ id: string; // Unique identifier
457
+ name: string; // Display name
458
+ group?: string; // Provider group (e.g., 'OpenStreetMap')
459
+ url?: string; // XYZ tile URL template
460
+ style?: string; // MapLibre style URL
461
+ attribution?: string;
462
+ thumbnail?: string; // Preview image URL
463
+ maxZoom?: number;
464
+ minZoom?: number;
465
+ requiresApiKey?: boolean;
466
+ apiKey?: string;
467
+ }
468
+
469
+ // Methods
470
+ basemapControl.show();
471
+ basemapControl.hide();
472
+ basemapControl.expand();
473
+ basemapControl.collapse();
474
+ basemapControl.toggle();
475
+ basemapControl.setBasemap(basemapId); // Switch to a basemap
476
+ basemapControl.getBasemaps(); // Get available basemaps
477
+ basemapControl.addBasemap(basemap); // Add a custom basemap
478
+ basemapControl.removeBasemap(id); // Remove a basemap
479
+ basemapControl.setApiKey(id, key); // Set API key for a basemap
480
+ basemapControl.getSelectedBasemap(); // Get currently selected basemap
481
+ basemapControl.update(options);
482
+ basemapControl.getState();
483
+ basemapControl.on("basemapchange", handler); // Listen for basemap changes
484
+ ```
485
+
486
+ **Available Provider Groups:**
487
+
488
+ - OpenStreetMap, CartoDB, Stadia, Esri, OpenTopoMap
489
+ - Thunderforest, MapBox, MapTiler (require API keys)
490
+ - NASAGIBS, OpenSeaMap, and 20+ more
491
+
492
+ ### SearchControl
493
+
494
+ A collapsible place search control with geocoding support.
495
+
496
+ ```typescript
497
+ interface SearchControlOptions {
498
+ position?: ControlPosition;
499
+ visible?: boolean; // Default: true
500
+ collapsed?: boolean; // Start collapsed (icon only). Default: true
501
+ placeholder?: string; // Search input placeholder. Default: 'Search places...'
502
+ geocoderUrl?: string; // Geocoding API URL. Default: Nominatim
503
+ maxResults?: number; // Max results to show. Default: 5
504
+ debounceMs?: number; // Debounce delay in ms. Default: 300
505
+ flyToZoom?: number; // Zoom level when selecting result. Default: 14
506
+ showMarker?: boolean; // Show marker at selected location. Default: true
507
+ markerColor?: string; // Marker color. Default: '#4264fb'
508
+ collapseOnSelect?: boolean; // Collapse after selecting. Default: true
509
+ clearOnSelect?: boolean; // Clear results after selecting. Default: true
510
+ geocoder?: (query: string) => Promise<SearchResult[]>; // Custom geocoder function
511
+ backgroundColor?: string;
512
+ borderRadius?: number;
513
+ opacity?: number;
514
+ width?: number; // Expanded width in pixels. Default: 280
515
+ fontSize?: number;
516
+ fontColor?: string;
517
+ minzoom?: number;
518
+ maxzoom?: number;
519
+ }
520
+
521
+ interface SearchResult {
522
+ id: string; // Unique identifier
523
+ name: string; // Place name
524
+ displayName: string; // Full display name with address
525
+ lng: number; // Longitude
526
+ lat: number; // Latitude
527
+ bbox?: [number, number, number, number]; // Bounding box [west, south, east, north]
528
+ type?: string; // Place type (city, street, etc.)
529
+ importance?: number; // Relevance score
530
+ }
531
+
532
+ // Methods
533
+ searchControl.show();
534
+ searchControl.hide();
535
+ searchControl.expand(); // Expand to show input
536
+ searchControl.collapse(); // Collapse to icon only
537
+ searchControl.toggle(); // Toggle expanded/collapsed
538
+ searchControl.search(query); // Perform a search
539
+ searchControl.selectResult(result); // Select a result and fly to it
540
+ searchControl.clear(); // Clear search and marker
541
+ searchControl.update(options);
542
+ searchControl.getState();
543
+ searchControl.on("resultselect", handler); // Listen for result selection
544
+ searchControl.on("search", handler); // Listen for search completion
545
+ searchControl.on("clear", handler); // Listen for clear events
546
+ ```
547
+
548
+ **Geocoding:**
549
+ By default, SearchControl uses [Nominatim](https://nominatim.openstreetmap.org/) (OpenStreetMap) for geocoding, which is free and requires no API key. You can also provide a custom geocoder function for other services.
550
+
551
+ ```typescript
552
+ // Using custom geocoder
553
+ const searchControl = new SearchControl({
554
+ geocoder: async (query) => {
555
+ const response = await fetch(`https://my-geocoder.com/search?q=${query}`);
556
+ const data = await response.json();
557
+ return data.map((item) => ({
558
+ id: item.id,
559
+ name: item.name,
560
+ displayName: item.address,
561
+ lng: item.longitude,
562
+ lat: item.latitude,
563
+ }));
564
+ },
565
+ });
566
+ ```
567
+
568
+ ### VectorDatasetControl
569
+
570
+ A control for loading GeoJSON files via file upload button or drag-and-drop.
571
+
572
+ ```typescript
573
+ interface VectorDatasetControlOptions {
574
+ position?: ControlPosition;
575
+ visible?: boolean; // Default: true
576
+ showDropZone?: boolean; // Show overlay when dragging. Default: true
577
+ acceptedExtensions?: string[]; // File extensions. Default: ['.geojson', '.json']
578
+ multiple?: boolean; // Allow multiple files. Default: true
579
+ defaultStyle?: VectorLayerStyle; // Default styling for loaded layers
580
+ fitBounds?: boolean; // Fit map to loaded data. Default: true
581
+ fitBoundsPadding?: number; // Padding for fitBounds. Default: 50
582
+ maxFileSize?: number; // Max file size in bytes. Default: 50MB
583
+ backgroundColor?: string;
584
+ borderRadius?: number;
585
+ opacity?: number;
586
+ minzoom?: number;
587
+ maxzoom?: number;
588
+ }
589
+
590
+ interface VectorLayerStyle {
591
+ fillColor?: string; // Polygon fill. Default: '#3388ff'
592
+ fillOpacity?: number; // Polygon fill opacity. Default: 0.3
593
+ strokeColor?: string; // Line/outline color. Default: '#3388ff'
594
+ strokeWidth?: number; // Line width. Default: 2
595
+ strokeOpacity?: number; // Line opacity. Default: 1
596
+ circleRadius?: number; // Point radius. Default: 6
597
+ circleColor?: string; // Point color. Default: '#3388ff'
598
+ circleStrokeColor?: string; // Point outline. Default: '#ffffff'
599
+ circleStrokeWidth?: number; // Point outline width. Default: 2
600
+ }
601
+
602
+ interface LoadedDataset {
603
+ id: string; // Unique ID
604
+ filename: string; // Original filename
605
+ sourceId: string; // MapLibre source ID
606
+ layerIds: string[]; // MapLibre layer IDs
607
+ featureCount: number; // Number of features
608
+ geometryTypes: string[]; // Geometry types present
609
+ loadedAt: Date; // When loaded
610
+ }
611
+
612
+ // Methods
613
+ vectorControl.show();
614
+ vectorControl.hide();
615
+ vectorControl.getLoadedDatasets(); // Get all loaded datasets
616
+ vectorControl.removeDataset(id); // Remove a dataset by ID
617
+ vectorControl.removeAllDatasets(); // Remove all datasets
618
+ vectorControl.loadGeoJSON(geojson, filename); // Programmatically load GeoJSON
619
+ vectorControl.update(options);
620
+ vectorControl.getState();
621
+ vectorControl.on("load", handler); // Fired when a dataset is loaded
622
+ vectorControl.on("error", handler); // Fired when an error occurs
623
+ ```
624
+
625
+ **Loading Methods:**
626
+
627
+ - Click the upload button to open a file picker
628
+ - Drag and drop GeoJSON files directly onto the map
629
+
630
+ **Supported Formats:**
631
+
632
+ - GeoJSON (.geojson, .json)
633
+ - FeatureCollection, Feature, or raw Geometry objects
634
+
635
+ ### AddVectorControl
636
+
637
+ A control for loading vector data from URLs with support for multiple formats and styling options.
638
+
639
+ ```typescript
640
+ interface AddVectorControlOptions {
641
+ position?: ControlPosition; // Control position (default: 'top-right')
642
+ className?: string; // Custom CSS class
643
+ visible?: boolean; // Initial visibility (default: true)
644
+ collapsed?: boolean; // Start collapsed (default: true)
645
+ beforeId?: string; // Layer ID to insert before
646
+ defaultUrl?: string; // Pre-filled URL
647
+ defaultLayerName?: string; // Pre-filled layer name
648
+ loadDefaultUrl?: boolean; // Auto-load defaultUrl on init (default: false)
649
+ defaultFormat?: "auto" | "geojson" | "geoparquet" | "flatgeobuf"; // Default format (default: 'auto')
650
+ defaultOpacity?: number; // Default opacity 0-1 (default: 1)
651
+ defaultFillColor?: string; // Default fill color (default: '#3388ff')
652
+ defaultStrokeColor?: string; // Default stroke color (default: '#3388ff')
653
+ defaultCircleColor?: string; // Default point color (default: '#3388ff')
654
+ defaultPickable?: boolean; // Enable click popups (default: true)
655
+ corsProxy?: string; // CORS proxy URL for cross-origin files
656
+ fitBounds?: boolean; // Fit to data bounds (default: true)
657
+ fitBoundsPadding?: number; // Padding for fitBounds (default: 50)
658
+ panelWidth?: number; // Panel width in pixels (default: 300)
659
+ backgroundColor?: string;
660
+ borderRadius?: number;
661
+ opacity?: number;
662
+ fontSize?: number;
663
+ fontColor?: string;
664
+ minzoom?: number;
665
+ maxzoom?: number;
666
+ }
667
+ ```
668
+
669
+ **Usage:**
670
+
671
+ ```typescript
672
+ import { AddVectorControl } from "maplibre-gl-components";
673
+
674
+ // Basic usage
675
+ const addVectorControl = new AddVectorControl({
676
+ position: "top-right",
677
+ collapsed: false,
678
+ });
679
+ map.addControl(addVectorControl);
680
+
681
+ // With pre-filled URL that auto-loads
682
+ const addVectorControl = new AddVectorControl({
683
+ defaultUrl: "https://example.com/data.geojson",
684
+ loadDefaultUrl: true,
685
+ defaultOpacity: 0.8,
686
+ defaultFillColor: "#ff6600",
687
+ fitBounds: true,
688
+ });
689
+ map.addControl(addVectorControl);
690
+
691
+ // Listen for events
692
+ addVectorControl.on("layeradd", (event) => {
693
+ console.log("Layer added:", event.layerId, event.url);
694
+ });
695
+
696
+ addVectorControl.on("layerremove", (event) => {
697
+ console.log("Layer removed:", event.layerId);
698
+ });
699
+
700
+ addVectorControl.on("error", (event) => {
701
+ console.error("Error:", event.error);
702
+ });
703
+ ```
704
+
705
+ **Methods:**
706
+
707
+ ```typescript
708
+ addVectorControl.expand()
709
+ addVectorControl.collapse()
710
+ addVectorControl.toggle()
711
+ addVectorControl.show()
712
+ addVectorControl.hide()
713
+ addVectorControl.getState()
714
+ addVectorControl.update(options)
715
+ addVectorControl.loadUrl(url, format?) // Programmatically load a URL
716
+ addVectorControl.getOpacity(layerId) // Get layer opacity
717
+ addVectorControl.setOpacity(layerId, value) // Set layer opacity
718
+ addVectorControl.getVisibility(layerId) // Get layer visibility
719
+ addVectorControl.setVisibility(layerId, visible) // Set layer visibility
720
+ addVectorControl.removeLayer(layerId) // Remove a layer
721
+ addVectorControl.removeAllLayers() // Remove all layers
722
+ ```
723
+
724
+ **Supported Formats:**
725
+
726
+ - GeoJSON (.geojson, .json)
727
+ - GeoParquet (.parquet, .geoparquet)
728
+ - FlatGeobuf (.fgb)
729
+
730
+ **Features:**
731
+
732
+ - Auto-detect format from URL extension
733
+ - Customizable fill, stroke, and point colors
734
+ - Opacity slider with real-time updates
735
+ - Pickable layers with feature info popups on click
736
+ - Layer name and beforeId for layer ordering
737
+ - Fit map bounds to loaded data
738
+
739
+ ### TerrainControl
740
+
741
+ A toggle control for 3D terrain rendering using free AWS Terrarium elevation tiles.
742
+
743
+ ```typescript
744
+ interface TerrainControlOptions {
745
+ sourceUrl?: string; // Terrain tile URL (default: AWS Terrarium)
746
+ encoding?: "terrarium" | "mapbox"; // Terrain encoding (default: 'terrarium')
747
+ exaggeration?: number; // Vertical scale factor (default: 1.0)
748
+ enabled?: boolean; // Initial terrain state (default: false)
749
+ hillshade?: boolean; // Add hillshade layer (default: true)
750
+ hillshadeExaggeration?: number; // Hillshade intensity (default: 0.5)
751
+ position?: ControlPosition;
752
+ visible?: boolean;
753
+ backgroundColor?: string;
754
+ borderRadius?: number;
755
+ opacity?: number;
756
+ minzoom?: number; // Min zoom level to show (default: 0)
757
+ maxzoom?: number; // Max zoom level to show (default: 24)
758
+ }
759
+
760
+ // Methods
761
+ terrainControl.show();
762
+ terrainControl.hide();
763
+ terrainControl.enable(); // Enable terrain
764
+ terrainControl.disable(); // Disable terrain
765
+ terrainControl.toggle(); // Toggle terrain on/off
766
+ terrainControl.isEnabled(); // Check if terrain is enabled
767
+ terrainControl.setExaggeration(value); // Set vertical exaggeration (0.1 - 10.0)
768
+ terrainControl.getExaggeration(); // Get current exaggeration
769
+ terrainControl.enableHillshade(); // Enable hillshade layer
770
+ terrainControl.disableHillshade(); // Disable hillshade layer
771
+ terrainControl.toggleHillshade(); // Toggle hillshade layer
772
+ terrainControl.update(options);
773
+ terrainControl.getState();
774
+ terrainControl.on("terrainchange", handler); // Listen for terrain toggle
775
+ ```
776
+
777
+ **Terrain Source:**
778
+ The control uses free terrain tiles from AWS:
779
+
780
+ - URL: `https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png`
781
+ - Encoding: Terrarium RGB-encoded elevation data
782
+ - No API key required
783
+
784
+ ### InspectControl
785
+
786
+ A control for inspecting vector features on the map. Click on features to view their properties/attributes in a popup.
787
+
788
+ ```typescript
789
+ interface InspectControlOptions {
790
+ position?: ControlPosition;
791
+ visible?: boolean; // Default: true
792
+ enabled?: boolean; // Start with inspect mode on. Default: false
793
+ maxFeatures?: number; // Max features at click point. Default: 10
794
+ includeLayers?: string[]; // Only inspect these layers
795
+ excludeLayers?: string[]; // Skip these layers
796
+ highlightStyle?: InspectHighlightStyle; // Style for selected feature
797
+ excludeProperties?: string[]; // Properties to hide (e.g., internal IDs)
798
+ showGeometryType?: boolean; // Show geometry type badge. Default: true
799
+ showLayerName?: boolean; // Show layer name. Default: true
800
+ maxWidth?: number; // Popup max width. Default: 320
801
+ maxHeight?: number; // Popup content max height. Default: 300
802
+ backgroundColor?: string;
803
+ borderRadius?: number;
804
+ opacity?: number;
805
+ fontSize?: number;
806
+ fontColor?: string;
807
+ minzoom?: number;
808
+ maxzoom?: number;
809
+ }
810
+
811
+ interface InspectHighlightStyle {
812
+ fillColor?: string; // Polygon fill. Default: '#ffff00'
813
+ fillOpacity?: number; // Polygon fill opacity. Default: 0.3
814
+ strokeColor?: string; // Line/outline color. Default: '#ffff00'
815
+ strokeWidth?: number; // Line width. Default: 3
816
+ circleRadius?: number; // Point radius. Default: 10
817
+ circleStrokeWidth?: number; // Point outline. Default: 3
818
+ }
819
+
820
+ interface InspectedFeature {
821
+ id: string; // Unique inspection ID
822
+ feature: GeoJSON.Feature; // The GeoJSON feature
823
+ layerId: string; // MapLibre layer ID
824
+ sourceId: string; // MapLibre source ID
825
+ lngLat: [number, number]; // Click coordinates
826
+ }
827
+
828
+ // Methods
829
+ inspectControl.show();
830
+ inspectControl.hide();
831
+ inspectControl.enable(); // Enable inspect mode
832
+ inspectControl.disable(); // Disable inspect mode
833
+ inspectControl.toggle(); // Toggle inspect mode on/off
834
+ inspectControl.isEnabled(); // Check if inspect mode is enabled
835
+ inspectControl.clear(); // Clear current inspection
836
+ inspectControl.getInspectedFeatures(); // Get all features at click point
837
+ inspectControl.getSelectedFeature(); // Get currently selected feature
838
+ inspectControl.selectFeature(index); // Select feature by index
839
+ inspectControl.nextFeature(); // Navigate to next feature
840
+ inspectControl.previousFeature(); // Navigate to previous feature
841
+ inspectControl.update(options);
842
+ inspectControl.getState();
843
+ inspectControl.on("enable", handler); // Fired when inspect mode is enabled
844
+ inspectControl.on("disable", handler); // Fired when inspect mode is disabled
845
+ inspectControl.on("featureselect", handler); // Fired when a feature is selected
846
+ inspectControl.on("clear", handler); // Fired when inspection is cleared
847
+ ```
848
+
849
+ **Usage:**
850
+
851
+ 1. Click the info button to enable inspect mode
852
+ 2. Click on any vector feature on the map
853
+ 3. View properties in the popup
854
+ 4. Use < > buttons to navigate when multiple features are at the same location
855
+ 5. Click elsewhere or the button again to disable
856
+
857
+ ### ViewStateControl
858
+
859
+ A control that displays live map view state (center, bounds, zoom, pitch, bearing) with optional bounding box drawing.
860
+
861
+ ```typescript
862
+ interface ViewStateControlOptions {
863
+ position?: ControlPosition;
864
+ className?: string; // Custom CSS class
865
+ visible?: boolean; // Default: true
866
+ collapsed?: boolean; // Start collapsed (button only). Default: true
867
+ precision?: number; // Decimal precision for coordinates. Default: 4
868
+ showCenter?: boolean; // Show center coordinates. Default: true
869
+ showBounds?: boolean; // Show map bounds. Default: true
870
+ showZoom?: boolean; // Show zoom level. Default: true
871
+ showPitch?: boolean; // Show pitch value. Default: true
872
+ showBearing?: boolean; // Show bearing value. Default: true
873
+ enableBBox?: boolean; // Enable bounding box drawing. Default: false
874
+ bboxFillColor?: string; // BBox fill color. Default: 'rgba(0, 120, 215, 0.1)'
875
+ bboxStrokeColor?: string; // BBox stroke color. Default: '#0078d7'
876
+ bboxStrokeWidth?: number; // BBox stroke width. Default: 2
877
+ panelWidth?: number; // Panel width in pixels. Default: 280
878
+ backgroundColor?: string;
879
+ borderRadius?: number;
880
+ opacity?: number;
881
+ fontSize?: number;
882
+ fontColor?: string;
883
+ minzoom?: number;
884
+ maxzoom?: number;
885
+ }
886
+
887
+ interface ViewStateControlState {
888
+ visible: boolean;
889
+ collapsed: boolean;
890
+ center: [number, number]; // [lng, lat]
891
+ bounds: [number, number, number, number]; // [west, south, east, north]
892
+ zoom: number;
893
+ pitch: number; // Degrees
894
+ bearing: number; // Degrees
895
+ drawingBBox: boolean; // Whether bbox drawing is active
896
+ drawnBBox: [number, number, number, number] | null; // Drawn bbox or null
897
+ }
898
+
899
+ // Methods
900
+ viewStateControl.show();
901
+ viewStateControl.hide();
902
+ viewStateControl.expand(); // Expand the panel
903
+ viewStateControl.collapse(); // Collapse to button only
904
+ viewStateControl.toggle(); // Toggle expanded/collapsed
905
+ viewStateControl.isCollapsed(); // Check if collapsed
906
+ viewStateControl.startBBoxDraw(); // Start bounding box drawing mode
907
+ viewStateControl.stopBBoxDraw(); // Stop bounding box drawing mode
908
+ viewStateControl.clearBBox(); // Clear the drawn bounding box
909
+ viewStateControl.update(options);
910
+ viewStateControl.getState();
911
+ viewStateControl.on("viewchange", handler); // Fired when map view changes
912
+ viewStateControl.on("bboxdraw", handler); // Fired when bbox is drawn
913
+ viewStateControl.on("bboxclear", handler); // Fired when bbox is cleared
914
+ viewStateControl.on("drawstart", handler); // Fired when drawing mode starts
915
+ viewStateControl.on("drawend", handler); // Fired when drawing mode ends
916
+ ```
917
+
918
+ **Usage:**
919
+
920
+ 1. Click the target button to expand/collapse the panel
921
+ 2. Pan, zoom, or tilt the map to see live updates
922
+ 3. Click "Draw BBox" to enter drawing mode
923
+ 4. Click and drag on the map to draw a bounding box
924
+ 5. Copy coordinates using the copy button next to each value
925
+
926
+ ### CogLayerControl
927
+
928
+ A control for adding Cloud Optimized GeoTIFF (COG) layers to the map. Uses deck.gl's COGLayer for efficient streaming and rendering.
929
+
930
+ ```typescript
931
+ interface CogLayerControlOptions {
932
+ position?: ControlPosition;
933
+ className?: string; // Custom CSS class
934
+ visible?: boolean; // Default: true
935
+ collapsed?: boolean; // Start collapsed. Default: true
936
+ beforeId?: string; // Layer ID to insert before (for ordering)
937
+ defaultUrl?: string; // Initial COG URL
938
+ loadDefaultUrl?: boolean; // Auto-load defaultUrl on add. Default: false
939
+ defaultBands?: string; // Band selection (e.g., "1" or "1,2,3"). Default: "1"
940
+ defaultColormap?: ColormapName | 'none'; // Colormap name. Default: 'none'
941
+ defaultRescaleMin?: number; // Min value for rescaling. Default: 0
942
+ defaultRescaleMax?: number; // Max value for rescaling. Default: 255
943
+ defaultNodata?: number; // Nodata value to mask
944
+ defaultOpacity?: number; // Layer opacity. Default: 1
945
+ panelWidth?: number; // Panel width in pixels. Default: 300
946
+ backgroundColor?: string;
947
+ borderRadius?: number;
948
+ opacity?: number;
949
+ fontSize?: number;
950
+ fontColor?: string;
951
+ minzoom?: number;
952
+ maxzoom?: number;
953
+ }
954
+
955
+ // Methods
956
+ cogControl.show()
957
+ cogControl.hide()
958
+ cogControl.expand()
959
+ cogControl.collapse()
960
+ cogControl.toggle()
961
+ cogControl.getState()
962
+ cogControl.update(options)
963
+ cogControl.addLayer(url?) // Add a COG layer
964
+ cogControl.removeLayer(id?) // Remove layer by ID or all
965
+ cogControl.getLayerIds() // Get all layer IDs
966
+ cogControl.getLayerOpacity(id) // Get layer opacity
967
+ cogControl.setLayerOpacity(id, opacity) // Set layer opacity
968
+ cogControl.getLayerVisibility(id) // Check if layer is visible
969
+ cogControl.setLayerVisibility(id, visible) // Show/hide layer
970
+ cogControl.getLayerUrl(id) // Get COG URL for a layer
971
+ cogControl.on('layeradd', handler) // Fired when layer is added
972
+ cogControl.on('layerremove', handler) // Fired when layer is removed
973
+ cogControl.on('error', handler) // Fired on error
974
+ ```
975
+
976
+ **Features:**
977
+
978
+ - Supports single-band and multi-band GeoTIFFs
979
+ - Automatic float/integer data type detection
980
+ - 21 built-in colormaps with live preview
981
+ - Rescale values for visualization
982
+ - Nodata value masking
983
+ - Opacity control
984
+ - Layer ordering with `beforeId`
985
+ - Multiple COG layers support
986
+
987
+ ### ZarrLayerControl
988
+
989
+ A control for adding Zarr layers to the map. Uses @carbonplan/zarr-layer for rendering multi-dimensional Zarr data.
990
+
991
+ ```typescript
992
+ interface ZarrLayerControlOptions {
993
+ position?: ControlPosition;
994
+ className?: string; // Custom CSS class
995
+ visible?: boolean; // Default: true
996
+ collapsed?: boolean; // Start collapsed. Default: true
997
+ beforeId?: string; // Layer ID to insert before (for ordering)
998
+ defaultUrl?: string; // Initial Zarr store URL
999
+ loadDefaultUrl?: boolean; // Auto-load defaultUrl on add. Default: false
1000
+ defaultVariable?: string; // Variable/array name to visualize
1001
+ defaultColormap?: string[]; // Array of hex colors. Default: viridis
1002
+ defaultClim?: [number, number]; // Color limits [min, max]. Default: [0, 1]
1003
+ defaultSelector?: Record<string, number | string>; // Dimension selectors
1004
+ defaultOpacity?: number; // Layer opacity. Default: 1
1005
+ panelWidth?: number; // Panel width in pixels. Default: 300
1006
+ backgroundColor?: string;
1007
+ borderRadius?: number;
1008
+ opacity?: number;
1009
+ fontSize?: number;
1010
+ fontColor?: string;
1011
+ minzoom?: number;
1012
+ maxzoom?: number;
1013
+ }
1014
+
1015
+ // Methods
1016
+ zarrControl.show()
1017
+ zarrControl.hide()
1018
+ zarrControl.expand()
1019
+ zarrControl.collapse()
1020
+ zarrControl.toggle()
1021
+ zarrControl.getState()
1022
+ zarrControl.update(options)
1023
+ zarrControl.addLayer(url?, variable?) // Add a Zarr layer
1024
+ zarrControl.removeLayer(id?) // Remove layer by ID or all
1025
+ zarrControl.getLayerIds() // Get all layer IDs
1026
+ zarrControl.getLayerOpacity(id) // Get layer opacity
1027
+ zarrControl.setLayerOpacity(id, opacity) // Set layer opacity
1028
+ zarrControl.getLayerVisibility(id) // Check if layer is visible
1029
+ zarrControl.setLayerVisibility(id, visible) // Show/hide layer
1030
+ zarrControl.getLayerUrl(id) // Get Zarr URL for a layer
1031
+ zarrControl.fetchVariables() // Fetch available variables from store
1032
+ zarrControl.on('layeradd', handler) // Fired when layer is added
1033
+ zarrControl.on('layerremove', handler) // Fired when layer is removed
1034
+ zarrControl.on('error', handler) // Fired on error
1035
+ ```
1036
+
1037
+ **Features:**
1038
+
1039
+ - Multi-dimensional array support (time, band, etc.)
1040
+ - Automatic variable discovery from Zarr metadata
1041
+ - 21 built-in colormaps with live preview
1042
+ - Custom colormap support (array of hex colors)
1043
+ - Dimension selectors for slicing data
1044
+ - Smooth opacity control
1045
+ - Layer ordering with `beforeId`
1046
+ - Multiple Zarr layers support
1047
+
1048
+ **Example with dimension selector:**
1049
+
1050
+ ```typescript
1051
+ const zarrControl = new ZarrLayerControl({
1052
+ defaultUrl: "https://example.com/climate.zarr",
1053
+ defaultVariable: "temperature",
1054
+ defaultColormap: ["#0000ff", "#ffffff", "#ff0000"], // Custom blue-white-red
1055
+ defaultClim: [-20, 40],
1056
+ defaultSelector: { time: 0, month: "January" }, // Select first time step, January
1057
+ });
1058
+ ```
1059
+
1060
+ ### StacLayerControl
1061
+
1062
+ A control for loading Cloud Optimized GeoTIFF (COG) layers from STAC (SpatioTemporal Asset Catalog) items. Fetches STAC item metadata and displays available COG assets for visualization.
1063
+
1064
+ ```typescript
1065
+ interface StacLayerControlOptions {
1066
+ position?: ControlPosition;
1067
+ className?: string; // Custom CSS class
1068
+ visible?: boolean; // Default: true
1069
+ collapsed?: boolean; // Start collapsed. Default: true
1070
+ beforeId?: string; // Layer ID to insert before (for ordering)
1071
+ defaultUrl?: string; // Initial STAC item URL
1072
+ loadDefaultUrl?: boolean; // Auto-load defaultUrl on add. Default: false
1073
+ defaultColormap?: ColormapName | "none"; // Colormap name. Default: 'none'
1074
+ defaultRescaleMin?: number; // Min value for rescaling. Default: 0
1075
+ defaultRescaleMax?: number; // Max value for rescaling. Default: 255
1076
+ defaultOpacity?: number; // Layer opacity. Default: 1
1077
+ defaultPickable?: boolean; // Enable click popups. Default: true
1078
+ panelWidth?: number; // Panel width in pixels. Default: 320
1079
+ backgroundColor?: string;
1080
+ borderRadius?: number;
1081
+ opacity?: number;
1082
+ fontSize?: number;
1083
+ fontColor?: string;
1084
+ minzoom?: number;
1085
+ maxzoom?: number;
1086
+ }
1087
+
1088
+ // Methods
1089
+ stacControl.show();
1090
+ stacControl.hide();
1091
+ stacControl.expand();
1092
+ stacControl.collapse();
1093
+ stacControl.toggle();
1094
+ stacControl.getState();
1095
+ stacControl.update(options);
1096
+ stacControl.loadStacUrl(url); // Fetch and parse a STAC item
1097
+ stacControl.on("stacload", handler); // Fired when STAC item is loaded
1098
+ stacControl.on("layeradd", handler); // Fired when layer is added
1099
+ stacControl.on("layerremove", handler); // Fired when layer is removed
1100
+ stacControl.on("error", handler); // Fired on error
1101
+ ```
1102
+
1103
+ **Usage:**
1104
+
1105
+ ```typescript
1106
+ import { StacLayerControl } from "maplibre-gl-components";
1107
+
1108
+ // Basic usage with STAC item URL
1109
+ const stacControl = new StacLayerControl({
1110
+ defaultUrl:
1111
+ "https://canada-spot-ortho.s3.amazonaws.com/.../S5_11055_6057_20070622.json",
1112
+ loadDefaultUrl: true,
1113
+ defaultColormap: "viridis",
1114
+ defaultRescaleMin: 0,
1115
+ defaultRescaleMax: 255,
1116
+ });
1117
+ map.addControl(stacControl, "top-right");
1118
+
1119
+ // Listen for events
1120
+ stacControl.on("stacload", (event) => {
1121
+ console.log("STAC loaded:", event.url);
1122
+ console.log("Available assets:", event.state.assets);
1123
+ });
1124
+
1125
+ stacControl.on("layeradd", (event) => {
1126
+ console.log("Layer added:", event.assetKey, event.layerId);
1127
+ });
1128
+ ```
1129
+
1130
+ **Features:**
1131
+
1132
+ - Fetches and parses STAC item JSON
1133
+ - Lists available COG assets (GeoTIFF files)
1134
+ - Asset selector dropdown
1135
+ - 21 built-in colormaps with live preview
1136
+ - Rescale values for visualization
1137
+ - Opacity control
1138
+ - Pickable layers with info popup on click
1139
+ - Auto-fits map to STAC item bounding box
1140
+ - Multiple layer support
1141
+
1142
+ **Workflow:**
1143
+
1144
+ 1. Enter a STAC item URL and click "Fetch STAC"
1145
+ 2. Select a COG asset from the dropdown
1146
+ 3. Configure colormap, rescale range, and opacity
1147
+ 4. Click "Add Layer" to visualize
1148
+ 5. Click on the layer to view info (if pickable)
1149
+
1150
+ ### StacSearchControl
1151
+
1152
+ A control for searching and visualizing STAC items from public STAC API catalogs. Search within the current map viewport, browse results, and display imagery with customizable band combinations and colormaps.
1153
+
1154
+ ```typescript
1155
+ interface StacSearchControlOptions {
1156
+ position?: ControlPosition;
1157
+ className?: string; // Custom CSS class
1158
+ visible?: boolean; // Default: true
1159
+ collapsed?: boolean; // Start collapsed. Default: true
1160
+ panelWidth?: number; // Panel width in pixels. Default: 360
1161
+ maxHeight?: number; // Max panel height in pixels. Default: none
1162
+ catalogs?: StacCatalog[]; // Predefined STAC catalogs
1163
+ maxItems?: number; // Max search results. Default: 20
1164
+ defaultRescaleMin?: number; // Min rescale value. Default: 0
1165
+ defaultRescaleMax?: number; // Max rescale value. Default: 10000
1166
+ defaultColormap?: string; // Colormap for single band. Default: 'viridis'
1167
+ defaultRgbMode?: boolean; // Start in RGB mode. Default: true
1168
+ showFootprints?: boolean; // Show item footprints on map. Default: true
1169
+ backgroundColor?: string;
1170
+ borderRadius?: number;
1171
+ opacity?: number;
1172
+ fontSize?: number;
1173
+ fontColor?: string;
1174
+ minzoom?: number;
1175
+ maxzoom?: number;
1176
+ }
1177
+
1178
+ interface StacCatalog {
1179
+ name: string; // Display name
1180
+ url: string; // STAC API URL
1181
+ }
1182
+ ```
1183
+
1184
+ **Usage:**
1185
+
1186
+ ```typescript
1187
+ import { StacSearchControl } from "maplibre-gl-components";
1188
+
1189
+ // Basic usage with default catalogs
1190
+ const stacSearch = new StacSearchControl({
1191
+ collapsed: false,
1192
+ maxItems: 20,
1193
+ showFootprints: true,
1194
+ });
1195
+ map.addControl(stacSearch, "top-right");
1196
+
1197
+ // With custom catalogs
1198
+ const stacSearch = new StacSearchControl({
1199
+ catalogs: [
1200
+ {
1201
+ name: "Element84 Earth Search",
1202
+ url: "https://earth-search.aws.element84.com/v1",
1203
+ },
1204
+ {
1205
+ name: "Microsoft Planetary Computer",
1206
+ url: "https://planetarycomputer.microsoft.com/api/stac/v1",
1207
+ },
1208
+ ],
1209
+ defaultRgbMode: true,
1210
+ defaultRescaleMin: 0,
1211
+ defaultRescaleMax: 3000,
1212
+ maxHeight: 500,
1213
+ });
1214
+ map.addControl(stacSearch, "top-right");
1215
+
1216
+ // Listen for events
1217
+ stacSearch.on("catalogselect", (event) => {
1218
+ console.log("Catalog selected:", event.catalog?.name);
1219
+ });
1220
+
1221
+ stacSearch.on("collectionsload", (event) => {
1222
+ console.log("Collections loaded:", event.state.collections.length);
1223
+ });
1224
+
1225
+ stacSearch.on("collectionselect", (event) => {
1226
+ console.log("Collection selected:", event.collection?.id);
1227
+ });
1228
+
1229
+ stacSearch.on("search", (event) => {
1230
+ console.log("Search completed:", event.state.items.length, "items found");
1231
+ });
1232
+
1233
+ stacSearch.on("itemselect", (event) => {
1234
+ console.log("Item selected:", event.item?.id);
1235
+ });
1236
+
1237
+ stacSearch.on("display", (event) => {
1238
+ console.log("Item displayed:", event.item?.id);
1239
+ });
1240
+
1241
+ stacSearch.on("error", (event) => {
1242
+ console.error("Error:", event.error);
1243
+ });
1244
+ ```
1245
+
1246
+ **Methods:**
1247
+
1248
+ ```typescript
1249
+ stacSearch.show();
1250
+ stacSearch.hide();
1251
+ stacSearch.expand();
1252
+ stacSearch.collapse();
1253
+ stacSearch.toggle();
1254
+ stacSearch.getState();
1255
+ stacSearch.update(options);
1256
+ stacSearch.getSelectedCatalog(); // Get current catalog
1257
+ stacSearch.getSelectedCollection(); // Get current collection
1258
+ stacSearch.getSelectedItem(); // Get current item
1259
+ stacSearch.on(event, handler); // Subscribe to events
1260
+ stacSearch.off(event, handler); // Unsubscribe from events
1261
+ ```
1262
+
1263
+ **Default Catalogs:**
1264
+
1265
+ - **Element84 Earth Search** - Sentinel-2, Landsat, NAIP, Copernicus DEM
1266
+ - **Microsoft Planetary Computer** - Extensive collection with tile server
1267
+
1268
+ **Features:**
1269
+
1270
+ - Search STAC items within current map viewport
1271
+ - Date range filtering
1272
+ - Query filter support (e.g., cloud cover)
1273
+ - Footprint visualization on map
1274
+ - **Visualization modes:**
1275
+ - **RGB Composite** - Select R, G, B bands for true/false color composites
1276
+ - **Single Band** - Select one band with colormap
1277
+ - 21 built-in colormaps for single band visualization
1278
+ - Rescale range adjustment
1279
+ - Auto-detects available bands/assets from STAC items
1280
+ - Custom catalog URL support
1281
+
1282
+ **Workflow:**
1283
+
1284
+ 1. Select a catalog (or enter custom URL)
1285
+ 2. Click "Collections" to load available collections
1286
+ 3. Select a collection (e.g., sentinel-2-l2a)
1287
+ 4. Optionally set date range and query filters
1288
+ 5. Click "Search" to find items in current map viewport
1289
+ 6. Select an item from the dropdown
1290
+ 7. Choose visualization mode (RGB or Single Band)
1291
+ 8. Select bands and adjust rescale range
1292
+ 9. Click "Display Item" to visualize
1293
+
1294
+ ### MeasureControl
1295
+
1296
+ A control for measuring distances and areas on the map.
1297
+
1298
+ See the [measure-control example](./examples/measure-control/) for a complete working example.
1299
+
1300
+ ### BookmarkControl
1301
+
1302
+ A control for saving and restoring map views with localStorage persistence.
1303
+
1304
+ See the [bookmark-control example](./examples/bookmark-control/) for a complete working example.
1305
+
1306
+ ### PrintControl
1307
+
1308
+ A control for exporting the current map view as PNG, JPEG, or PDF. Supports optional title overlays, custom filenames, quality settings, and custom export sizes. PDF export requires the optional `jspdf` peer dependency.
1309
+
1310
+ ```typescript
1311
+ interface PrintControlOptions {
1312
+ position?: ControlPosition; // Control position (default: 'top-right')
1313
+ className?: string; // Custom CSS class
1314
+ visible?: boolean; // Initial visibility (default: true)
1315
+ collapsed?: boolean; // Start collapsed (default: true)
1316
+ format?: "png" | "jpeg" | "pdf"; // Default format (default: 'png')
1317
+ quality?: number; // JPEG quality 0-1 (default: 0.92)
1318
+ filename?: string; // Default filename without extension (default: 'map-export')
1319
+ title?: string; // Optional title rendered on the image
1320
+ includeNorthArrow?: boolean; // Include north arrow in export (default: false)
1321
+ includeScaleBar?: boolean; // Include scale bar in export (default: false)
1322
+ titleFontSize?: number; // Title font size in pixels (default: 24)
1323
+ titleFontColor?: string; // Title font color (default: '#333333')
1324
+ titleBackground?: string; // Title background (default: 'rgba(255,255,255,0.8)')
1325
+ showSizeOptions?: boolean; // Show current/custom size options (default: false)
1326
+ width?: number; // Width override in pixels
1327
+ height?: number; // Height override in pixels
1328
+ panelWidth?: number; // Panel width in pixels (default: 280)
1329
+ backgroundColor?: string;
1330
+ borderRadius?: number;
1331
+ opacity?: number;
1332
+ fontSize?: number;
1333
+ fontColor?: string;
1334
+ minzoom?: number;
1335
+ maxzoom?: number;
1336
+ }
1337
+
1338
+ // Methods
1339
+ printControl.show()
1340
+ printControl.hide()
1341
+ printControl.getState()
1342
+ printControl.setFormat(format) // Set format: 'png', 'jpeg', or 'pdf'
1343
+ printControl.setQuality(quality) // Set JPEG quality (0.1 - 1)
1344
+ printControl.setTitle(title) // Set title text
1345
+ printControl.exportMap(options?) // Programmatic export, returns data URL (empty string for PDF)
1346
+ printControl.on('export', handler) // Fired after successful export
1347
+ printControl.on('copy', handler) // Fired after clipboard copy
1348
+ printControl.on('error', handler) // Fired on error
1349
+ ```
1350
+
1351
+ **Usage:**
1352
+
1353
+ ```typescript
1354
+ import { PrintControl } from "maplibre-gl-components";
1355
+
1356
+ const printControl = new PrintControl({
1357
+ filename: "my-map",
1358
+ format: "png",
1359
+ title: "My Map Title",
1360
+ includeNorthArrow: true,
1361
+ includeScaleBar: true,
1362
+ });
1363
+ map.addControl(printControl, "top-right");
1364
+
1365
+ // Listen for export events
1366
+ printControl.on("export", (event) => {
1367
+ console.log("Exported:", event.state.filename);
1368
+ });
1369
+
1370
+ // Programmatic export
1371
+ const dataUrl = await printControl.exportMap({
1372
+ format: "jpeg",
1373
+ quality: 0.95,
1374
+ title: "Custom Title",
1375
+ includeNorthArrow: true,
1376
+ includeScaleBar: true,
1377
+ });
1378
+
1379
+ // Export as PDF (requires jspdf)
1380
+ await printControl.exportMap({ format: "pdf" });
1381
+ ```
1382
+
1383
+ **Features:**
1384
+
1385
+ - Export as PNG, JPEG, or PDF
1386
+ - Optional title overlay rendered on the exported image
1387
+ - Optional north arrow and scale bar overlays in exported output
1388
+ - Customizable filename, quality, and export size
1389
+ - Copy to clipboard (PNG/JPEG only)
1390
+ - PDF export with auto landscape/portrait detection, fitted to A4 page
1391
+ - Programmatic export API
1392
+
1393
+ **PDF Export:**
1394
+
1395
+ PDF export requires the optional [`jspdf`](https://www.npmjs.com/package/jspdf) package:
1396
+
1397
+ ```bash
1398
+ npm install jspdf
1399
+ ```
1400
+
1401
+ The library is dynamically imported only when PDF format is selected, keeping the main bundle lean.
1402
+
1403
+ ### MinimapControl
1404
+
1405
+ An inset overview map that shows the current viewport extent on a smaller map. Supports click-to-navigate and customizable styling.
1406
+
1407
+ ```typescript
1408
+ interface MinimapControlOptions {
1409
+ position?: ControlPosition; // Control position (default: 'bottom-left')
1410
+ className?: string; // Custom CSS class
1411
+ visible?: boolean; // Initial visibility (default: true)
1412
+ collapsed?: boolean; // Start collapsed (default: false)
1413
+ width?: number; // Minimap width in pixels (default: 250)
1414
+ height?: number; // Minimap height in pixels (default: 180)
1415
+ zoomOffset?: number; // Zoom offset from main map (default: -5)
1416
+ style?: string | object; // Map style URL or object
1417
+ viewportRectColor?: string; // Viewport rectangle color (default: '#0078d7')
1418
+ viewportRectOpacity?: number; // Viewport rectangle fill opacity (default: 0.2)
1419
+ toggleable?: boolean; // Whether minimap can be toggled (default: true)
1420
+ interactive?: boolean; // Click minimap to navigate main map (default: false)
1421
+ minzoom?: number;
1422
+ maxzoom?: number;
1423
+ }
1424
+
1425
+ // Methods
1426
+ minimapControl.show()
1427
+ minimapControl.hide()
1428
+ minimapControl.expand() // Show the minimap panel
1429
+ minimapControl.collapse() // Hide the minimap panel
1430
+ minimapControl.toggle() // Toggle panel visibility
1431
+ minimapControl.getState()
1432
+ minimapControl.on(event, handler) // 'show' | 'hide' | 'expand' | 'collapse'
1433
+ minimapControl.off(event, handler)
1434
+ ```
1435
+
1436
+ **Usage:**
1437
+
1438
+ ```typescript
1439
+ import { MinimapControl } from "maplibre-gl-components";
1440
+
1441
+ const minimapControl = new MinimapControl({
1442
+ width: 250,
1443
+ height: 180,
1444
+ zoomOffset: -5,
1445
+ interactive: true,
1446
+ });
1447
+ map.addControl(minimapControl, "bottom-left");
1448
+
1449
+ minimapControl.on("expand", (event) => {
1450
+ console.log("Minimap expanded:", event.state);
1451
+ });
1452
+ ```
1453
+
1454
+ **Features:**
1455
+
1456
+ - Syncs center and zoom with the main map
1457
+ - Viewport rectangle overlay showing the visible area
1458
+ - Click anywhere on the minimap to fly the main map to that location
1459
+ - Drag on the minimap to pan the main map in real time
1460
+ - Toggleable panel with button
1461
+ - Customizable size, zoom offset, and style
1462
+
1463
+ See the [minimap-control example](./examples/minimap-control/) for a complete working example.
1464
+
1465
+ ### ControlGrid
1466
+
1467
+ A collapsible toolbar grid that organizes multiple controls (built-in and plugin) in a configurable rows × columns layout.
1468
+
1469
+ ```typescript
1470
+ interface ControlGridOptions {
1471
+ title?: string; // Header title
1472
+ position?: ControlPosition; // Control position (default: 'top-right')
1473
+ visible?: boolean; // Initial visibility (default: true)
1474
+ collapsible?: boolean; // Whether grid is collapsible (default: true)
1475
+ collapsed?: boolean; // Start collapsed (default: true)
1476
+ rows?: number; // Grid rows, 1-12 (default: 1)
1477
+ columns?: number; // Grid columns, 1-12 (default: 3)
1478
+ showRowColumnControls?: boolean; // Show row/column input fields (default: true)
1479
+ controls?: IControl[]; // Custom IControl instances
1480
+ defaultControls?: DefaultControlName[]; // Built-in control names (26 available)
1481
+ gap?: number; // Gap between cells in pixels (default: 6)
1482
+ basemapStyleUrl?: string; // Basemap style URL for SwipeControl
1483
+ excludeLayers?: string[]; // Layer patterns to exclude from SwipeControl
1484
+ streetViewOptions?: Partial<StreetViewControlOptions>; // Optional API keys and StreetView config overrides
1485
+ backgroundColor?: string;
1486
+ padding?: number;
1487
+ borderRadius?: number;
1488
+ opacity?: number;
1489
+ minzoom?: number;
1490
+ maxzoom?: number;
1491
+ }
1492
+ ```
1493
+
1494
+ **Available default controls:** `fullscreen`, `globe`, `north`, `terrain`, `search`, `viewState`, `inspect`, `vectorDataset`, `basemap`, `cogLayer`, `minimap`, `measure`, `bookmark`, `print`, `zarrLayer`, `pmtilesLayer`, `stacLayer`, `stacSearch`, `addVector`, `geoEditor`, `lidar`, `planetaryComputer`, `gaussianSplat`, `streetView`, `swipe`, `usgsLidar`
1495
+
1496
+ **StreetView env setup (for `streetView` default control):**
1497
+
1498
+ ```bash
1499
+ # .env
1500
+ VITE_GOOGLE_MAPS_API_KEY=your_google_maps_api_key
1501
+ VITE_MAPILLARY_ACCESS_TOKEN=your_mapillary_access_token
1502
+ ```
1503
+
1504
+ `ControlGrid` auto-reads these values for the built-in `streetView` control. You can also override explicitly with `streetViewOptions`.
1505
+ Mapillary viewer CSS is bundled by `maplibre-gl-components`, so no extra `mapillary-js` CSS import is needed.
1506
+
1507
+ ```typescript
1508
+ // Methods
1509
+ controlGrid.addControl(control) // Add a control to the grid
1510
+ controlGrid.removeControl(control) // Remove a control from the grid
1511
+ controlGrid.getControls() // Get all controls in the grid
1512
+ controlGrid.getAdapters() // Get layer adapters for LayerControl integration
1513
+ controlGrid.show()
1514
+ controlGrid.hide()
1515
+ controlGrid.expand()
1516
+ controlGrid.collapse()
1517
+ controlGrid.toggle()
1518
+ controlGrid.setRows(n)
1519
+ controlGrid.setColumns(n)
1520
+ controlGrid.getState()
1521
+ controlGrid.on(event, handler) // 'show' | 'hide' | 'expand' | 'collapse' | 'controladd' | 'controlremove'
1522
+ controlGrid.off(event, handler)
1523
+ ```
1524
+
1525
+ **Usage:**
1526
+
1527
+ ```typescript
1528
+ import { ControlGrid } from "maplibre-gl-components";
1529
+
1530
+ const controlGrid = new ControlGrid({
1531
+ rows: 5,
1532
+ columns: 5,
1533
+ collapsible: true,
1534
+ collapsed: true,
1535
+ basemapStyleUrl: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
1536
+ streetViewOptions: {
1537
+ // Optional explicit override (otherwise auto-read from VITE_* env vars)
1538
+ googleApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY,
1539
+ mapillaryAccessToken: import.meta.env.VITE_MAPILLARY_ACCESS_TOKEN,
1540
+ },
1541
+ defaultControls: [
1542
+ "globe", "fullscreen", "north", "terrain", "search",
1543
+ "viewState", "inspect", "basemap", "measure", "bookmark",
1544
+ ],
1545
+ });
1546
+ map.addControl(controlGrid, "top-right");
1547
+ ```
1548
+
1549
+ See the [control-grid example](./examples/control-grid/) for a complete working example.
1550
+
1551
+ ### addControlGrid
1552
+
1553
+ A convenience function that adds a ControlGrid with all 26 default controls in one call. Auto-calculates grid dimensions and sets sensible defaults.
1554
+
1555
+ ```typescript
1556
+ import { addControlGrid } from "maplibre-gl-components";
1557
+
1558
+ // Add all 26 default controls
1559
+ const grid = addControlGrid(map);
1560
+
1561
+ // Or use as a Map method (auto-installed on import)
1562
+ const grid = map.addControlGrid();
1563
+
1564
+ // Exclude specific controls
1565
+ const grid = addControlGrid(map, {
1566
+ exclude: ["minimap", "streetView", "gaussianSplat"],
1567
+ });
1568
+
1569
+ // Only specific controls
1570
+ const grid = addControlGrid(map, {
1571
+ defaultControls: ["search", "basemap", "terrain", "fullscreen"],
1572
+ });
1573
+
1574
+ // With basemap style for SwipeControl layer grouping
1575
+ const grid = addControlGrid(map, {
1576
+ basemapStyleUrl: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
1577
+ });
1578
+ ```
1579
+
1580
+ ```typescript
1581
+ interface AddControlGridOptions extends ControlGridOptions {
1582
+ exclude?: DefaultControlName[]; // Controls to exclude (ignored if defaultControls is set)
1583
+ }
1584
+ ```
1585
+
1586
+ **Defaults applied by `addControlGrid`:**
1587
+
1588
+ - All 26 controls included (unless filtered by `exclude` or `defaultControls`)
1589
+ - Grid dimensions auto-calculated as near-square layout
1590
+ - `collapsed: true`, `collapsible: true`, `showRowColumnControls: true`
1591
+ - `gap: 2`, `position: "top-right"`
1592
+ - `excludeLayers` set to default patterns for internal/helper layers
1593
+
1594
+ **With LayerControl integration:**
1595
+
1596
+ ```typescript
1597
+ import { addControlGrid, DEFAULT_EXCLUDE_LAYERS } from "maplibre-gl-components";
1598
+ import { LayerControl } from "maplibre-gl-layer-control";
1599
+
1600
+ const layerControl = new LayerControl({ collapsed: true });
1601
+ map.addControl(layerControl, "top-right");
1602
+
1603
+ const grid = addControlGrid(map, { basemapStyleUrl: BASEMAP_STYLE });
1604
+
1605
+ for (const adapter of grid.getAdapters()) {
1606
+ layerControl.registerCustomAdapter(adapter);
1607
+ }
1608
+ ```
1609
+
1610
+ See the [add-control-grid example](./examples/add-control-grid/) for a complete working example.
1611
+
1612
+ ### Layer Control Adapters
1613
+
1614
+ To integrate COG and Zarr layers with [maplibre-gl-layer-control](https://github.com/AJPNorthwest/maplibre-gl-layer-control), use the included adapters:
1615
+
1616
+ ```typescript
1617
+ import { LayerControl, CustomLayerAdapter } from "maplibre-gl-layer-control";
1618
+ import {
1619
+ CogLayerControl,
1620
+ ZarrLayerControl,
1621
+ CogLayerAdapter,
1622
+ ZarrLayerAdapter,
1623
+ } from "maplibre-gl-components";
1624
+
1625
+ // Create layer controls
1626
+ const cogControl = new CogLayerControl({ collapsed: true });
1627
+ const zarrControl = new ZarrLayerControl({ collapsed: true });
1628
+
1629
+ map.addControl(cogControl);
1630
+ map.addControl(zarrControl);
1631
+
1632
+ // Add layers
1633
+ await cogControl.addLayer("https://example.com/dem.tif");
1634
+ await zarrControl.addLayer("https://example.com/data.zarr", "temperature");
1635
+
1636
+ // Create layer control with adapters
1637
+ const layerControl = new LayerControl({
1638
+ customLayers: [
1639
+ new CogLayerAdapter(cogControl, { name: "Elevation DEM" }),
1640
+ new ZarrLayerAdapter(zarrControl, { name: "Temperature Data" }),
1641
+ ],
1642
+ });
1643
+ map.addControl(layerControl);
1644
+ ```
1645
+
1646
+ **Adapter Options:**
1647
+
1648
+ ```typescript
1649
+ interface AdapterOptions {
1650
+ name?: string; // Display name in layer control
1651
+ defaultOpacity?: number; // Opacity when toggled on (default: 1)
1652
+ }
1653
+ ```
1654
+
1655
+ ## Built-in Colormaps
1656
+
1657
+ ### Sequential
1658
+
1659
+ - `viridis` - Perceptually uniform, colorblind-friendly
1660
+ - `plasma` - Perceptually uniform
1661
+ - `inferno` - Perceptually uniform
1662
+ - `magma` - Perceptually uniform
1663
+ - `cividis` - Colorblind-friendly
1664
+
1665
+ ### Diverging
1666
+
1667
+ - `coolwarm` - Blue to red through white
1668
+ - `bwr` - Blue-white-red
1669
+ - `seismic` - Blue to red
1670
+ - `RdBu` - Red to blue
1671
+ - `RdYlBu` - Red-yellow-blue
1672
+ - `RdYlGn` - Red-yellow-green
1673
+ - `spectral` - Rainbow-like diverging
1674
+
1675
+ ### Miscellaneous
1676
+
1677
+ - `jet` - Classic rainbow
1678
+ - `rainbow` - Full spectrum
1679
+ - `turbo` - Improved rainbow
1680
+ - `terrain` - Elevation-like
1681
+ - `ocean` - Ocean depths
1682
+ - `hot` - Black-red-yellow-white
1683
+ - `cool` - Cyan to magenta
1684
+ - `gray` - Grayscale
1685
+ - `bone` - Blue-tinted grayscale
1686
+
1687
+ ### Custom Colors
1688
+
1689
+ ```typescript
1690
+ // Use an array of colors
1691
+ const colorbar = new Colorbar({
1692
+ colormap: ["#0000ff", "#00ff00", "#ffff00", "#ff0000"],
1693
+ vmin: 0,
1694
+ vmax: 100,
1695
+ });
1696
+
1697
+ // Or use color stops for precise control
1698
+ const colorbar = new Colorbar({
1699
+ colorStops: [
1700
+ { position: 0, color: "#0000ff" },
1701
+ { position: 0.3, color: "#00ff00" },
1702
+ { position: 0.7, color: "#ffff00" },
1703
+ { position: 1, color: "#ff0000" },
1704
+ ],
1705
+ vmin: 0,
1706
+ vmax: 100,
1707
+ });
1708
+ ```
1709
+
1710
+ ## Zoom-based Visibility
1711
+
1712
+ All components support `minzoom` and `maxzoom` options to control visibility based on the map's zoom level. This is useful for showing different legends at different zoom levels, similar to how map layers work.
1713
+
1714
+ ```typescript
1715
+ // Show legend only when zoomed in (zoom >= 10)
1716
+ const detailLegend = new Legend({
1717
+ title: 'Detailed Features',
1718
+ items: [...],
1719
+ minzoom: 10, // Only visible at zoom 10 and above
1720
+ });
1721
+
1722
+ // Show legend only when zoomed out (zoom <= 8)
1723
+ const overviewLegend = new Legend({
1724
+ title: 'Overview',
1725
+ items: [...],
1726
+ maxzoom: 8, // Only visible at zoom 8 and below
1727
+ });
1728
+
1729
+ // Show colorbar only within a specific zoom range
1730
+ const colorbar = new Colorbar({
1731
+ colormap: 'viridis',
1732
+ vmin: 0,
1733
+ vmax: 100,
1734
+ minzoom: 5, // Visible from zoom 5...
1735
+ maxzoom: 15, // ...up to zoom 15
1736
+ });
1737
+ ```
1738
+
1739
+ ### React Example
1740
+
1741
+ ```tsx
1742
+ <LegendReact
1743
+ map={map}
1744
+ title="Lidar Point Cloud"
1745
+ items={[
1746
+ { label: "QL0 (Approx. <= 0.35m NPS)", color: "#003300" },
1747
+ { label: "QL1 (Approx. 0.35m NPS)", color: "#006600" },
1748
+ { label: "QL2 (Approx. 0.7m NPS)", color: "#00cc00" },
1749
+ { label: "QL3 (Approx. 1.4m NPS)", color: "#ccff00" },
1750
+ { label: "Other", color: "#99ccff" },
1751
+ ]}
1752
+ minzoom={8}
1753
+ maxzoom={18}
1754
+ position="top-left"
1755
+ />
1756
+ ```
1757
+
1758
+ **Note:** The `visible` option takes precedence - if `visible` is `false`, the component will be hidden regardless of zoom level.
1759
+
1760
+ ## React Hooks
1761
+
1762
+ ```typescript
1763
+ import { useColorbar, useLegend, useHtmlControl, useBasemap, useTerrain, useSearchControl, useVectorDataset, useViewState } from 'maplibre-gl-components/react';
1764
+
1765
+ function MyComponent() {
1766
+ const colorbar = useColorbar({ colormap: 'viridis', vmin: 0, vmax: 100 });
1767
+ const legend = useLegend({ items: [...] });
1768
+ const htmlControl = useHtmlControl({ html: '...' });
1769
+ const basemap = useBasemap({ selectedBasemap: 'OpenStreetMap.Mapnik' });
1770
+ const terrain = useTerrain({ enabled: false, exaggeration: 1.5 });
1771
+ const search = useSearchControl({ collapsed: true });
1772
+ const vectorDataset = useVectorDataset();
1773
+ const viewState = useViewState({ collapsed: false, enableBBox: true });
1774
+
1775
+ return (
1776
+ <>
1777
+ <button onClick={() => colorbar.setColormap('plasma')}>
1778
+ Change Colormap
1779
+ </button>
1780
+ <button onClick={() => legend.toggle()}>
1781
+ Toggle Legend
1782
+ </button>
1783
+ <button onClick={() => basemap.setBasemap('CartoDB.Positron')}>
1784
+ Change Basemap
1785
+ </button>
1786
+ <button onClick={() => terrain.toggle()}>
1787
+ Toggle Terrain
1788
+ </button>
1789
+ <button onClick={() => search.toggle()}>
1790
+ Toggle Search
1791
+ </button>
1792
+
1793
+ <SearchControlReact
1794
+ map={map}
1795
+ collapsed={search.state.collapsed}
1796
+ onResultSelect={(result) => search.selectResult(result)}
1797
+ />
1798
+
1799
+ <TerrainReact
1800
+ map={map}
1801
+ enabled={terrain.state.enabled}
1802
+ exaggeration={terrain.state.exaggeration}
1803
+ onTerrainChange={(enabled) => terrain.setEnabled(enabled)}
1804
+ />
1805
+
1806
+ <BasemapReact
1807
+ map={map}
1808
+ defaultBasemap={basemap.state.selectedBasemap}
1809
+ onBasemapChange={(b) => basemap.setBasemap(b.id)}
1810
+ />
1811
+
1812
+ <ColorbarReact
1813
+ map={map}
1814
+ {...colorbar.state}
1815
+ vmin={colorbar.state.vmin}
1816
+ vmax={colorbar.state.vmax}
1817
+ />
1818
+ </>
1819
+ );
1820
+ }
1821
+ ```
1822
+
1823
+ ## Styling
1824
+
1825
+ The default styles can be customized using CSS:
1826
+
1827
+ ```css
1828
+ /* Override colorbar styles */
1829
+ .maplibre-gl-colorbar {
1830
+ background: rgba(0, 0, 0, 0.8);
1831
+ color: white;
1832
+ }
1833
+
1834
+ /* Override legend styles */
1835
+ .maplibre-gl-legend {
1836
+ font-size: 14px;
1837
+ border-radius: 8px;
1838
+ }
1839
+
1840
+ /* Override HTML control styles */
1841
+ .maplibre-gl-html-control {
1842
+ max-width: 400px;
1843
+ }
1844
+
1845
+ /* Override basemap control styles */
1846
+ .maplibre-gl-basemap {
1847
+ max-width: 300px;
1848
+ }
1849
+
1850
+ /* Override terrain control styles */
1851
+ .maplibre-gl-terrain-button {
1852
+ color: #0078d7;
1853
+ }
1854
+
1855
+ /* Override search control styles */
1856
+ .maplibre-gl-search {
1857
+ background: rgba(255, 255, 255, 0.95);
1858
+ }
1859
+
1860
+ .maplibre-gl-search-toggle:hover {
1861
+ color: #0078d7;
1862
+ }
1863
+
1864
+ /* Override vector dataset control styles */
1865
+ .maplibre-gl-vector-dataset-button:hover {
1866
+ color: #0078d7;
1867
+ }
1868
+
1869
+ .maplibre-gl-vector-dataset-dropzone {
1870
+ background: rgba(0, 120, 215, 0.2);
1871
+ }
1872
+
1873
+ /* Override view state control styles */
1874
+ .maplibre-gl-view-state-button:hover {
1875
+ color: #0078d7;
1876
+ }
1877
+
1878
+ .maplibre-gl-view-state-panel {
1879
+ background: rgba(255, 255, 255, 0.95);
1880
+ }
1881
+
1882
+ .maplibre-gl-view-state-bbox-toggle--active {
1883
+ background: #0078d7;
1884
+ color: white;
1885
+ }
1886
+ ```
1887
+
1888
+ ## Examples
1889
+
1890
+ See the [examples](./examples/) directory for complete working examples:
1891
+
1892
+ - **Basic Example** - Vanilla TypeScript with all components
1893
+ - **React Example** - React with hooks and dynamic updates
1894
+ - **View State Example** - View state control with bounding box drawing
1895
+ - **COG Layer Example** - Cloud Optimized GeoTIFF visualization with colormaps
1896
+ - **Zarr Layer Example** - Multi-dimensional Zarr data visualization
1897
+ - **STAC Layer Example** - Load COG layers from STAC catalog items
1898
+ - **STAC Search Example** - Search and visualize STAC items from public catalogs
1899
+ - **Print Control Example** - Export map as PNG, JPEG, or PDF
1900
+ - **Minimap Control Example** - Inset overview map with viewport rectangle
1901
+ - **Control Grid Example** - ControlGrid with all built-in and plugin controls
1902
+ - **addControlGrid Example** - One-call convenience function with all default controls
1903
+ - **Three.js Plugin Example** - Integrate `MapScene` from maplibre-three-plugin with a rotating 3D object
1904
+
1905
+ ## Development
1906
+
1907
+ ```bash
1908
+ # Install dependencies
1909
+ npm install
1910
+
1911
+ # Start development server
1912
+ npm run dev
1913
+
1914
+ # Run tests
1915
+ npm test
1916
+
1917
+ # Build for production
1918
+ npm run build
1919
+
1920
+ # Build examples
1921
+ npm run build:examples
1922
+ ```
1923
+
1924
+ ## Docker
1925
+
1926
+ The examples can be run using Docker. The image is automatically built and published to GitHub Container Registry.
1927
+
1928
+ ### Pull and Run
1929
+
1930
+ ```bash
1931
+ # Pull the latest image
1932
+ docker pull ghcr.io/opengeos/maplibre-gl-components:latest
1933
+
1934
+ # Run the container
1935
+ docker run -p 8080:80 ghcr.io/opengeos/maplibre-gl-components:latest
1936
+ ```
1937
+
1938
+ Then open http://localhost:8080/maplibre-gl-components/ in your browser to view the examples.
1939
+
1940
+ ### Build Locally
1941
+
1942
+ ```bash
1943
+ # Build the image
1944
+ docker build -t maplibre-gl-components .
1945
+
1946
+ # Run the container
1947
+ docker run -p 8080:80 maplibre-gl-components
1948
+ ```
1949
+
1950
+ ### Available Tags
1951
+
1952
+ | Tag | Description |
1953
+ | -------- | -------------------------------- |
1954
+ | `latest` | Latest release |
1955
+ | `x.y.z` | Specific version (e.g., `1.0.0`) |
1956
+ | `x.y` | Minor version (e.g., `1.0`) |
1957
+
1958
+ ## License
1959
+
1960
+ MIT License - see [LICENSE](./LICENSE) for details.