maplibre-gl-layer-control 0.4.0 → 0.5.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/README.md +31 -0
- package/dist/index.cjs +357 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +357 -0
- package/dist/index.mjs.map +1 -1
- package/dist/maplibre-gl-layer-control.css +32 -0
- package/dist/types/index.d.ts +84 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ A comprehensive layer control for MapLibre GL with advanced styling capabilities
|
|
|
11
11
|
- ✅ **Auto-detection** - Automatically detects layer properties (opacity, visibility) and generates friendly names
|
|
12
12
|
- ✅ **Layer visibility toggle** - Checkbox control for each layer
|
|
13
13
|
- ✅ **Layer opacity control** - Smooth opacity slider with type-aware property mapping
|
|
14
|
+
- ✅ **Layer symbols** - Visual type indicators (colored shapes) next to layer names, auto-detected from layer paint properties
|
|
14
15
|
- ✅ **Resizable panel** - Adjustable panel width (240-420px) with keyboard support
|
|
15
16
|
- ✅ **Advanced style editor** - Per-layer-type styling controls:
|
|
16
17
|
- **Fill layers**: color, opacity, outline-color
|
|
@@ -153,8 +154,11 @@ function MapComponent() {
|
|
|
153
154
|
| `panelWidth` | `number` | `320` | Initial panel width in pixels |
|
|
154
155
|
| `panelMinWidth` | `number` | `240` | Minimum panel width |
|
|
155
156
|
| `panelMaxWidth` | `number` | `420` | Maximum panel width |
|
|
157
|
+
| `panelMaxHeight` | `number` | `600` | Maximum panel height (scrollable when exceeded) |
|
|
156
158
|
| `showStyleEditor` | `boolean` | `true` | Show gear icon for style editor |
|
|
157
159
|
| `showOpacitySlider` | `boolean` | `true` | Show opacity slider for layers |
|
|
160
|
+
| `showLayerSymbol` | `boolean` | `true` | Show layer type symbols (colored icons) next to layer names |
|
|
161
|
+
| `excludeDrawnLayers` | `boolean` | `true` | Exclude layers from drawing libraries (Geoman, Mapbox GL Draw, etc.) |
|
|
158
162
|
|
|
159
163
|
### LayerState
|
|
160
164
|
|
|
@@ -175,6 +179,33 @@ See the [examples](./examples) folder for complete working examples:
|
|
|
175
179
|
- **[background-legend](./examples/background-legend)** - Background layer visibility control
|
|
176
180
|
- **[react](./examples/react)** - React integration example
|
|
177
181
|
|
|
182
|
+
### Layer Symbols
|
|
183
|
+
|
|
184
|
+
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:
|
|
185
|
+
|
|
186
|
+
| Layer Type | Symbol |
|
|
187
|
+
|------------|--------|
|
|
188
|
+
| `fill` | Colored rectangle with border |
|
|
189
|
+
| `line` | Horizontal line |
|
|
190
|
+
| `circle` | Colored circle |
|
|
191
|
+
| `symbol` | Marker/pin icon |
|
|
192
|
+
| `raster` | Gradient rectangle |
|
|
193
|
+
| `heatmap` | Orange-red gradient |
|
|
194
|
+
| `hillshade` | Gray gradient |
|
|
195
|
+
| `fill-extrusion` | 3D rectangle |
|
|
196
|
+
| `background` | Rectangle with inner border |
|
|
197
|
+
| Background group | Stacked layers icon |
|
|
198
|
+
|
|
199
|
+
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.
|
|
200
|
+
|
|
201
|
+
To disable layer symbols:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const layerControl = new LayerControl({
|
|
205
|
+
showLayerSymbol: false
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
178
209
|
### Background Layer Legend
|
|
179
210
|
|
|
180
211
|
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:
|
package/dist/index.cjs
CHANGED
|
@@ -168,6 +168,258 @@ function formatNumericValue(value, step) {
|
|
|
168
168
|
function clamp(value, min, max) {
|
|
169
169
|
return Math.max(min, Math.min(max, value));
|
|
170
170
|
}
|
|
171
|
+
const COLOR_PROPERTY_MAP = {
|
|
172
|
+
fill: ["fill-color", "fill-outline-color"],
|
|
173
|
+
line: ["line-color"],
|
|
174
|
+
circle: ["circle-color", "circle-stroke-color"],
|
|
175
|
+
symbol: ["icon-color", "text-color"],
|
|
176
|
+
background: ["background-color"],
|
|
177
|
+
heatmap: ["heatmap-color"],
|
|
178
|
+
"fill-extrusion": ["fill-extrusion-color"]
|
|
179
|
+
};
|
|
180
|
+
function extractColorFromExpression(expression) {
|
|
181
|
+
if (!Array.isArray(expression) || expression.length === 0) return null;
|
|
182
|
+
for (const item of expression) {
|
|
183
|
+
if (typeof item === "string") {
|
|
184
|
+
if (item.startsWith("#") || item.startsWith("rgb") || item.startsWith("hsl")) {
|
|
185
|
+
return normalizeColor(item);
|
|
186
|
+
}
|
|
187
|
+
} else if (Array.isArray(item)) {
|
|
188
|
+
const result = extractColorFromExpression(item);
|
|
189
|
+
if (result) return result;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
function getLayerColor(map, layerId, layerType) {
|
|
195
|
+
var _a;
|
|
196
|
+
const propertyNames = COLOR_PROPERTY_MAP[layerType];
|
|
197
|
+
if (!propertyNames) return null;
|
|
198
|
+
for (const propertyName of propertyNames) {
|
|
199
|
+
try {
|
|
200
|
+
const runtimeColor = map.getPaintProperty(layerId, propertyName);
|
|
201
|
+
if (runtimeColor) {
|
|
202
|
+
if (typeof runtimeColor === "string") {
|
|
203
|
+
return normalizeColor(runtimeColor);
|
|
204
|
+
}
|
|
205
|
+
if (Array.isArray(runtimeColor)) {
|
|
206
|
+
const extracted = extractColorFromExpression(runtimeColor);
|
|
207
|
+
if (extracted) return extracted;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
const style = map.getStyle();
|
|
213
|
+
const layer = (_a = style == null ? void 0 : style.layers) == null ? void 0 : _a.find(
|
|
214
|
+
(l) => l.id === layerId
|
|
215
|
+
);
|
|
216
|
+
if (layer && "paint" in layer && layer.paint) {
|
|
217
|
+
const paintColor = layer.paint[propertyName];
|
|
218
|
+
if (paintColor) {
|
|
219
|
+
if (typeof paintColor === "string") {
|
|
220
|
+
return normalizeColor(paintColor);
|
|
221
|
+
}
|
|
222
|
+
if (Array.isArray(paintColor)) {
|
|
223
|
+
const extracted = extractColorFromExpression(paintColor);
|
|
224
|
+
if (extracted) return extracted;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
function getLayerColorFromSpec(layer) {
|
|
232
|
+
const propertyNames = COLOR_PROPERTY_MAP[layer.type];
|
|
233
|
+
if (!propertyNames) return null;
|
|
234
|
+
for (const propertyName of propertyNames) {
|
|
235
|
+
if ("paint" in layer && layer.paint) {
|
|
236
|
+
const paintColor = layer.paint[propertyName];
|
|
237
|
+
if (paintColor) {
|
|
238
|
+
if (typeof paintColor === "string") {
|
|
239
|
+
return normalizeColor(paintColor);
|
|
240
|
+
}
|
|
241
|
+
if (Array.isArray(paintColor)) {
|
|
242
|
+
const extracted = extractColorFromExpression(paintColor);
|
|
243
|
+
if (extracted) return extracted;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
function darkenColor(hexColor, amount) {
|
|
251
|
+
let hex = hexColor.replace("#", "");
|
|
252
|
+
if (hex.length === 3) {
|
|
253
|
+
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
254
|
+
}
|
|
255
|
+
const r = Math.max(
|
|
256
|
+
0,
|
|
257
|
+
parseInt(hex.slice(0, 2), 16) - Math.round(255 * amount)
|
|
258
|
+
);
|
|
259
|
+
const g = Math.max(
|
|
260
|
+
0,
|
|
261
|
+
parseInt(hex.slice(2, 4), 16) - Math.round(255 * amount)
|
|
262
|
+
);
|
|
263
|
+
const b = Math.max(
|
|
264
|
+
0,
|
|
265
|
+
parseInt(hex.slice(4, 6), 16) - Math.round(255 * amount)
|
|
266
|
+
);
|
|
267
|
+
return rgbToHex(r, g, b);
|
|
268
|
+
}
|
|
269
|
+
function createFillSymbol(size, color) {
|
|
270
|
+
const padding = 2;
|
|
271
|
+
const borderColor = darkenColor(color, 0.3);
|
|
272
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
273
|
+
<rect x="${padding}" y="${padding}" width="${size - padding * 2}" height="${size - padding * 2}"
|
|
274
|
+
fill="${color}" stroke="${borderColor}" stroke-width="1" rx="1"/>
|
|
275
|
+
</svg>`;
|
|
276
|
+
}
|
|
277
|
+
function createLineSymbol(size, color, strokeWidth = 2) {
|
|
278
|
+
const y = size / 2;
|
|
279
|
+
const padding = 2;
|
|
280
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
281
|
+
<line x1="${padding}" y1="${y}" x2="${size - padding}" y2="${y}"
|
|
282
|
+
stroke="${color}" stroke-width="${strokeWidth}" stroke-linecap="round"/>
|
|
283
|
+
</svg>`;
|
|
284
|
+
}
|
|
285
|
+
function createCircleSymbol(size, color) {
|
|
286
|
+
const cx = size / 2;
|
|
287
|
+
const cy = size / 2;
|
|
288
|
+
const r = size / 2 - 3;
|
|
289
|
+
const borderColor = darkenColor(color, 0.3);
|
|
290
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
291
|
+
<circle cx="${cx}" cy="${cy}" r="${r}" fill="${color}"
|
|
292
|
+
stroke="${borderColor}" stroke-width="1"/>
|
|
293
|
+
</svg>`;
|
|
294
|
+
}
|
|
295
|
+
function createMarkerSymbol(size, color) {
|
|
296
|
+
const borderColor = darkenColor(color, 0.3);
|
|
297
|
+
const cx = size / 2;
|
|
298
|
+
const pinWidth = size * 0.5;
|
|
299
|
+
const pinHeight = size * 0.7;
|
|
300
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
301
|
+
<path d="M${cx} ${size - 2}
|
|
302
|
+
L${cx - pinWidth / 2} ${size - pinHeight}
|
|
303
|
+
A${pinWidth / 2} ${pinWidth / 2} 0 1 1 ${cx + pinWidth / 2} ${size - pinHeight}
|
|
304
|
+
Z"
|
|
305
|
+
fill="${color}" stroke="${borderColor}" stroke-width="1"/>
|
|
306
|
+
<circle cx="${cx}" cy="${size - pinHeight - pinWidth / 4}" r="${pinWidth / 5}" fill="white"/>
|
|
307
|
+
</svg>`;
|
|
308
|
+
}
|
|
309
|
+
function createRasterSymbol(size) {
|
|
310
|
+
const padding = 2;
|
|
311
|
+
const id = `rasterGrad_${Math.random().toString(36).slice(2, 9)}`;
|
|
312
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
313
|
+
<defs>
|
|
314
|
+
<linearGradient id="${id}" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
315
|
+
<stop offset="0%" stop-color="#e0e0e0"/>
|
|
316
|
+
<stop offset="50%" stop-color="#808080"/>
|
|
317
|
+
<stop offset="100%" stop-color="#404040"/>
|
|
318
|
+
</linearGradient>
|
|
319
|
+
</defs>
|
|
320
|
+
<rect x="${padding}" y="${padding}" width="${size - padding * 2}" height="${size - padding * 2}"
|
|
321
|
+
fill="url(#${id})" rx="1"/>
|
|
322
|
+
</svg>`;
|
|
323
|
+
}
|
|
324
|
+
function createBackgroundSymbol(size, color) {
|
|
325
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
326
|
+
<rect x="1" y="1" width="${size - 2}" height="${size - 2}" fill="${color}" rx="2"/>
|
|
327
|
+
<rect x="3" y="3" width="${size - 6}" height="${size - 6}" fill="none"
|
|
328
|
+
stroke="white" stroke-width="1" stroke-opacity="0.5" rx="1"/>
|
|
329
|
+
</svg>`;
|
|
330
|
+
}
|
|
331
|
+
function createHeatmapSymbol(size) {
|
|
332
|
+
const padding = 2;
|
|
333
|
+
const id = `heatmapGrad_${Math.random().toString(36).slice(2, 9)}`;
|
|
334
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
335
|
+
<defs>
|
|
336
|
+
<radialGradient id="${id}" cx="50%" cy="50%" r="50%">
|
|
337
|
+
<stop offset="0%" stop-color="#ffff00"/>
|
|
338
|
+
<stop offset="50%" stop-color="#ff8800"/>
|
|
339
|
+
<stop offset="100%" stop-color="#ff0000"/>
|
|
340
|
+
</radialGradient>
|
|
341
|
+
</defs>
|
|
342
|
+
<rect x="${padding}" y="${padding}" width="${size - padding * 2}" height="${size - padding * 2}"
|
|
343
|
+
fill="url(#${id})" rx="1"/>
|
|
344
|
+
</svg>`;
|
|
345
|
+
}
|
|
346
|
+
function createHillshadeSymbol(size) {
|
|
347
|
+
const padding = 2;
|
|
348
|
+
const id = `hillshadeGrad_${Math.random().toString(36).slice(2, 9)}`;
|
|
349
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
350
|
+
<defs>
|
|
351
|
+
<linearGradient id="${id}" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
352
|
+
<stop offset="0%" stop-color="#ffffff"/>
|
|
353
|
+
<stop offset="100%" stop-color="#666666"/>
|
|
354
|
+
</linearGradient>
|
|
355
|
+
</defs>
|
|
356
|
+
<rect x="${padding}" y="${padding}" width="${size - padding * 2}" height="${size - padding * 2}"
|
|
357
|
+
fill="url(#${id})" rx="1"/>
|
|
358
|
+
</svg>`;
|
|
359
|
+
}
|
|
360
|
+
function createFillExtrusionSymbol(size, color) {
|
|
361
|
+
const borderColor = darkenColor(color, 0.3);
|
|
362
|
+
const topColor = color;
|
|
363
|
+
const sideColor = darkenColor(color, 0.2);
|
|
364
|
+
const depth = 3;
|
|
365
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
366
|
+
<polygon points="${2 + depth},2 ${size - 2},2 ${size - 2},${size - 2 - depth} ${size - 2 - depth},${size - 2} 2,${size - 2} 2,${2 + depth}"
|
|
367
|
+
fill="${topColor}" stroke="${borderColor}" stroke-width="1"/>
|
|
368
|
+
<polygon points="2,${2 + depth} ${2 + depth},2 ${2 + depth},${size - 2 - depth} 2,${size - 2}"
|
|
369
|
+
fill="${sideColor}" stroke="${borderColor}" stroke-width="0.5"/>
|
|
370
|
+
<polygon points="${2 + depth},${size - 2 - depth} ${size - 2},${size - 2 - depth} ${size - 2 - depth},${size - 2} 2,${size - 2}"
|
|
371
|
+
fill="${sideColor}" stroke="${borderColor}" stroke-width="0.5"/>
|
|
372
|
+
</svg>`;
|
|
373
|
+
}
|
|
374
|
+
function createDefaultSymbol(size, color) {
|
|
375
|
+
const padding = 2;
|
|
376
|
+
const borderColor = darkenColor(color, 0.3);
|
|
377
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
378
|
+
<rect x="${padding}" y="${padding}" width="${size - padding * 2}" height="${size - padding * 2}"
|
|
379
|
+
fill="${color}" stroke="${borderColor}" stroke-width="1"/>
|
|
380
|
+
</svg>`;
|
|
381
|
+
}
|
|
382
|
+
function createStackedLayersSymbol(size) {
|
|
383
|
+
const colors = ["#a8d4a8", "#8ec4e8", "#d4c4a8"];
|
|
384
|
+
const borderColor = "#666666";
|
|
385
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
386
|
+
<rect x="4" y="1" width="${size - 6}" height="${size - 6}" fill="${colors[2]}" stroke="${borderColor}" stroke-width="0.75" rx="1"/>
|
|
387
|
+
<rect x="2" y="3" width="${size - 6}" height="${size - 6}" fill="${colors[1]}" stroke="${borderColor}" stroke-width="0.75" rx="1"/>
|
|
388
|
+
<rect x="0" y="5" width="${size - 6}" height="${size - 6}" fill="${colors[0]}" stroke="${borderColor}" stroke-width="0.75" rx="1"/>
|
|
389
|
+
</svg>`;
|
|
390
|
+
}
|
|
391
|
+
function createLayerSymbolSVG(layerType, color, options = {}) {
|
|
392
|
+
const size = options.size || 16;
|
|
393
|
+
const strokeWidth = options.strokeWidth || 2;
|
|
394
|
+
const fillColor = color || "#888888";
|
|
395
|
+
switch (layerType) {
|
|
396
|
+
case "fill":
|
|
397
|
+
return createFillSymbol(size, fillColor);
|
|
398
|
+
case "line":
|
|
399
|
+
return createLineSymbol(size, fillColor, strokeWidth);
|
|
400
|
+
case "circle":
|
|
401
|
+
return createCircleSymbol(size, fillColor);
|
|
402
|
+
case "symbol":
|
|
403
|
+
return createMarkerSymbol(size, fillColor);
|
|
404
|
+
case "raster":
|
|
405
|
+
return createRasterSymbol(size);
|
|
406
|
+
case "background":
|
|
407
|
+
return createBackgroundSymbol(size, fillColor);
|
|
408
|
+
case "heatmap":
|
|
409
|
+
return createHeatmapSymbol(size);
|
|
410
|
+
case "hillshade":
|
|
411
|
+
return createHillshadeSymbol(size);
|
|
412
|
+
case "fill-extrusion":
|
|
413
|
+
return createFillExtrusionSymbol(size, fillColor);
|
|
414
|
+
case "background-group":
|
|
415
|
+
return createStackedLayersSymbol(size);
|
|
416
|
+
default:
|
|
417
|
+
return createDefaultSymbol(size, fillColor);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function createBackgroundGroupSymbolSVG(size = 16) {
|
|
421
|
+
return createStackedLayersSymbol(size);
|
|
422
|
+
}
|
|
171
423
|
class LayerControl {
|
|
172
424
|
constructor(options = {}) {
|
|
173
425
|
__publicField(this, "map");
|
|
@@ -181,8 +433,11 @@ class LayerControl {
|
|
|
181
433
|
// Panel width management
|
|
182
434
|
__publicField(this, "minPanelWidth");
|
|
183
435
|
__publicField(this, "maxPanelWidth");
|
|
436
|
+
__publicField(this, "maxPanelHeight");
|
|
184
437
|
__publicField(this, "showStyleEditor");
|
|
185
438
|
__publicField(this, "showOpacitySlider");
|
|
439
|
+
__publicField(this, "showLayerSymbol");
|
|
440
|
+
__publicField(this, "excludeDrawnLayers");
|
|
186
441
|
__publicField(this, "widthSliderEl", null);
|
|
187
442
|
__publicField(this, "widthThumbEl", null);
|
|
188
443
|
__publicField(this, "widthValueEl", null);
|
|
@@ -193,8 +448,11 @@ class LayerControl {
|
|
|
193
448
|
__publicField(this, "widthFrame", null);
|
|
194
449
|
this.minPanelWidth = options.panelMinWidth || 240;
|
|
195
450
|
this.maxPanelWidth = options.panelMaxWidth || 420;
|
|
451
|
+
this.maxPanelHeight = options.panelMaxHeight || 600;
|
|
196
452
|
this.showStyleEditor = options.showStyleEditor !== false;
|
|
197
453
|
this.showOpacitySlider = options.showOpacitySlider !== false;
|
|
454
|
+
this.showLayerSymbol = options.showLayerSymbol !== false;
|
|
455
|
+
this.excludeDrawnLayers = options.excludeDrawnLayers !== false;
|
|
198
456
|
this.state = {
|
|
199
457
|
collapsed: options.collapsed !== false,
|
|
200
458
|
panelWidth: options.panelWidth || 320,
|
|
@@ -250,6 +508,10 @@ class LayerControl {
|
|
|
250
508
|
allLayerIds.forEach((layerId) => {
|
|
251
509
|
const layer = this.map.getLayer(layerId);
|
|
252
510
|
if (!layer) return;
|
|
511
|
+
if (this.excludeDrawnLayers && this.isDrawnLayer(layerId)) {
|
|
512
|
+
backgroundLayerIds.push(layerId);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
253
515
|
const sourceId = layer.source;
|
|
254
516
|
if (!sourceId || originalSourceIds.has(sourceId)) {
|
|
255
517
|
backgroundLayerIds.push(layerId);
|
|
@@ -375,6 +637,28 @@ class LayerControl {
|
|
|
375
637
|
name = name.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
376
638
|
return name || layerId;
|
|
377
639
|
}
|
|
640
|
+
/**
|
|
641
|
+
* Check if a layer ID belongs to a drawing library (Geoman, Mapbox GL Draw, etc.)
|
|
642
|
+
* @param layerId The layer ID to check
|
|
643
|
+
* @returns true if the layer is from a drawing library
|
|
644
|
+
*/
|
|
645
|
+
isDrawnLayer(layerId) {
|
|
646
|
+
const drawnLayerPatterns = [
|
|
647
|
+
/^gm[-_\s]/i,
|
|
648
|
+
// Geoman (gm-main-*, gm_*, Gm Temporary...)
|
|
649
|
+
/^gl-draw[-_]/i,
|
|
650
|
+
// Mapbox GL Draw
|
|
651
|
+
/^mapbox-gl-draw[-_]/i,
|
|
652
|
+
// Mapbox GL Draw alternative
|
|
653
|
+
/^terra-draw[-_]/i,
|
|
654
|
+
// Terra Draw
|
|
655
|
+
/^maplibre-gl-draw[-_]/i,
|
|
656
|
+
// MapLibre GL Draw
|
|
657
|
+
/^draw[-_]layer/i
|
|
658
|
+
// Generic draw layers
|
|
659
|
+
];
|
|
660
|
+
return drawnLayerPatterns.some((pattern) => pattern.test(layerId));
|
|
661
|
+
}
|
|
378
662
|
/**
|
|
379
663
|
* Create the main container element
|
|
380
664
|
*/
|
|
@@ -404,6 +688,7 @@ class LayerControl {
|
|
|
404
688
|
const panel = document.createElement("div");
|
|
405
689
|
panel.className = "layer-control-panel";
|
|
406
690
|
panel.style.width = `${this.state.panelWidth}px`;
|
|
691
|
+
panel.style.maxHeight = `${this.maxPanelHeight}px`;
|
|
407
692
|
if (!this.state.collapsed) {
|
|
408
693
|
panel.classList.add("expanded");
|
|
409
694
|
}
|
|
@@ -739,6 +1024,17 @@ class LayerControl {
|
|
|
739
1024
|
name.textContent = state.name || layerId;
|
|
740
1025
|
name.title = state.name || layerId;
|
|
741
1026
|
row.appendChild(checkbox);
|
|
1027
|
+
if (this.showLayerSymbol) {
|
|
1028
|
+
if (layerId === "Background") {
|
|
1029
|
+
const symbol = this.createBackgroundGroupSymbol();
|
|
1030
|
+
row.appendChild(symbol);
|
|
1031
|
+
} else {
|
|
1032
|
+
const symbol = this.createLayerSymbol(layerId);
|
|
1033
|
+
if (symbol) {
|
|
1034
|
+
row.appendChild(symbol);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
742
1038
|
row.appendChild(name);
|
|
743
1039
|
if (this.showOpacitySlider) {
|
|
744
1040
|
const opacity = document.createElement("input");
|
|
@@ -775,6 +1071,50 @@ class LayerControl {
|
|
|
775
1071
|
item.appendChild(row);
|
|
776
1072
|
this.panel.appendChild(item);
|
|
777
1073
|
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Create a symbol element for a layer
|
|
1076
|
+
* @param layerId The layer ID
|
|
1077
|
+
* @returns The symbol HTML element, or null if layer not found
|
|
1078
|
+
*/
|
|
1079
|
+
createLayerSymbol(layerId) {
|
|
1080
|
+
const layer = this.map.getLayer(layerId);
|
|
1081
|
+
if (!layer) return null;
|
|
1082
|
+
const layerType = layer.type;
|
|
1083
|
+
const color = getLayerColor(this.map, layerId, layerType);
|
|
1084
|
+
const svgMarkup = createLayerSymbolSVG(layerType, color);
|
|
1085
|
+
const symbolContainer = document.createElement("span");
|
|
1086
|
+
symbolContainer.className = "layer-control-symbol";
|
|
1087
|
+
symbolContainer.innerHTML = svgMarkup;
|
|
1088
|
+
symbolContainer.title = `Layer type: ${layerType}`;
|
|
1089
|
+
return symbolContainer;
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Create a symbol element for a background layer
|
|
1093
|
+
* @param layer The layer specification
|
|
1094
|
+
* @returns The symbol HTML element
|
|
1095
|
+
*/
|
|
1096
|
+
createBackgroundLayerSymbol(layer) {
|
|
1097
|
+
const color = getLayerColorFromSpec(layer);
|
|
1098
|
+
const svgMarkup = createLayerSymbolSVG(layer.type, color, { size: 14 });
|
|
1099
|
+
const symbolContainer = document.createElement("span");
|
|
1100
|
+
symbolContainer.className = "background-legend-layer-symbol";
|
|
1101
|
+
symbolContainer.innerHTML = svgMarkup;
|
|
1102
|
+
symbolContainer.title = `Layer type: ${layer.type}`;
|
|
1103
|
+
return symbolContainer;
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Create a symbol element for the Background layer group
|
|
1107
|
+
* Shows a stacked layers icon to represent multiple background layers
|
|
1108
|
+
* @returns The symbol HTML element
|
|
1109
|
+
*/
|
|
1110
|
+
createBackgroundGroupSymbol() {
|
|
1111
|
+
const svgMarkup = createBackgroundGroupSymbolSVG(16);
|
|
1112
|
+
const symbolContainer = document.createElement("span");
|
|
1113
|
+
symbolContainer.className = "layer-control-symbol";
|
|
1114
|
+
symbolContainer.innerHTML = svgMarkup;
|
|
1115
|
+
symbolContainer.title = "Background layers";
|
|
1116
|
+
return symbolContainer;
|
|
1117
|
+
}
|
|
778
1118
|
/**
|
|
779
1119
|
* Toggle layer visibility
|
|
780
1120
|
*/
|
|
@@ -1015,6 +1355,9 @@ class LayerControl {
|
|
|
1015
1355
|
const styleLayers = this.map.getStyle().layers || [];
|
|
1016
1356
|
styleLayers.forEach((layer) => {
|
|
1017
1357
|
if (!this.isUserAddedLayer(layer.id)) {
|
|
1358
|
+
if (this.excludeDrawnLayers && this.isDrawnLayer(layer.id)) {
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1018
1361
|
if (this.state.onlyRenderedFilter && !this.isLayerRendered(layer.id)) {
|
|
1019
1362
|
return;
|
|
1020
1363
|
}
|
|
@@ -1039,6 +1382,12 @@ class LayerControl {
|
|
|
1039
1382
|
typeIndicator.className = "background-legend-layer-type";
|
|
1040
1383
|
typeIndicator.textContent = layer.type;
|
|
1041
1384
|
layerRow.appendChild(checkbox);
|
|
1385
|
+
if (this.showLayerSymbol) {
|
|
1386
|
+
const symbol = this.createBackgroundLayerSymbol(layer);
|
|
1387
|
+
if (symbol) {
|
|
1388
|
+
layerRow.appendChild(symbol);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1042
1391
|
layerRow.appendChild(name);
|
|
1043
1392
|
layerRow.appendChild(typeIndicator);
|
|
1044
1393
|
container.appendChild(layerRow);
|
|
@@ -1528,6 +1877,9 @@ class LayerControl {
|
|
|
1528
1877
|
const layer = this.map.getLayer(layerId);
|
|
1529
1878
|
if (layer) {
|
|
1530
1879
|
if (isAutoDetectMode) {
|
|
1880
|
+
if (this.excludeDrawnLayers && this.isDrawnLayer(layerId)) {
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1531
1883
|
const sourceId = layer.source;
|
|
1532
1884
|
if (!sourceId || originalSourceIds.has(sourceId)) {
|
|
1533
1885
|
return;
|
|
@@ -1579,7 +1931,12 @@ class LayerControl {
|
|
|
1579
1931
|
}
|
|
1580
1932
|
exports.LayerControl = LayerControl;
|
|
1581
1933
|
exports.clamp = clamp;
|
|
1934
|
+
exports.createBackgroundGroupSymbolSVG = createBackgroundGroupSymbolSVG;
|
|
1935
|
+
exports.createLayerSymbolSVG = createLayerSymbolSVG;
|
|
1936
|
+
exports.darkenColor = darkenColor;
|
|
1582
1937
|
exports.formatNumericValue = formatNumericValue;
|
|
1938
|
+
exports.getLayerColor = getLayerColor;
|
|
1939
|
+
exports.getLayerColorFromSpec = getLayerColorFromSpec;
|
|
1583
1940
|
exports.getLayerOpacity = getLayerOpacity;
|
|
1584
1941
|
exports.getLayerType = getLayerType;
|
|
1585
1942
|
exports.isStyleableLayerType = isStyleableLayerType;
|