maplibre-gl-lidar 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/{LidarLayerAdapter-Sw_plY5a.cjs → LidarLayerAdapter-eh59KEMT.cjs} +1347 -152
  2. package/dist/LidarLayerAdapter-eh59KEMT.cjs.map +1 -0
  3. package/dist/{LidarLayerAdapter-mP-0IyJm.js → LidarLayerAdapter-kyyfQw05.js} +1356 -161
  4. package/dist/LidarLayerAdapter-kyyfQw05.js.map +1 -0
  5. package/dist/index.cjs +5 -1
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.mjs +14 -10
  8. package/dist/maplibre-gl-lidar.css +119 -0
  9. package/dist/react.cjs +8 -1
  10. package/dist/react.cjs.map +1 -1
  11. package/dist/react.mjs +10 -3
  12. package/dist/react.mjs.map +1 -1
  13. package/dist/types/index.d.ts +2 -1
  14. package/dist/types/index.d.ts.map +1 -1
  15. package/dist/types/lib/colorizers/ColorScheme.d.ts +75 -6
  16. package/dist/types/lib/colorizers/ColorScheme.d.ts.map +1 -1
  17. package/dist/types/lib/colorizers/Colormaps.d.ts +25 -0
  18. package/dist/types/lib/colorizers/Colormaps.d.ts.map +1 -0
  19. package/dist/types/lib/core/LidarControl.d.ts +43 -1
  20. package/dist/types/lib/core/LidarControl.d.ts.map +1 -1
  21. package/dist/types/lib/core/types.d.ts +46 -0
  22. package/dist/types/lib/core/types.d.ts.map +1 -1
  23. package/dist/types/lib/gui/Colorbar.d.ts +86 -0
  24. package/dist/types/lib/gui/Colorbar.d.ts.map +1 -0
  25. package/dist/types/lib/gui/DualRangeSlider.d.ts +9 -0
  26. package/dist/types/lib/gui/DualRangeSlider.d.ts.map +1 -1
  27. package/dist/types/lib/gui/PanelBuilder.d.ts +31 -5
  28. package/dist/types/lib/gui/PanelBuilder.d.ts.map +1 -1
  29. package/dist/types/lib/gui/PercentileRangeControl.d.ts +113 -0
  30. package/dist/types/lib/gui/PercentileRangeControl.d.ts.map +1 -0
  31. package/dist/types/lib/gui/index.d.ts +4 -0
  32. package/dist/types/lib/gui/index.d.ts.map +1 -1
  33. package/dist/types/lib/hooks/useLidarState.d.ts.map +1 -1
  34. package/dist/types/lib/layers/PointCloudManager.d.ts +22 -1
  35. package/dist/types/lib/layers/PointCloudManager.d.ts.map +1 -1
  36. package/dist/types/lib/layers/types.d.ts +11 -1
  37. package/dist/types/lib/layers/types.d.ts.map +1 -1
  38. package/dist/types/lib/loaders/CopcStreamingLoader.d.ts.map +1 -1
  39. package/dist/types/lib/loaders/EptStreamingLoader.d.ts +44 -0
  40. package/dist/types/lib/loaders/EptStreamingLoader.d.ts.map +1 -1
  41. package/dist/types/lib/loaders/streaming-types.d.ts +6 -0
  42. package/dist/types/lib/loaders/streaming-types.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/dist/LidarLayerAdapter-Sw_plY5a.cjs.map +0 -1
  45. package/dist/LidarLayerAdapter-mP-0IyJm.js.map +0 -1
@@ -34361,10 +34361,12 @@ function clampLatLng$1(lng, lat, context = "") {
34361
34361
  }
34362
34362
  const DEFAULT_OPTIONS$2 = {
34363
34363
  pointBudget: 5e6,
34364
- maxConcurrentRequests: 4,
34365
- viewportDebounceMs: 150,
34364
+ maxConcurrentRequests: 8,
34365
+ viewportDebounceMs: 100,
34366
34366
  minDetailZoom: 10,
34367
- maxOctreeDepth: 20
34367
+ maxOctreeDepth: 20,
34368
+ maxSubtreesPerViewport: 60
34369
+ // Not used by COPC, but required by interface
34368
34370
  };
34369
34371
  class CopcStreamingLoader {
34370
34372
  /**
@@ -35104,10 +35106,11 @@ function getVerticalUnitConversionFactor(wkt2) {
35104
35106
  }
35105
35107
  const DEFAULT_OPTIONS$1 = {
35106
35108
  pointBudget: 5e6,
35107
- maxConcurrentRequests: 4,
35108
- viewportDebounceMs: 150,
35109
+ maxConcurrentRequests: 8,
35110
+ viewportDebounceMs: 100,
35109
35111
  minDetailZoom: 10,
35110
- maxOctreeDepth: 20
35112
+ maxOctreeDepth: 20,
35113
+ maxSubtreesPerViewport: 60
35111
35114
  };
35112
35115
  class EptStreamingLoader {
35113
35116
  /**
@@ -35122,6 +35125,8 @@ class EptStreamingLoader {
35122
35125
  __publicField(this, "_metadata", null);
35123
35126
  // Hierarchy cache
35124
35127
  __publicField(this, "_hierarchyCache", /* @__PURE__ */ new Map());
35128
+ __publicField(this, "_hierarchyLoading", /* @__PURE__ */ new Set());
35129
+ __publicField(this, "_hierarchyFailures", /* @__PURE__ */ new Map());
35125
35130
  __publicField(this, "_subtreeRoots", /* @__PURE__ */ new Set());
35126
35131
  __publicField(this, "_rootHierarchyLoaded", false);
35127
35132
  // Node cache
@@ -35159,6 +35164,7 @@ class EptStreamingLoader {
35159
35164
  __publicField(this, "_pendingLayerUpdate", false);
35160
35165
  __publicField(this, "_updateBatchTimeout", null);
35161
35166
  __publicField(this, "_onPointsLoaded");
35167
+ __publicField(this, "_isResetting", false);
35162
35168
  this._baseUrl = eptUrl.endsWith("/ept.json") ? eptUrl.slice(0, -9) : eptUrl.replace(/\/$/, "");
35163
35169
  this._options = { ...DEFAULT_OPTIONS$1, ...options };
35164
35170
  }
