maplibre-gl-raster 0.1.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 (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +362 -0
  3. package/dist/LercDecode.es-CZm5toNk.js +442 -0
  4. package/dist/LercDecode.es-CZm5toNk.js.map +1 -0
  5. package/dist/RasterControl-B7XdXYSu.js +18838 -0
  6. package/dist/RasterControl-B7XdXYSu.js.map +1 -0
  7. package/dist/__vite-browser-external-BgMKmFg9.js +9 -0
  8. package/dist/__vite-browser-external-BgMKmFg9.js.map +1 -0
  9. package/dist/assets/LercDecode.es-Bvg6-wb5.js +442 -0
  10. package/dist/assets/LercDecode.es-Bvg6-wb5.js.map +1 -0
  11. package/dist/assets/__vite-browser-external-BgMKmFg9.js +9 -0
  12. package/dist/assets/__vite-browser-external-BgMKmFg9.js.map +1 -0
  13. package/dist/assets/chunk-FDOR9p9I.js +24 -0
  14. package/dist/assets/decode-BvR5vy7g.js +940 -0
  15. package/dist/assets/decode-BvR5vy7g.js.map +1 -0
  16. package/dist/assets/lerc-By2TvjAX.js +34 -0
  17. package/dist/assets/lerc-By2TvjAX.js.map +1 -0
  18. package/dist/assets/lzw-CQJJDca2.js +174 -0
  19. package/dist/assets/lzw-CQJJDca2.js.map +1 -0
  20. package/dist/assets/worker-C6cg9T3Y.js +51 -0
  21. package/dist/assets/worker-C6cg9T3Y.js.map +1 -0
  22. package/dist/assets/zstd-DBZv9xja.js +569 -0
  23. package/dist/assets/zstd-DBZv9xja.js.map +1 -0
  24. package/dist/chunk-FDOR9p9I.js +24 -0
  25. package/dist/decode-BMFOVF9X.js +1966 -0
  26. package/dist/decode-BMFOVF9X.js.map +1 -0
  27. package/dist/index.mjs +223 -0
  28. package/dist/index.mjs.map +1 -0
  29. package/dist/lerc-B7WY-v3y.js +34 -0
  30. package/dist/lerc-B7WY-v3y.js.map +1 -0
  31. package/dist/lzw-YEsReV21.js +174 -0
  32. package/dist/lzw-YEsReV21.js.map +1 -0
  33. package/dist/maplibre-gl-raster.css +670 -0
  34. package/dist/react.mjs +145 -0
  35. package/dist/react.mjs.map +1 -0
  36. package/dist/types/index.d.ts +649 -0
  37. package/dist/types/react.d.ts +477 -0
  38. package/dist/zstd-DBZv9xja.js +569 -0
  39. package/dist/zstd-DBZv9xja.js.map +1 -0
  40. package/package.json +125 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Qiusheng Wu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,362 @@
1
+ # maplibre-gl-raster
2
+
3
+ A MapLibre GL JS plugin for visualizing local and remote raster datasets (GeoTIFF / Cloud Optimized GeoTIFF) directly in the browser. No tile server required: COGs are read with HTTP range requests and rendered on the GPU through a deck.gl pipeline.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/maplibre-gl-raster.svg)](https://www.npmjs.com/package/maplibre-gl-raster)
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-raster)
8
+ [![Open in StackBlitz](https://img.shields.io/badge/Open%20in-StackBlitz-blue?logo=stackblitz)](https://stackblitz.com/github/opengeos/maplibre-gl-raster)
9
+
10
+ ## Features
11
+
12
+ - **Local and remote rasters** - Load Cloud Optimized GeoTIFFs from any CORS-enabled URL, or drag-and-drop local GeoTIFF files
13
+ - **Multiple layers** - Layer list with visibility toggles, reordering, zoom-to, and per-layer settings
14
+ - **GPU rendering pipeline** - Band compositing, per-band rescale, 90+ colormaps, nodata filtering, linear/sqrt/log stretch, and gamma correction as deck.gl shader modules; parameter changes re-render without re-fetching tiles
15
+ - **Auto statistics** - Per-band min/max and histograms sampled from COG overviews (or GDAL metadata), with draggable histogram handles for the rescale range
16
+ - **Collapsible control** - A compact 29x29 map button that expands into a floating panel
17
+ - **TypeScript + React** - Full type definitions, a React wrapper component, and hooks
18
+ - **GeoLibre bundle output** - Builds a zip with root `plugin.json`, bundled ESM, and CSS for GeoLibre Desktop
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install maplibre-gl-raster
24
+ ```
25
+
26
+ The plugin declares `maplibre-gl`, `@deck.gl/*`, and `@luma.gl/*` as peer dependencies (npm 7+ installs them automatically). This package is ESM-only.
27
+
28
+ ## Quick Start
29
+
30
+ ### Vanilla JavaScript/TypeScript
31
+
32
+ ```typescript
33
+ import maplibregl from "maplibre-gl";
34
+ import { RasterControl } from "maplibre-gl-raster";
35
+ import "maplibre-gl-raster/style.css";
36
+
37
+ const map = new maplibregl.Map({
38
+ container: "map",
39
+ style: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
40
+ center: [0, 0],
41
+ zoom: 2,
42
+ });
43
+
44
+ map.on("load", () => {
45
+ const control = new RasterControl({ collapsed: false });
46
+ map.addControl(control, "top-right");
47
+
48
+ // Optionally add a raster programmatically (users can also paste a URL
49
+ // or drop a local GeoTIFF file in the panel).
50
+ control.addRaster("https://example.com/data/cog.tif");
51
+ });
52
+ ```
53
+
54
+ ### React
55
+
56
+ ```tsx
57
+ import { useEffect, useRef, useState } from "react";
58
+ import maplibregl, { Map } from "maplibre-gl";
59
+ import { RasterControlReact, useRasterState } from "maplibre-gl-raster/react";
60
+ import "maplibre-gl-raster/style.css";
61
+
62
+ function App() {
63
+ const mapContainer = useRef<HTMLDivElement>(null);
64
+ const [map, setMap] = useState<Map | null>(null);
65
+ const { state, toggle } = useRasterState();
66
+
67
+ useEffect(() => {
68
+ if (!mapContainer.current) return;
69
+
70
+ const mapInstance = new maplibregl.Map({
71
+ container: mapContainer.current,
72
+ style: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
73
+ center: [0, 0],
74
+ zoom: 2,
75
+ });
76
+
77
+ mapInstance.on("load", () => setMap(mapInstance));
78
+
79
+ return () => mapInstance.remove();
80
+ }, []);
81
+
82
+ return (
83
+ <div style={{ width: "100%", height: "100vh" }}>
84
+ <div ref={mapContainer} style={{ width: "100%", height: "100%" }} />
85
+ {map && (
86
+ <RasterControlReact
87
+ map={map}
88
+ collapsed={state.collapsed}
89
+ onReady={(control) => control.addRaster("https://example.com/cog.tif")}
90
+ />
91
+ )}
92
+ </div>
93
+ );
94
+ }
95
+ ```
96
+
97
+ ## API
98
+
99
+ ### RasterControl
100
+
101
+ The main control class implementing MapLibre's `IControl` interface.
102
+
103
+ #### Constructor Options
104
+
105
+ | Option | Type | Default | Description |
106
+ | ------------- | --------- | ------------- | ------------------------------------------------------------------------- |
107
+ | `collapsed` | `boolean` | `true` | Whether the panel starts collapsed (showing only the 29x29 toggle button) |
108
+ | `position` | `string` | `'top-right'` | Control position on the map |
109
+ | `title` | `string` | `'Raster'` | Title displayed in the header |
110
+ | `panelWidth` | `number` | `360` | Width of the dropdown panel in pixels |
111
+ | `className` | `string` | `''` | Custom CSS class name |
112
+ | `interleaved` | `boolean` | `true` | Render the deck.gl overlay interleaved with the basemap layers |
113
+ | `defaultUrl` | `string` | `''` | Prefills the Add data URL input (not loaded until the user clicks Load) |
114
+ | `autoLoad` | `boolean` | `false` | Load `defaultUrl` automatically when the control is added to the map |
115
+
116
+ #### Raster Methods
117
+
118
+ - `addRaster(source, options?)` - Add a raster from a COG URL (`string`) or a local GeoTIFF `File`; resolves with the layer id
119
+ - `removeRaster(id)` - Remove a raster layer
120
+ - `getRaster(id)` / `getRasters()` - Get layer snapshots (`RasterLayerInfo`)
121
+ - `setRasterState(id, patch)` - Update visualization state (mode, bands, rescale, colormap, nodata, opacity, gamma, stretch, visible)
122
+ - `setVisible(id, visible)` - Show / hide a layer
123
+ - `selectRaster(id | null)` - Choose which layer the panel's settings edit
124
+ - `zoomToRaster(id)` - Fit the map to a layer's bounds
125
+ - `reorderRaster(id, toIndex)` - Move a layer in the draw order (0 = bottom)
126
+
127
+ `addRaster` options (`AddRasterOptions`): `id`, `name`, `state` (initial `Partial<RasterLayerState>` overrides), `zoomTo` (default `true`), and `beforeId` (insert the raster beneath an existing style layer, e.g. a label layer; also available as an input in the panel's Add data section).
128
+
129
+ #### Panel Methods
130
+
131
+ - `toggle()` / `expand()` / `collapse()` - Control the panel
132
+ - `getState()` / `setState(state)` - Control-level state (collapsed, panelWidth)
133
+ - `on(event, handler)` / `off(event, handler)` - Event handlers
134
+ - `getMap()` / `getContainer()` - Access the map / container
135
+
136
+ #### Events
137
+
138
+ - `collapse` / `expand` / `statechange` - Panel state events
139
+ - `rasteradd` / `rasterremove` / `rasterchange` / `rasterselect` - Layer lifecycle events (payload includes `layerId`)
140
+ - `error` - Loading or rendering errors (payload includes `error`)
141
+
142
+ ### RasterLayerState
143
+
144
+ Per-layer visualization state, editable via the panel or `setRasterState`:
145
+
146
+ ```typescript
147
+ interface RasterLayerState {
148
+ mode: "rgb" | "single"; // RGB composite or single band + colormap
149
+ bands: number[]; // 1-indexed band selection
150
+ rescale: [number, number][] | null; // per-channel min/max; null = auto (2-98%)
151
+ colormap: string; // colormap name; "palette" = embedded color table
152
+ nodata: number | "off" | "auto"; // nodata handling
153
+ opacity: number; // 0..1
154
+ gamma: number; // power-law correction (1 = off)
155
+ stretch: "linear" | "log" | "sqrt"; // curve applied after rescale
156
+ visible: boolean;
157
+ }
158
+ ```
159
+
160
+ When a raster loads, the mode and bands are picked automatically (3+ bands → RGB `[1, 2, 3]`; otherwise single-band), and the rescale range defaults to the 2-98% percentile of sampled statistics. Single-band rasters use the image's embedded color table when it carries one (`colormap: "palette"`) and grayscale otherwise. The first four bands are fetched as GPU textures, so band combinations among them re-render instantly without re-downloading tiles.
161
+
162
+ ### RasterControlReact
163
+
164
+ React wrapper component for `RasterControl`.
165
+
166
+ #### Props
167
+
168
+ All `RasterControl` options plus:
169
+
170
+ | Prop | Type | Description |
171
+ | --------------- | ---------- | ------------------------------------------------------ |
172
+ | `map` | `Map` | MapLibre GL map instance (required) |
173
+ | `onStateChange` | `function` | Callback fired when the control state changes |
174
+ | `onReady` | `function` | Receives the `RasterControl` instance after map attach |
175
+
176
+ ### useRasterState
177
+
178
+ Custom React hook for managing control state.
179
+
180
+ ```typescript
181
+ const {
182
+ state, // Current state
183
+ setState, // Update entire state
184
+ setCollapsed, // Set collapsed state
185
+ setPanelWidth, // Set panel width
186
+ setData, // Set custom data
187
+ reset, // Reset to initial state
188
+ toggle, // Toggle collapsed state
189
+ } = useRasterState(initialState);
190
+ ```
191
+
192
+ ### Utilities
193
+
194
+ The package also exports lower-level building blocks for advanced use:
195
+
196
+ - `loadGeoTIFF(url)` - Open a (CORS-safe) GeoTIFF from a URL or blob URL
197
+ - `computeAutoStats(tiff, signal, onProgress?)` - Per-band min/max + histograms
198
+ - `summarizeGeoTIFF(tiff)` - Image / CRS / band / GDAL metadata summary
199
+ - `readBandNames(tiff)` / `percentileFromHistogram(stats, p)`
200
+ - `COLORMAP_NAMES` / `COLORMAP_OPTIONS` / `colormapsPngUrl`
201
+ - `clamp`, `formatNumericValue`, `generateId`, `debounce`, `throttle`, `classNames`
202
+
203
+ ## CORS requirements for remote COGs
204
+
205
+ Remote COGs must be served with CORS enabled (`Access-Control-Allow-Origin`). The loader includes a workaround for buckets that do not expose `Content-Range` via `Access-Control-Expose-Headers`, so most public S3/R2 buckets work out of the box.
206
+
207
+ ## Build a GeoLibre plugin zip
208
+
209
+ 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.
210
+
211
+ ```bash
212
+ npm install
213
+ npm run package:geolibre
214
+ ```
215
+
216
+ This creates:
217
+
218
+ ```text
219
+ geolibre-plugin/maplibre-gl-raster-0.1.0.zip
220
+ ```
221
+
222
+ The generated zip contains:
223
+
224
+ ```text
225
+ plugin.json
226
+ dist/index.js
227
+ dist/style.css
228
+ ```
229
+
230
+ 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:
231
+
232
+ ```text
233
+ ~/.local/share/org.geolibre.desktop/plugins/
234
+ ```
235
+
236
+ For the GeoLibre web app, serve the unpacked plugin with CORS enabled:
237
+
238
+ ```bash
239
+ npm run package:geolibre
240
+ npm run serve:geolibre -- 8000
241
+ ```
242
+
243
+ Then add this manifest URL in GeoLibre Settings > Plugins:
244
+
245
+ ```text
246
+ http://localhost:8000/plugin.json
247
+ ```
248
+
249
+ Using `python -m http.server` for this cross-origin web app case is not enough
250
+ because it does not send `Access-Control-Allow-Origin`.
251
+
252
+ ## Development
253
+
254
+ ### Setup
255
+
256
+ ```bash
257
+ # Clone the repository
258
+ git clone https://github.com/opengeos/maplibre-gl-raster.git
259
+ cd maplibre-gl-raster
260
+
261
+ # Install dependencies
262
+ npm install
263
+
264
+ # Start development server
265
+ npm run dev
266
+ ```
267
+
268
+ ### Scripts
269
+
270
+ | Script | Description |
271
+ | -------------------------- | ---------------------------------------- |
272
+ | `npm run dev` | Start development server |
273
+ | `npm run build` | Build the library and GeoLibre bundle |
274
+ | `npm run build:lib` | Build the standalone MapLibre library |
275
+ | `npm run build:geolibre` | Build the GeoLibre ESM and CSS bundle |
276
+ | `npm run package:geolibre` | Build and zip the GeoLibre plugin bundle |
277
+ | `npm run build:examples` | Build examples for deployment |
278
+ | `npm run test` | Run tests |
279
+ | `npm run test:ui` | Run tests with UI |
280
+ | `npm run test:coverage` | Run tests with coverage |
281
+ | `npm run lint` | Lint the code |
282
+ | `npm run format` | Format the code |
283
+
284
+ ### Project Structure
285
+
286
+ ```text
287
+ maplibre-gl-raster/
288
+ ├── geolibre-plugin/
289
+ │ └── plugin.json # GeoLibre external plugin manifest
290
+ ├── scripts/
291
+ │ └── package-geolibre-plugin.mjs
292
+ ├── src/
293
+ │ ├── index.ts # Main entry point
294
+ │ ├── geolibre.ts # GeoLibre plugin wrapper entry point
295
+ │ ├── react.ts # React entry point
296
+ │ ├── index.css # Root styles
297
+ │ └── lib/
298
+ │ ├── core/ # RasterControl, React wrapper, types
299
+ │ ├── raster/ # GeoTIFF loading, stats, GPU render pipeline
300
+ │ ├── state/ # RasterLayer model + LayerManager
301
+ │ ├── ui/ # Vanilla DOM panel components
302
+ │ ├── hooks/ # React hooks
303
+ │ ├── utils/ # Utility functions
304
+ │ └── styles/ # Component styles
305
+ ├── tests/ # Test files
306
+ ├── examples/ # Example applications
307
+ │ ├── basic/ # Vanilla JS example
308
+ │ └── react/ # React example
309
+ └── .github/workflows/ # CI/CD workflows
310
+ ```
311
+
312
+ ## Docker
313
+
314
+ The examples can be run using Docker. The image is automatically built and published to GitHub Container Registry.
315
+
316
+ ### Pull and Run
317
+
318
+ ```bash
319
+ # Pull the latest image
320
+ docker pull ghcr.io/opengeos/maplibre-gl-raster:latest
321
+
322
+ # Run the container
323
+ docker run -p 8080:80 ghcr.io/opengeos/maplibre-gl-raster:latest
324
+ ```
325
+
326
+ Then open http://localhost:8080/maplibre-gl-raster/ in your browser to view the examples.
327
+
328
+ ### Build Locally
329
+
330
+ ```bash
331
+ # Build the image
332
+ docker build -t maplibre-gl-raster .
333
+
334
+ # Run the container
335
+ docker run -p 8080:80 maplibre-gl-raster
336
+ ```
337
+
338
+ ### Available Tags
339
+
340
+ | Tag | Description |
341
+ | -------- | -------------------------------- |
342
+ | `latest` | Latest release |
343
+ | `x.y.z` | Specific version (e.g., `1.0.0`) |
344
+ | `x.y` | Minor version (e.g., `1.0`) |
345
+
346
+ ### Publish to npm
347
+
348
+ ```bash
349
+ npm login
350
+ npm whoami
351
+ npm publish --access public
352
+ ```
353
+
354
+ Set up Trusted Publisher on npmjs.com
355
+
356
+ ## Credits
357
+
358
+ The rendering pipeline, GeoTIFF loading strategy, statistics sampling, and much of the visualization UX are ported from Source Cooperative's [cog-viewer](https://github.com/source-cooperative/cog-viewer), built on their excellent [@developmentseed/deck.gl-geotiff](https://github.com/developmentseed/deck.gl-raster) and [@developmentseed/deck.gl-raster](https://github.com/developmentseed/deck.gl-raster) libraries.
359
+
360
+ ## License
361
+
362
+ MIT License - see [LICENSE](LICENSE) for details.