maplibre-gl-layer-control 0.5.1 → 0.6.2
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/README.md +152 -0
- package/dist/index.cjs +347 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +347 -10
- package/dist/index.mjs.map +1 -1
- package/dist/maplibre-gl-layer-control.css +12 -0
- package/dist/types/index.d.ts +113 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ A comprehensive layer control for MapLibre GL with advanced styling capabilities
|
|
|
25
25
|
- ✅ **Accessibility** - Full ARIA support and keyboard navigation
|
|
26
26
|
- ✅ **TypeScript** - Full type safety and IntelliSense support
|
|
27
27
|
- ✅ **React integration** - Optional React components and hooks
|
|
28
|
+
- ✅ **Custom layer adapters** - Integrate non-MapLibre layers (deck.gl, Zarr, etc.)
|
|
28
29
|
|
|
29
30
|
## Installation
|
|
30
31
|
|
|
@@ -159,6 +160,7 @@ function MapComponent() {
|
|
|
159
160
|
| `showOpacitySlider` | `boolean` | `true` | Show opacity slider for layers |
|
|
160
161
|
| `showLayerSymbol` | `boolean` | `true` | Show layer type symbols (colored icons) next to layer names |
|
|
161
162
|
| `excludeDrawnLayers` | `boolean` | `true` | Exclude layers from drawing libraries (Geoman, Mapbox GL Draw, etc.) |
|
|
163
|
+
| `customLayerAdapters` | `CustomLayerAdapter[]` | `undefined` | Adapters for non-MapLibre layers (deck.gl, Zarr, etc.) |
|
|
162
164
|
|
|
163
165
|
### LayerState
|
|
164
166
|
|
|
@@ -218,6 +220,156 @@ When using the `layers` option to specify specific layers, all other layers are
|
|
|
218
220
|
|
|
219
221
|
This allows fine-grained control over which basemap layers are visible while maintaining a simplified layer control interface.
|
|
220
222
|
|
|
223
|
+
### Custom Layer Adapters
|
|
224
|
+
|
|
225
|
+
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.
|
|
226
|
+
|
|
227
|
+
#### CustomLayerAdapter Interface
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
interface CustomLayerAdapter {
|
|
231
|
+
/** Unique type identifier for this adapter (e.g., 'cog', 'zarr', 'deck') */
|
|
232
|
+
type: string;
|
|
233
|
+
|
|
234
|
+
/** Get all layer IDs managed by this adapter */
|
|
235
|
+
getLayerIds(): string[];
|
|
236
|
+
|
|
237
|
+
/** Get the current state of a layer */
|
|
238
|
+
getLayerState(layerId: string): LayerState | null;
|
|
239
|
+
|
|
240
|
+
/** Set layer visibility */
|
|
241
|
+
setVisibility(layerId: string, visible: boolean): void;
|
|
242
|
+
|
|
243
|
+
/** Set layer opacity (0-1) */
|
|
244
|
+
setOpacity(layerId: string, opacity: number): void;
|
|
245
|
+
|
|
246
|
+
/** Get display name for a layer */
|
|
247
|
+
getName(layerId: string): string;
|
|
248
|
+
|
|
249
|
+
/** Get layer symbol type for UI display (optional) */
|
|
250
|
+
getSymbolType?(layerId: string): string;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Subscribe to layer changes (add/remove).
|
|
254
|
+
* Returns an unsubscribe function.
|
|
255
|
+
*/
|
|
256
|
+
onLayerChange?(callback: (event: 'add' | 'remove', layerId: string) => void): () => void;
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### Implementing a Custom Adapter
|
|
261
|
+
|
|
262
|
+
Here's an example of implementing an adapter for deck.gl layers:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import type { CustomLayerAdapter, LayerState } from 'maplibre-gl-layer-control';
|
|
266
|
+
import type { MapboxOverlay } from '@deck.gl/mapbox';
|
|
267
|
+
|
|
268
|
+
class DeckLayerAdapter implements CustomLayerAdapter {
|
|
269
|
+
readonly type = 'deck';
|
|
270
|
+
|
|
271
|
+
private deckOverlay: MapboxOverlay;
|
|
272
|
+
private deckLayers: Map<string, any>;
|
|
273
|
+
private changeCallbacks: Array<(event: 'add' | 'remove', layerId: string) => void> = [];
|
|
274
|
+
|
|
275
|
+
constructor(deckOverlay: MapboxOverlay, deckLayers: Map<string, any>) {
|
|
276
|
+
this.deckOverlay = deckOverlay;
|
|
277
|
+
this.deckLayers = deckLayers;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
getLayerIds(): string[] {
|
|
281
|
+
return Array.from(this.deckLayers.keys());
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
getLayerState(layerId: string): LayerState | null {
|
|
285
|
+
const layer = this.deckLayers.get(layerId);
|
|
286
|
+
if (!layer?.props) return null;
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
visible: layer.props.visible !== false,
|
|
290
|
+
opacity: layer.props.opacity ?? 1,
|
|
291
|
+
name: this.getName(layerId),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
setVisibility(layerId: string, visible: boolean): void {
|
|
296
|
+
const layer = this.deckLayers.get(layerId);
|
|
297
|
+
if (!layer?.clone) return;
|
|
298
|
+
|
|
299
|
+
// deck.gl layers are immutable; clone with new props
|
|
300
|
+
const updatedLayer = layer.clone({ visible });
|
|
301
|
+
this.deckLayers.set(layerId, updatedLayer);
|
|
302
|
+
this.updateOverlay();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
setOpacity(layerId: string, opacity: number): void {
|
|
306
|
+
const layer = this.deckLayers.get(layerId);
|
|
307
|
+
if (!layer?.clone) return;
|
|
308
|
+
|
|
309
|
+
const updatedLayer = layer.clone({ opacity });
|
|
310
|
+
this.deckLayers.set(layerId, updatedLayer);
|
|
311
|
+
this.updateOverlay();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
getName(layerId: string): string {
|
|
315
|
+
return layerId.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
getSymbolType(): string {
|
|
319
|
+
return 'raster'; // Use raster symbol for deck.gl layers
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
onLayerChange(callback: (event: 'add' | 'remove', layerId: string) => void): () => void {
|
|
323
|
+
this.changeCallbacks.push(callback);
|
|
324
|
+
return () => {
|
|
325
|
+
const idx = this.changeCallbacks.indexOf(callback);
|
|
326
|
+
if (idx >= 0) this.changeCallbacks.splice(idx, 1);
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Call this when layers are added/removed
|
|
331
|
+
notifyLayerAdded(layerId: string): void {
|
|
332
|
+
this.changeCallbacks.forEach(cb => cb('add', layerId));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
notifyLayerRemoved(layerId: string): void {
|
|
336
|
+
this.changeCallbacks.forEach(cb => cb('remove', layerId));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private updateOverlay(): void {
|
|
340
|
+
this.deckOverlay.setProps({ layers: Array.from(this.deckLayers.values()) });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
#### Using Custom Adapters
|
|
346
|
+
|
|
347
|
+
Pass your custom adapters to the `customLayerAdapters` option:
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
import { LayerControl } from 'maplibre-gl-layer-control';
|
|
351
|
+
|
|
352
|
+
// Create your custom adapter
|
|
353
|
+
const deckAdapter = new DeckLayerAdapter(deckOverlay, deckLayers);
|
|
354
|
+
|
|
355
|
+
// Create the layer control with the adapter
|
|
356
|
+
const layerControl = new LayerControl({
|
|
357
|
+
collapsed: false,
|
|
358
|
+
customLayerAdapters: [deckAdapter]
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
map.addControl(layerControl, 'top-right');
|
|
362
|
+
|
|
363
|
+
// When you add a new deck.gl layer, notify the adapter
|
|
364
|
+
deckLayers.set('my-deck-layer', myDeckLayer);
|
|
365
|
+
deckAdapter.notifyLayerAdded('my-deck-layer');
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
#### Limitations
|
|
369
|
+
|
|
370
|
+
- **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.
|
|
371
|
+
- **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.
|
|
372
|
+
|
|
221
373
|
## Development
|
|
222
374
|
|
|
223
375
|
```bash
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,145 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
4
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
5
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
6
|
+
class CustomLayerRegistry {
|
|
7
|
+
constructor() {
|
|
8
|
+
__publicField(this, "adapters", /* @__PURE__ */ new Map());
|
|
9
|
+
__publicField(this, "changeListeners", []);
|
|
10
|
+
__publicField(this, "unsubscribers", []);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Register a custom layer adapter.
|
|
14
|
+
* @param adapter The adapter to register
|
|
15
|
+
*/
|
|
16
|
+
register(adapter) {
|
|
17
|
+
this.adapters.set(adapter.type, adapter);
|
|
18
|
+
if (adapter.onLayerChange) {
|
|
19
|
+
const unsubscribe = adapter.onLayerChange((event, layerId) => {
|
|
20
|
+
this.notifyChange(event, layerId);
|
|
21
|
+
});
|
|
22
|
+
this.unsubscribers.push(unsubscribe);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Unregister an adapter by type.
|
|
27
|
+
* @param type The adapter type to unregister
|
|
28
|
+
*/
|
|
29
|
+
unregister(type) {
|
|
30
|
+
this.adapters.delete(type);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get all custom layer IDs across all adapters.
|
|
34
|
+
* @returns Array of layer IDs
|
|
35
|
+
*/
|
|
36
|
+
getAllLayerIds() {
|
|
37
|
+
const ids = [];
|
|
38
|
+
this.adapters.forEach((adapter) => {
|
|
39
|
+
ids.push(...adapter.getLayerIds());
|
|
40
|
+
});
|
|
41
|
+
return ids;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Check if a layer ID is managed by any adapter.
|
|
45
|
+
* @param layerId The layer ID to check
|
|
46
|
+
* @returns true if the layer is managed by an adapter
|
|
47
|
+
*/
|
|
48
|
+
hasLayer(layerId) {
|
|
49
|
+
for (const adapter of this.adapters.values()) {
|
|
50
|
+
if (adapter.getLayerIds().includes(layerId)) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get the adapter responsible for a specific layer.
|
|
58
|
+
* @param layerId The layer ID
|
|
59
|
+
* @returns The adapter or null if not found
|
|
60
|
+
*/
|
|
61
|
+
getAdapterForLayer(layerId) {
|
|
62
|
+
for (const adapter of this.adapters.values()) {
|
|
63
|
+
if (adapter.getLayerIds().includes(layerId)) {
|
|
64
|
+
return adapter;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get the state of a custom layer.
|
|
71
|
+
* @param layerId The layer ID
|
|
72
|
+
* @returns The layer state or null if not found
|
|
73
|
+
*/
|
|
74
|
+
getLayerState(layerId) {
|
|
75
|
+
const adapter = this.getAdapterForLayer(layerId);
|
|
76
|
+
return adapter ? adapter.getLayerState(layerId) : null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Set visibility of a custom layer.
|
|
80
|
+
* @param layerId The layer ID
|
|
81
|
+
* @param visible Whether the layer should be visible
|
|
82
|
+
* @returns true if the operation was handled by an adapter
|
|
83
|
+
*/
|
|
84
|
+
setVisibility(layerId, visible) {
|
|
85
|
+
const adapter = this.getAdapterForLayer(layerId);
|
|
86
|
+
if (adapter) {
|
|
87
|
+
adapter.setVisibility(layerId, visible);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Set opacity of a custom layer.
|
|
94
|
+
* @param layerId The layer ID
|
|
95
|
+
* @param opacity The opacity value (0-1)
|
|
96
|
+
* @returns true if the operation was handled by an adapter
|
|
97
|
+
*/
|
|
98
|
+
setOpacity(layerId, opacity) {
|
|
99
|
+
const adapter = this.getAdapterForLayer(layerId);
|
|
100
|
+
if (adapter) {
|
|
101
|
+
adapter.setOpacity(layerId, opacity);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get the symbol type for a custom layer (for UI display).
|
|
108
|
+
* @param layerId The layer ID
|
|
109
|
+
* @returns The symbol type or null if not available
|
|
110
|
+
*/
|
|
111
|
+
getSymbolType(layerId) {
|
|
112
|
+
const adapter = this.getAdapterForLayer(layerId);
|
|
113
|
+
if (adapter && adapter.getSymbolType) {
|
|
114
|
+
return adapter.getSymbolType(layerId);
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Subscribe to layer changes across all adapters.
|
|
120
|
+
* @param callback Function called when layers are added or removed
|
|
121
|
+
* @returns Unsubscribe function
|
|
122
|
+
*/
|
|
123
|
+
onChange(callback) {
|
|
124
|
+
this.changeListeners.push(callback);
|
|
125
|
+
return () => {
|
|
126
|
+
const idx = this.changeListeners.indexOf(callback);
|
|
127
|
+
if (idx >= 0) {
|
|
128
|
+
this.changeListeners.splice(idx, 1);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
notifyChange(event, layerId) {
|
|
133
|
+
this.changeListeners.forEach((cb) => cb(event, layerId));
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Clean up all subscriptions and adapters.
|
|
137
|
+
*/
|
|
138
|
+
destroy() {
|
|
139
|
+
this.unsubscribers.forEach((unsub) => unsub());
|
|
140
|
+
this.unsubscribers = [];
|
|
141
|
+
this.adapters.clear();
|
|
142
|
+
this.changeListeners = [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
6
145
|
function getOpacityProperty(layerType) {
|
|
7
146
|
switch (layerType) {
|
|
8
147
|
case "fill":
|
|
@@ -379,6 +518,46 @@ function createDefaultSymbol(size, color) {
|
|
|
379
518
|
fill="${color}" stroke="${borderColor}" stroke-width="1"/>
|
|
380
519
|
</svg>`;
|
|
381
520
|
}
|
|
521
|
+
function createCOGSymbol(size, color) {
|
|
522
|
+
const padding = 2;
|
|
523
|
+
const borderColor = darkenColor(color, 0.3);
|
|
524
|
+
const cellSize = (size - padding * 2) / 2;
|
|
525
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
526
|
+
<rect x="${padding}" y="${padding}" width="${cellSize}" height="${cellSize}" fill="${color}" stroke="${borderColor}" stroke-width="0.5"/>
|
|
527
|
+
<rect x="${padding + cellSize}" y="${padding}" width="${cellSize}" height="${cellSize}" fill="${color}" stroke="${borderColor}" stroke-width="0.5" opacity="0.8"/>
|
|
528
|
+
<rect x="${padding}" y="${padding + cellSize}" width="${cellSize}" height="${cellSize}" fill="${color}" stroke="${borderColor}" stroke-width="0.5" opacity="0.6"/>
|
|
529
|
+
<rect x="${padding + cellSize}" y="${padding + cellSize}" width="${cellSize}" height="${cellSize}" fill="${color}" stroke="${borderColor}" stroke-width="0.5" opacity="0.4"/>
|
|
530
|
+
</svg>`;
|
|
531
|
+
}
|
|
532
|
+
function createZarrSymbol(size, color) {
|
|
533
|
+
const padding = 2;
|
|
534
|
+
const borderColor = darkenColor(color, 0.3);
|
|
535
|
+
const innerSize = size - padding * 2;
|
|
536
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
537
|
+
<rect x="${padding + 2}" y="${padding}" width="${innerSize - 2}" height="${innerSize - 2}" fill="${darkenColor(color, 0.2)}" stroke="${borderColor}" stroke-width="0.5" rx="1"/>
|
|
538
|
+
<rect x="${padding + 1}" y="${padding + 1}" width="${innerSize - 2}" height="${innerSize - 2}" fill="${darkenColor(color, 0.1)}" stroke="${borderColor}" stroke-width="0.5" rx="1"/>
|
|
539
|
+
<rect x="${padding}" y="${padding + 2}" width="${innerSize - 2}" height="${innerSize - 2}" fill="${color}" stroke="${borderColor}" stroke-width="0.5" rx="1"/>
|
|
540
|
+
<line x1="${padding + 3}" y1="${padding + 5}" x2="${padding + innerSize - 5}" y2="${padding + 5}" stroke="${borderColor}" stroke-width="0.5" opacity="0.5"/>
|
|
541
|
+
<line x1="${padding + 3}" y1="${padding + 8}" x2="${padding + innerSize - 5}" y2="${padding + 8}" stroke="${borderColor}" stroke-width="0.5" opacity="0.5"/>
|
|
542
|
+
</svg>`;
|
|
543
|
+
}
|
|
544
|
+
function createCustomRasterSymbol(size, color) {
|
|
545
|
+
const padding = 2;
|
|
546
|
+
const borderColor = darkenColor(color, 0.3);
|
|
547
|
+
const id = `customRasterGrad_${Math.random().toString(36).slice(2, 9)}`;
|
|
548
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
549
|
+
<defs>
|
|
550
|
+
<linearGradient id="${id}" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
551
|
+
<stop offset="0%" stop-color="${color}"/>
|
|
552
|
+
<stop offset="100%" stop-color="${darkenColor(color, 0.4)}"/>
|
|
553
|
+
</linearGradient>
|
|
554
|
+
</defs>
|
|
555
|
+
<rect x="${padding}" y="${padding}" width="${size - padding * 2}" height="${size - padding * 2}"
|
|
556
|
+
fill="url(#${id})" stroke="${borderColor}" stroke-width="1" rx="1"/>
|
|
557
|
+
<line x1="${size / 2}" y1="${padding}" x2="${size / 2}" y2="${size - padding}" stroke="${borderColor}" stroke-width="0.5" opacity="0.3"/>
|
|
558
|
+
<line x1="${padding}" y1="${size / 2}" x2="${size - padding}" y2="${size / 2}" stroke="${borderColor}" stroke-width="0.5" opacity="0.3"/>
|
|
559
|
+
</svg>`;
|
|
560
|
+
}
|
|
382
561
|
function createStackedLayersSymbol(size) {
|
|
383
562
|
const colors = ["#a8d4a8", "#8ec4e8", "#d4c4a8"];
|
|
384
563
|
const borderColor = "#666666";
|
|
@@ -413,6 +592,12 @@ function createLayerSymbolSVG(layerType, color, options = {}) {
|
|
|
413
592
|
return createFillExtrusionSymbol(size, fillColor);
|
|
414
593
|
case "background-group":
|
|
415
594
|
return createStackedLayersSymbol(size);
|
|
595
|
+
case "cog":
|
|
596
|
+
return createCOGSymbol(size, fillColor);
|
|
597
|
+
case "zarr":
|
|
598
|
+
return createZarrSymbol(size, fillColor);
|
|
599
|
+
case "custom-raster":
|
|
600
|
+
return createCustomRasterSymbol(size, fillColor);
|
|
416
601
|
default:
|
|
417
602
|
return createDefaultSymbol(size, fillColor);
|
|
418
603
|
}
|
|
@@ -438,6 +623,8 @@ class LayerControl {
|
|
|
438
623
|
__publicField(this, "showOpacitySlider");
|
|
439
624
|
__publicField(this, "showLayerSymbol");
|
|
440
625
|
__publicField(this, "excludeDrawnLayers");
|
|
626
|
+
__publicField(this, "customLayerRegistry", null);
|
|
627
|
+
__publicField(this, "customLayerUnsubscribe", null);
|
|
441
628
|
__publicField(this, "widthSliderEl", null);
|
|
442
629
|
__publicField(this, "widthThumbEl", null);
|
|
443
630
|
__publicField(this, "widthValueEl", null);
|
|
@@ -455,7 +642,7 @@ class LayerControl {
|
|
|
455
642
|
this.excludeDrawnLayers = options.excludeDrawnLayers !== false;
|
|
456
643
|
this.state = {
|
|
457
644
|
collapsed: options.collapsed !== false,
|
|
458
|
-
panelWidth: options.panelWidth ||
|
|
645
|
+
panelWidth: options.panelWidth || 348,
|
|
459
646
|
activeStyleEditor: null,
|
|
460
647
|
layerStates: options.layerStates || {},
|
|
461
648
|
originalStyles: /* @__PURE__ */ new Map(),
|
|
@@ -466,6 +653,12 @@ class LayerControl {
|
|
|
466
653
|
};
|
|
467
654
|
this.targetLayers = options.layers || Object.keys(this.state.layerStates);
|
|
468
655
|
this.styleEditors = /* @__PURE__ */ new Map();
|
|
656
|
+
if (options.customLayerAdapters && options.customLayerAdapters.length > 0) {
|
|
657
|
+
this.customLayerRegistry = new CustomLayerRegistry();
|
|
658
|
+
options.customLayerAdapters.forEach((adapter) => {
|
|
659
|
+
this.customLayerRegistry.register(adapter);
|
|
660
|
+
});
|
|
661
|
+
}
|
|
469
662
|
}
|
|
470
663
|
/**
|
|
471
664
|
* Called when the control is added to the map
|
|
@@ -490,6 +683,14 @@ class LayerControl {
|
|
|
490
683
|
*/
|
|
491
684
|
onRemove() {
|
|
492
685
|
var _a;
|
|
686
|
+
if (this.customLayerUnsubscribe) {
|
|
687
|
+
this.customLayerUnsubscribe();
|
|
688
|
+
this.customLayerUnsubscribe = null;
|
|
689
|
+
}
|
|
690
|
+
if (this.customLayerRegistry) {
|
|
691
|
+
this.customLayerRegistry.destroy();
|
|
692
|
+
this.customLayerRegistry = null;
|
|
693
|
+
}
|
|
493
694
|
(_a = this.container.parentNode) == null ? void 0 : _a.removeChild(this.container);
|
|
494
695
|
}
|
|
495
696
|
/**
|
|
@@ -572,6 +773,22 @@ class LayerControl {
|
|
|
572
773
|
};
|
|
573
774
|
});
|
|
574
775
|
}
|
|
776
|
+
if (this.customLayerRegistry) {
|
|
777
|
+
const customLayerIds = this.customLayerRegistry.getAllLayerIds();
|
|
778
|
+
customLayerIds.forEach((layerId) => {
|
|
779
|
+
if (this.state.layerStates[layerId]) return;
|
|
780
|
+
const customState = this.customLayerRegistry.getLayerState(layerId);
|
|
781
|
+
if (customState) {
|
|
782
|
+
this.state.layerStates[layerId] = {
|
|
783
|
+
visible: customState.visible,
|
|
784
|
+
opacity: customState.opacity,
|
|
785
|
+
name: customState.name,
|
|
786
|
+
isCustomLayer: true,
|
|
787
|
+
customLayerType: this.customLayerRegistry.getSymbolType(layerId) || void 0
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
}
|
|
575
792
|
this.targetLayers = Object.keys(this.state.layerStates);
|
|
576
793
|
}
|
|
577
794
|
/**
|
|
@@ -725,12 +942,7 @@ class LayerControl {
|
|
|
725
942
|
*/
|
|
726
943
|
setAllLayersVisibility(visible) {
|
|
727
944
|
Object.keys(this.state.layerStates).forEach((layerId) => {
|
|
728
|
-
|
|
729
|
-
this.toggleBackgroundVisibility(visible);
|
|
730
|
-
} else {
|
|
731
|
-
this.state.layerStates[layerId].visible = visible;
|
|
732
|
-
this.map.setLayoutProperty(layerId, "visibility", visible ? "visible" : "none");
|
|
733
|
-
}
|
|
945
|
+
this.toggleLayerVisibility(layerId, visible);
|
|
734
946
|
const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
|
|
735
947
|
if (itemEl) {
|
|
736
948
|
const checkbox = itemEl.querySelector(".layer-control-checkbox");
|
|
@@ -965,6 +1177,11 @@ class LayerControl {
|
|
|
965
1177
|
}, 150);
|
|
966
1178
|
}
|
|
967
1179
|
});
|
|
1180
|
+
if (this.customLayerRegistry) {
|
|
1181
|
+
this.customLayerUnsubscribe = this.customLayerRegistry.onChange(() => {
|
|
1182
|
+
setTimeout(() => this.checkForNewLayers(), 100);
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
968
1185
|
}
|
|
969
1186
|
/**
|
|
970
1187
|
* Toggle panel expanded/collapsed state
|
|
@@ -1077,6 +1294,17 @@ class LayerControl {
|
|
|
1077
1294
|
* @returns The symbol HTML element, or null if layer not found
|
|
1078
1295
|
*/
|
|
1079
1296
|
createLayerSymbol(layerId) {
|
|
1297
|
+
const layerState = this.state.layerStates[layerId];
|
|
1298
|
+
if (layerState == null ? void 0 : layerState.isCustomLayer) {
|
|
1299
|
+
const symbolType = layerState.customLayerType || "custom-raster";
|
|
1300
|
+
const color2 = "#4a90d9";
|
|
1301
|
+
const svgMarkup2 = createLayerSymbolSVG(symbolType, color2);
|
|
1302
|
+
const symbolContainer2 = document.createElement("span");
|
|
1303
|
+
symbolContainer2.className = "layer-control-symbol";
|
|
1304
|
+
symbolContainer2.innerHTML = svgMarkup2;
|
|
1305
|
+
symbolContainer2.title = `Layer type: ${symbolType}`;
|
|
1306
|
+
return symbolContainer2;
|
|
1307
|
+
}
|
|
1080
1308
|
const layer = this.map.getLayer(layerId);
|
|
1081
1309
|
if (!layer) return null;
|
|
1082
1310
|
const layerType = layer.type;
|
|
@@ -1119,6 +1347,7 @@ class LayerControl {
|
|
|
1119
1347
|
* Toggle layer visibility
|
|
1120
1348
|
*/
|
|
1121
1349
|
toggleLayerVisibility(layerId, visible) {
|
|
1350
|
+
var _a;
|
|
1122
1351
|
if (layerId === "Background") {
|
|
1123
1352
|
this.toggleBackgroundVisibility(visible);
|
|
1124
1353
|
return;
|
|
@@ -1126,12 +1355,16 @@ class LayerControl {
|
|
|
1126
1355
|
if (this.state.layerStates[layerId]) {
|
|
1127
1356
|
this.state.layerStates[layerId].visible = visible;
|
|
1128
1357
|
}
|
|
1358
|
+
if ((_a = this.customLayerRegistry) == null ? void 0 : _a.setVisibility(layerId, visible)) {
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1129
1361
|
this.map.setLayoutProperty(layerId, "visibility", visible ? "visible" : "none");
|
|
1130
1362
|
}
|
|
1131
1363
|
/**
|
|
1132
1364
|
* Change layer opacity
|
|
1133
1365
|
*/
|
|
1134
1366
|
changeLayerOpacity(layerId, opacity) {
|
|
1367
|
+
var _a;
|
|
1135
1368
|
if (layerId === "Background") {
|
|
1136
1369
|
this.changeBackgroundOpacity(opacity);
|
|
1137
1370
|
return;
|
|
@@ -1139,6 +1372,9 @@ class LayerControl {
|
|
|
1139
1372
|
if (this.state.layerStates[layerId]) {
|
|
1140
1373
|
this.state.layerStates[layerId].opacity = opacity;
|
|
1141
1374
|
}
|
|
1375
|
+
if ((_a = this.customLayerRegistry) == null ? void 0 : _a.setOpacity(layerId, opacity)) {
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1142
1378
|
const layerType = getLayerType(this.map, layerId);
|
|
1143
1379
|
if (layerType) {
|
|
1144
1380
|
setLayerOpacity(this.map, layerId, layerType, opacity);
|
|
@@ -1461,11 +1697,18 @@ class LayerControl {
|
|
|
1461
1697
|
if (layerId === "Background") {
|
|
1462
1698
|
return null;
|
|
1463
1699
|
}
|
|
1700
|
+
const layerState = this.state.layerStates[layerId];
|
|
1701
|
+
const isCustomLayer = (layerState == null ? void 0 : layerState.isCustomLayer) === true;
|
|
1464
1702
|
const button = document.createElement("button");
|
|
1465
1703
|
button.className = "layer-control-style-button";
|
|
1466
1704
|
button.innerHTML = "⚙";
|
|
1467
|
-
|
|
1468
|
-
|
|
1705
|
+
if (isCustomLayer) {
|
|
1706
|
+
button.title = "Layer info (style editing not available)";
|
|
1707
|
+
button.setAttribute("aria-label", `Layer info for ${layerId}`);
|
|
1708
|
+
} else {
|
|
1709
|
+
button.title = "Edit layer style";
|
|
1710
|
+
button.setAttribute("aria-label", `Edit style for ${layerId}`);
|
|
1711
|
+
}
|
|
1469
1712
|
button.addEventListener("click", (e) => {
|
|
1470
1713
|
e.stopPropagation();
|
|
1471
1714
|
this.toggleStyleEditor(layerId);
|
|
@@ -1491,6 +1734,17 @@ class LayerControl {
|
|
|
1491
1734
|
openStyleEditor(layerId) {
|
|
1492
1735
|
const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
|
|
1493
1736
|
if (!itemEl) return;
|
|
1737
|
+
const layerState = this.state.layerStates[layerId];
|
|
1738
|
+
if (layerState == null ? void 0 : layerState.isCustomLayer) {
|
|
1739
|
+
const editor2 = this.createCustomLayerInfoPanel(layerId);
|
|
1740
|
+
itemEl.appendChild(editor2);
|
|
1741
|
+
this.styleEditors.set(layerId, editor2);
|
|
1742
|
+
this.state.activeStyleEditor = layerId;
|
|
1743
|
+
setTimeout(() => {
|
|
1744
|
+
editor2.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
1745
|
+
}, 50);
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1494
1748
|
if (!this.state.originalStyles.has(layerId)) {
|
|
1495
1749
|
const layer = this.map.getLayer(layerId);
|
|
1496
1750
|
if (layer) {
|
|
@@ -1506,6 +1760,48 @@ class LayerControl {
|
|
|
1506
1760
|
editor.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
1507
1761
|
}, 50);
|
|
1508
1762
|
}
|
|
1763
|
+
/**
|
|
1764
|
+
* Create info panel for custom layers (style editing not supported)
|
|
1765
|
+
*/
|
|
1766
|
+
createCustomLayerInfoPanel(layerId) {
|
|
1767
|
+
const editor = document.createElement("div");
|
|
1768
|
+
editor.className = "layer-control-style-editor layer-control-custom-info";
|
|
1769
|
+
const header = document.createElement("div");
|
|
1770
|
+
header.className = "style-editor-header";
|
|
1771
|
+
const title = document.createElement("span");
|
|
1772
|
+
title.className = "style-editor-title";
|
|
1773
|
+
title.textContent = "Layer Info";
|
|
1774
|
+
const closeBtn = document.createElement("button");
|
|
1775
|
+
closeBtn.className = "style-editor-close";
|
|
1776
|
+
closeBtn.innerHTML = "×";
|
|
1777
|
+
closeBtn.title = "Close";
|
|
1778
|
+
closeBtn.addEventListener("click", (e) => {
|
|
1779
|
+
e.stopPropagation();
|
|
1780
|
+
this.closeStyleEditor(layerId);
|
|
1781
|
+
});
|
|
1782
|
+
header.appendChild(title);
|
|
1783
|
+
header.appendChild(closeBtn);
|
|
1784
|
+
const content = document.createElement("div");
|
|
1785
|
+
content.className = "style-editor-controls";
|
|
1786
|
+
const infoText = document.createElement("p");
|
|
1787
|
+
infoText.className = "layer-control-custom-info-text";
|
|
1788
|
+
infoText.textContent = `This is a custom layer. Style editing is not available for this layer type. Use the visibility toggle and opacity slider to control the layer.`;
|
|
1789
|
+
content.appendChild(infoText);
|
|
1790
|
+
const actions = document.createElement("div");
|
|
1791
|
+
actions.className = "style-editor-actions";
|
|
1792
|
+
const closeActionBtn = document.createElement("button");
|
|
1793
|
+
closeActionBtn.className = "style-editor-button style-editor-button-close";
|
|
1794
|
+
closeActionBtn.textContent = "Close";
|
|
1795
|
+
closeActionBtn.addEventListener("click", (e) => {
|
|
1796
|
+
e.stopPropagation();
|
|
1797
|
+
this.closeStyleEditor(layerId);
|
|
1798
|
+
});
|
|
1799
|
+
actions.appendChild(closeActionBtn);
|
|
1800
|
+
editor.appendChild(header);
|
|
1801
|
+
editor.appendChild(content);
|
|
1802
|
+
editor.appendChild(actions);
|
|
1803
|
+
return editor;
|
|
1804
|
+
}
|
|
1509
1805
|
/**
|
|
1510
1806
|
* Close style editor for a layer
|
|
1511
1807
|
*/
|
|
@@ -1823,7 +2119,14 @@ class LayerControl {
|
|
|
1823
2119
|
return;
|
|
1824
2120
|
}
|
|
1825
2121
|
Object.keys(this.state.layerStates).forEach((layerId) => {
|
|
2122
|
+
var _a;
|
|
1826
2123
|
try {
|
|
2124
|
+
if ((_a = this.state.layerStates[layerId]) == null ? void 0 : _a.isCustomLayer) {
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
if (layerId === "Background") {
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
1827
2130
|
const layer = this.map.getLayer(layerId);
|
|
1828
2131
|
if (!layer) return;
|
|
1829
2132
|
const visibility = this.map.getLayoutProperty(layerId, "visibility");
|
|
@@ -1891,7 +2194,8 @@ class LayerControl {
|
|
|
1891
2194
|
});
|
|
1892
2195
|
const removedLayers = [];
|
|
1893
2196
|
Object.keys(this.state.layerStates).forEach((layerId) => {
|
|
1894
|
-
|
|
2197
|
+
const state = this.state.layerStates[layerId];
|
|
2198
|
+
if (layerId !== "Background" && !state.isCustomLayer && !currentMapLayerIds.has(layerId)) {
|
|
1895
2199
|
removedLayers.push(layerId);
|
|
1896
2200
|
}
|
|
1897
2201
|
});
|
|
@@ -1924,11 +2228,44 @@ class LayerControl {
|
|
|
1924
2228
|
this.addLayerItem(layerId, this.state.layerStates[layerId]);
|
|
1925
2229
|
});
|
|
1926
2230
|
}
|
|
2231
|
+
if (this.customLayerRegistry) {
|
|
2232
|
+
const customLayerIds = this.customLayerRegistry.getAllLayerIds();
|
|
2233
|
+
customLayerIds.forEach((layerId) => {
|
|
2234
|
+
if (!this.state.layerStates[layerId]) {
|
|
2235
|
+
const customState = this.customLayerRegistry.getLayerState(layerId);
|
|
2236
|
+
if (customState) {
|
|
2237
|
+
this.state.layerStates[layerId] = {
|
|
2238
|
+
visible: customState.visible,
|
|
2239
|
+
opacity: customState.opacity,
|
|
2240
|
+
name: customState.name,
|
|
2241
|
+
isCustomLayer: true,
|
|
2242
|
+
customLayerType: this.customLayerRegistry.getSymbolType(layerId) || void 0
|
|
2243
|
+
};
|
|
2244
|
+
this.addLayerItem(layerId, this.state.layerStates[layerId]);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
Object.keys(this.state.layerStates).forEach((layerId) => {
|
|
2249
|
+
const state = this.state.layerStates[layerId];
|
|
2250
|
+
if (state.isCustomLayer && !customLayerIds.includes(layerId)) {
|
|
2251
|
+
delete this.state.layerStates[layerId];
|
|
2252
|
+
const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
|
|
2253
|
+
if (itemEl) {
|
|
2254
|
+
itemEl.remove();
|
|
2255
|
+
}
|
|
2256
|
+
if (this.state.activeStyleEditor === layerId) {
|
|
2257
|
+
this.state.activeStyleEditor = null;
|
|
2258
|
+
}
|
|
2259
|
+
this.styleEditors.delete(layerId);
|
|
2260
|
+
}
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
1927
2263
|
} catch (error) {
|
|
1928
2264
|
console.warn("Failed to check for new layers:", error);
|
|
1929
2265
|
}
|
|
1930
2266
|
}
|
|
1931
2267
|
}
|
|
2268
|
+
exports.CustomLayerRegistry = CustomLayerRegistry;
|
|
1932
2269
|
exports.LayerControl = LayerControl;
|
|
1933
2270
|
exports.clamp = clamp;
|
|
1934
2271
|
exports.createBackgroundGroupSymbolSVG = createBackgroundGroupSymbolSVG;
|