@@ -35418,22 +35424,26 @@ class EptStreamingLoader {
35418
35424
  * @param key - Hierarchy key (e.g., "0-0-0-0")
35419
35425
  */
35420
35426
  async _loadHierarchy(key) {
35421
- if (this._hierarchyCache.has(key)) return;
35427
+ if (this._hierarchyCache.has(key) || this._hierarchyLoading.has(key)) return;
35422
35428
  const url = `${this._baseUrl}/ept-hierarchy/${key}.json`;
35429
+ this._hierarchyLoading.add(key);
35423
35430
  try {
35424
35431
  const response = await fetch(url);
35425
35432
  if (!response.ok) {
35433
+ this._hierarchyFailures.set(key, Date.now());
35426
35434
  console.warn(`Failed to load hierarchy ${key}: ${response.status}`);
35427
35435
  return;
35428
35436
  }
35429
35437
  const hierarchy2 = await response.json();
35430
35438
  this._hierarchyCache.set(key, hierarchy2);
35439
+ this._hierarchyFailures.delete(key);
35431
35440
  for (const [nodeKey, value] of Object.entries(hierarchy2)) {
35432
35441
  const keyArray = this._parseNodeKey(nodeKey);
35433
35442
  const { bounds: bounds2, boundsWgs84 } = this._calculateNodeBounds(keyArray);
35443
+ const existingNode = this._nodeCache.get(nodeKey);
35434
35444
  if (value === -1) {
35435
35445
  this._subtreeRoots.add(nodeKey);
35436
- if (!this._nodeCache.has(nodeKey)) {
35446
+ if (!existingNode) {
35437
35447
  this._nodeCache.set(nodeKey, {
35438
35448
  key: nodeKey,
35439
35449
  keyArray,
@@ -35444,19 +35454,29 @@ class EptStreamingLoader {
35444
35454
  boundsWgs84
35445
35455
  });
35446
35456
  }
35447
- } else if (value > 0 && !this._nodeCache.has(nodeKey)) {
35448
- this._nodeCache.set(nodeKey, {
35449
- key: nodeKey,
35450
- keyArray,
35451
- state: "pending",
35452
- pointCount: value,
35453
- bounds: bounds2,
35454
- boundsWgs84
35455
- });
35457
+ } else if (value > 0) {
35458
+ if ((existingNode == null ? void 0 : existingNode.state) === "subtree") {
35459
+ existingNode.state = "pending";
35460
+ existingNode.pointCount = value;
35461
+ existingNode.bounds = bounds2;
35462
+ existingNode.boundsWgs84 = boundsWgs84;
35463
+ } else if (!existingNode) {
35464
+ this._nodeCache.set(nodeKey, {
35465
+ key: nodeKey,
35466
+ keyArray,
35467
+ state: "pending",
35468
+ pointCount: value,
35469
+ bounds: bounds2,
35470
+ boundsWgs84
35471
+ });
35472
+ }
35456
35473
  }
35457
35474
  }
35458
35475
  } catch (error) {
35476
+ this._hierarchyFailures.set(key, Date.now());
35459
35477
  console.warn(`Error loading hierarchy ${key}:`, error);
35478
+ } finally {
35479
+ this._hierarchyLoading.delete(key);
35460
35480
  }
35461
35481
  }
35462
35482
  /**
@@ -35481,22 +35501,31 @@ class EptStreamingLoader {
35481
35501
  }
35482
35502
  await this._ensureHierarchyLoaded();
35483
35503
  const targetDepth = viewport.targetDepth;
35484
- const subtreesToLoad = [];
35485
- for (const [, node] of this._nodeCache) {
35486
- const depth = node.keyArray[0];
35487
- if (depth > targetDepth + 2) continue;
35488
- if (node.state === "subtree" && this._boundsIntersectsViewport(node.boundsWgs84, viewport)) {
35489
- if (!this._hierarchyCache.has(node.key)) {
35490
- subtreesToLoad.push(node.key);
35491
- }
35492
- }
35493
- }
35494
- const maxSubtreesToLoad = 10;
35495
- for (const subtreeKey of subtreesToLoad.slice(0, maxSubtreesToLoad)) {
35496
- await this._loadHierarchy(subtreeKey);
35504
+ const maxSubtreesToLoad = Math.max(1, this._options.maxSubtreesPerViewport);
35505
+ const loadedSubtrees = /* @__PURE__ */ new Set();
35506
+ const maxPasses = 3;
35507
+ const now = Date.now();
35508
+ const hierarchyRetryCooldownMs = 5e3;
35509
+ for (let pass = 0; pass < maxPasses; pass++) {
35510
+ const subtreeCandidates = [];
35511
+ for (const [, node] of this._nodeCache) {
35512
+ const depth = node.keyArray[0];
35513
+ if (depth > targetDepth + 3) continue;
35514
+ const lastFailure = this._hierarchyFailures.get(node.key);
35515
+ if (node.state === "subtree" && !this._hierarchyCache.has(node.key) && !this._hierarchyLoading.has(node.key) && !loadedSubtrees.has(node.key) && (!lastFailure || now - lastFailure >= hierarchyRetryCooldownMs) && this._boundsIntersectsViewport(node.boundsWgs84, viewport)) {
35516
+ const priority = this._calculateNodePriority(node.boundsWgs84, viewport);
35517
+ subtreeCandidates.push({ key: node.key, priority });
35518
+ }
35519
+ }
35520
+ if (subtreeCandidates.length === 0) break;
35521
+ subtreeCandidates.sort((a, b) => a.priority - b.priority);
35522
+ const perPassLimit = Math.ceil(maxSubtreesToLoad / maxPasses);
35523
+ const subtreesToProcess = subtreeCandidates.slice(0, perPassLimit).map((s) => s.key);
35524
+ await Promise.all(subtreesToProcess.map((subtreeKey) => this._loadHierarchy(subtreeKey)));
35525
+ subtreesToProcess.forEach((key) => loadedSubtrees.add(key));
35526
+ if (loadedSubtrees.size >= maxSubtreesToLoad) break;
35497
35527
  }
35498
35528
  const nodesToLoad = [];
35499
- const now = Date.now();
35500
35529
  const retryCooldownMs = 5e3;
35501
35530
  for (const [, node] of this._nodeCache) {
35502
35531
  const depth = node.keyArray[0];
@@ -35505,7 +35534,7 @@ class EptStreamingLoader {
35505
35534
  if (node.lastFailedAt && now - node.lastFailedAt < retryCooldownMs) {
35506
35535
  continue;
35507
35536
  }
35508
- if (depth > targetDepth + 1) continue;
35537
+ if (depth > targetDepth + 2) continue;
35509
35538
  if (!this._boundsIntersectsViewport(node.boundsWgs84, viewport)) {
35510
35539
  continue;
35511
35540
  }
@@ -35826,6 +35855,27 @@ class EptStreamingLoader {
35826
35855
  wkt: (_c = (_b = this._metadata) == null ? void 0 : _b.srs) == null ? void 0 : _c.wkt
35827
35856
  };
35828
35857
  }
35858
+ /**
35859
+ * Checks whether there are subtree hierarchies still pending for the viewport.
35860
+ *
35861
+ * @param viewport - Current viewport information
35862
+ * @returns True if more subtree hierarchies should be loaded
35863
+ */
35864
+ hasPendingSubtrees(viewport) {
35865
+ if (!this._isInitialized) return false;
35866
+ const targetDepth = viewport.targetDepth;
35867
+ const now = Date.now();
35868
+ const hierarchyRetryCooldownMs = 5e3;
35869
+ for (const [, node] of this._nodeCache) {
35870
+ const depth = node.keyArray[0];
35871
+ if (depth > targetDepth + 3) continue;
35872
+ const lastFailure = this._hierarchyFailures.get(node.key);
35873
+ if (node.state === "subtree" && !this._hierarchyCache.has(node.key) && !this._hierarchyLoading.has(node.key) && (!lastFailure || now - lastFailure >= hierarchyRetryCooldownMs) && this._boundsIntersectsViewport(node.boundsWgs84, viewport)) {
35874
+ return true;
35875
+ }
35876
+ }
35877
+ return false;
35878
+ }
35829
35879
  /**
35830
35880
  * Gets the current streaming progress.
35831
35881
  */
@@ -35881,6 +35931,60 @@ class EptStreamingLoader {
35881
35931
  getLoadedPointCount() {
35882
35932
  return this._totalLoadedPoints;
35883
35933
  }
35934
+ /**
35935
+ * Gets the current point budget.
35936
+ */
35937
+ getPointBudget() {
35938
+ return this._options.pointBudget;
35939
+ }
35940
+ /**
35941
+ * Checks if any nodes intersecting the viewport have already been loaded.
35942
+ *
35943
+ * @param viewport - Current viewport information
35944
+ * @returns True if viewport has loaded coverage
35945
+ */
35946
+ hasLoadedNodesInViewport(viewport, minDepth = 0) {
35947
+ for (const [, node] of this._nodeCache) {
35948
+ if (node.state !== "loaded") continue;
35949
+ if (node.keyArray[0] < minDepth) continue;
35950
+ if (this._boundsIntersectsViewport(node.boundsWgs84, viewport)) {
35951
+ return true;
35952
+ }
35953
+ }
35954
+ return false;
35955
+ }
35956
+ /**
35957
+ * Estimates viewport coverage ratio by loaded nodes.
35958
+ * Returns a value from 0 to 1 representing how much of the viewport
35959
+ * is covered by loaded tiles.
35960
+ *
35961
+ * @param viewport - Current viewport information
35962
+ * @param minDepth - Minimum octree depth to consider
35963
+ * @returns Coverage ratio (0-1)
35964
+ */
35965
+ getViewportCoverageRatio(viewport, minDepth = 0) {
35966
+ const [west, south, east, north] = viewport.bounds;
35967
+ const viewportArea = (east - west) * (north - south);
35968
+ if (viewportArea <= 0) return 0;
35969
+ let coveredArea = 0;
35970
+ for (const [, node] of this._nodeCache) {
35971
+ if (node.state !== "loaded") continue;
35972
+ if (node.keyArray[0] < minDepth) continue;
35973
+ const nodeWest = node.boundsWgs84.minX;
35974
+ const nodeSouth = node.boundsWgs84.minY;
35975
+ const nodeEast = node.boundsWgs84.maxX;
35976
+ const nodeNorth = node.boundsWgs84.maxY;
35977
+ const intersectWest = Math.max(west, nodeWest);
35978
+ const intersectEast = Math.min(east, nodeEast);
35979
+ const intersectSouth = Math.max(south, nodeSouth);
35980
+ const intersectNorth = Math.min(north, nodeNorth);
35981
+ if (intersectWest < intersectEast && intersectSouth < intersectNorth) {
35982
+ const intersectArea = (intersectEast - intersectWest) * (intersectNorth - intersectSouth);
35983
+ coveredArea += intersectArea;
35984
+ }
35985
+ }
35986
+ return Math.min(1, coveredArea / viewportArea);
35987
+ }
35884
35988
  /**
35885
35989
  * Gets the total number of loaded nodes.
35886
35990
  */
@@ -35893,6 +35997,48 @@ class EptStreamingLoader {
35893
35997
  isLoading() {
35894
35998
  return this._activeRequests > 0 || this._loadingQueue.length > 0;
35895
35999
  }
36000
+ /**
36001
+ * Removes queued nodes that are outside the current viewport and re-sorts priorities.
36002
+ *
36003
+ * @param viewport - Current viewport information
36004
+ */
36005
+ pruneQueueForViewport(viewport) {
36006
+ if (this._loadingQueue.length === 0) return;
36007
+ this._loadingQueue = this._loadingQueue.filter(
36008
+ (node) => this._boundsIntersectsViewport(node.boundsWgs84, viewport)
36009
+ );
36010
+ for (const node of this._loadingQueue) {
36011
+ const distPriority = this._calculateNodePriority(node.boundsWgs84, viewport);
36012
+ const depth = node.keyArray[0];
36013
+ node.priority = distPriority - depth * 1e-4;
36014
+ }
36015
+ this._loadingQueue.sort((a, b) => (a.priority || Infinity) - (b.priority || Infinity));
36016
+ }
36017
+ /**
36018
+ * Resets loaded node data to allow loading a new area.
36019
+ * Keeps hierarchy cache intact but clears loaded points and node states.
36020
+ *
36021
+ * @returns True if reset occurred
36022
+ */
36023
+ resetLoadedData() {
36024
+ if (this._activeRequests > 0 || this._isResetting) return false;
36025
+ this._isResetting = true;
36026
+ this._loadingQueue = [];
36027
+ this._totalLoadedPoints = 0;
36028
+ this._totalLoadedNodes = 0;
36029
+ for (const [, node] of this._nodeCache) {
36030
+ if (node.state === "loaded" || node.state === "loading" || node.state === "error") {
36031
+ node.state = "pending";
36032
+ node.bufferStartIndex = void 0;
36033
+ node.error = void 0;
36034
+ node.retryCount = void 0;
36035
+ node.lastFailedAt = void 0;
36036
+ }
36037
+ }
36038
+ this._scheduleLayerUpdate();
36039
+ this._isResetting = false;
36040
+ return true;
36041
+ }
35896
36042
  /**
35897
36043
  * Gets the EPT metadata.
35898
36044
  */
@@ -35909,6 +36055,8 @@ class EptStreamingLoader {
35909
36055
  this._loadingQueue = [];
35910
36056
  this._nodeCache.clear();
35911
36057
  this._hierarchyCache.clear();
36058
+ this._hierarchyLoading.clear();
36059
+ this._hierarchyFailures.clear();
35912
36060
  this._subtreeRoots.clear();
35913
36061
  this._eventHandlers.clear();
35914
36062
  this._positions = null;
@@ -36006,37 +36154,180 @@ function computePercentileBounds(arr, lowerPercentile = 2, upperPercentile = 98)
36006
36154
  }
36007
36155
  return { min, max };
36008
36156
  }
36009
- const ELEVATION_RAMP = [
36157
+ const VIRIDIS = [
36010
36158
  [68, 1, 84],
36011
- // dark purple
36012
36159
  [72, 40, 120],
36013
- // purple
36014
36160
  [62, 74, 137],
36015
- // blue-purple
36016
36161
  [49, 104, 142],
36017
- // blue
36018
36162
  [38, 130, 142],
36019
- // teal-blue
36020
36163
  [31, 158, 137],
36021
- // teal
36022
36164
  [53, 183, 121],
36023
- // green-teal
36024
36165
  [109, 205, 89],
36025
- // green
36026
36166
  [180, 222, 44],
36027
- // yellow-green
36028
36167
  [253, 231, 37]
36029
- // yellow
36030
36168
  ];
36031
- const INTENSITY_RAMP = [
36169
+ const PLASMA = [
36170
+ [13, 8, 135],
36171
+ [75, 3, 161],
36172
+ [125, 3, 168],
36173
+ [168, 34, 150],
36174
+ [203, 70, 121],
36175
+ [229, 107, 93],
36176
+ [248, 148, 65],
36177
+ [253, 195, 40],
36178
+ [240, 249, 33],
36179
+ [240, 249, 33]
36180
+ ];
36181
+ const INFERNO = [
36182
+ [0, 0, 4],
36183
+ [40, 11, 84],
36184
+ [89, 13, 115],
36185
+ [137, 31, 107],
36186
+ [179, 55, 79],
36187
+ [213, 87, 49],
36188
+ [240, 130, 24],
36189
+ [253, 184, 43],
36190
+ [249, 251, 146],
36191
+ [252, 255, 164]
36192
+ ];
36193
+ const MAGMA = [
36194
+ [0, 0, 4],
36195
+ [28, 16, 68],
36196
+ [79, 18, 123],
36197
+ [129, 37, 129],
36198
+ [181, 54, 122],
36199
+ [229, 80, 100],
36200
+ [251, 135, 97],
36201
+ [254, 194, 135],
36202
+ [254, 247, 187],
36203
+ [252, 253, 191]
36204
+ ];
36205
+ const CIVIDIS = [
36206
+ [0, 32, 77],
36207
+ [0, 58, 103],
36208
+ [52, 77, 105],
36209
+ [87, 95, 108],
36210
+ [115, 113, 112],
36211
+ [143, 132, 108],
36212
+ [171, 152, 97],
36213
+ [200, 173, 79],
36214
+ [231, 196, 55],
36215
+ [253, 231, 37]
36216
+ ];
36217
+ const TURBO = [
36218
+ [48, 18, 59],
36219
+ [70, 107, 227],
36220
+ [40, 170, 225],
36221
+ [35, 221, 162],
36222
+ [122, 249, 85],
36223
+ [194, 241, 45],
36224
+ [241, 206, 51],
36225
+ [250, 144, 42],
36226
+ [229, 68, 25],
36227
+ [122, 4, 3]
36228
+ ];
36229
+ const JET = [
36230
+ [0, 0, 127],
36231
+ [0, 0, 255],
36232
+ [0, 127, 255],
36233
+ [0, 255, 255],
36234
+ [127, 255, 127],
36235
+ [255, 255, 0],
36236
+ [255, 127, 0],
36237
+ [255, 0, 0],
36238
+ [127, 0, 0],
36239
+ [127, 0, 0]
36240
+ ];
36241
+ const RAINBOW = [
36242
+ [150, 0, 90],
36243
+ [0, 0, 200],
36244
+ [0, 125, 255],
36245
+ [0, 200, 255],
36246
+ [0, 255, 125],
36247
+ [125, 255, 0],
36248
+ [255, 255, 0],
36249
+ [255, 125, 0],
36250
+ [255, 0, 0],
36251
+ [128, 0, 0]
36252
+ ];
36253
+ const TERRAIN = [
36254
+ [51, 51, 153],
36255
+ [51, 102, 153],
36256
+ [51, 153, 153],
36257
+ [102, 178, 102],
36258
+ [153, 204, 102],
36259
+ [204, 229, 102],
36260
+ [204, 204, 153],
36261
+ [178, 153, 102],
36262
+ [153, 102, 51],
36263
+ [255, 255, 255]
36264
+ ];
36265
+ const COOLWARM = [
36266
+ [59, 76, 192],
36267
+ [98, 130, 234],
36268
+ [141, 176, 254],
36269
+ [184, 208, 249],
36270
+ [221, 221, 221],
36271
+ [245, 196, 173],
36272
+ [244, 154, 123],
36273
+ [222, 96, 77],
36274
+ [180, 4, 38],
36275
+ [180, 4, 38]
36276
+ ];
36277
+ const GRAY = [
36032
36278
  [0, 0, 0],
36033
- // black
36034
- [64, 64, 64],
36035
- [128, 128, 128],
36036
- [192, 192, 192],
36279
+ [28, 28, 28],
36280
+ [57, 57, 57],
36281
+ [85, 85, 85],
36282
+ [113, 113, 113],
36283
+ [142, 142, 142],
36284
+ [170, 170, 170],
36285
+ [198, 198, 198],
36286
+ [227, 227, 227],
36037
36287
  [255, 255, 255]
36038
- // white
36039
36288
  ];
36289
+ const COLORMAPS = {
36290
+ viridis: VIRIDIS,
36291
+ plasma: PLASMA,
36292
+ inferno: INFERNO,
36293
+ magma: MAGMA,
36294
+ cividis: CIVIDIS,
36295
+ turbo: TURBO,
36296
+ jet: JET,
36297
+ rainbow: RAINBOW,
36298
+ terrain: TERRAIN,
36299
+ coolwarm: COOLWARM,
36300
+ gray: GRAY
36301
+ };
36302
+ const COLORMAP_NAMES = [
36303
+ "viridis",
36304
+ "plasma",
36305
+ "inferno",
36306
+ "magma",
36307
+ "cividis",
36308
+ "turbo",
36309
+ "jet",
36310
+ "rainbow",
36311
+ "terrain",
36312
+ "coolwarm",
36313
+ "gray"
36314
+ ];
36315
+ const COLORMAP_LABELS = {
36316
+ viridis: "Viridis",
36317
+ plasma: "Plasma",
36318
+ inferno: "Inferno",
36319
+ magma: "Magma",
36320
+ cividis: "Cividis",
36321
+ turbo: "Turbo",
36322
+ jet: "Jet",
36323
+ rainbow: "Rainbow",
36324
+ terrain: "Terrain",
36325
+ coolwarm: "Cool-Warm",
36326
+ gray: "Grayscale"
36327
+ };
36328
+ function getColormap(name) {
36329
+ return COLORMAPS[name] || COLORMAPS.viridis;
36330
+ }
36040
36331
  const CLASSIFICATION_COLORS = {
36041
36332
  0: [128, 128, 128],
36042
36333
  // Created, never classified
@@ -36078,6 +36369,10 @@ const CLASSIFICATION_COLORS = {
36078
36369
  // High Noise
36079
36370
  };
36080
36371
  class ColorSchemeProcessor {
36372
+ constructor() {
36373
+ /** Last computed color bounds (for colorbar display) */
36374
+ __publicField(this, "_lastComputedBounds");
36375
+ }
36081
36376
  /**
36082
36377
  * Generates a color array for the point cloud based on the color scheme.
36083
36378
  *
@@ -36087,100 +36382,150 @@ class ColorSchemeProcessor {
36087
36382
  * @returns Uint8Array of RGBA colors (length = pointCount * 4)
36088
36383
  */
36089
36384
  getColors(data, scheme, options = {}) {
36385
+ const result = this.getColorsWithBounds(data, scheme, options);
36386
+ return result.colors;
36387
+ }
36388
+ /**
36389
+ * Generates a color array and returns the computed bounds.
36390
+ *
36391
+ * @param data - Point cloud data
36392
+ * @param scheme - Color scheme to apply
36393
+ * @param options - Optional color generation options
36394
+ * @returns ColorResult containing colors and computed bounds
36395
+ */
36396
+ getColorsWithBounds(data, scheme, options = {}) {
36090
36397
  const colors = new Uint8Array(data.pointCount * 4);
36398
+ const colormap = options.colormap ?? "viridis";
36399
+ const colorRange = options.colorRange;
36091
36400
  const usePercentile = options.usePercentile ?? true;
36092
36401
  if (typeof scheme === "string") {
36093
36402
  switch (scheme) {
36094
36403
  case "elevation":
36095
- return this._colorByElevation(data, colors, usePercentile);
36404
+ return this._colorByElevation(data, colors, colormap, colorRange, usePercentile);
36096
36405
  case "intensity":
36097
- return this._colorByIntensity(data, colors, usePercentile);
36406
+ return this._colorByIntensity(data, colors, colormap, colorRange, usePercentile);
36098
36407
  case "classification":
36099
- return this._colorByClassification(data, colors, options.hiddenClassifications);
36408
+ return { colors: this._colorByClassification(data, colors, options.hiddenClassifications) };
36100
36409
  case "rgb":
36101
- return this._colorByRGB(data, colors);
36410
+ return { colors: this._colorByRGB(data, colors) };
36102
36411
  default:
36103
- return this._colorByElevation(data, colors, usePercentile);
36412
+ return this._colorByElevation(data, colors, colormap, colorRange, usePercentile);
36413
+ }
36414
+ } else {
36415
+ return { colors: this._colorByCustom(data, colors, scheme, colormap, colorRange, usePercentile) };
36416
+ }
36417
+ }
36418
+ /**
36419
+ * Gets the last computed color bounds (for colorbar display).
36420
+ *
36421
+ * @returns The last computed bounds or undefined
36422
+ */
36423
+ getLastComputedBounds() {
36424
+ return this._lastComputedBounds;
36425
+ }
36426
+ /**
36427
+ * Computes the color bounds based on the configuration.
36428
+ *
36429
+ * @param values - Array of values to compute bounds for
36430
+ * @param dataBounds - Data bounds (min/max)
36431
+ * @param colorRange - Color range configuration
36432
+ * @param usePercentile - Legacy percentile flag
36433
+ * @returns Computed min and max bounds
36434
+ */
36435
+ _computeBounds(values, dataBounds, colorRange, usePercentile) {
36436
+ if (colorRange) {
36437
+ if (colorRange.mode === "absolute") {
36438
+ return {
36439
+ min: colorRange.absoluteMin ?? dataBounds.min,
36440
+ max: colorRange.absoluteMax ?? dataBounds.max
36441
+ };
36442
+ } else {
36443
+ const pLow = colorRange.percentileLow ?? 2;
36444
+ const pHigh = colorRange.percentileHigh ?? 98;
36445
+ return computePercentileBounds(values, pLow, pHigh);
36104
36446
  }
36447
+ } else if (usePercentile) {
36448
+ return computePercentileBounds(values, 2, 98);
36105
36449
  } else {
36106
- return this._colorByCustom(data, colors, scheme);
36450
+ return dataBounds;
36107
36451
  }
36108
36452
  }
36109
36453
  /**
36110
- * Colors points by elevation using terrain-like ramp.
36454
+ * Colors points by elevation using the specified colormap.
36111
36455
  *
36112
36456
  * @param data - Point cloud data
36113
36457
  * @param colors - Output color array
36114
- * @param usePercentile - Whether to use percentile bounds (2-98%) for better color distribution
36458
+ * @param colormap - Colormap name to use
36459
+ * @param colorRange - Color range configuration
36460
+ * @param usePercentile - Legacy percentile flag
36461
+ * @returns ColorResult with colors and computed bounds
36115
36462
  */
36116
- _colorByElevation(data, colors, usePercentile) {
36463
+ _colorByElevation(data, colors, colormap, colorRange, usePercentile) {
36117
36464
  var _a, _b;
36118
36465
  if (!data.positions || data.positions.length === 0) {
36119
- return colors;
36120
- }
36121
- let minZ;
36122
- let maxZ;
36123
- if (usePercentile) {
36124
- const zValues = new Float32Array(data.pointCount);
36125
- for (let i = 0; i < data.pointCount; i++) {
36126
- zValues[i] = data.positions[i * 3 + 2] ?? 0;
36127
- }
36128
- const bounds2 = computePercentileBounds(zValues, 2, 98);
36129
- minZ = bounds2.min;
36130
- maxZ = bounds2.max;
36131
- } else {
36132
- minZ = ((_a = data.bounds) == null ? void 0 : _a.minZ) ?? 0;
36133
- maxZ = ((_b = data.bounds) == null ? void 0 : _b.maxZ) ?? 1;
36466
+ return { colors };
36134
36467
  }
36468
+ const zValues = new Float32Array(data.pointCount);
36469
+ for (let i = 0; i < data.pointCount; i++) {
36470
+ zValues[i] = data.positions[i * 3 + 2] ?? 0;
36471
+ }
36472
+ const dataBounds = {
36473
+ min: ((_a = data.bounds) == null ? void 0 : _a.minZ) ?? 0,
36474
+ max: ((_b = data.bounds) == null ? void 0 : _b.maxZ) ?? 1
36475
+ };
36476
+ const bounds2 = this._computeBounds(zValues, dataBounds, colorRange, usePercentile);
36477
+ this._lastComputedBounds = bounds2;
36478
+ const { min: minZ, max: maxZ } = bounds2;
36135
36479
  const range = maxZ - minZ || 1;
36480
+ const ramp = COLORMAPS[colormap] || COLORMAPS.viridis;
36136
36481
  for (let i = 0; i < data.pointCount; i++) {
36137
- const z = data.positions[i * 3 + 2] ?? 0;
36482
+ const z = zValues[i];
36138
36483
  const t = (z - minZ) / range;
36139
- const color = this._interpolateRamp(ELEVATION_RAMP, t);
36484
+ const color = this._interpolateRamp(ramp, t);
36140
36485
  colors[i * 4] = color[0];
36141
36486
  colors[i * 4 + 1] = color[1];
36142
36487
  colors[i * 4 + 2] = color[2];
36143
36488
  colors[i * 4 + 3] = 255;
36144
36489
  }
36145
- return colors;
36490
+ return { colors, bounds: bounds2 };
36146
36491
  }
36147
36492
  /**
36148
- * Colors points by intensity using grayscale ramp.
36493
+ * Colors points by intensity using the specified colormap.
36149
36494
  *
36150
36495
  * @param data - Point cloud data
36151
36496
  * @param colors - Output color array
36152
- * @param usePercentile - Whether to use percentile bounds (2-98%) for better color distribution
36497
+ * @param colormap - Colormap name to use
36498
+ * @param colorRange - Color range configuration
36499
+ * @param usePercentile - Legacy percentile flag
36500
+ * @returns ColorResult with colors and computed bounds
36153
36501
  */
36154
- _colorByIntensity(data, colors, usePercentile) {
36502
+ _colorByIntensity(data, colors, colormap, colorRange, usePercentile) {
36155
36503
  if (!data.hasIntensity || !data.intensities) {
36156
- return this._colorByElevation(data, colors, usePercentile);
36157
- }
36158
- let minI;
36159
- let maxI;
36160
- if (usePercentile) {
36161
- const bounds2 = computePercentileBounds(data.intensities, 2, 98);
36162
- minI = bounds2.min;
36163
- maxI = bounds2.max;
36164
- } else {
36165
- minI = Infinity;
36166
- maxI = -Infinity;
36167
- for (let i = 0; i < data.pointCount; i++) {
36168
- const intensity = data.intensities[i];
36169
- if (intensity < minI) minI = intensity;
36170
- if (intensity > maxI) maxI = intensity;
36171
- }
36504
+ return this._colorByElevation(data, colors, colormap, colorRange, usePercentile);
36172
36505
  }
36173
- const range = maxI - minI || 1;
36506
+ let minI = Infinity;
36507
+ let maxI = -Infinity;
36174
36508
  for (let i = 0; i < data.pointCount; i++) {
36175
36509
  const intensity = data.intensities[i];
36176
- const t = (intensity - minI) / range;
36177
- const color = this._interpolateRamp(INTENSITY_RAMP, t);
36510
+ if (intensity < minI) minI = intensity;
36511
+ if (intensity > maxI) maxI = intensity;
36512
+ }
36513
+ const dataBounds = { min: minI, max: maxI };
36514
+ const bounds2 = this._computeBounds(data.intensities, dataBounds, colorRange, usePercentile);
36515
+ this._lastComputedBounds = bounds2;
36516
+ const { min: minVal, max: maxVal } = bounds2;
36517
+ const range = maxVal - minVal || 1;
36518
+ const ramp = COLORMAPS[colormap] || COLORMAPS.gray;
36519
+ for (let i = 0; i < data.pointCount; i++) {
36520
+ const intensity = data.intensities[i];
36521
+ const t = (intensity - minVal) / range;
36522
+ const color = this._interpolateRamp(ramp, t);
36178
36523
  colors[i * 4] = color[0];
36179
36524
  colors[i * 4 + 1] = color[1];
36180
36525
  colors[i * 4 + 2] = color[2];
36181
36526
  colors[i * 4 + 3] = 255;
36182
36527
  }
36183
- return colors;
36528
+ return { colors, bounds: bounds2 };
36184
36529
  }
36185
36530
  /**
36186
36531
  * Colors points by classification using ASPRS standard colors.
@@ -36188,10 +36533,12 @@ class ColorSchemeProcessor {
36188
36533
  * @param data - Point cloud data
36189
36534
  * @param colors - Output color array
36190
36535
  * @param hiddenClassifications - Optional set of classification codes to hide (alpha=0)
36536
+ * @returns Color array
36191
36537
  */
36192
36538
  _colorByClassification(data, colors, hiddenClassifications) {
36193
36539
  if (!data.hasClassification || !data.classifications) {
36194
- return this._colorByElevation(data, colors, true);
36540
+ const result = this._colorByElevation(data, colors, "viridis", void 0, true);
36541
+ return result.colors;
36195
36542
  }
36196
36543
  for (let i = 0; i < data.pointCount; i++) {
36197
36544
  const cls = data.classifications[i];
@@ -36205,10 +36552,15 @@ class ColorSchemeProcessor {
36205
36552
  }
36206
36553
  /**
36207
36554
  * Uses embedded RGB colors from the point cloud.
36555
+ *
36556
+ * @param data - Point cloud data
36557
+ * @param colors - Output color array
36558
+ * @returns Color array
36208
36559
  */
36209
36560
  _colorByRGB(data, colors) {
36210
36561
  if (!data.hasRGB || !data.colors) {
36211
- return this._colorByElevation(data, colors, true);
36562
+ const result = this._colorByElevation(data, colors, "viridis", void 0, true);
36563
+ return result.colors;
36212
36564
  }
36213
36565
  for (let i = 0; i < data.pointCount; i++) {
36214
36566
  colors[i * 4] = data.colors[i * 4];
@@ -36220,12 +36572,25 @@ class ColorSchemeProcessor {
36220
36572
  }
36221
36573
  /**
36222
36574
  * Applies a custom color scheme configuration.
36575
+ *
36576
+ * @param data - Point cloud data
36577
+ * @param colors - Output color array
36578
+ * @param _config - Custom color scheme config
36579
+ * @param colormap - Colormap name to use
36580
+ * @param colorRange - Color range configuration
36581
+ * @param usePercentile - Legacy percentile flag
36582
+ * @returns Color array
36223
36583
  */
36224
- _colorByCustom(data, colors, _config) {
36225
- return this._colorByElevation(data, colors, true);
36584
+ _colorByCustom(data, colors, _config, colormap, colorRange, usePercentile) {
36585
+ const result = this._colorByElevation(data, colors, colormap, colorRange, usePercentile);
36586
+ return result.colors;
36226
36587
  }
36227
36588
  /**
36228
36589
  * Interpolates a color from a color ramp.
36590
+ *
36591
+ * @param ramp - Color ramp array
36592
+ * @param t - Interpolation parameter (0-1)
36593
+ * @returns Interpolated RGB color
36229
36594
  */
36230
36595
  _interpolateRamp(ramp, t) {
36231
36596
  if (!Number.isFinite(t)) {
@@ -36283,6 +36648,7 @@ class PointCloudManager {
36283
36648
  __publicField(this, "_pointClouds");
36284
36649
  __publicField(this, "_options");
36285
36650
  __publicField(this, "_colorProcessor");
36651
+ __publicField(this, "_lastComputedBounds");
36286
36652
  this._deckOverlay = deckOverlay;
36287
36653
  this._pointClouds = /* @__PURE__ */ new Map();
36288
36654
  this._colorProcessor = new ColorSchemeProcessor();
@@ -36291,6 +36657,8 @@ class PointCloudManager {
36291
36657
  opacity: options.opacity ?? 1,
36292
36658
  colorScheme: options.colorScheme ?? "elevation",
36293
36659
  usePercentile: options.usePercentile ?? true,
36660
+ colormap: options.colormap ?? "viridis",
36661
+ colorRange: options.colorRange,
36294
36662
  elevationRange: options.elevationRange ?? null,
36295
36663
  pickable: options.pickable ?? false,
36296
36664
  zOffset: options.zOffset ?? 0,
@@ -36312,15 +36680,20 @@ class PointCloudManager {
36312
36680
  * @param data - Point cloud data (positions are already offsets from coordinateOrigin)
36313
36681
  */
36314
36682
  addPointCloud(id, data) {
36315
- const colors = this._colorProcessor.getColors(data, this._options.colorScheme, {
36683
+ const result = this._colorProcessor.getColorsWithBounds(data, this._options.colorScheme, {
36316
36684
  usePercentile: this._options.usePercentile,
36685
+ colormap: this._options.colormap,
36686
+ colorRange: this._options.colorRange,
36317
36687
  hiddenClassifications: this._options.hiddenClassifications
36318
36688
  });
36689
+ if (result.bounds) {
36690
+ this._lastComputedBounds = result.bounds;
36691
+ }
36319
36692
  const coordinateOrigin = data.coordinateOrigin;
36320
36693
  this._pointClouds.set(id, {
36321
36694
  id,
36322
36695
  data,
36323
- colors,
36696
+ colors: result.colors,
36324
36697
  coordinateOrigin,
36325
36698
  visible: true,
36326
36699
  opacityOverride: null
@@ -36340,14 +36713,19 @@ class PointCloudManager {
36340
36713
  }
36341
36714
  const existing = this._pointClouds.get(id);
36342
36715
  if (existing) {
36343
- const colors = this._colorProcessor.getColors(data, this._options.colorScheme, {
36716
+ const result = this._colorProcessor.getColorsWithBounds(data, this._options.colorScheme, {
36344
36717
  usePercentile: this._options.usePercentile,
36718
+ colormap: this._options.colormap,
36719
+ colorRange: this._options.colorRange,
36345
36720
  hiddenClassifications: this._options.hiddenClassifications
36346
36721
  });
36722
+ if (result.bounds) {
36723
+ this._lastComputedBounds = result.bounds;
36724
+ }
36347
36725
  this._pointClouds.set(id, {
36348
36726
  id,
36349
36727
  data,
36350
- colors,
36728
+ colors: result.colors,
36351
36729
  coordinateOrigin: data.coordinateOrigin,
36352
36730
  visible: existing.visible,
36353
36731
  opacityOverride: existing.opacityOverride
@@ -36418,17 +36796,24 @@ class PointCloudManager {
36418
36796
  updateStyle(options) {
36419
36797
  const colorSchemeChanged = options.colorScheme !== void 0 && options.colorScheme !== this._options.colorScheme;
36420
36798
  const percentileChanged = options.usePercentile !== void 0 && options.usePercentile !== this._options.usePercentile;
36799
+ const colormapChanged = options.colormap !== void 0 && options.colormap !== this._options.colormap;
36800
+ const colorRangeChanged = options.colorRange !== void 0;
36421
36801
  const hiddenClassificationsChanged = options.hiddenClassifications !== void 0;
36422
36802
  this._options = { ...this._options, ...options };
36423
- if (colorSchemeChanged || percentileChanged || hiddenClassificationsChanged) {
36803
+ if (colorSchemeChanged || percentileChanged || colormapChanged || colorRangeChanged || hiddenClassificationsChanged) {
36424
36804
  for (const [id, pc] of this._pointClouds) {
36425
- const colors = this._colorProcessor.getColors(pc.data, this._options.colorScheme, {
36805
+ const result = this._colorProcessor.getColorsWithBounds(pc.data, this._options.colorScheme, {
36426
36806
  usePercentile: this._options.usePercentile,
36807
+ colormap: this._options.colormap,
36808
+ colorRange: this._options.colorRange,
36427
36809
  hiddenClassifications: this._options.hiddenClassifications
36428
36810
  });
36811
+ if (result.bounds) {
36812
+ this._lastComputedBounds = result.bounds;
36813
+ }
36429
36814
  this._pointClouds.set(id, {
36430
36815
  ...pc,
36431
- colors,
36816
+ colors: result.colors,
36432
36817
  coordinateOrigin: pc.coordinateOrigin,
36433
36818
  visible: pc.visible,
36434
36819
  opacityOverride: pc.opacityOverride
@@ -36475,6 +36860,22 @@ class PointCloudManager {
36475
36860
  setUsePercentile(usePercentile) {
36476
36861
  this.updateStyle({ usePercentile });
36477
36862
  }
36863
+ /**
36864
+ * Sets the colormap for elevation/intensity coloring.
36865
+ *
36866
+ * @param colormap - Colormap name
36867
+ */
36868
+ setColormap(colormap) {
36869
+ this.updateStyle({ colormap });
36870
+ }
36871
+ /**
36872
+ * Sets the color range configuration.
36873
+ *
36874
+ * @param colorRange - Color range configuration
36875
+ */
36876
+ setColorRange(colorRange) {
36877
+ this.updateStyle({ colorRange });
36878
+ }
36478
36879
  /**
36479
36880
  * Sets the elevation range filter.
36480
36881
  *
@@ -36573,6 +36974,13 @@ class PointCloudManager {
36573
36974
  getOptions() {
36574
36975
  return { ...this._options };
36575
36976
  }
36977
+ /**
36978
+ * Gets the last computed color bounds.
36979
+ * Used for displaying accurate colorbar min/max values.
36980
+ */
36981
+ getLastComputedBounds() {
36982
+ return this._lastComputedBounds;
36983
+ }
36576
36984
  /**
36577
36985
  * Creates a deck.gl layer for a point cloud.
36578
36986
  * Chunks large point clouds into multiple layers to avoid WebGL buffer limits.
@@ -37061,6 +37469,7 @@ class DualRangeSlider {
37061
37469
  __publicField(this, "_sliderLow");
37062
37470
  __publicField(this, "_sliderHigh");
37063
37471
  __publicField(this, "_valueDisplay");
37472
+ __publicField(this, "_rangeHighlight");
37064
37473
  this._options = options;
37065
37474
  }
37066
37475
  /**
@@ -37108,6 +37517,7 @@ class DualRangeSlider {
37108
37517
  background: #159895;
37109
37518
  border-radius: 2px;
37110
37519
  `;
37520
+ this._rangeHighlight = range;
37111
37521
  sliderContainer.appendChild(range);
37112
37522
  const sliderLow = document.createElement("input");
37113
37523
  sliderLow.type = "range";
@@ -37225,6 +37635,7 @@ class DualRangeSlider {
37225
37635
  if (this._valueDisplay) {
37226
37636
  this._valueDisplay.textContent = this._formatRange(low, high);
37227
37637
  }
37638
+ this._updateRangeHighlight();
37228
37639
  }
37229
37640
  /**
37230
37641
  * Updates the min/max bounds of the slider.
@@ -37240,6 +37651,33 @@ class DualRangeSlider {
37240
37651
  this._sliderHigh.min = String(min);
37241
37652
  this._sliderHigh.max = String(max);
37242
37653
  }
37654
+ this._updateRangeHighlight();
37655
+ }
37656
+ /**
37657
+ * Updates the step value of the slider.
37658
+ */
37659
+ setStep(step2) {
37660
+ this._options.step = step2;
37661
+ if (this._sliderLow) {
37662
+ this._sliderLow.step = String(step2);
37663
+ }
37664
+ if (this._sliderHigh) {
37665
+ this._sliderHigh.step = String(step2);
37666
+ }
37667
+ }
37668
+ /**
37669
+ * Updates the visual range highlight bar.
37670
+ */
37671
+ _updateRangeHighlight() {
37672
+ if (!this._rangeHighlight || !this._sliderLow || !this._sliderHigh) return;
37673
+ const low = parseFloat(this._sliderLow.value);
37674
+ const high = parseFloat(this._sliderHigh.value);
37675
+ const min = this._options.min;
37676
+ const max = this._options.max;
37677
+ const percentLow = (low - min) / (max - min) * 100;
37678
+ const percentHigh = (high - min) / (max - min) * 100;
37679
+ this._rangeHighlight.style.left = `${percentLow}%`;
37680
+ this._rangeHighlight.style.width = `${percentHigh - percentLow}%`;
37243
37681
  }
37244
37682
  /**
37245
37683
  * Gets the current range values.
@@ -37392,6 +37830,457 @@ class ClassificationLegend {
37392
37830
  return this._container;
37393
37831
  }
37394
37832
  }
37833
+ class Colorbar {
37834
+ /**
37835
+ * Creates a new Colorbar instance.
37836
+ *
37837
+ * @param options - Colorbar configuration options
37838
+ */
37839
+ constructor(options) {
37840
+ __publicField(this, "_options");
37841
+ __publicField(this, "_canvas");
37842
+ __publicField(this, "_minLabel");
37843
+ __publicField(this, "_maxLabel");
37844
+ this._options = { ...options };
37845
+ }
37846
+ /**
37847
+ * Renders the colorbar component.
37848
+ *
37849
+ * @returns The colorbar container element
37850
+ */
37851
+ render() {
37852
+ const container = document.createElement("div");
37853
+ container.className = "lidar-colorbar";
37854
+ if (this._options.label) {
37855
+ const label = document.createElement("div");
37856
+ label.className = "lidar-colorbar-label";
37857
+ label.textContent = this._options.label;
37858
+ container.appendChild(label);
37859
+ }
37860
+ const canvas = document.createElement("canvas");
37861
+ canvas.className = "lidar-colorbar-gradient";
37862
+ canvas.width = 200;
37863
+ canvas.height = 14;
37864
+ this._canvas = canvas;
37865
+ container.appendChild(canvas);
37866
+ const labelsContainer = document.createElement("div");
37867
+ labelsContainer.className = "lidar-colorbar-labels";
37868
+ const minLabel = document.createElement("span");
37869
+ minLabel.className = "lidar-colorbar-min";
37870
+ this._minLabel = minLabel;
37871
+ const maxLabel = document.createElement("span");
37872
+ maxLabel.className = "lidar-colorbar-max";
37873
+ this._maxLabel = maxLabel;
37874
+ labelsContainer.appendChild(minLabel);
37875
+ labelsContainer.appendChild(maxLabel);
37876
+ container.appendChild(labelsContainer);
37877
+ this._drawGradient();
37878
+ this._updateLabels();
37879
+ return container;
37880
+ }
37881
+ /**
37882
+ * Updates the colorbar with new options.
37883
+ *
37884
+ * @param options - Partial options to update
37885
+ */
37886
+ update(options) {
37887
+ if (options.colormap !== void 0) {
37888
+ this._options.colormap = options.colormap;
37889
+ }
37890
+ if (options.minValue !== void 0) {
37891
+ this._options.minValue = options.minValue;
37892
+ }
37893
+ if (options.maxValue !== void 0) {
37894
+ this._options.maxValue = options.maxValue;
37895
+ }
37896
+ if (options.label !== void 0) {
37897
+ this._options.label = options.label;
37898
+ }
37899
+ this._drawGradient();
37900
+ this._updateLabels();
37901
+ }
37902
+ /**
37903
+ * Sets the colormap.
37904
+ *
37905
+ * @param colormap - The colormap name
37906
+ */
37907
+ setColormap(colormap) {
37908
+ this._options.colormap = colormap;
37909
+ this._drawGradient();
37910
+ }
37911
+ /**
37912
+ * Sets the value range.
37913
+ *
37914
+ * @param min - Minimum value
37915
+ * @param max - Maximum value
37916
+ */
37917
+ setRange(min, max) {
37918
+ this._options.minValue = min;
37919
+ this._options.maxValue = max;
37920
+ this._updateLabels();
37921
+ }
37922
+ /**
37923
+ * Gets the current colormap.
37924
+ *
37925
+ * @returns The current colormap name
37926
+ */
37927
+ getColormap() {
37928
+ return this._options.colormap;
37929
+ }
37930
+ /**
37931
+ * Gets the current value range.
37932
+ *
37933
+ * @returns Object with min and max values
37934
+ */
37935
+ getRange() {
37936
+ return {
37937
+ min: this._options.minValue,
37938
+ max: this._options.maxValue
37939
+ };
37940
+ }
37941
+ /**
37942
+ * Draws the color gradient on the canvas.
37943
+ */
37944
+ _drawGradient() {
37945
+ if (!this._canvas) return;
37946
+ const ctx = this._canvas.getContext("2d");
37947
+ if (!ctx) return;
37948
+ const width = this._canvas.width;
37949
+ const height = this._canvas.height;
37950
+ const ramp = COLORMAPS[this._options.colormap] || COLORMAPS.viridis;
37951
+ const gradient = ctx.createLinearGradient(0, 0, width, 0);
37952
+ for (let i = 0; i < ramp.length; i++) {
37953
+ const t = i / (ramp.length - 1);
37954
+ const [r, g, b] = ramp[i];
37955
+ gradient.addColorStop(t, `rgb(${r}, ${g}, ${b})`);
37956
+ }
37957
+ ctx.fillStyle = gradient;
37958
+ ctx.fillRect(0, 0, width, height);
37959
+ }
37960
+ /**
37961
+ * Updates the min/max value labels.
37962
+ */
37963
+ _updateLabels() {
37964
+ if (this._minLabel) {
37965
+ this._minLabel.textContent = this._formatValue(this._options.minValue);
37966
+ }
37967
+ if (this._maxLabel) {
37968
+ this._maxLabel.textContent = this._formatValue(this._options.maxValue);
37969
+ }
37970
+ }
37971
+ /**
37972
+ * Formats a value for display.
37973
+ *
37974
+ * @param value - The value to format
37975
+ * @returns Formatted string
37976
+ */
37977
+ _formatValue(value) {
37978
+ if (!Number.isFinite(value)) {
37979
+ return "—";
37980
+ }
37981
+ const range = Math.abs(this._options.maxValue - this._options.minValue);
37982
+ if (range < 1) {
37983
+ return value.toFixed(3);
37984
+ } else if (range < 10) {
37985
+ return value.toFixed(2);
37986
+ } else if (range < 100) {
37987
+ return value.toFixed(1);
37988
+ } else {
37989
+ return value.toFixed(0);
37990
+ }
37991
+ }
37992
+ }
37993
+ const DEFAULT_PERCENTILE_LOW = 2;
37994
+ const DEFAULT_PERCENTILE_HIGH = 98;
37995
+ class PercentileRangeControl {
37996
+ /**
37997
+ * Creates a new PercentileRangeControl instance.
37998
+ *
37999
+ * @param options - Control configuration options
38000
+ */
38001
+ constructor(options) {
38002
+ __publicField(this, "_options");
38003
+ __publicField(this, "_percentileRadio");
38004
+ __publicField(this, "_absoluteRadio");
38005
+ __publicField(this, "_percentileSliderContainer");
38006
+ __publicField(this, "_absoluteSliderContainer");
38007
+ __publicField(this, "_percentileSlider");
38008
+ __publicField(this, "_absoluteSlider");
38009
+ __publicField(this, "_computedBounds");
38010
+ this._options = { ...options };
38011
+ this._computedBounds = options.computedBounds;
38012
+ }
38013
+ /**
38014
+ * Renders the control component.
38015
+ *
38016
+ * @returns The control container element
38017
+ */
38018
+ render() {
38019
+ const container = document.createElement("div");
38020
+ container.className = "lidar-color-range";
38021
+ const labelRow = document.createElement("div");
38022
+ labelRow.className = "lidar-color-range-header";
38023
+ const label = document.createElement("div");
38024
+ label.className = "lidar-control-label";
38025
+ label.textContent = "Color Range";
38026
+ labelRow.appendChild(label);
38027
+ const resetButton = document.createElement("button");
38028
+ resetButton.className = "lidar-range-reset-btn";
38029
+ resetButton.textContent = "Reset";
38030
+ resetButton.title = "Reset to default (2-98% percentile)";
38031
+ resetButton.addEventListener("click", () => this._onReset());
38032
+ labelRow.appendChild(resetButton);
38033
+ container.appendChild(labelRow);
38034
+ const modeContainer = document.createElement("div");
38035
+ modeContainer.className = "lidar-range-mode";
38036
+ const percentileLabel = document.createElement("label");
38037
+ const percentileRadio = document.createElement("input");
38038
+ percentileRadio.type = "radio";
38039
+ percentileRadio.name = "lidar-range-mode";
38040
+ percentileRadio.value = "percentile";
38041
+ percentileRadio.checked = this._options.config.mode === "percentile";
38042
+ this._percentileRadio = percentileRadio;
38043
+ percentileLabel.appendChild(percentileRadio);
38044
+ percentileLabel.appendChild(document.createTextNode(" Percentile"));
38045
+ modeContainer.appendChild(percentileLabel);
38046
+ const absoluteLabel = document.createElement("label");
38047
+ const absoluteRadio = document.createElement("input");
38048
+ absoluteRadio.type = "radio";
38049
+ absoluteRadio.name = "lidar-range-mode";
38050
+ absoluteRadio.value = "absolute";
38051
+ absoluteRadio.checked = this._options.config.mode === "absolute";
38052
+ this._absoluteRadio = absoluteRadio;
38053
+ absoluteLabel.appendChild(absoluteRadio);
38054
+ absoluteLabel.appendChild(document.createTextNode(" Absolute"));
38055
+ modeContainer.appendChild(absoluteLabel);
38056
+ container.appendChild(modeContainer);
38057
+ const percentileSliderContainer = document.createElement("div");
38058
+ percentileSliderContainer.style.display = this._options.config.mode === "percentile" ? "block" : "none";
38059
+ this._percentileSliderContainer = percentileSliderContainer;
38060
+ this._percentileSlider = new DualRangeSlider({
38061
+ label: "",
38062
+ min: 0,
38063
+ max: 100,
38064
+ step: 1,
38065
+ valueLow: this._options.config.percentileLow ?? DEFAULT_PERCENTILE_LOW,
38066
+ valueHigh: this._options.config.percentileHigh ?? DEFAULT_PERCENTILE_HIGH,
38067
+ onChange: (low, high) => this._onPercentileChange(low, high),
38068
+ formatValue: (v) => `${v.toFixed(0)}%`
38069
+ });
38070
+ percentileSliderContainer.appendChild(this._percentileSlider.render());
38071
+ container.appendChild(percentileSliderContainer);
38072
+ const absoluteSliderContainer = document.createElement("div");
38073
+ absoluteSliderContainer.style.display = this._options.config.mode === "absolute" ? "block" : "none";
38074
+ this._absoluteSliderContainer = absoluteSliderContainer;
38075
+ const dataBounds = this._options.dataBounds || { min: 0, max: 100 };
38076
+ const absMin = this._options.config.absoluteMin ?? dataBounds.min;
38077
+ const absMax = this._options.config.absoluteMax ?? dataBounds.max;
38078
+ this._absoluteSlider = new DualRangeSlider({
38079
+ label: "",
38080
+ min: dataBounds.min,
38081
+ max: dataBounds.max,
38082
+ step: this._getAbsoluteStep(dataBounds),
38083
+ valueLow: absMin,
38084
+ valueHigh: absMax,
38085
+ onChange: (low, high) => this._onAbsoluteChange(low, high),
38086
+ formatValue: (v) => this._formatAbsoluteValue(v)
38087
+ });
38088
+ absoluteSliderContainer.appendChild(this._absoluteSlider.render());
38089
+ container.appendChild(absoluteSliderContainer);
38090
+ percentileRadio.addEventListener("change", () => this._onModeChange());
38091
+ absoluteRadio.addEventListener("change", () => this._onModeChange());
38092
+ return container;
38093
+ }
38094
+ /**
38095
+ * Updates the control with new configuration.
38096
+ *
38097
+ * @param config - New color range configuration
38098
+ */
38099
+ setConfig(config) {
38100
+ this._options.config = { ...config };
38101
+ if (this._percentileRadio && this._absoluteRadio) {
38102
+ this._percentileRadio.checked = config.mode === "percentile";
38103
+ this._absoluteRadio.checked = config.mode === "absolute";
38104
+ }
38105
+ if (this._percentileSlider) {
38106
+ this._percentileSlider.setRange(
38107
+ config.percentileLow ?? DEFAULT_PERCENTILE_LOW,
38108
+ config.percentileHigh ?? DEFAULT_PERCENTILE_HIGH
38109
+ );
38110
+ }
38111
+ if (this._absoluteSlider) {
38112
+ const dataBounds = this._options.dataBounds || { min: 0, max: 100 };
38113
+ this._absoluteSlider.setRange(
38114
+ config.absoluteMin ?? dataBounds.min,
38115
+ config.absoluteMax ?? dataBounds.max
38116
+ );
38117
+ }
38118
+ this._updateSlidersVisibility();
38119
+ }
38120
+ /**
38121
+ * Updates the data bounds (for absolute mode reference).
38122
+ *
38123
+ * @param bounds - Data bounds
38124
+ */
38125
+ setDataBounds(bounds2) {
38126
+ this._options.dataBounds = bounds2;
38127
+ if (this._absoluteSlider) {
38128
+ this._absoluteSlider.setBounds(bounds2.min, bounds2.max);
38129
+ this._absoluteSlider.setStep(this._getAbsoluteStep(bounds2));
38130
+ if (this._options.config.absoluteMin === void 0) {
38131
+ this._options.config.absoluteMin = bounds2.min;
38132
+ }
38133
+ if (this._options.config.absoluteMax === void 0) {
38134
+ this._options.config.absoluteMax = bounds2.max;
38135
+ }
38136
+ const currentMin = this._options.config.absoluteMin ?? bounds2.min;
38137
+ const currentMax = this._options.config.absoluteMax ?? bounds2.max;
38138
+ const clampedMin = Math.max(bounds2.min, Math.min(bounds2.max, currentMin));
38139
+ const clampedMax = Math.max(bounds2.min, Math.min(bounds2.max, currentMax));
38140
+ this._absoluteSlider.setRange(clampedMin, clampedMax);
38141
+ }
38142
+ }
38143
+ /**
38144
+ * Sets the actual computed bounds from percentile calculation.
38145
+ * These bounds are used when switching from percentile to absolute mode.
38146
+ *
38147
+ * @param bounds - The actual computed bounds
38148
+ */
38149
+ setComputedBounds(bounds2) {
38150
+ this._computedBounds = bounds2;
38151
+ }
38152
+ /**
38153
+ * Gets the current configuration.
38154
+ *
38155
+ * @returns The current color range configuration
38156
+ */
38157
+ getConfig() {
38158
+ return { ...this._options.config };
38159
+ }
38160
+ /**
38161
+ * Handles percentile slider change.
38162
+ */
38163
+ _onPercentileChange(low, high) {
38164
+ this._options.config.percentileLow = low;
38165
+ this._options.config.percentileHigh = high;
38166
+ this._emitChange();
38167
+ }
38168
+ /**
38169
+ * Handles absolute slider change.
38170
+ */
38171
+ _onAbsoluteChange(low, high) {
38172
+ this._options.config.absoluteMin = low;
38173
+ this._options.config.absoluteMax = high;
38174
+ this._emitChange();
38175
+ }
38176
+ /**
38177
+ * Handles mode change (percentile/absolute toggle).
38178
+ * Syncs values when switching between modes using actual computed bounds.
38179
+ */
38180
+ _onModeChange() {
38181
+ var _a;
38182
+ const newMode = ((_a = this._percentileRadio) == null ? void 0 : _a.checked) ? "percentile" : "absolute";
38183
+ const oldMode = this._options.config.mode;
38184
+ if (newMode !== oldMode) {
38185
+ if (newMode === "absolute") {
38186
+ if (this._computedBounds) {
38187
+ this._options.config.absoluteMin = this._computedBounds.min;
38188
+ this._options.config.absoluteMax = this._computedBounds.max;
38189
+ } else if (this._options.dataBounds) {
38190
+ const { min: dataMin, max: dataMax } = this._options.dataBounds;
38191
+ const range = dataMax - dataMin;
38192
+ const pLow = this._options.config.percentileLow ?? DEFAULT_PERCENTILE_LOW;
38193
+ const pHigh = this._options.config.percentileHigh ?? DEFAULT_PERCENTILE_HIGH;
38194
+ this._options.config.absoluteMin = parseFloat((dataMin + range * (pLow / 100)).toFixed(2));
38195
+ this._options.config.absoluteMax = parseFloat((dataMin + range * (pHigh / 100)).toFixed(2));
38196
+ }
38197
+ if (this._absoluteSlider && this._options.config.absoluteMin !== void 0 && this._options.config.absoluteMax !== void 0) {
38198
+ this._absoluteSlider.setRange(this._options.config.absoluteMin, this._options.config.absoluteMax);
38199
+ }
38200
+ }
38201
+ }
38202
+ this._options.config.mode = newMode;
38203
+ this._updateSlidersVisibility();
38204
+ this._emitChange();
38205
+ }
38206
+ /**
38207
+ * Handles reset button click.
38208
+ * Resets to default percentile mode with 2-98% range.
38209
+ */
38210
+ _onReset() {
38211
+ this._options.config.mode = "percentile";
38212
+ this._options.config.percentileLow = DEFAULT_PERCENTILE_LOW;
38213
+ this._options.config.percentileHigh = DEFAULT_PERCENTILE_HIGH;
38214
+ if (this._options.dataBounds) {
38215
+ const { min: dataMin, max: dataMax } = this._options.dataBounds;
38216
+ const range = dataMax - dataMin;
38217
+ this._options.config.absoluteMin = parseFloat((dataMin + range * (DEFAULT_PERCENTILE_LOW / 100)).toFixed(2));
38218
+ this._options.config.absoluteMax = parseFloat((dataMin + range * (DEFAULT_PERCENTILE_HIGH / 100)).toFixed(2));
38219
+ }
38220
+ if (this._percentileRadio) {
38221
+ this._percentileRadio.checked = true;
38222
+ }
38223
+ if (this._absoluteRadio) {
38224
+ this._absoluteRadio.checked = false;
38225
+ }
38226
+ if (this._percentileSlider) {
38227
+ this._percentileSlider.setRange(DEFAULT_PERCENTILE_LOW, DEFAULT_PERCENTILE_HIGH);
38228
+ }
38229
+ if (this._absoluteSlider && this._options.config.absoluteMin !== void 0 && this._options.config.absoluteMax !== void 0) {
38230
+ this._absoluteSlider.setRange(this._options.config.absoluteMin, this._options.config.absoluteMax);
38231
+ }
38232
+ this._updateSlidersVisibility();
38233
+ this._emitChange();
38234
+ }
38235
+ /**
38236
+ * Updates the visibility of slider containers based on mode.
38237
+ */
38238
+ _updateSlidersVisibility() {
38239
+ if (this._percentileSliderContainer) {
38240
+ this._percentileSliderContainer.style.display = this._options.config.mode === "percentile" ? "block" : "none";
38241
+ }
38242
+ if (this._absoluteSliderContainer) {
38243
+ this._absoluteSliderContainer.style.display = this._options.config.mode === "absolute" ? "block" : "none";
38244
+ }
38245
+ }
38246
+ /**
38247
+ * Gets the appropriate step value for absolute slider based on data range.
38248
+ */
38249
+ _getAbsoluteStep(bounds2) {
38250
+ const range = bounds2.max - bounds2.min;
38251
+ if (range <= 1) {
38252
+ return 0.01;
38253
+ } else if (range <= 10) {
38254
+ return 0.1;
38255
+ } else if (range <= 100) {
38256
+ return 1;
38257
+ } else if (range <= 1e3) {
38258
+ return 1;
38259
+ } else {
38260
+ return Math.round(range / 100);
38261
+ }
38262
+ }
38263
+ /**
38264
+ * Formats absolute value for display based on the data range.
38265
+ */
38266
+ _formatAbsoluteValue(value) {
38267
+ const bounds2 = this._options.dataBounds || { min: 0, max: 100 };
38268
+ const range = bounds2.max - bounds2.min;
38269
+ if (range <= 1) {
38270
+ return value.toFixed(2);
38271
+ } else if (range <= 10) {
38272
+ return value.toFixed(1);
38273
+ } else {
38274
+ return value.toFixed(0);
38275
+ }
38276
+ }
38277
+ /**
38278
+ * Emits a change event with the current configuration.
38279
+ */
38280
+ _emitChange() {
38281
+ this._options.onChange({ ...this._options.config });
38282
+ }
38283
+ }
37395
38284
  class PanelBuilder {
37396
38285
  constructor(callbacks, initialState) {
37397
38286
  __publicField(this, "_callbacks");
@@ -37402,6 +38291,12 @@ class PanelBuilder {
37402
38291
  __publicField(this, "_urlInput");
37403
38292
  __publicField(this, "_loadButton");
37404
38293
  __publicField(this, "_colorSelect");
38294
+ __publicField(this, "_colormapSelect");
38295
+ __publicField(this, "_colormapGroup");
38296
+ __publicField(this, "_colorbar");
38297
+ __publicField(this, "_colorbarContainer");
38298
+ __publicField(this, "_colorRangeControl");
38299
+ __publicField(this, "_colorRangeContainer");
37405
38300
  __publicField(this, "_percentileCheckbox");
37406
38301
  __publicField(this, "_percentileGroup");
37407
38302
  __publicField(this, "_pointSizeSlider");
@@ -37477,6 +38372,25 @@ class PanelBuilder {
37477
38372
  this._colorSelect.value = state.colorScheme;
37478
38373
  this._updatePercentileVisibility(state.colorScheme);
37479
38374
  }
38375
+ if (this._colormapSelect && state.colormap) {
38376
+ this._colormapSelect.value = state.colormap;
38377
+ }
38378
+ if (this._colorbar) {
38379
+ if (state.colormap) {
38380
+ this._colorbar.setColormap(state.colormap);
38381
+ }
38382
+ if (state.computedColorBounds) {
38383
+ this._colorbar.setRange(state.computedColorBounds.min, state.computedColorBounds.max);
38384
+ }
38385
+ }
38386
+ if (this._colorRangeControl && state.colorRange) {
38387
+ this._colorRangeControl.setConfig(state.colorRange);
38388
+ const bounds2 = this._getDataBoundsForCurrentScheme();
38389
+ this._colorRangeControl.setDataBounds(bounds2);
38390
+ if (state.computedColorBounds) {
38391
+ this._colorRangeControl.setComputedBounds(state.computedColorBounds);
38392
+ }
38393
+ }
37480
38394
  if (this._percentileCheckbox) {
37481
38395
  this._percentileCheckbox.checked = state.usePercentile ?? true;
37482
38396
  }
@@ -37617,8 +38531,10 @@ class PanelBuilder {
37617
38531
  this._colorSelect = colorSelect;
37618
38532
  colorGroup.appendChild(colorSelect);
37619
38533
  section.appendChild(colorGroup);
38534
+ section.appendChild(this._buildColormapSelector());
38535
+ section.appendChild(this._buildColorbar());
37620
38536
  section.appendChild(this._buildClassificationLegend());
37621
- section.appendChild(this._buildPercentileCheckbox());
38537
+ section.appendChild(this._buildColorRangeControl());
37622
38538
  this._pointSizeSlider = new RangeSlider({
37623
38539
  label: "Point Size",
37624
38540
  min: 1,
@@ -37803,46 +38719,120 @@ class PanelBuilder {
37803
38719
  return { min: minZ, max: maxZ };
37804
38720
  }
37805
38721
  /**
37806
- * Builds the percentile checkbox control for elevation/intensity coloring.
38722
+ * Gets the intensity bounds.
38723
+ * Intensity values are normalized to 0-1 range during loading.
38724
+ */
38725
+ _getIntensityBounds() {
38726
+ return { min: 0, max: 1 };
38727
+ }
38728
+ /**
38729
+ * Gets the appropriate data bounds based on the current color scheme.
38730
+ */
38731
+ _getDataBoundsForCurrentScheme() {
38732
+ const colorScheme = typeof this._state.colorScheme === "string" ? this._state.colorScheme : "elevation";
38733
+ if (colorScheme === "intensity") {
38734
+ return this._getIntensityBounds();
38735
+ }
38736
+ return this._getElevationBounds();
38737
+ }
38738
+ /**
38739
+ * Builds the colormap selector dropdown.
37807
38740
  */
37808
- _buildPercentileCheckbox() {
38741
+ _buildColormapSelector() {
37809
38742
  const group = document.createElement("div");
37810
- group.className = "lidar-control-group";
37811
- this._percentileGroup = group;
37812
- const labelRow = document.createElement("div");
37813
- labelRow.className = "lidar-control-label-row";
37814
- labelRow.style.cursor = "pointer";
37815
- const checkbox = document.createElement("input");
37816
- checkbox.type = "checkbox";
37817
- checkbox.id = "lidar-percentile-checkbox";
37818
- checkbox.checked = this._state.usePercentile ?? true;
37819
- checkbox.style.marginRight = "6px";
37820
- this._percentileCheckbox = checkbox;
38743
+ group.className = "lidar-colormap-group";
38744
+ this._colormapGroup = group;
38745
+ const currentScheme = typeof this._state.colorScheme === "string" ? this._state.colorScheme : "elevation";
38746
+ const showColormap = currentScheme === "elevation" || currentScheme === "intensity";
38747
+ group.style.display = showColormap ? "block" : "none";
37821
38748
  const label = document.createElement("label");
37822
38749
  label.className = "lidar-control-label";
37823
- label.htmlFor = "lidar-percentile-checkbox";
37824
- label.style.display = "inline";
37825
- label.style.cursor = "pointer";
37826
- label.textContent = "Use percentile range (2-98%)";
37827
- label.title = "Clip outliers for better color distribution";
37828
- checkbox.addEventListener("change", () => {
37829
- this._callbacks.onUsePercentileChange(checkbox.checked);
38750
+ label.textContent = "Colormap";
38751
+ group.appendChild(label);
38752
+ const select = document.createElement("select");
38753
+ select.className = "lidar-colormap-select";
38754
+ for (const name of COLORMAP_NAMES) {
38755
+ const option = document.createElement("option");
38756
+ option.value = name;
38757
+ option.textContent = COLORMAP_LABELS[name];
38758
+ select.appendChild(option);
38759
+ }
38760
+ select.value = this._state.colormap || "viridis";
38761
+ this._colormapSelect = select;
38762
+ select.addEventListener("change", () => {
38763
+ const colormap = select.value;
38764
+ this._callbacks.onColormapChange(colormap);
37830
38765
  });
37831
- labelRow.appendChild(checkbox);
37832
- labelRow.appendChild(label);
37833
- group.appendChild(labelRow);
37834
- const currentScheme = typeof this._state.colorScheme === "string" ? this._state.colorScheme : "elevation";
37835
- this._updatePercentileVisibility(currentScheme);
38766
+ group.appendChild(select);
37836
38767
  return group;
37837
38768
  }
37838
38769
  /**
37839
- * Updates the visibility of the percentile checkbox and classification legend based on color scheme.
37840
- * Percentile shows for elevation and intensity. Legend shows for classification.
38770
+ * Builds the colorbar component.
38771
+ */
38772
+ _buildColorbar() {
38773
+ var _a, _b;
38774
+ const container = document.createElement("div");
38775
+ container.className = "lidar-control-group";
38776
+ this._colorbarContainer = container;
38777
+ const currentScheme = typeof this._state.colorScheme === "string" ? this._state.colorScheme : "elevation";
38778
+ const showColorbar = (currentScheme === "elevation" || currentScheme === "intensity") && this._state.showColorbar;
38779
+ container.style.display = showColorbar ? "block" : "none";
38780
+ this._colorbar = new Colorbar({
38781
+ colormap: this._state.colormap || "viridis",
38782
+ minValue: ((_a = this._state.computedColorBounds) == null ? void 0 : _a.min) ?? 0,
38783
+ maxValue: ((_b = this._state.computedColorBounds) == null ? void 0 : _b.max) ?? 100
38784
+ });
38785
+ container.appendChild(this._colorbar.render());
38786
+ return container;
38787
+ }
38788
+ /**
38789
+ * Builds the color range control (replaces percentile checkbox).
38790
+ */
38791
+ _buildColorRangeControl() {
38792
+ const container = document.createElement("div");
38793
+ container.className = "lidar-control-group";
38794
+ this._colorRangeContainer = container;
38795
+ const currentScheme = typeof this._state.colorScheme === "string" ? this._state.colorScheme : "elevation";
38796
+ const showControl = currentScheme === "elevation" || currentScheme === "intensity";
38797
+ container.style.display = showControl ? "block" : "none";
38798
+ const dataBounds = this._getDataBoundsForCurrentScheme();
38799
+ this._colorRangeControl = new PercentileRangeControl({
38800
+ config: this._state.colorRange || {
38801
+ mode: "percentile",
38802
+ percentileLow: 2,
38803
+ percentileHigh: 98
38804
+ },
38805
+ dataBounds,
38806
+ computedBounds: this._state.computedColorBounds,
38807
+ onChange: (config) => {
38808
+ this._callbacks.onColorRangeChange(config);
38809
+ }
38810
+ });
38811
+ container.appendChild(this._colorRangeControl.render());
38812
+ return container;
38813
+ }
38814
+ /**
38815
+ * Updates the visibility of color-related controls based on color scheme.
38816
+ * Shows colormap/colorbar/range for elevation and intensity.
38817
+ * Shows classification legend for classification.
37841
38818
  */
37842
38819
  _updatePercentileVisibility(colorScheme) {
38820
+ const showColorControls = colorScheme === "elevation" || colorScheme === "intensity";
38821
+ if (this._colormapGroup) {
38822
+ this._colormapGroup.style.display = showColorControls ? "block" : "none";
38823
+ }
38824
+ if (this._colorbarContainer) {
38825
+ this._colorbarContainer.style.display = showColorControls && this._state.showColorbar ? "block" : "none";
38826
+ }
38827
+ if (this._colorRangeContainer) {
38828
+ this._colorRangeContainer.style.display = showColorControls ? "block" : "none";
38829
+ }
38830
+ if (this._colorRangeControl && showColorControls) {
38831
+ const bounds2 = this._getDataBoundsForCurrentScheme();
38832
+ this._colorRangeControl.setDataBounds(bounds2);
38833
+ }
37843
38834
  if (this._percentileGroup) {
37844
- const showPercentile = colorScheme === "elevation" || colorScheme === "intensity";
37845
- this._percentileGroup.style.display = showPercentile ? "block" : "none";
38835
+ this._percentileGroup.style.display = "none";
37846
38836
  }
37847
38837
  if (this._classificationLegendContainer) {
37848
38838
  this._classificationLegendContainer.style.display = colorScheme === "classification" ? "block" : "none";
@@ -38009,6 +38999,9 @@ const DEFAULT_OPTIONS = {
38009
38999
  opacity: 1,
38010
39000
  colorScheme: "elevation",
38011
39001
  usePercentile: true,
39002
+ colormap: "viridis",
39003
+ colorRange: { mode: "percentile", percentileLow: 2, percentileHigh: 98 },
39004
+ showColorbar: true,
38012
39005
  pointBudget: 1e6,
38013
39006
  elevationRange: null,
38014
39007
  pickable: false,
@@ -38055,7 +39048,14 @@ class LidarControl {
38055
39048
  __publicField(this, "_streamingLoaders", /* @__PURE__ */ new Map());
38056
39049
  __publicField(this, "_eptStreamingLoaders", /* @__PURE__ */ new Map());
38057
39050
  __publicField(this, "_viewportManagers", /* @__PURE__ */ new Map());
39051
+ __publicField(this, "_eptViewportRequestIds", /* @__PURE__ */ new Map());
39052
+ __publicField(this, "_eptLastViewport", /* @__PURE__ */ new Map());
38058
39053
  this._options = { ...DEFAULT_OPTIONS, ...options };
39054
+ const defaultColorRange = this._options.colorRange ?? {
39055
+ mode: "percentile",
39056
+ percentileLow: 2,
39057
+ percentileHigh: 98
39058
+ };
38059
39059
  this._state = {
38060
39060
  collapsed: this._options.collapsed,
38061
39061
  panelWidth: this._options.panelWidth,
@@ -38065,6 +39065,9 @@ class LidarControl {
38065
39065
  pointSize: this._options.pointSize,
38066
39066
  opacity: this._options.opacity,
38067
39067
  colorScheme: this._options.colorScheme,
39068
+ colormap: this._options.colormap ?? "viridis",
39069
+ colorRange: defaultColorRange,
39070
+ showColorbar: this._options.showColorbar ?? true,
38068
39071
  usePercentile: this._options.usePercentile,
38069
39072
  elevationRange: this._options.elevationRange,
38070
39073
  pointBudget: this._options.pointBudget,
@@ -38260,7 +39263,7 @@ class LidarControl {
38260
39263
  * @returns Promise resolving to the point cloud info
38261
39264
  */
38262
39265
  async loadPointCloud(source, options) {
38263
- var _a, _b, _c;
39266
+ var _a, _b, _c, _d;
38264
39267
  const isEptUrl = typeof source === "string" && (source.endsWith("/ept.json") || source.includes("/ept.json?"));
38265
39268
  if (isEptUrl) {
38266
39269
  return this.loadPointCloudEptStreaming(source);
@@ -38335,6 +39338,8 @@ class LidarControl {
38335
39338
  zOffset: zOffset ?? this._state.zOffset,
38336
39339
  zOffsetEnabled
38337
39340
  });
39341
+ this._updateComputedColorBounds();
39342
+ (_c = this._panelBuilder) == null ? void 0 : _c.updateState(this._state);
38338
39343
  this._emitWithData("load", { pointCloud: info2 });
38339
39344
  if (this._options.autoZoom) {
38340
39345
  this.flyToPointCloud(id);
@@ -38348,7 +39353,7 @@ class LidarControl {
38348
39353
  console.warn(
38349
39354
  `CORS error detected for ${source}. Falling back to download mode...`
38350
39355
  );
38351
- (_c = this._panelBuilder) == null ? void 0 : _c.updateLoadingProgress(5, "CORS blocked - downloading file...");
39356
+ (_d = this._panelBuilder) == null ? void 0 : _d.updateLoadingProgress(5, "CORS blocked - downloading file...");
38352
39357
  return this._loadPointCloudFullDownload(source);
38353
39358
  }
38354
39359
  this.setState({
@@ -38406,7 +39411,7 @@ class LidarControl {
38406
39411
  * @returns Promise resolving to initial point cloud info
38407
39412
  */
38408
39413
  async loadPointCloudStreaming(source, options) {
38409
- var _a, _b, _c, _d;
39414
+ var _a, _b, _c, _d, _e;
38410
39415
  const id = generateId("pc-stream");
38411
39416
  let name;
38412
39417
  if (typeof source === "string") {
@@ -38511,13 +39516,15 @@ class LidarControl {
38511
39516
  pointClouds,
38512
39517
  activePointCloudId: id
38513
39518
  });
39519
+ this._updateComputedColorBounds();
39520
+ (_c = this._panelBuilder) == null ? void 0 : _c.updateState(this._state);
38514
39521
  viewportManager.start();
38515
39522
  if (this._options.autoZoom) {
38516
39523
  const clampedMinY = Math.max(-90, Math.min(90, bounds2.minY));
38517
39524
  const clampedMaxY = Math.max(-90, Math.min(90, bounds2.maxY));
38518
39525
  const clampedMinX = Math.max(-180, Math.min(180, bounds2.minX));
38519
39526
  const clampedMaxX = Math.max(-180, Math.min(180, bounds2.maxX));
38520
- (_c = this._map) == null ? void 0 : _c.fitBounds(
39527
+ (_d = this._map) == null ? void 0 : _d.fitBounds(
38521
39528
  [
38522
39529
  [clampedMinX, clampedMinY],
38523
39530
  [clampedMaxX, clampedMaxY]
@@ -38556,7 +39563,7 @@ class LidarControl {
38556
39563
  streamingActive: hasActiveStreaming,
38557
39564
  error: null
38558
39565
  });
38559
- (_d = this._panelBuilder) == null ? void 0 : _d.updateLoadingProgress(5, "CORS blocked - downloading file...");
39566
+ (_e = this._panelBuilder) == null ? void 0 : _e.updateLoadingProgress(5, "CORS blocked - downloading file...");
38560
39567
  return this._loadPointCloudFullDownload(source);
38561
39568
  }
38562
39569
  this.setState({
@@ -38577,7 +39584,7 @@ class LidarControl {
38577
39584
  * @returns Promise resolving to initial point cloud info
38578
39585
  */
38579
39586
  async loadPointCloudEptStreaming(eptUrl, options) {
38580
- var _a, _b, _c, _d;
39587
+ var _a, _b, _c, _d, _e;
38581
39588
  const id = generateId("ept-stream");
38582
39589
  const name = getFilename(eptUrl.replace("/ept.json", ""));
38583
39590
  this.setState({ loading: true, error: null, streamingActive: true });
@@ -38675,13 +39682,15 @@ class LidarControl {
38675
39682
  pointClouds,
38676
39683
  activePointCloudId: id
38677
39684
  });
39685
+ this._updateComputedColorBounds();
39686
+ (_d = this._panelBuilder) == null ? void 0 : _d.updateState(this._state);
38678
39687
  viewportManager.start();
38679
39688
  if (this._options.autoZoom) {
38680
39689
  const clampedMinY = Math.max(-90, Math.min(90, bounds2.minY));
38681
39690
  const clampedMaxY = Math.max(-90, Math.min(90, bounds2.maxY));
38682
39691
  const clampedMinX = Math.max(-180, Math.min(180, bounds2.minX));
38683
39692
  const clampedMaxX = Math.max(-180, Math.min(180, bounds2.maxX));
38684
- (_d = this._map) == null ? void 0 : _d.fitBounds(
39693
+ (_e = this._map) == null ? void 0 : _e.fitBounds(
38685
39694
  [
38686
39695
  [clampedMinX, clampedMinY],
38687
39696
  [clampedMaxX, clampedMaxY]
@@ -38704,6 +39713,8 @@ class LidarControl {
38704
39713
  eptLoader.destroy();
38705
39714
  this._eptStreamingLoaders.delete(id);
38706
39715
  }
39716
+ this._eptViewportRequestIds.delete(id);
39717
+ this._eptLastViewport.delete(id);
38707
39718
  const viewportManager = this._viewportManagers.get(id);
38708
39719
  if (viewportManager) {
38709
39720
  viewportManager.destroy();
@@ -38725,15 +39736,77 @@ class LidarControl {
38725
39736
  * @param viewport - Current viewport information
38726
39737
  * @param datasetId - ID of the EPT dataset
38727
39738
  */
38728
- async _handleViewportChangeForEptStreaming(viewport, datasetId) {
39739
+ _shouldResetEptForViewportChange(previous, current) {
39740
+ if (!previous) return false;
39741
+ const [prevWest, prevSouth, prevEast, prevNorth] = previous.bounds;
39742
+ const [curWest, curSouth, curEast, curNorth] = current.bounds;
39743
+ const intersects = !(curEast < prevWest || curWest > prevEast || curNorth < prevSouth || curSouth > prevNorth);
39744
+ if (!intersects) return true;
39745
+ const prevWidth = prevEast - prevWest;
39746
+ const prevHeight = prevNorth - prevSouth;
39747
+ const dx = current.center[0] - previous.center[0];
39748
+ const dy = current.center[1] - previous.center[1];
39749
+ const centerDistance = Math.sqrt(dx * dx + dy * dy);
39750
+ const threshold = Math.max(prevWidth, prevHeight) * 0.3;
39751
+ return centerDistance > threshold;
39752
+ }
39753
+ async _handleViewportChangeForEptStreaming(viewport, datasetId, requestId) {
38729
39754
  const eptLoader = this._eptStreamingLoaders.get(datasetId);
38730
39755
  if (!eptLoader) return;
38731
39756
  try {
38732
- const nodesToLoad = await eptLoader.selectNodesForViewport(viewport);
39757
+ const currentRequestId = requestId ?? (this._eptViewportRequestIds.get(datasetId) ?? 0) + 1;
39758
+ if (requestId === void 0) {
39759
+ this._eptViewportRequestIds.set(datasetId, currentRequestId);
39760
+ }
39761
+ if (this._eptViewportRequestIds.get(datasetId) !== currentRequestId) return;
39762
+ const previousViewport = this._eptLastViewport.get(datasetId);
39763
+ const shouldResetForMove = this._shouldResetEptForViewportChange(previousViewport, viewport);
39764
+ this._eptLastViewport.set(datasetId, viewport);
39765
+ eptLoader.pruneQueueForViewport(viewport);
39766
+ if (shouldResetForMove) {
39767
+ const resetSucceeded2 = eptLoader.resetLoadedData();
39768
+ if (!resetSucceeded2) {
39769
+ setTimeout(() => {
39770
+ this._handleViewportChangeForEptStreaming(viewport, datasetId, currentRequestId);
39771
+ }, 200);
39772
+ return;
39773
+ }
39774
+ }
39775
+ let nodesToLoad = await eptLoader.selectNodesForViewport(viewport);
39776
+ let resetSucceeded = false;
39777
+ const loadedPoints = eptLoader.getLoadedPointCount();
39778
+ const pointBudget = eptLoader.getPointBudget();
39779
+ const budgetReached = loadedPoints >= pointBudget * 0.8;
39780
+ const minDepthForCoverage = Math.max(0, viewport.targetDepth - 2);
39781
+ const coverageRatio = eptLoader.getViewportCoverageRatio(viewport, minDepthForCoverage);
39782
+ const needsCoverage = coverageRatio < 0.5;
39783
+ const hasPendingSubtrees = eptLoader.hasPendingSubtrees(viewport);
39784
+ const hasPendingWork = nodesToLoad.length > 0 || hasPendingSubtrees;
39785
+ if (budgetReached && needsCoverage && hasPendingWork) {
39786
+ resetSucceeded = eptLoader.resetLoadedData();
39787
+ if (resetSucceeded) {
39788
+ nodesToLoad = await eptLoader.selectNodesForViewport(viewport);
39789
+ }
39790
+ }
39791
+ if (!budgetReached && coverageRatio < 0.1 && nodesToLoad.length === 0) {
39792
+ nodesToLoad = await eptLoader.selectNodesForViewport(viewport);
39793
+ }
38733
39794
  for (const node of nodesToLoad) {
38734
39795
  eptLoader.queueNode(node);
38735
39796
  }
38736
39797
  await eptLoader.loadQueuedNodes();
39798
+ if (this._eptViewportRequestIds.get(datasetId) !== currentRequestId) return;
39799
+ if (budgetReached && needsCoverage && nodesToLoad.length > 0 && !resetSucceeded) {
39800
+ setTimeout(() => {
39801
+ this._handleViewportChangeForEptStreaming(viewport, datasetId, currentRequestId);
39802
+ }, 200);
39803
+ return;
39804
+ }
39805
+ if (hasPendingSubtrees) {
39806
+ setTimeout(() => {
39807
+ this._handleViewportChangeForEptStreaming(viewport, datasetId, currentRequestId);
39808
+ }, 100);
39809
+ }
38737
39810
  } catch (err) {
38738
39811
  console.warn("Failed to load EPT nodes for viewport:", err);
38739
39812
  }
@@ -38743,7 +39816,7 @@ class LidarControl {
38743
39816
  * Used as fallback when streaming fails due to CORS.
38744
39817
  */
38745
39818
  async _loadPointCloudFullDownload(url) {
38746
- var _a, _b, _c, _d, _e, _f, _g, _h;
39819
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
38747
39820
  const id = generateId("pc");
38748
39821
  const name = getFilename(url);
38749
39822
  try {
@@ -38830,6 +39903,8 @@ class LidarControl {
38830
39903
  zOffset: zOffset ?? this._state.zOffset,
38831
39904
  zOffsetEnabled
38832
39905
  });
39906
+ this._updateComputedColorBounds();
39907
+ (_i = this._panelBuilder) == null ? void 0 : _i.updateState(this._state);
38833
39908
  this._emitWithData("load", { pointCloud: info2 });
38834
39909
  if (this._options.autoZoom) {
38835
39910
  this.flyToPointCloud(id);
@@ -38926,6 +40001,8 @@ class LidarControl {
38926
40001
  eptLoader.destroy();
38927
40002
  }
38928
40003
  this._eptStreamingLoaders.clear();
40004
+ this._eptViewportRequestIds.clear();
40005
+ this._eptLastViewport.clear();
38929
40006
  for (const streamingId of streamingIds) {
38930
40007
  (_c = this._pointCloudManager) == null ? void 0 : _c.removePointCloud(streamingId);
38931
40008
  }
@@ -38994,16 +40071,84 @@ class LidarControl {
38994
40071
  }
38995
40072
  /**
38996
40073
  * Sets the color scheme.
40074
+ * Automatically switches colormap and resets color range when changing between elevation and intensity.
38997
40075
  *
38998
40076
  * @param scheme - Color scheme to apply
38999
40077
  */
39000
40078
  setColorScheme(scheme) {
39001
- var _a;
40079
+ var _a, _b, _c, _d, _e;
40080
+ const previousScheme = this._state.colorScheme;
39002
40081
  this._state.colorScheme = scheme;
39003
40082
  (_a = this._pointCloudManager) == null ? void 0 : _a.setColorScheme(scheme);
40083
+ if (typeof scheme === "string" && typeof previousScheme === "string") {
40084
+ const isNewElevationOrIntensity = scheme === "elevation" || scheme === "intensity";
40085
+ const wasElevationOrIntensity = previousScheme === "elevation" || previousScheme === "intensity";
40086
+ const switchedBetweenElevationAndIntensity = isNewElevationOrIntensity && wasElevationOrIntensity && scheme !== previousScheme;
40087
+ if (switchedBetweenElevationAndIntensity) {
40088
+ this._state.colorRange = {
40089
+ mode: "percentile",
40090
+ percentileLow: 2,
40091
+ percentileHigh: 98
40092
+ };
40093
+ (_b = this._pointCloudManager) == null ? void 0 : _b.setColorRange(this._state.colorRange);
40094
+ }
40095
+ if (scheme === "intensity" && previousScheme !== "intensity") {
40096
+ this._state.colormap = "gray";
40097
+ (_c = this._pointCloudManager) == null ? void 0 : _c.setColormap("gray");
40098
+ } else if (scheme === "elevation" && previousScheme === "intensity") {
40099
+ this._state.colormap = "viridis";
40100
+ (_d = this._pointCloudManager) == null ? void 0 : _d.setColormap("viridis");
40101
+ }
40102
+ }
40103
+ this._updateComputedColorBounds();
40104
+ (_e = this._panelBuilder) == null ? void 0 : _e.updateState(this._state);
40105
+ this._emit("stylechange");
40106
+ this._emit("statechange");
40107
+ }
40108
+ /**
40109
+ * Sets the colormap for elevation/intensity coloring.
40110
+ *
40111
+ * @param colormap - The colormap name (e.g., 'viridis', 'plasma', 'turbo')
40112
+ */
40113
+ setColormap(colormap) {
40114
+ var _a, _b;
40115
+ this._state.colormap = colormap;
40116
+ (_a = this._pointCloudManager) == null ? void 0 : _a.setColormap(colormap);
40117
+ this._updateComputedColorBounds();
40118
+ (_b = this._panelBuilder) == null ? void 0 : _b.updateState(this._state);
40119
+ this._emit("stylechange");
40120
+ this._emit("statechange");
40121
+ }
40122
+ /**
40123
+ * Gets the current colormap.
40124
+ *
40125
+ * @returns The current colormap name
40126
+ */
40127
+ getColormap() {
40128
+ return this._state.colormap;
40129
+ }
40130
+ /**
40131
+ * Sets the color range configuration.
40132
+ *
40133
+ * @param config - The color range configuration
40134
+ */
40135
+ setColorRange(config) {
40136
+ var _a, _b;
40137
+ this._state.colorRange = config;
40138
+ (_a = this._pointCloudManager) == null ? void 0 : _a.setColorRange(config);
40139
+ this._updateComputedColorBounds();
40140
+ (_b = this._panelBuilder) == null ? void 0 : _b.updateState(this._state);
39004
40141
  this._emit("stylechange");
39005
40142
  this._emit("statechange");
39006
40143
  }
40144
+ /**
40145
+ * Gets the current color range configuration.
40146
+ *
40147
+ * @returns The current color range configuration
40148
+ */
40149
+ getColorRange() {
40150
+ return this._state.colorRange;
40151
+ }
39007
40152
  /**
39008
40153
  * Sets whether to use percentile range for elevation/intensity coloring.
39009
40154
  *
@@ -39213,6 +40358,50 @@ class LidarControl {
39213
40358
  handlers.forEach((handler) => handler(eventData));
39214
40359
  }
39215
40360
  }
40361
+ /**
40362
+ * Updates the computed color bounds based on the current color scheme and range settings.
40363
+ * This is used to display accurate min/max values in the colorbar.
40364
+ */
40365
+ _updateComputedColorBounds() {
40366
+ var _a;
40367
+ if (this._state.pointClouds.length === 0) {
40368
+ this._state.computedColorBounds = void 0;
40369
+ return;
40370
+ }
40371
+ const actualBounds = (_a = this._pointCloudManager) == null ? void 0 : _a.getLastComputedBounds();
40372
+ if (actualBounds) {
40373
+ this._state.computedColorBounds = actualBounds;
40374
+ } else {
40375
+ const colorScheme = typeof this._state.colorScheme === "string" ? this._state.colorScheme : "elevation";
40376
+ if (colorScheme === "intensity") {
40377
+ this._state.computedColorBounds = this._getIntensityBounds();
40378
+ } else {
40379
+ this._state.computedColorBounds = this._getElevationBounds();
40380
+ }
40381
+ }
40382
+ }
40383
+ /**
40384
+ * Gets the elevation bounds from loaded point clouds.
40385
+ */
40386
+ _getElevationBounds() {
40387
+ if (this._state.pointClouds.length === 0) {
40388
+ return { min: 0, max: 100 };
40389
+ }
40390
+ let minZ = Infinity;
40391
+ let maxZ = -Infinity;
40392
+ for (const pc of this._state.pointClouds) {
40393
+ minZ = Math.min(minZ, pc.bounds.minZ);
40394
+ maxZ = Math.max(maxZ, pc.bounds.maxZ);
40395
+ }
40396
+ return { min: minZ, max: maxZ };
40397
+ }
40398
+ /**
40399
+ * Gets the intensity bounds from loaded point clouds.
40400
+ * Intensity values are typically normalized to 0-1 range.
40401
+ */
40402
+ _getIntensityBounds() {
40403
+ return { min: 0, max: 1 };
40404
+ }
39216
40405
  /**
39217
40406
  * Creates the main container element for the control.
39218
40407
  *
@@ -39273,6 +40462,8 @@ class LidarControl {
39273
40462
  onPointSizeChange: (size) => this.setPointSize(size),
39274
40463
  onOpacityChange: (opacity) => this.setOpacity(opacity),
39275
40464
  onColorSchemeChange: (scheme) => this.setColorScheme(scheme),
40465
+ onColormapChange: (colormap) => this.setColormap(colormap),
40466
+ onColorRangeChange: (config) => this.setColorRange(config),
39276
40467
  onUsePercentileChange: (usePercentile) => this.setUsePercentile(usePercentile),
39277
40468
  onElevationRangeChange: (range) => {
39278
40469
  if (range) {
@@ -39775,6 +40966,9 @@ class LidarLayerAdapter {
39775
40966
  this._changeCallbacks = [];
39776
40967
  }
39777
40968
  }
40969
+ exports.COLORMAPS = COLORMAPS;
40970
+ exports.COLORMAP_LABELS = COLORMAP_LABELS;
40971
+ exports.COLORMAP_NAMES = COLORMAP_NAMES;
39778
40972
  exports.ColorSchemeProcessor = ColorSchemeProcessor;
39779
40973
  exports.CopcStreamingLoader = CopcStreamingLoader;
39780
40974
  exports.DeckOverlay = DeckOverlay;
@@ -39792,6 +40986,7 @@ exports.formatNumber = formatNumber;
39792
40986
  exports.formatNumericValue = formatNumericValue;
39793
40987
  exports.generateId = generateId;
39794
40988
  exports.getClassificationName = getClassificationName;
40989
+ exports.getColormap = getColormap;
39795
40990
  exports.getFilename = getFilename;
39796
40991
  exports.throttle = throttle;
39797
- //# sourceMappingURL=LidarLayerAdapter-Sw_plY5a.cjs.map
40992
+ //# sourceMappingURL=LidarLayerAdapter-eh59KEMT.cjs.map