maplibre-gl-layer-control-tidop 0.0.1
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.
- package/LICENSE +21 -0
- package/README.md +506 -0
- package/dist/index.cjs +3499 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +3499 -0
- package/dist/index.mjs.map +1 -0
- package/dist/maplibre-gl-layer-control-tidop.css +1088 -0
- package/dist/types/index.d.ts +796 -0
- package/package.json +93 -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,506 @@
|
|
|
1
|
+
# maplibre-gl-layer-control-tidop
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/maplibre-gl-layer-control-tidop)
|
|
4
|
+
[](https://www.npmjs.com/package/maplibre-gl-layer-control-tidop)
|
|
5
|
+
[](https://github.com/opengeos/maplibre-gl-layer-control-tidop/blob/main/LICENSE)
|
|
6
|
+
[](https://codesandbox.io/p/github/opengeos/maplibre-gl-layer-control-tidop)
|
|
7
|
+
[](https://stackblitz.com/github/opengeos/maplibre-gl-layer-control-tidop)
|
|
8
|
+
|
|
9
|
+
A comprehensive layer control for MapLibre GL with advanced styling capabilities. Built with TypeScript and React, providing both vanilla JavaScript and React integration options.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- ✅ **Auto-detection** - Automatically detects layer properties (opacity, visibility) and generates friendly names
|
|
14
|
+
- ✅ **Layer visibility toggle** - Checkbox control for each layer
|
|
15
|
+
- ✅ **Layer opacity control** - Smooth opacity slider with type-aware property mapping
|
|
16
|
+
- ✅ **Layer symbols** - Visual type indicators (colored shapes) next to layer names, auto-detected from layer paint properties
|
|
17
|
+
- ✅ **Resizable panel** - Adjustable panel width (240-420px) with keyboard support
|
|
18
|
+
- ✅ **Advanced style editor** - Per-layer-type styling controls:
|
|
19
|
+
- **Fill layers**: color, opacity, outline-color
|
|
20
|
+
- **Line layers**: color, width, opacity, blur
|
|
21
|
+
- **Circle layers**: color, radius, opacity, blur, stroke properties
|
|
22
|
+
- **Symbol layers**: text-color, text-halo-color, halo-width, text/icon-opacity
|
|
23
|
+
- **Raster layers**: opacity, brightness, saturation, contrast, hue-rotate
|
|
24
|
+
- ✅ **Dynamic layer detection** - Automatically detect and manage new layers
|
|
25
|
+
- ✅ **Background layer grouping** - Control all basemap layers as one group
|
|
26
|
+
- ✅ **Background layer legend** - Gear icon to toggle individual background layer visibility
|
|
27
|
+
- ✅ **Accessibility** - Full ARIA support and keyboard navigation
|
|
28
|
+
- ✅ **TypeScript** - Full type safety and IntelliSense support
|
|
29
|
+
- ✅ **React integration** - Optional React components and hooks
|
|
30
|
+
- ✅ **Custom layer adapters** - Integrate non-MapLibre layers (deck.gl, Zarr, etc.)
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install maplibre-gl-layer-control-tidop
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
### Vanilla JavaScript
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import maplibregl from 'maplibre-gl';
|
|
44
|
+
import { LayerControl } from 'maplibre-gl-layer-control-tidop';
|
|
45
|
+
import 'maplibre-gl-layer-control-tidop/style.css';
|
|
46
|
+
|
|
47
|
+
const map = new maplibregl.Map({
|
|
48
|
+
container: 'map',
|
|
49
|
+
style: 'https://demotiles.maplibre.org/style.json',
|
|
50
|
+
center: [0, 0],
|
|
51
|
+
zoom: 2
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
map.on('load', () => {
|
|
55
|
+
// Add your custom layers
|
|
56
|
+
map.addLayer({
|
|
57
|
+
id: 'my-layer',
|
|
58
|
+
type: 'fill',
|
|
59
|
+
source: 'my-source',
|
|
60
|
+
paint: {
|
|
61
|
+
'fill-color': '#088',
|
|
62
|
+
'fill-opacity': 0.5
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Create layer control with auto-detection
|
|
67
|
+
// Option 1: Specify which layers to control (recommended for most use cases)
|
|
68
|
+
// - Shows specified layers with auto-detected opacity, visibility, and friendly names
|
|
69
|
+
// - Groups all other layers as "Background"
|
|
70
|
+
const layerControl = new LayerControl({
|
|
71
|
+
collapsed: false,
|
|
72
|
+
layers: ['my-layer'], // LayerControl auto-detects opacity, visibility, and generates friendly names
|
|
73
|
+
panelWidth: 340,
|
|
74
|
+
panelMinWidth: 240,
|
|
75
|
+
panelMaxWidth: 450
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Option 2: Auto-detect with basemapStyleUrl (recommended for reliable basemap detection)
|
|
79
|
+
// - Fetches the basemap style to identify basemap layers
|
|
80
|
+
// - All basemap layers are grouped under "Background"
|
|
81
|
+
// - User-added layers are shown individually
|
|
82
|
+
// const BASEMAP_STYLE = 'https://demotiles.maplibre.org/style.json';
|
|
83
|
+
// const layerControl = new LayerControl({
|
|
84
|
+
// collapsed: false,
|
|
85
|
+
// basemapStyleUrl: BASEMAP_STYLE
|
|
86
|
+
// });
|
|
87
|
+
|
|
88
|
+
// Option 3: Show ALL layers individually (no layers parameter)
|
|
89
|
+
// - Auto-detects ALL layers from the map
|
|
90
|
+
// - Generates friendly names from layer IDs (e.g., 'countries-layer' → 'Countries Layer')
|
|
91
|
+
// const layerControl = new LayerControl({
|
|
92
|
+
// collapsed: false,
|
|
93
|
+
// panelWidth: 340,
|
|
94
|
+
// panelMinWidth: 240,
|
|
95
|
+
// panelMaxWidth: 450
|
|
96
|
+
// });
|
|
97
|
+
|
|
98
|
+
// Option 4: Manually specify layer states (for full control over names)
|
|
99
|
+
// const layerControl = new LayerControl({
|
|
100
|
+
// collapsed: false,
|
|
101
|
+
// layerStates: {
|
|
102
|
+
// 'my-layer': {
|
|
103
|
+
// visible: true,
|
|
104
|
+
// opacity: 0.5,
|
|
105
|
+
// name: 'My Custom Layer Name'
|
|
106
|
+
// }
|
|
107
|
+
// }
|
|
108
|
+
// });
|
|
109
|
+
|
|
110
|
+
map.addControl(layerControl, 'top-right');
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### React
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { useState, useEffect } from 'react';
|
|
118
|
+
import maplibregl, { Map as MapLibreMap } from 'maplibre-gl';
|
|
119
|
+
import { LayerControlReact } from 'maplibre-gl-layer-control-tidop/react';
|
|
120
|
+
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
121
|
+
import 'maplibre-gl-layer-control-tidop/style.css';
|
|
122
|
+
|
|
123
|
+
function MapComponent() {
|
|
124
|
+
const [map, setMap] = useState<MapLibreMap | null>(null);
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
const newMap = new maplibregl.Map({
|
|
128
|
+
container: 'map',
|
|
129
|
+
style: 'https://demotiles.maplibre.org/style.json',
|
|
130
|
+
center: [0, 0],
|
|
131
|
+
zoom: 2
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
newMap.on('load', () => {
|
|
135
|
+
// Add your custom layers here
|
|
136
|
+
setMap(newMap);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return () => newMap.remove();
|
|
140
|
+
}, []);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<div>
|
|
144
|
+
<div id="map" style={{ width: '100%', height: '600px' }} />
|
|
145
|
+
{map && (
|
|
146
|
+
<LayerControlReact
|
|
147
|
+
map={map}
|
|
148
|
+
position="top-right"
|
|
149
|
+
layers={['my-layer']}
|
|
150
|
+
collapsed={false}
|
|
151
|
+
/>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## API
|
|
159
|
+
|
|
160
|
+
### LayerControl Options
|
|
161
|
+
|
|
162
|
+
| Option | Type | Default | Description |
|
|
163
|
+
|--------|------|---------|-------------|
|
|
164
|
+
| `collapsed` | `boolean` | `true` | Start with panel collapsed |
|
|
165
|
+
| `layers` | `string[]` | `undefined` | Layer IDs to control (auto-detects all if omitted) |
|
|
166
|
+
| `layerStates` | `Record<string, LayerState>` | `undefined` | Manual layer state configuration |
|
|
167
|
+
| `panelWidth` | `number` | `320` | Initial panel width in pixels |
|
|
168
|
+
| `panelMinWidth` | `number` | `240` | Minimum panel width |
|
|
169
|
+
| `panelMaxWidth` | `number` | `420` | Maximum panel width |
|
|
170
|
+
| `panelMaxHeight` | `number` | `600` | Maximum panel height (scrollable when exceeded) |
|
|
171
|
+
| `showStyleEditor` | `boolean` | `true` | Show gear icon for style editor |
|
|
172
|
+
| `showOpacitySlider` | `boolean` | `true` | Show opacity slider for layers |
|
|
173
|
+
| `showLayerSymbol` | `boolean` | `true` | Show layer type symbols (colored icons) next to layer names |
|
|
174
|
+
| `excludeDrawnLayers` | `boolean` | `true` | Exclude layers from drawing libraries (Geoman, Mapbox GL Draw, etc.) |
|
|
175
|
+
| `excludeLayers` | `string[]` | `undefined` | Array of wildcard patterns to exclude layers by name (e.g., `['*-temp-*', 'debug-*']`) |
|
|
176
|
+
| `customLayerAdapters` | `CustomLayerAdapter[]` | `undefined` | Adapters for non-MapLibre layers (deck.gl, Zarr, etc.) |
|
|
177
|
+
| `basemapStyleUrl` | `string` | `undefined` | URL of basemap style JSON for reliable layer detection (see below) |
|
|
178
|
+
|
|
179
|
+
### LayerState
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
interface LayerState {
|
|
183
|
+
visible: boolean; // Layer visibility
|
|
184
|
+
opacity: number; // Opacity (0-1)
|
|
185
|
+
name?: string; // Display name (auto-generated if omitted)
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Basemap Style URL Detection
|
|
190
|
+
|
|
191
|
+
When using auto-detection (without specifying `layers`), the control needs to distinguish between basemap layers and user-added layers. By default, it uses heuristics based on source detection, which may not always be reliable.
|
|
192
|
+
|
|
193
|
+
For **reliable detection**, provide the `basemapStyleUrl` option with the same URL used for the map's style:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const BASEMAP_STYLE_URL = 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json';
|
|
197
|
+
|
|
198
|
+
const map = new maplibregl.Map({
|
|
199
|
+
container: 'map',
|
|
200
|
+
style: BASEMAP_STYLE_URL,
|
|
201
|
+
center: [0, 0],
|
|
202
|
+
zoom: 2
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
map.on('load', () => {
|
|
206
|
+
// Add your custom layers
|
|
207
|
+
map.addLayer({
|
|
208
|
+
id: 'my-custom-layer',
|
|
209
|
+
type: 'fill',
|
|
210
|
+
source: 'my-source',
|
|
211
|
+
paint: { 'fill-color': '#088' }
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Create layer control with basemapStyleUrl for reliable detection
|
|
215
|
+
const layerControl = new LayerControl({
|
|
216
|
+
collapsed: false,
|
|
217
|
+
basemapStyleUrl: BASEMAP_STYLE_URL // All layers from this URL go to "Background"
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
map.addControl(layerControl, 'top-right');
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
When `basemapStyleUrl` is provided:
|
|
225
|
+
- The control fetches the style JSON and extracts all layer IDs
|
|
226
|
+
- Layers that exist in the basemap style are grouped under "Background"
|
|
227
|
+
- All other layers (user-added) are shown individually in the control
|
|
228
|
+
- New layers added later are automatically detected as user layers
|
|
229
|
+
|
|
230
|
+
### Automatic Detection Without basemapStyleUrl
|
|
231
|
+
|
|
232
|
+
Even without `basemapStyleUrl`, the control uses source-based heuristics to detect user-added layers. Custom MapLibre layers (using `map.addLayer()`) are automatically detected whether they are added **before** or **after** the layer control - no custom adapter is needed for standard MapLibre layer types!
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
map.on('load', () => {
|
|
236
|
+
// Add custom layers BEFORE the control - they will be detected
|
|
237
|
+
map.addSource('my-source', { type: 'geojson', data: myGeoJson });
|
|
238
|
+
map.addLayer({ id: 'my-layer', type: 'fill', source: 'my-source', ... });
|
|
239
|
+
|
|
240
|
+
// Add the control - it detects existing custom layers
|
|
241
|
+
const layerControl = new LayerControl({ collapsed: false });
|
|
242
|
+
map.addControl(layerControl, 'top-right');
|
|
243
|
+
|
|
244
|
+
// Add more layers AFTER the control - they will also be detected automatically
|
|
245
|
+
map.addLayer({ id: 'another-layer', type: 'circle', source: 'another-source', ... });
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Examples
|
|
250
|
+
|
|
251
|
+
See the [examples](./examples) folder for complete working examples:
|
|
252
|
+
|
|
253
|
+
- **[basic](./examples/basic)** - Simple vanilla JavaScript example
|
|
254
|
+
- **[full-demo](./examples/full-demo)** - Full demo with multiple layer types and `basemapStyleUrl` for reliable basemap detection
|
|
255
|
+
- **[dynamic-layers](./examples/dynamic-layers)** - Auto-detect layers added before or after control
|
|
256
|
+
- **[background-legend](./examples/background-legend)** - Background layer visibility control
|
|
257
|
+
- **[react](./examples/react)** - React integration example
|
|
258
|
+
- **[cdn](./examples/cdn)** - Browser-only example using CDN (no build step required)
|
|
259
|
+
|
|
260
|
+
### Layer Symbols
|
|
261
|
+
|
|
262
|
+
The layer control displays visual symbols (colored icons) next to each layer name to indicate the layer type. Symbols are automatically generated based on the layer's type and paint properties:
|
|
263
|
+
|
|
264
|
+
| Layer Type | Symbol |
|
|
265
|
+
|------------|--------|
|
|
266
|
+
| `fill` | Colored rectangle with border |
|
|
267
|
+
| `line` | Horizontal line |
|
|
268
|
+
| `circle` | Colored circle |
|
|
269
|
+
| `symbol` | Marker/pin icon |
|
|
270
|
+
| `raster` | Gradient rectangle |
|
|
271
|
+
| `heatmap` | Orange-red gradient |
|
|
272
|
+
| `hillshade` | Gray gradient |
|
|
273
|
+
| `fill-extrusion` | 3D rectangle |
|
|
274
|
+
| `background` | Rectangle with inner border |
|
|
275
|
+
| Background group | Stacked layers icon |
|
|
276
|
+
|
|
277
|
+
The symbol color is automatically extracted from the layer's paint properties (e.g., `fill-color`, `line-color`, `circle-color`). If a color cannot be determined, a neutral gray is used.
|
|
278
|
+
|
|
279
|
+
To disable layer symbols:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
const layerControl = new LayerControl({
|
|
283
|
+
showLayerSymbol: false
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Background Layer Legend
|
|
288
|
+
|
|
289
|
+
When using the `layers` option to specify specific layers, all other layers are grouped under a "Background" entry. The Background layer includes a **gear icon** that opens a detailed legend panel showing:
|
|
290
|
+
|
|
291
|
+
- Individual visibility toggles for each background layer
|
|
292
|
+
- Layer type indicators (fill, line, symbol, etc.)
|
|
293
|
+
- Quick "Show All" / "Hide All" buttons
|
|
294
|
+
- **"Only rendered" filter** - Shows only layers that are currently rendered in the map viewport
|
|
295
|
+
- Indeterminate checkbox state when some layers are hidden
|
|
296
|
+
|
|
297
|
+
This allows fine-grained control over which basemap layers are visible while maintaining a simplified layer control interface.
|
|
298
|
+
|
|
299
|
+
### Custom Layer Adapters
|
|
300
|
+
|
|
301
|
+
The layer control supports non-MapLibre layers (such as deck.gl or Zarr layers) through the Custom Layer Adapter interface. This allows you to integrate any custom layer type with the layer control's visibility toggle, opacity slider, and layer list.
|
|
302
|
+
|
|
303
|
+
#### CustomLayerAdapter Interface
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
interface CustomLayerAdapter {
|
|
307
|
+
/** Unique type identifier for this adapter (e.g., 'cog', 'zarr', 'deck') */
|
|
308
|
+
type: string;
|
|
309
|
+
|
|
310
|
+
/** Get all layer IDs managed by this adapter */
|
|
311
|
+
getLayerIds(): string[];
|
|
312
|
+
|
|
313
|
+
/** Get the current state of a layer */
|
|
314
|
+
getLayerState(layerId: string): LayerState | null;
|
|
315
|
+
|
|
316
|
+
/** Set layer visibility */
|
|
317
|
+
setVisibility(layerId: string, visible: boolean): void;
|
|
318
|
+
|
|
319
|
+
/** Set layer opacity (0-1) */
|
|
320
|
+
setOpacity(layerId: string, opacity: number): void;
|
|
321
|
+
|
|
322
|
+
/** Get display name for a layer */
|
|
323
|
+
getName(layerId: string): string;
|
|
324
|
+
|
|
325
|
+
/** Get layer symbol type for UI display (optional) */
|
|
326
|
+
getSymbolType?(layerId: string): string;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Subscribe to layer changes (add/remove).
|
|
330
|
+
* Returns an unsubscribe function.
|
|
331
|
+
*/
|
|
332
|
+
onLayerChange?(callback: (event: 'add' | 'remove', layerId: string) => void): () => void;
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
#### Implementing a Custom Adapter
|
|
337
|
+
|
|
338
|
+
Here's an example of implementing an adapter for deck.gl layers:
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import type { CustomLayerAdapter, LayerState } from 'maplibre-gl-layer-control-tidop';
|
|
342
|
+
import type { MapboxOverlay } from '@deck.gl/mapbox';
|
|
343
|
+
|
|
344
|
+
class DeckLayerAdapter implements CustomLayerAdapter {
|
|
345
|
+
readonly type = 'deck';
|
|
346
|
+
|
|
347
|
+
private deckOverlay: MapboxOverlay;
|
|
348
|
+
private deckLayers: Map<string, any>;
|
|
349
|
+
private changeCallbacks: Array<(event: 'add' | 'remove', layerId: string) => void> = [];
|
|
350
|
+
|
|
351
|
+
constructor(deckOverlay: MapboxOverlay, deckLayers: Map<string, any>) {
|
|
352
|
+
this.deckOverlay = deckOverlay;
|
|
353
|
+
this.deckLayers = deckLayers;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
getLayerIds(): string[] {
|
|
357
|
+
return Array.from(this.deckLayers.keys());
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
getLayerState(layerId: string): LayerState | null {
|
|
361
|
+
const layer = this.deckLayers.get(layerId);
|
|
362
|
+
if (!layer?.props) return null;
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
visible: layer.props.visible !== false,
|
|
366
|
+
opacity: layer.props.opacity ?? 1,
|
|
367
|
+
name: this.getName(layerId),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
setVisibility(layerId: string, visible: boolean): void {
|
|
372
|
+
const layer = this.deckLayers.get(layerId);
|
|
373
|
+
if (!layer?.clone) return;
|
|
374
|
+
|
|
375
|
+
// deck.gl layers are immutable; clone with new props
|
|
376
|
+
const updatedLayer = layer.clone({ visible });
|
|
377
|
+
this.deckLayers.set(layerId, updatedLayer);
|
|
378
|
+
this.updateOverlay();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
setOpacity(layerId: string, opacity: number): void {
|
|
382
|
+
const layer = this.deckLayers.get(layerId);
|
|
383
|
+
if (!layer?.clone) return;
|
|
384
|
+
|
|
385
|
+
const updatedLayer = layer.clone({ opacity });
|
|
386
|
+
this.deckLayers.set(layerId, updatedLayer);
|
|
387
|
+
this.updateOverlay();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
getName(layerId: string): string {
|
|
391
|
+
return layerId.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
getSymbolType(): string {
|
|
395
|
+
return 'raster'; // Use raster symbol for deck.gl layers
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
onLayerChange(callback: (event: 'add' | 'remove', layerId: string) => void): () => void {
|
|
399
|
+
this.changeCallbacks.push(callback);
|
|
400
|
+
return () => {
|
|
401
|
+
const idx = this.changeCallbacks.indexOf(callback);
|
|
402
|
+
if (idx >= 0) this.changeCallbacks.splice(idx, 1);
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Call this when layers are added/removed
|
|
407
|
+
notifyLayerAdded(layerId: string): void {
|
|
408
|
+
this.changeCallbacks.forEach(cb => cb('add', layerId));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
notifyLayerRemoved(layerId: string): void {
|
|
412
|
+
this.changeCallbacks.forEach(cb => cb('remove', layerId));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private updateOverlay(): void {
|
|
416
|
+
this.deckOverlay.setProps({ layers: Array.from(this.deckLayers.values()) });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
#### Using Custom Adapters
|
|
422
|
+
|
|
423
|
+
Pass your custom adapters to the `customLayerAdapters` option:
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import { LayerControl } from 'maplibre-gl-layer-control-tidop';
|
|
427
|
+
|
|
428
|
+
// Create your custom adapter
|
|
429
|
+
const deckAdapter = new DeckLayerAdapter(deckOverlay, deckLayers);
|
|
430
|
+
|
|
431
|
+
// Create the layer control with the adapter
|
|
432
|
+
const layerControl = new LayerControl({
|
|
433
|
+
collapsed: false,
|
|
434
|
+
customLayerAdapters: [deckAdapter]
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
map.addControl(layerControl, 'top-right');
|
|
438
|
+
|
|
439
|
+
// When you add a new deck.gl layer, notify the adapter
|
|
440
|
+
deckLayers.set('my-deck-layer', myDeckLayer);
|
|
441
|
+
deckAdapter.notifyLayerAdded('my-deck-layer');
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
#### Limitations
|
|
445
|
+
|
|
446
|
+
- **Style Editor**: The style editor (gear icon) is not available for custom layers since they don't use MapLibre's paint properties. Clicking the gear icon will show an info panel explaining this.
|
|
447
|
+
- **Opacity Support**: Some layer types (like deck.gl's COGLayer) may not support dynamic opacity changes due to underlying library limitations. In these cases, the opacity slider will have no effect.
|
|
448
|
+
|
|
449
|
+
## Development
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
# Install dependencies
|
|
453
|
+
npm install
|
|
454
|
+
|
|
455
|
+
# Run development server
|
|
456
|
+
npm run dev
|
|
457
|
+
|
|
458
|
+
# Run tests
|
|
459
|
+
npm test
|
|
460
|
+
|
|
461
|
+
# Build for production
|
|
462
|
+
npm run build
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## Docker
|
|
466
|
+
|
|
467
|
+
The examples can be run using Docker. The image is automatically built and published to GitHub Container Registry.
|
|
468
|
+
|
|
469
|
+
### Pull and Run
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
# Pull the latest image
|
|
473
|
+
docker pull ghcr.io/opengeos/maplibre-gl-layer-control-tidop:latest
|
|
474
|
+
|
|
475
|
+
# Run the container
|
|
476
|
+
docker run -p 8080:80 ghcr.io/opengeos/maplibre-gl-layer-control-tidop:latest
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Then open http://localhost:8080/maplibre-gl-layer-control-tidop/ in your browser to view the examples.
|
|
480
|
+
|
|
481
|
+
### Build Locally
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
# Build the image
|
|
485
|
+
docker build -t maplibre-gl-layer-control-tidop .
|
|
486
|
+
|
|
487
|
+
# Run the container
|
|
488
|
+
docker run -p 8080:80 maplibre-gl-layer-control-tidop
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Available Tags
|
|
492
|
+
|
|
493
|
+
| Tag | Description |
|
|
494
|
+
|-----|-------------|
|
|
495
|
+
| `latest` | Latest release |
|
|
496
|
+
| `x.y.z` | Specific version (e.g., `1.0.0`) |
|
|
497
|
+
| `x.y` | Minor version (e.g., `1.0`) |
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
## License
|
|
501
|
+
|
|
502
|
+
MIT © Qiusheng Wu
|
|
503
|
+
|
|
504
|
+
## Contributing
|
|
505
|
+
|
|
506
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|