maplibre-gl-components 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -10,6 +10,7 @@ Legend, colorbar, and HTML control components for MapLibre GL JS maps.
10
10
  - **Colorbar** - Continuous gradient legends with built-in matplotlib colormaps
11
11
  - **Legend** - Categorical legends with color swatches and labels
12
12
  - **HtmlControl** - Flexible HTML content control for custom info panels
13
+ - **Zoom-based Visibility** - Show/hide components at specific zoom levels with `minzoom`/`maxzoom`
13
14
  - **React Support** - First-class React components and hooks
14
15
  - **TypeScript** - Full type definitions included
15
16
  - **20+ Built-in Colormaps** - viridis, plasma, terrain, jet, and more
@@ -160,6 +161,8 @@ interface ColorbarOptions {
160
161
  opacity?: number;
161
162
  fontSize?: number;
162
163
  fontColor?: string;
164
+ minzoom?: number; // Min zoom level to show (default: 0)
165
+ maxzoom?: number; // Max zoom level to show (default: 24)
163
166
  }
164
167
 
165
168
  // Methods
@@ -190,6 +193,8 @@ interface LegendOptions {
190
193
  opacity?: number;
191
194
  fontSize?: number;
192
195
  fontColor?: string;
196
+ minzoom?: number; // Min zoom level to show (default: 0)
197
+ maxzoom?: number; // Max zoom level to show (default: 24)
193
198
  }
194
199
 
195
200
  interface LegendItem {
@@ -229,6 +234,8 @@ interface HtmlControlOptions {
229
234
  opacity?: number;
230
235
  maxWidth?: number;
231
236
  maxHeight?: number;
237
+ minzoom?: number; // Min zoom level to show (default: 0)
238
+ maxzoom?: number; // Max zoom level to show (default: 24)
232
239
  }
233
240
 
234
241
  // Methods
@@ -293,6 +300,56 @@ const colorbar = new Colorbar({
293
300
  });
294
301
  ```
295
302
 
303
+ ## Zoom-based Visibility
304
+
305
+ All components support `minzoom` and `maxzoom` options to control visibility based on the map's zoom level. This is useful for showing different legends at different zoom levels, similar to how map layers work.
306
+
307
+ ```typescript
308
+ // Show legend only when zoomed in (zoom >= 10)
309
+ const detailLegend = new Legend({
310
+ title: 'Detailed Features',
311
+ items: [...],
312
+ minzoom: 10, // Only visible at zoom 10 and above
313
+ });
314
+
315
+ // Show legend only when zoomed out (zoom <= 8)
316
+ const overviewLegend = new Legend({
317
+ title: 'Overview',
318
+ items: [...],
319
+ maxzoom: 8, // Only visible at zoom 8 and below
320
+ });
321
+
322
+ // Show colorbar only within a specific zoom range
323
+ const colorbar = new Colorbar({
324
+ colormap: 'viridis',
325
+ vmin: 0,
326
+ vmax: 100,
327
+ minzoom: 5, // Visible from zoom 5...
328
+ maxzoom: 15, // ...up to zoom 15
329
+ });
330
+ ```
331
+
332
+ ### React Example
333
+
334
+ ```tsx
335
+ <LegendReact
336
+ map={map}
337
+ title="Lidar Point Cloud"
338
+ items={[
339
+ { label: 'QL0 (Approx. <= 0.35m NPS)', color: '#003300' },
340
+ { label: 'QL1 (Approx. 0.35m NPS)', color: '#006600' },
341
+ { label: 'QL2 (Approx. 0.7m NPS)', color: '#00cc00' },
342
+ { label: 'QL3 (Approx. 1.4m NPS)', color: '#ccff00' },
343
+ { label: 'Other', color: '#99ccff' },
344
+ ]}
345
+ minzoom={8}
346
+ maxzoom={18}
347
+ position="top-left"
348
+ />
349
+ ```
350
+
351
+ **Note:** The `visible` option takes precedence - if `visible` is `false`, the component will be hidden regardless of zoom level.
352
+
296
353
  ## React Hooks
297
354
 
298
355
  ```typescript
@@ -375,6 +432,41 @@ npm run build
375
432
  npm run build:examples
376
433
  ```
377
434
 
435
+ ## Docker
436
+
437
+ The examples can be run using Docker. The image is automatically built and published to GitHub Container Registry.
438
+
439
+ ### Pull and Run
440
+
441
+ ```bash
442
+ # Pull the latest image
443
+ docker pull ghcr.io/opengeos/maplibre-gl-components:latest
444
+
445
+ # Run the container
446
+ docker run -p 8080:80 ghcr.io/opengeos/maplibre-gl-components:latest
447
+ ```
448
+
449
+ Then open http://localhost:8080/maplibre-gl-components/ in your browser to view the examples.
450
+
451
+ ### Build Locally
452
+
453
+ ```bash
454
+ # Build the image
455
+ docker build -t maplibre-gl-components .
456
+
457
+ # Run the container
458
+ docker run -p 8080:80 maplibre-gl-components
459
+ ```
460
+
461
+ ### Available Tags
462
+
463
+ | Tag | Description |
464
+ |-----|-------------|
465
+ | `latest` | Latest release |
466
+ | `x.y.z` | Specific version (e.g., `1.0.0`) |
467
+ | `x.y` | Minor version (e.g., `1.0`) |
468
+
469
+
378
470
  ## License
379
471
 
380
472
  MIT License - see [LICENSE](./LICENSE) for details.
@@ -305,7 +305,9 @@ const DEFAULT_OPTIONS$2 = {
305
305
  fontSize: 11,
306
306
  fontColor: "#333",
307
307
  borderRadius: 4,
308
- padding: 8
308
+ padding: 8,
309
+ minzoom: 0,
310
+ maxzoom: 24
309
311
  };
310
312
  class Colorbar {
311
313
  /**
@@ -318,6 +320,9 @@ class Colorbar {
318
320
  __publicField(this, "_options");
319
321
  __publicField(this, "_state");
320
322
  __publicField(this, "_eventHandlers", /* @__PURE__ */ new Map());
323
+ __publicField(this, "_map");
324
+ __publicField(this, "_handleZoom");
325
+ __publicField(this, "_zoomVisible", true);
321
326
  this._options = { ...DEFAULT_OPTIONS$2, ...options };
322
327
  this._state = {
323
328
  visible: this._options.visible,
@@ -333,9 +338,13 @@ class Colorbar {
333
338
  * @param map - The MapLibre GL map instance.
334
339
  * @returns The control's container element.
335
340
  */
336
- onAdd(_map) {
341
+ onAdd(map) {
342
+ this._map = map;
337
343
  this._container = this._createContainer();
338
344
  this._render();
345
+ this._handleZoom = () => this._checkZoomVisibility();
346
+ this._map.on("zoom", this._handleZoom);
347
+ this._checkZoomVisibility();
339
348
  return this._container;
340
349
  }
341
350
  /**
@@ -344,6 +353,11 @@ class Colorbar {
344
353
  */
345
354
  onRemove() {
346
355
  var _a, _b;
356
+ if (this._map && this._handleZoom) {
357
+ this._map.off("zoom", this._handleZoom);
358
+ this._handleZoom = void 0;
359
+ }
360
+ this._map = void 0;
347
361
  (_b = (_a = this._container) == null ? void 0 : _a.parentNode) == null ? void 0 : _b.removeChild(this._container);
348
362
  this._container = void 0;
349
363
  this._eventHandlers.clear();
@@ -354,9 +368,7 @@ class Colorbar {
354
368
  show() {
355
369
  if (!this._state.visible) {
356
370
  this._state.visible = true;
357
- if (this._container) {
358
- this._container.style.display = "flex";
359
- }
371
+ this._updateDisplayState();
360
372
  this._emit("show");
361
373
  }
362
374
  }
@@ -366,9 +378,7 @@ class Colorbar {
366
378
  hide() {
367
379
  if (this._state.visible) {
368
380
  this._state.visible = false;
369
- if (this._container) {
370
- this._container.style.display = "none";
371
- }
381
+ this._updateDisplayState();
372
382
  this._emit("hide");
373
383
  }
374
384
  }
@@ -489,6 +499,27 @@ class Colorbar {
489
499
  handlers.forEach((handler) => handler(eventData));
490
500
  }
491
501
  }
502
+ /**
503
+ * Checks if the current zoom level is within the visibility range.
504
+ */
505
+ _checkZoomVisibility() {
506
+ if (!this._map) return;
507
+ const zoom = this._map.getZoom();
508
+ const { minzoom, maxzoom } = this._options;
509
+ const inRange = zoom >= minzoom && zoom <= maxzoom;
510
+ if (inRange !== this._zoomVisible) {
511
+ this._zoomVisible = inRange;
512
+ this._updateDisplayState();
513
+ }
514
+ }
515
+ /**
516
+ * Updates the display state based on visibility and zoom level.
517
+ */
518
+ _updateDisplayState() {
519
+ if (!this._container) return;
520
+ const shouldShow = this._state.visible && this._zoomVisible;
521
+ this._container.style.display = shouldShow ? "flex" : "none";
522
+ }
492
523
  /**
493
524
  * Creates the main container element.
494
525
  *
@@ -497,7 +528,8 @@ class Colorbar {
497
528
  _createContainer() {
498
529
  const container = document.createElement("div");
499
530
  container.className = `maplibregl-ctrl maplibre-gl-colorbar${this._options.className ? ` ${this._options.className}` : ""}`;
500
- if (!this._state.visible) {
531
+ const shouldShow = this._state.visible && this._zoomVisible;
532
+ if (!shouldShow) {
501
533
  container.style.display = "none";
502
534
  }
503
535
  return container;
@@ -522,6 +554,7 @@ class Colorbar {
522
554
  const isVertical = orientation === "vertical";
523
555
  const ticks = this._generateTicks();
524
556
  this._container.innerHTML = "";
557
+ const shouldShow = this._state.visible && this._zoomVisible;
525
558
  Object.assign(this._container.style, {
526
559
  backgroundColor,
527
560
  opacity: opacity.toString(),
@@ -529,7 +562,7 @@ class Colorbar {
529
562
  padding: `${padding}px`,
530
563
  fontSize: `${fontSize}px`,
531
564
  color: fontColor,
532
- display: this._state.visible ? "flex" : "none",
565
+ display: shouldShow ? "flex" : "none",
533
566
  flexDirection: isVertical ? "row" : "column",
534
567
  alignItems: "stretch",
535
568
  gap: "6px",
@@ -604,7 +637,9 @@ const DEFAULT_OPTIONS$1 = {
604
637
  fontColor: "#333",
605
638
  swatchSize: 16,
606
639
  borderRadius: 4,
607
- padding: 10
640
+ padding: 10,
641
+ minzoom: 0,
642
+ maxzoom: 24
608
643
  };
609
644
  class Legend {
610
645
  /**
@@ -617,6 +652,9 @@ class Legend {
617
652
  __publicField(this, "_options");
618
653
  __publicField(this, "_state");
619
654
  __publicField(this, "_eventHandlers", /* @__PURE__ */ new Map());
655
+ __publicField(this, "_map");
656
+ __publicField(this, "_handleZoom");
657
+ __publicField(this, "_zoomVisible", true);
620
658
  this._options = { ...DEFAULT_OPTIONS$1, ...options };
621
659
  this._state = {
622
660
  visible: this._options.visible,
@@ -630,9 +668,13 @@ class Legend {
630
668
  * @param map - The MapLibre GL map instance.
631
669
  * @returns The control's container element.
632
670
  */
633
- onAdd(_map) {
671
+ onAdd(map) {
672
+ this._map = map;
634
673
  this._container = this._createContainer();
635
674
  this._render();
675
+ this._handleZoom = () => this._checkZoomVisibility();
676
+ this._map.on("zoom", this._handleZoom);
677
+ this._checkZoomVisibility();
636
678
  return this._container;
637
679
  }
638
680
  /**
@@ -640,6 +682,11 @@ class Legend {
640
682
  */
641
683
  onRemove() {
642
684
  var _a, _b;
685
+ if (this._map && this._handleZoom) {
686
+ this._map.off("zoom", this._handleZoom);
687
+ this._handleZoom = void 0;
688
+ }
689
+ this._map = void 0;
643
690
  (_b = (_a = this._container) == null ? void 0 : _a.parentNode) == null ? void 0 : _b.removeChild(this._container);
644
691
  this._container = void 0;
645
692
  this._eventHandlers.clear();
@@ -650,9 +697,7 @@ class Legend {
650
697
  show() {
651
698
  if (!this._state.visible) {
652
699
  this._state.visible = true;
653
- if (this._container) {
654
- this._container.style.display = "block";
655
- }
700
+ this._updateDisplayState();
656
701
  this._emit("show");
657
702
  }
658
703
  }
@@ -662,9 +707,7 @@ class Legend {
662
707
  hide() {
663
708
  if (this._state.visible) {
664
709
  this._state.visible = false;
665
- if (this._container) {
666
- this._container.style.display = "none";
667
- }
710
+ this._updateDisplayState();
668
711
  this._emit("hide");
669
712
  }
670
713
  }
@@ -786,6 +829,27 @@ class Legend {
786
829
  handlers.forEach((handler) => handler(eventData));
787
830
  }
788
831
  }
832
+ /**
833
+ * Checks if the current zoom level is within the visibility range.
834
+ */
835
+ _checkZoomVisibility() {
836
+ if (!this._map) return;
837
+ const zoom = this._map.getZoom();
838
+ const { minzoom, maxzoom } = this._options;
839
+ const inRange = zoom >= minzoom && zoom <= maxzoom;
840
+ if (inRange !== this._zoomVisible) {
841
+ this._zoomVisible = inRange;
842
+ this._updateDisplayState();
843
+ }
844
+ }
845
+ /**
846
+ * Updates the display state based on visibility and zoom level.
847
+ */
848
+ _updateDisplayState() {
849
+ if (!this._container) return;
850
+ const shouldShow = this._state.visible && this._zoomVisible;
851
+ this._container.style.display = shouldShow ? "block" : "none";
852
+ }
789
853
  /**
790
854
  * Creates the main container element.
791
855
  *
@@ -794,7 +858,8 @@ class Legend {
794
858
  _createContainer() {
795
859
  const container = document.createElement("div");
796
860
  container.className = `maplibregl-ctrl maplibre-gl-legend${this._options.className ? ` ${this._options.className}` : ""}`;
797
- if (!this._state.visible) {
861
+ const shouldShow = this._state.visible && this._zoomVisible;
862
+ if (!shouldShow) {
798
863
  container.style.display = "none";
799
864
  }
800
865
  return container;
@@ -877,6 +942,7 @@ class Legend {
877
942
  this._container.innerHTML = "";
878
943
  const isCollapsedWithHeader = this._state.collapsed && (title || collapsible);
879
944
  const vertPadding = isCollapsedWithHeader ? 4 : padding;
945
+ const shouldShow = this._state.visible && this._zoomVisible;
880
946
  Object.assign(this._container.style, {
881
947
  backgroundColor,
882
948
  opacity: opacity.toString(),
@@ -887,7 +953,7 @@ class Legend {
887
953
  width: isCollapsedWithHeader ? "auto" : `${width}px`,
888
954
  maxWidth: `${width}px`,
889
955
  boxShadow: "0 0 0 2px rgba(0, 0, 0, 0.1)",
890
- display: this._state.visible ? "block" : "none",
956
+ display: shouldShow ? "block" : "none",
891
957
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
892
958
  });
893
959
  if (title || collapsible) {
@@ -960,7 +1026,9 @@ const DEFAULT_OPTIONS = {
960
1026
  maxWidth: 300,
961
1027
  maxHeight: 400,
962
1028
  fontSize: 12,
963
- fontColor: "#333"
1029
+ fontColor: "#333",
1030
+ minzoom: 0,
1031
+ maxzoom: 24
964
1032
  };
965
1033
  class HtmlControl {
966
1034
  /**
@@ -974,6 +1042,9 @@ class HtmlControl {
974
1042
  __publicField(this, "_options");
975
1043
  __publicField(this, "_state");
976
1044
  __publicField(this, "_eventHandlers", /* @__PURE__ */ new Map());
1045
+ __publicField(this, "_map");
1046
+ __publicField(this, "_handleZoom");
1047
+ __publicField(this, "_zoomVisible", true);
977
1048
  this._options = { ...DEFAULT_OPTIONS, ...options };
978
1049
  this._state = {
979
1050
  visible: this._options.visible,
@@ -988,9 +1059,13 @@ class HtmlControl {
988
1059
  * @param map - The MapLibre GL map instance.
989
1060
  * @returns The control's container element.
990
1061
  */
991
- onAdd(_map) {
1062
+ onAdd(map) {
1063
+ this._map = map;
992
1064
  this._container = this._createContainer();
993
1065
  this._render();
1066
+ this._handleZoom = () => this._checkZoomVisibility();
1067
+ this._map.on("zoom", this._handleZoom);
1068
+ this._checkZoomVisibility();
994
1069
  return this._container;
995
1070
  }
996
1071
  /**
@@ -999,6 +1074,11 @@ class HtmlControl {
999
1074
  */
1000
1075
  onRemove() {
1001
1076
  var _a, _b;
1077
+ if (this._map && this._handleZoom) {
1078
+ this._map.off("zoom", this._handleZoom);
1079
+ this._handleZoom = void 0;
1080
+ }
1081
+ this._map = void 0;
1002
1082
  (_b = (_a = this._container) == null ? void 0 : _a.parentNode) == null ? void 0 : _b.removeChild(this._container);
1003
1083
  this._container = void 0;
1004
1084
  this._contentEl = void 0;
@@ -1010,9 +1090,7 @@ class HtmlControl {
1010
1090
  show() {
1011
1091
  if (!this._state.visible) {
1012
1092
  this._state.visible = true;
1013
- if (this._container) {
1014
- this._container.style.display = "block";
1015
- }
1093
+ this._updateDisplayState();
1016
1094
  this._emit("show");
1017
1095
  }
1018
1096
  }
@@ -1022,9 +1100,7 @@ class HtmlControl {
1022
1100
  hide() {
1023
1101
  if (this._state.visible) {
1024
1102
  this._state.visible = false;
1025
- if (this._container) {
1026
- this._container.style.display = "none";
1027
- }
1103
+ this._updateDisplayState();
1028
1104
  this._emit("hide");
1029
1105
  }
1030
1106
  }
@@ -1150,6 +1226,27 @@ class HtmlControl {
1150
1226
  handlers.forEach((handler) => handler(eventData));
1151
1227
  }
1152
1228
  }
1229
+ /**
1230
+ * Checks if the current zoom level is within the visibility range.
1231
+ */
1232
+ _checkZoomVisibility() {
1233
+ if (!this._map) return;
1234
+ const zoom = this._map.getZoom();
1235
+ const { minzoom, maxzoom } = this._options;
1236
+ const inRange = zoom >= minzoom && zoom <= maxzoom;
1237
+ if (inRange !== this._zoomVisible) {
1238
+ this._zoomVisible = inRange;
1239
+ this._updateDisplayState();
1240
+ }
1241
+ }
1242
+ /**
1243
+ * Updates the display state based on visibility and zoom level.
1244
+ */
1245
+ _updateDisplayState() {
1246
+ if (!this._container) return;
1247
+ const shouldShow = this._state.visible && this._zoomVisible;
1248
+ this._container.style.display = shouldShow ? "block" : "none";
1249
+ }
1153
1250
  /**
1154
1251
  * Creates the main container element.
1155
1252
  *
@@ -1158,7 +1255,8 @@ class HtmlControl {
1158
1255
  _createContainer() {
1159
1256
  const container = document.createElement("div");
1160
1257
  container.className = `maplibregl-ctrl maplibre-gl-html-control${this._options.className ? ` ${this._options.className}` : ""}`;
1161
- if (!this._state.visible) {
1258
+ const shouldShow = this._state.visible && this._zoomVisible;
1259
+ if (!shouldShow) {
1162
1260
  container.style.display = "none";
1163
1261
  }
1164
1262
  return container;
@@ -1183,6 +1281,7 @@ class HtmlControl {
1183
1281
  this._container.innerHTML = "";
1184
1282
  const isCollapsedWithHeader = this._state.collapsed && (title || collapsible);
1185
1283
  const vertPadding = isCollapsedWithHeader ? 4 : padding;
1284
+ const shouldShow = this._state.visible && this._zoomVisible;
1186
1285
  Object.assign(this._container.style, {
1187
1286
  backgroundColor,
1188
1287
  opacity: String(opacity),
@@ -1192,7 +1291,7 @@ class HtmlControl {
1192
1291
  fontSize: `${fontSize}px`,
1193
1292
  color: fontColor,
1194
1293
  boxShadow: "0 0 0 2px rgba(0, 0, 0, 0.1)",
1195
- display: this._state.visible ? "block" : "none",
1294
+ display: shouldShow ? "block" : "none",
1196
1295
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
1197
1296
  });
1198
1297
  if (collapsible || title) {
@@ -1278,4 +1377,4 @@ export {
1278
1377
  clamp as y,
1279
1378
  formatNumericValue as z
1280
1379
  };
1281
- //# sourceMappingURL=HtmlControl-mwWlMV6V.js.map
1380
+ //# sourceMappingURL=HtmlControl-CxD6T9bG.js.map