maplibre-gl-lidar 0.6.1 → 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.
- package/dist/{LidarLayerAdapter-BW2Hj7QW.cjs → LidarLayerAdapter-eh59KEMT.cjs} +1454 -169
- package/dist/LidarLayerAdapter-eh59KEMT.cjs.map +1 -0
- package/dist/{LidarLayerAdapter-BmGNLzm8.js → LidarLayerAdapter-kyyfQw05.js} +1463 -178
- package/dist/LidarLayerAdapter-kyyfQw05.js.map +1 -0
- package/dist/index.cjs +5 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +14 -10
- package/dist/maplibre-gl-lidar.css +119 -0
- package/dist/react.cjs +8 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +10 -3
- package/dist/react.mjs.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/colorizers/ColorScheme.d.ts +75 -6
- package/dist/types/lib/colorizers/ColorScheme.d.ts.map +1 -1
- package/dist/types/lib/colorizers/Colormaps.d.ts +25 -0
- package/dist/types/lib/colorizers/Colormaps.d.ts.map +1 -0
- package/dist/types/lib/core/LidarControl.d.ts +43 -1
- package/dist/types/lib/core/LidarControl.d.ts.map +1 -1
- package/dist/types/lib/core/types.d.ts +46 -0
- package/dist/types/lib/core/types.d.ts.map +1 -1
- package/dist/types/lib/gui/Colorbar.d.ts +86 -0
- package/dist/types/lib/gui/Colorbar.d.ts.map +1 -0
- package/dist/types/lib/gui/DualRangeSlider.d.ts +9 -0
- package/dist/types/lib/gui/DualRangeSlider.d.ts.map +1 -1
- package/dist/types/lib/gui/PanelBuilder.d.ts +31 -5
- package/dist/types/lib/gui/PanelBuilder.d.ts.map +1 -1
- package/dist/types/lib/gui/PercentileRangeControl.d.ts +113 -0
- package/dist/types/lib/gui/PercentileRangeControl.d.ts.map +1 -0
- package/dist/types/lib/gui/index.d.ts +4 -0
- package/dist/types/lib/gui/index.d.ts.map +1 -1
- package/dist/types/lib/hooks/useLidarState.d.ts.map +1 -1
- package/dist/types/lib/layers/PointCloudManager.d.ts +22 -1
- package/dist/types/lib/layers/PointCloudManager.d.ts.map +1 -1
- package/dist/types/lib/layers/types.d.ts +11 -1
- package/dist/types/lib/layers/types.d.ts.map +1 -1
- package/dist/types/lib/loaders/CopcStreamingLoader.d.ts.map +1 -1
- package/dist/types/lib/loaders/EptStreamingLoader.d.ts +44 -0
- package/dist/types/lib/loaders/EptStreamingLoader.d.ts.map +1 -1
- package/dist/types/lib/loaders/streaming-types.d.ts +6 -0
- package/dist/types/lib/loaders/streaming-types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/LidarLayerAdapter-BW2Hj7QW.cjs.map +0 -1
- package/dist/LidarLayerAdapter-BmGNLzm8.js.map +0 -1
|
@@ -34221,6 +34221,7 @@ class PointCloudLoader {
|
|
|
34221
34221
|
};
|
|
34222
34222
|
}
|
|
34223
34223
|
}
|
|
34224
|
+
proj4.defs("EPSG:2180", "+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs");
|
|
34224
34225
|
function createBufferGetter(buffer) {
|
|
34225
34226
|
const uint8 = new Uint8Array(buffer);
|
|
34226
34227
|
return async (begin, end) => {
|
|
@@ -34332,12 +34333,40 @@ function getVerticalUnitConversionFactor$1(wkt2) {
|
|
|
34332
34333
|
}
|
|
34333
34334
|
return 1;
|
|
34334
34335
|
}
|
|
34336
|
+
function clampLatLng$1(lng, lat, context = "") {
|
|
34337
|
+
let clampedLng = lng;
|
|
34338
|
+
let clampedLat = lat;
|
|
34339
|
+
let wasClamped = false;
|
|
34340
|
+
if (lat > 90) {
|
|
34341
|
+
clampedLat = 90;
|
|
34342
|
+
wasClamped = true;
|
|
34343
|
+
} else if (lat < -90) {
|
|
34344
|
+
clampedLat = -90;
|
|
34345
|
+
wasClamped = true;
|
|
34346
|
+
}
|
|
34347
|
+
if (lng > 180) {
|
|
34348
|
+
clampedLng = 180;
|
|
34349
|
+
wasClamped = true;
|
|
34350
|
+
} else if (lng < -180) {
|
|
34351
|
+
clampedLng = -180;
|
|
34352
|
+
wasClamped = true;
|
|
34353
|
+
}
|
|
34354
|
+
if (wasClamped) {
|
|
34355
|
+
console.warn(
|
|
34356
|
+
`COPC: Clamped transformed coordinates to valid WGS84 range${context ? ` (${context})` : ""}:`,
|
|
34357
|
+
`[${lng.toFixed(6)}, ${lat.toFixed(6)}] -> [${clampedLng.toFixed(6)}, ${clampedLat.toFixed(6)}]`
|
|
34358
|
+
);
|
|
34359
|
+
}
|
|
34360
|
+
return [clampedLng, clampedLat];
|
|
34361
|
+
}
|
|
34335
34362
|
const DEFAULT_OPTIONS$2 = {
|
|
34336
34363
|
pointBudget: 5e6,
|
|
34337
|
-
maxConcurrentRequests:
|
|
34338
|
-
viewportDebounceMs:
|
|
34364
|
+
maxConcurrentRequests: 8,
|
|
34365
|
+
viewportDebounceMs: 100,
|
|
34339
34366
|
minDetailZoom: 10,
|
|
34340
|
-
maxOctreeDepth: 20
|
|
34367
|
+
maxOctreeDepth: 20,
|
|
34368
|
+
maxSubtreesPerViewport: 60
|
|
34369
|
+
// Not used by COPC, but required by interface
|
|
34341
34370
|
};
|
|
34342
34371
|
class CopcStreamingLoader {
|
|
34343
34372
|
/**
|
|
@@ -34439,10 +34468,30 @@ class CopcStreamingLoader {
|
|
|
34439
34468
|
} catch (e) {
|
|
34440
34469
|
console.warn("Failed to setup coordinate transformation:", e);
|
|
34441
34470
|
}
|
|
34471
|
+
} else {
|
|
34472
|
+
const minX = header2.min[0];
|
|
34473
|
+
const minY = header2.min[1];
|
|
34474
|
+
const maxX = header2.max[0];
|
|
34475
|
+
const maxY = header2.max[1];
|
|
34476
|
+
let detectedEPSG = null;
|
|
34477
|
+
if (minX >= 1e5 && maxX <= 9e5 && minY >= 1e5 && maxY <= 8e5) {
|
|
34478
|
+
detectedEPSG = "EPSG:2180";
|
|
34479
|
+
}
|
|
34480
|
+
if (detectedEPSG) {
|
|
34481
|
+
try {
|
|
34482
|
+
const projConverter = proj4(detectedEPSG, "EPSG:4326");
|
|
34483
|
+
this._transformer = (coord) => projConverter.forward(coord);
|
|
34484
|
+
this._needsTransform = true;
|
|
34485
|
+
} catch (e) {
|
|
34486
|
+
console.warn(`Failed to setup coordinate transformation from ${detectedEPSG}:`, e);
|
|
34487
|
+
}
|
|
34488
|
+
}
|
|
34442
34489
|
}
|
|
34443
34490
|
if (this._needsTransform && this._transformer) {
|
|
34444
|
-
const [
|
|
34445
|
-
const [
|
|
34491
|
+
const [rawMinLng, rawMinLat] = this._transformer([header2.min[0], header2.min[1]]);
|
|
34492
|
+
const [rawMaxLng, rawMaxLat] = this._transformer([header2.max[0], header2.max[1]]);
|
|
34493
|
+
const [minLng, minLat] = clampLatLng$1(rawMinLng, rawMinLat, "header bounds min");
|
|
34494
|
+
const [maxLng, maxLat] = clampLatLng$1(rawMaxLng, rawMaxLat, "header bounds max");
|
|
34446
34495
|
this._bounds = {
|
|
34447
34496
|
minX: Math.min(minLng, maxLng),
|
|
34448
34497
|
minY: Math.min(minLat, maxLat),
|
|
@@ -34532,8 +34581,10 @@ class CopcStreamingLoader {
|
|
|
34532
34581
|
};
|
|
34533
34582
|
let boundsWgs84 = bounds2;
|
|
34534
34583
|
if (this._needsTransform && this._transformer) {
|
|
34535
|
-
const [
|
|
34536
|
-
const [
|
|
34584
|
+
const [rawSwLng, rawSwLat] = this._transformer([minX, minY]);
|
|
34585
|
+
const [rawNeLng, rawNeLat] = this._transformer([minX + nodeSize, minY + nodeSize]);
|
|
34586
|
+
const [sw_lng, sw_lat] = clampLatLng$1(rawSwLng, rawSwLat, "node bounds SW");
|
|
34587
|
+
const [ne_lng, ne_lat] = clampLatLng$1(rawNeLng, rawNeLat, "node bounds NE");
|
|
34537
34588
|
boundsWgs84 = {
|
|
34538
34589
|
minX: Math.min(sw_lng, ne_lng),
|
|
34539
34590
|
minY: Math.min(sw_lat, ne_lat),
|
|
@@ -34765,7 +34816,8 @@ class CopcStreamingLoader {
|
|
|
34765
34816
|
const y = yGetter(i);
|
|
34766
34817
|
const z = zGetter(i);
|
|
34767
34818
|
if (this._needsTransform && this._transformer) {
|
|
34768
|
-
const [
|
|
34819
|
+
const [rawLng, rawLat] = this._transformer([x, y]);
|
|
34820
|
+
const [lng, lat] = clampLatLng$1(rawLng, rawLat, "");
|
|
34769
34821
|
this._positions[pointIndex * 3] = lng - this._coordinateOrigin[0];
|
|
34770
34822
|
this._positions[pointIndex * 3 + 1] = lat - this._coordinateOrigin[1];
|
|
34771
34823
|
this._positions[pointIndex * 3 + 2] = z * this._verticalUnitFactor;
|
|
@@ -34986,6 +35038,32 @@ function createAttributeArray(type, length) {
|
|
|
34986
35038
|
return new Float32Array(length);
|
|
34987
35039
|
}
|
|
34988
35040
|
}
|
|
35041
|
+
function clampLatLng(lng, lat, context = "") {
|
|
35042
|
+
let clampedLng = lng;
|
|
35043
|
+
let clampedLat = lat;
|
|
35044
|
+
let wasClamped = false;
|
|
35045
|
+
if (lat > 90) {
|
|
35046
|
+
clampedLat = 90;
|
|
35047
|
+
wasClamped = true;
|
|
35048
|
+
} else if (lat < -90) {
|
|
35049
|
+
clampedLat = -90;
|
|
35050
|
+
wasClamped = true;
|
|
35051
|
+
}
|
|
35052
|
+
if (lng > 180) {
|
|
35053
|
+
clampedLng = 180;
|
|
35054
|
+
wasClamped = true;
|
|
35055
|
+
} else if (lng < -180) {
|
|
35056
|
+
clampedLng = -180;
|
|
35057
|
+
wasClamped = true;
|
|
35058
|
+
}
|
|
35059
|
+
if (wasClamped && context) {
|
|
35060
|
+
console.warn(
|
|
35061
|
+
`EPT: Clamped transformed coordinates to valid WGS84 range${context ? ` (${context})` : ""}:`,
|
|
35062
|
+
`[${lng.toFixed(6)}, ${lat.toFixed(6)}] -> [${clampedLng.toFixed(6)}, ${clampedLat.toFixed(6)}]`
|
|
35063
|
+
);
|
|
35064
|
+
}
|
|
35065
|
+
return [clampedLng, clampedLat];
|
|
35066
|
+
}
|
|
34989
35067
|
function extractProjcsFromWkt(wkt2) {
|
|
34990
35068
|
if (wkt2.startsWith("COMPD_CS[")) {
|
|
34991
35069
|
const projcsStart = wkt2.indexOf("PROJCS[");
|
|
@@ -35028,10 +35106,11 @@ function getVerticalUnitConversionFactor(wkt2) {
|
|
|
35028
35106
|
}
|
|
35029
35107
|
const DEFAULT_OPTIONS$1 = {
|
|
35030
35108
|
pointBudget: 5e6,
|
|
35031
|
-
maxConcurrentRequests:
|
|
35032
|
-
viewportDebounceMs:
|
|
35109
|
+
maxConcurrentRequests: 8,
|
|
35110
|
+
viewportDebounceMs: 100,
|
|
35033
35111
|
minDetailZoom: 10,
|
|
35034
|
-
maxOctreeDepth: 20
|
|
35112
|
+
maxOctreeDepth: 20,
|
|
35113
|
+
maxSubtreesPerViewport: 60
|
|
35035
35114
|
};
|
|
35036
35115
|
class EptStreamingLoader {
|
|
35037
35116
|
/**
|
|
@@ -35046,6 +35125,8 @@ class EptStreamingLoader {
|
|
|
35046
35125
|
__publicField(this, "_metadata", null);
|
|
35047
35126
|
// Hierarchy cache
|
|
35048
35127
|
__publicField(this, "_hierarchyCache", /* @__PURE__ */ new Map());
|
|
35128
|
+
__publicField(this, "_hierarchyLoading", /* @__PURE__ */ new Set());
|
|
35129
|
+
__publicField(this, "_hierarchyFailures", /* @__PURE__ */ new Map());
|
|
35049
35130
|
__publicField(this, "_subtreeRoots", /* @__PURE__ */ new Set());
|
|
35050
35131
|
__publicField(this, "_rootHierarchyLoaded", false);
|
|
35051
35132
|
// Node cache
|
|
@@ -35083,6 +35164,7 @@ class EptStreamingLoader {
|
|
|
35083
35164
|
__publicField(this, "_pendingLayerUpdate", false);
|
|
35084
35165
|
__publicField(this, "_updateBatchTimeout", null);
|
|
35085
35166
|
__publicField(this, "_onPointsLoaded");
|
|
35167
|
+
__publicField(this, "_isResetting", false);
|
|
35086
35168
|
this._baseUrl = eptUrl.endsWith("/ept.json") ? eptUrl.slice(0, -9) : eptUrl.replace(/\/$/, "");
|
|
35087
35169
|
this._options = { ...DEFAULT_OPTIONS$1, ...options };
|
|
35088
35170
|
}
|
|
@@ -35125,17 +35207,19 @@ class EptStreamingLoader {
|
|
|
35125
35207
|
}
|
|
35126
35208
|
const [minX, minY, minZ, maxX, maxY, maxZ] = this._metadata.boundsConforming;
|
|
35127
35209
|
if (this._needsTransform && this._transformer) {
|
|
35128
|
-
const [
|
|
35129
|
-
const [
|
|
35130
|
-
if (isNaN(
|
|
35210
|
+
const [rawMinLng, rawMinLat] = this._transformer([minX, minY]);
|
|
35211
|
+
const [rawMaxLng, rawMaxLat] = this._transformer([maxX, maxY]);
|
|
35212
|
+
if (isNaN(rawMinLng) || isNaN(rawMinLat) || isNaN(rawMaxLng) || isNaN(rawMaxLat) || !isFinite(rawMinLng) || !isFinite(rawMinLat) || !isFinite(rawMaxLng) || !isFinite(rawMaxLat)) {
|
|
35131
35213
|
console.error("EPT coordinate transformation produced invalid bounds:", {
|
|
35132
35214
|
input: { minX, minY, maxX, maxY },
|
|
35133
|
-
output: {
|
|
35215
|
+
output: { rawMinLng, rawMinLat, rawMaxLng, rawMaxLat }
|
|
35134
35216
|
});
|
|
35135
35217
|
this._bounds = { minX, minY, minZ, maxX, maxY, maxZ };
|
|
35136
35218
|
this._needsTransform = false;
|
|
35137
35219
|
this._transformer = null;
|
|
35138
35220
|
} else {
|
|
35221
|
+
const [minLng, minLat] = clampLatLng(rawMinLng, rawMinLat, "header bounds min");
|
|
35222
|
+
const [maxLng, maxLat] = clampLatLng(rawMaxLng, rawMaxLat, "header bounds max");
|
|
35139
35223
|
this._bounds = {
|
|
35140
35224
|
minX: Math.min(minLng, maxLng),
|
|
35141
35225
|
minY: Math.min(minLat, maxLat),
|
|
@@ -35286,8 +35370,10 @@ class EptStreamingLoader {
|
|
|
35286
35370
|
};
|
|
35287
35371
|
let boundsWgs84 = bounds2;
|
|
35288
35372
|
if (this._needsTransform && this._transformer) {
|
|
35289
|
-
const [
|
|
35290
|
-
const [
|
|
35373
|
+
const [rawSwLng, rawSwLat] = this._transformer([minX, minY]);
|
|
35374
|
+
const [rawNeLng, rawNeLat] = this._transformer([minX + nodeSize, minY + nodeSize]);
|
|
35375
|
+
const [sw_lng, sw_lat] = clampLatLng(rawSwLng, rawSwLat, "node bounds SW");
|
|
35376
|
+
const [ne_lng, ne_lat] = clampLatLng(rawNeLng, rawNeLat, "node bounds NE");
|
|
35291
35377
|
boundsWgs84 = {
|
|
35292
35378
|
minX: Math.min(sw_lng, ne_lng),
|
|
35293
35379
|
minY: Math.min(sw_lat, ne_lat),
|
|
@@ -35338,22 +35424,26 @@ class EptStreamingLoader {
|
|
|
35338
35424
|
* @param key - Hierarchy key (e.g., "0-0-0-0")
|
|
35339
35425
|
*/
|
|
35340
35426
|
async _loadHierarchy(key) {
|
|
35341
|
-
if (this._hierarchyCache.has(key)) return;
|
|
35427
|
+
if (this._hierarchyCache.has(key) || this._hierarchyLoading.has(key)) return;
|
|
35342
35428
|
const url = `${this._baseUrl}/ept-hierarchy/${key}.json`;
|
|
35429
|
+
this._hierarchyLoading.add(key);
|
|
35343
35430
|
try {
|
|
35344
35431
|
const response = await fetch(url);
|
|
35345
35432
|
if (!response.ok) {
|
|
35433
|
+
this._hierarchyFailures.set(key, Date.now());
|
|
35346
35434
|
console.warn(`Failed to load hierarchy ${key}: ${response.status}`);
|
|
35347
35435
|
return;
|
|
35348
35436
|
}
|
|
35349
35437
|
const hierarchy2 = await response.json();
|
|
35350
35438
|
this._hierarchyCache.set(key, hierarchy2);
|
|
35439
|
+
this._hierarchyFailures.delete(key);
|
|
35351
35440
|
for (const [nodeKey, value] of Object.entries(hierarchy2)) {
|
|
35352
35441
|
const keyArray = this._parseNodeKey(nodeKey);
|
|
35353
35442
|
const { bounds: bounds2, boundsWgs84 } = this._calculateNodeBounds(keyArray);
|
|
35443
|
+
const existingNode = this._nodeCache.get(nodeKey);
|
|
35354
35444
|
if (value === -1) {
|
|
35355
35445
|
this._subtreeRoots.add(nodeKey);
|
|
35356
|
-
if (!
|
|
35446
|
+
if (!existingNode) {
|
|
35357
35447
|
this._nodeCache.set(nodeKey, {
|
|
35358
35448
|
key: nodeKey,
|
|
35359
35449
|
keyArray,
|
|
@@ -35364,19 +35454,29 @@ class EptStreamingLoader {
|
|
|
35364
35454
|
boundsWgs84
|
|
35365
35455
|
});
|
|
35366
35456
|
}
|
|
35367
|
-
} else if (value > 0
|
|
35368
|
-
|
|
35369
|
-
|
|
35370
|
-
|
|
35371
|
-
|
|
35372
|
-
|
|
35373
|
-
|
|
35374
|
-
|
|
35375
|
-
|
|
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
|
+
}
|
|
35376
35473
|
}
|
|
35377
35474
|
}
|
|
35378
35475
|
} catch (error) {
|
|
35476
|
+
this._hierarchyFailures.set(key, Date.now());
|
|
35379
35477
|
console.warn(`Error loading hierarchy ${key}:`, error);
|
|
35478
|
+
} finally {
|
|
35479
|
+
this._hierarchyLoading.delete(key);
|
|
35380
35480
|
}
|
|
35381
35481
|
}
|
|
35382
35482
|
/**
|
|
@@ -35401,22 +35501,31 @@ class EptStreamingLoader {
|
|
|
35401
35501
|
}
|
|
35402
35502
|
await this._ensureHierarchyLoaded();
|
|
35403
35503
|
const targetDepth = viewport.targetDepth;
|
|
35404
|
-
const
|
|
35405
|
-
|
|
35406
|
-
|
|
35407
|
-
|
|
35408
|
-
|
|
35409
|
-
|
|
35410
|
-
|
|
35411
|
-
|
|
35412
|
-
|
|
35413
|
-
|
|
35414
|
-
|
|
35415
|
-
|
|
35416
|
-
|
|
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;
|
|
35417
35527
|
}
|
|
35418
35528
|
const nodesToLoad = [];
|
|
35419
|
-
const now = Date.now();
|
|
35420
35529
|
const retryCooldownMs = 5e3;
|
|
35421
35530
|
for (const [, node] of this._nodeCache) {
|
|
35422
35531
|
const depth = node.keyArray[0];
|
|
@@ -35425,7 +35534,7 @@ class EptStreamingLoader {
|
|
|
35425
35534
|
if (node.lastFailedAt && now - node.lastFailedAt < retryCooldownMs) {
|
|
35426
35535
|
continue;
|
|
35427
35536
|
}
|
|
35428
|
-
if (depth > targetDepth +
|
|
35537
|
+
if (depth > targetDepth + 2) continue;
|
|
35429
35538
|
if (!this._boundsIntersectsViewport(node.boundsWgs84, viewport)) {
|
|
35430
35539
|
continue;
|
|
35431
35540
|
}
|
|
@@ -35571,7 +35680,8 @@ class EptStreamingLoader {
|
|
|
35571
35680
|
const y = positions[i * 3 + 1];
|
|
35572
35681
|
const z = positions[i * 3 + 2];
|
|
35573
35682
|
if (this._needsTransform && this._transformer) {
|
|
35574
|
-
const [
|
|
35683
|
+
const [rawLng, rawLat] = this._transformer([x, y]);
|
|
35684
|
+
const [lng, lat] = clampLatLng(rawLng, rawLat, "");
|
|
35575
35685
|
this._positions[pointIndex * 3] = lng - this._coordinateOrigin[0];
|
|
35576
35686
|
this._positions[pointIndex * 3 + 1] = lat - this._coordinateOrigin[1];
|
|
35577
35687
|
this._positions[pointIndex * 3 + 2] = z * this._verticalUnitFactor;
|
|
@@ -35651,7 +35761,8 @@ class EptStreamingLoader {
|
|
|
35651
35761
|
const y = yGetter(dataView, byteOffset);
|
|
35652
35762
|
const z = zGetter(dataView, byteOffset);
|
|
35653
35763
|
if (this._needsTransform && this._transformer) {
|
|
35654
|
-
const [
|
|
35764
|
+
const [rawLng, rawLat] = this._transformer([x, y]);
|
|
35765
|
+
const [lng, lat] = clampLatLng(rawLng, rawLat, "");
|
|
35655
35766
|
this._positions[pointIndex * 3] = lng - this._coordinateOrigin[0];
|
|
35656
35767
|
this._positions[pointIndex * 3 + 1] = lat - this._coordinateOrigin[1];
|
|
35657
35768
|
this._positions[pointIndex * 3 + 2] = z * this._verticalUnitFactor;
|
|
@@ -35744,6 +35855,27 @@ class EptStreamingLoader {
|
|
|
35744
35855
|
wkt: (_c = (_b = this._metadata) == null ? void 0 : _b.srs) == null ? void 0 : _c.wkt
|
|
35745
35856
|
};
|
|
35746
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
|
+
}
|
|
35747
35879
|
/**
|
|
35748
35880
|
* Gets the current streaming progress.
|
|
35749
35881
|
*/
|
|
@@ -35799,6 +35931,60 @@ class EptStreamingLoader {
|
|
|
35799
35931
|
getLoadedPointCount() {
|
|
35800
35932
|
return this._totalLoadedPoints;
|
|
35801
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
|
+
}
|
|
35802
35988
|
/**
|
|
35803
35989
|
* Gets the total number of loaded nodes.
|
|
35804
35990
|
*/
|
|
@@ -35811,6 +35997,48 @@ class EptStreamingLoader {
|
|
|
35811
35997
|
isLoading() {
|
|
35812
35998
|
return this._activeRequests > 0 || this._loadingQueue.length > 0;
|
|
35813
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
|
+
}
|
|
35814
36042
|
/**
|
|
35815
36043
|
* Gets the EPT metadata.
|
|
35816
36044
|
*/
|
|
@@ -35827,6 +36055,8 @@ class EptStreamingLoader {
|
|
|
35827
36055
|
this._loadingQueue = [];
|
|
35828
36056
|
this._nodeCache.clear();
|
|
35829
36057
|
this._hierarchyCache.clear();
|
|
36058
|
+
this._hierarchyLoading.clear();
|
|
36059
|
+
this._hierarchyFailures.clear();
|
|
35830
36060
|
this._subtreeRoots.clear();
|
|
35831
36061
|
this._eventHandlers.clear();
|
|
35832
36062
|
this._positions = null;
|
|
@@ -35924,37 +36154,180 @@ function computePercentileBounds(arr, lowerPercentile = 2, upperPercentile = 98)
|
|
|
35924
36154
|
}
|
|
35925
36155
|
return { min, max };
|
|
35926
36156
|
}
|
|
35927
|
-
const
|
|
36157
|
+
const VIRIDIS = [
|
|
35928
36158
|
[68, 1, 84],
|
|
35929
|
-
// dark purple
|
|
35930
36159
|
[72, 40, 120],
|
|
35931
|
-
// purple
|
|
35932
36160
|
[62, 74, 137],
|
|
35933
|
-
// blue-purple
|
|
35934
36161
|
[49, 104, 142],
|
|
35935
|
-
// blue
|
|
35936
36162
|
[38, 130, 142],
|
|
35937
|
-
// teal-blue
|
|
35938
36163
|
[31, 158, 137],
|
|
35939
|
-
// teal
|
|
35940
36164
|
[53, 183, 121],
|
|
35941
|
-
// green-teal
|
|
35942
36165
|
[109, 205, 89],
|
|
35943
|
-
// green
|
|
35944
36166
|
[180, 222, 44],
|
|
35945
|
-
// yellow-green
|
|
35946
36167
|
[253, 231, 37]
|
|
35947
|
-
// yellow
|
|
35948
36168
|
];
|
|
35949
|
-
const
|
|
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 = [
|
|
35950
36278
|
[0, 0, 0],
|
|
35951
|
-
|
|
35952
|
-
[
|
|
35953
|
-
[
|
|
35954
|
-
[
|
|
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],
|
|
35955
36287
|
[255, 255, 255]
|
|
35956
|
-
// white
|
|
35957
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
|
+
}
|
|
35958
36331
|
const CLASSIFICATION_COLORS = {
|
|
35959
36332
|
0: [128, 128, 128],
|
|
35960
36333
|
// Created, never classified
|
|
@@ -35996,6 +36369,10 @@ const CLASSIFICATION_COLORS = {
|
|
|
35996
36369
|
// High Noise
|
|
35997
36370
|
};
|
|
35998
36371
|
class ColorSchemeProcessor {
|
|
36372
|
+
constructor() {
|
|
36373
|
+
/** Last computed color bounds (for colorbar display) */
|
|
36374
|
+
__publicField(this, "_lastComputedBounds");
|
|
36375
|
+
}
|
|
35999
36376
|
/**
|
|
36000
36377
|
* Generates a color array for the point cloud based on the color scheme.
|
|
36001
36378
|
*
|
|
@@ -36005,100 +36382,150 @@ class ColorSchemeProcessor {
|
|
|
36005
36382
|
* @returns Uint8Array of RGBA colors (length = pointCount * 4)
|
|
36006
36383
|
*/
|
|
36007
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 = {}) {
|
|
36008
36397
|
const colors = new Uint8Array(data.pointCount * 4);
|
|
36398
|
+
const colormap = options.colormap ?? "viridis";
|
|
36399
|
+
const colorRange = options.colorRange;
|
|
36009
36400
|
const usePercentile = options.usePercentile ?? true;
|
|
36010
36401
|
if (typeof scheme === "string") {
|
|
36011
36402
|
switch (scheme) {
|
|
36012
36403
|
case "elevation":
|
|
36013
|
-
return this._colorByElevation(data, colors, usePercentile);
|
|
36404
|
+
return this._colorByElevation(data, colors, colormap, colorRange, usePercentile);
|
|
36014
36405
|
case "intensity":
|
|
36015
|
-
return this._colorByIntensity(data, colors, usePercentile);
|
|
36406
|
+
return this._colorByIntensity(data, colors, colormap, colorRange, usePercentile);
|
|
36016
36407
|
case "classification":
|
|
36017
|
-
return this._colorByClassification(data, colors, options.hiddenClassifications);
|
|
36408
|
+
return { colors: this._colorByClassification(data, colors, options.hiddenClassifications) };
|
|
36018
36409
|
case "rgb":
|
|
36019
|
-
return this._colorByRGB(data, colors);
|
|
36410
|
+
return { colors: this._colorByRGB(data, colors) };
|
|
36020
36411
|
default:
|
|
36021
|
-
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);
|
|
36022
36446
|
}
|
|
36447
|
+
} else if (usePercentile) {
|
|
36448
|
+
return computePercentileBounds(values, 2, 98);
|
|
36023
36449
|
} else {
|
|
36024
|
-
return
|
|
36450
|
+
return dataBounds;
|
|
36025
36451
|
}
|
|
36026
36452
|
}
|
|
36027
36453
|
/**
|
|
36028
|
-
* Colors points by elevation using
|
|
36454
|
+
* Colors points by elevation using the specified colormap.
|
|
36029
36455
|
*
|
|
36030
36456
|
* @param data - Point cloud data
|
|
36031
36457
|
* @param colors - Output color array
|
|
36032
|
-
* @param
|
|
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
|
|
36033
36462
|
*/
|
|
36034
|
-
_colorByElevation(data, colors, usePercentile) {
|
|
36463
|
+
_colorByElevation(data, colors, colormap, colorRange, usePercentile) {
|
|
36035
36464
|
var _a, _b;
|
|
36036
36465
|
if (!data.positions || data.positions.length === 0) {
|
|
36037
|
-
return colors;
|
|
36038
|
-
}
|
|
36039
|
-
let minZ;
|
|
36040
|
-
let maxZ;
|
|
36041
|
-
if (usePercentile) {
|
|
36042
|
-
const zValues = new Float32Array(data.pointCount);
|
|
36043
|
-
for (let i = 0; i < data.pointCount; i++) {
|
|
36044
|
-
zValues[i] = data.positions[i * 3 + 2] ?? 0;
|
|
36045
|
-
}
|
|
36046
|
-
const bounds2 = computePercentileBounds(zValues, 2, 98);
|
|
36047
|
-
minZ = bounds2.min;
|
|
36048
|
-
maxZ = bounds2.max;
|
|
36049
|
-
} else {
|
|
36050
|
-
minZ = ((_a = data.bounds) == null ? void 0 : _a.minZ) ?? 0;
|
|
36051
|
-
maxZ = ((_b = data.bounds) == null ? void 0 : _b.maxZ) ?? 1;
|
|
36466
|
+
return { colors };
|
|
36052
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;
|
|
36053
36479
|
const range = maxZ - minZ || 1;
|
|
36480
|
+
const ramp = COLORMAPS[colormap] || COLORMAPS.viridis;
|
|
36054
36481
|
for (let i = 0; i < data.pointCount; i++) {
|
|
36055
|
-
const z =
|
|
36482
|
+
const z = zValues[i];
|
|
36056
36483
|
const t = (z - minZ) / range;
|
|
36057
|
-
const color = this._interpolateRamp(
|
|
36484
|
+
const color = this._interpolateRamp(ramp, t);
|
|
36058
36485
|
colors[i * 4] = color[0];
|
|
36059
36486
|
colors[i * 4 + 1] = color[1];
|
|
36060
36487
|
colors[i * 4 + 2] = color[2];
|
|
36061
36488
|
colors[i * 4 + 3] = 255;
|
|
36062
36489
|
}
|
|
36063
|
-
return colors;
|
|
36490
|
+
return { colors, bounds: bounds2 };
|
|
36064
36491
|
}
|
|
36065
36492
|
/**
|
|
36066
|
-
* Colors points by intensity using
|
|
36493
|
+
* Colors points by intensity using the specified colormap.
|
|
36067
36494
|
*
|
|
36068
36495
|
* @param data - Point cloud data
|
|
36069
36496
|
* @param colors - Output color array
|
|
36070
|
-
* @param
|
|
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
|
|
36071
36501
|
*/
|
|
36072
|
-
_colorByIntensity(data, colors, usePercentile) {
|
|
36502
|
+
_colorByIntensity(data, colors, colormap, colorRange, usePercentile) {
|
|
36073
36503
|
if (!data.hasIntensity || !data.intensities) {
|
|
36074
|
-
return this._colorByElevation(data, colors, usePercentile);
|
|
36075
|
-
}
|
|
36076
|
-
let minI;
|
|
36077
|
-
let maxI;
|
|
36078
|
-
if (usePercentile) {
|
|
36079
|
-
const bounds2 = computePercentileBounds(data.intensities, 2, 98);
|
|
36080
|
-
minI = bounds2.min;
|
|
36081
|
-
maxI = bounds2.max;
|
|
36082
|
-
} else {
|
|
36083
|
-
minI = Infinity;
|
|
36084
|
-
maxI = -Infinity;
|
|
36085
|
-
for (let i = 0; i < data.pointCount; i++) {
|
|
36086
|
-
const intensity = data.intensities[i];
|
|
36087
|
-
if (intensity < minI) minI = intensity;
|
|
36088
|
-
if (intensity > maxI) maxI = intensity;
|
|
36089
|
-
}
|
|
36504
|
+
return this._colorByElevation(data, colors, colormap, colorRange, usePercentile);
|
|
36090
36505
|
}
|
|
36091
|
-
|
|
36506
|
+
let minI = Infinity;
|
|
36507
|
+
let maxI = -Infinity;
|
|
36092
36508
|
for (let i = 0; i < data.pointCount; i++) {
|
|
36093
36509
|
const intensity = data.intensities[i];
|
|
36094
|
-
|
|
36095
|
-
|
|
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);
|
|
36096
36523
|
colors[i * 4] = color[0];
|
|
36097
36524
|
colors[i * 4 + 1] = color[1];
|
|
36098
36525
|
colors[i * 4 + 2] = color[2];
|
|
36099
36526
|
colors[i * 4 + 3] = 255;
|
|
36100
36527
|
}
|
|
36101
|
-
return colors;
|
|
36528
|
+
return { colors, bounds: bounds2 };
|
|
36102
36529
|
}
|
|
36103
36530
|
/**
|
|
36104
36531
|
* Colors points by classification using ASPRS standard colors.
|
|
@@ -36106,10 +36533,12 @@ class ColorSchemeProcessor {
|
|
|
36106
36533
|
* @param data - Point cloud data
|
|
36107
36534
|
* @param colors - Output color array
|
|
36108
36535
|
* @param hiddenClassifications - Optional set of classification codes to hide (alpha=0)
|
|
36536
|
+
* @returns Color array
|
|
36109
36537
|
*/
|
|
36110
36538
|
_colorByClassification(data, colors, hiddenClassifications) {
|
|
36111
36539
|
if (!data.hasClassification || !data.classifications) {
|
|
36112
|
-
|
|
36540
|
+
const result = this._colorByElevation(data, colors, "viridis", void 0, true);
|
|
36541
|
+
return result.colors;
|
|
36113
36542
|
}
|
|
36114
36543
|
for (let i = 0; i < data.pointCount; i++) {
|
|
36115
36544
|
const cls = data.classifications[i];
|
|
@@ -36123,10 +36552,15 @@ class ColorSchemeProcessor {
|
|
|
36123
36552
|
}
|
|
36124
36553
|
/**
|
|
36125
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
|
|
36126
36559
|
*/
|
|
36127
36560
|
_colorByRGB(data, colors) {
|
|
36128
36561
|
if (!data.hasRGB || !data.colors) {
|
|
36129
|
-
|
|
36562
|
+
const result = this._colorByElevation(data, colors, "viridis", void 0, true);
|
|
36563
|
+
return result.colors;
|
|
36130
36564
|
}
|
|
36131
36565
|
for (let i = 0; i < data.pointCount; i++) {
|
|
36132
36566
|
colors[i * 4] = data.colors[i * 4];
|
|
@@ -36138,12 +36572,25 @@ class ColorSchemeProcessor {
|
|
|
36138
36572
|
}
|
|
36139
36573
|
/**
|
|
36140
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
|
|
36141
36583
|
*/
|
|
36142
|
-
_colorByCustom(data, colors, _config) {
|
|
36143
|
-
|
|
36584
|
+
_colorByCustom(data, colors, _config, colormap, colorRange, usePercentile) {
|
|
36585
|
+
const result = this._colorByElevation(data, colors, colormap, colorRange, usePercentile);
|
|
36586
|
+
return result.colors;
|
|
36144
36587
|
}
|
|
36145
36588
|
/**
|
|
36146
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
|
|
36147
36594
|
*/
|
|
36148
36595
|
_interpolateRamp(ramp, t) {
|
|
36149
36596
|
if (!Number.isFinite(t)) {
|
|
@@ -36201,6 +36648,7 @@ class PointCloudManager {
|
|
|
36201
36648
|
__publicField(this, "_pointClouds");
|
|
36202
36649
|
__publicField(this, "_options");
|
|
36203
36650
|
__publicField(this, "_colorProcessor");
|
|
36651
|
+
__publicField(this, "_lastComputedBounds");
|
|
36204
36652
|
this._deckOverlay = deckOverlay;
|
|
36205
36653
|
this._pointClouds = /* @__PURE__ */ new Map();
|
|
36206
36654
|
this._colorProcessor = new ColorSchemeProcessor();
|
|
@@ -36209,6 +36657,8 @@ class PointCloudManager {
|
|
|
36209
36657
|
opacity: options.opacity ?? 1,
|
|
36210
36658
|
colorScheme: options.colorScheme ?? "elevation",
|
|
36211
36659
|
usePercentile: options.usePercentile ?? true,
|
|
36660
|
+
colormap: options.colormap ?? "viridis",
|
|
36661
|
+
colorRange: options.colorRange,
|
|
36212
36662
|
elevationRange: options.elevationRange ?? null,
|
|
36213
36663
|
pickable: options.pickable ?? false,
|
|
36214
36664
|
zOffset: options.zOffset ?? 0,
|
|
@@ -36230,15 +36680,20 @@ class PointCloudManager {
|
|
|
36230
36680
|
* @param data - Point cloud data (positions are already offsets from coordinateOrigin)
|
|
36231
36681
|
*/
|
|
36232
36682
|
addPointCloud(id, data) {
|
|
36233
|
-
const
|
|
36683
|
+
const result = this._colorProcessor.getColorsWithBounds(data, this._options.colorScheme, {
|
|
36234
36684
|
usePercentile: this._options.usePercentile,
|
|
36685
|
+
colormap: this._options.colormap,
|
|
36686
|
+
colorRange: this._options.colorRange,
|
|
36235
36687
|
hiddenClassifications: this._options.hiddenClassifications
|
|
36236
36688
|
});
|
|
36689
|
+
if (result.bounds) {
|
|
36690
|
+
this._lastComputedBounds = result.bounds;
|
|
36691
|
+
}
|
|
36237
36692
|
const coordinateOrigin = data.coordinateOrigin;
|
|
36238
36693
|
this._pointClouds.set(id, {
|
|
36239
36694
|
id,
|
|
36240
36695
|
data,
|
|
36241
|
-
colors,
|
|
36696
|
+
colors: result.colors,
|
|
36242
36697
|
coordinateOrigin,
|
|
36243
36698
|
visible: true,
|
|
36244
36699
|
opacityOverride: null
|
|
@@ -36258,14 +36713,19 @@ class PointCloudManager {
|
|
|
36258
36713
|
}
|
|
36259
36714
|
const existing = this._pointClouds.get(id);
|
|
36260
36715
|
if (existing) {
|
|
36261
|
-
const
|
|
36716
|
+
const result = this._colorProcessor.getColorsWithBounds(data, this._options.colorScheme, {
|
|
36262
36717
|
usePercentile: this._options.usePercentile,
|
|
36718
|
+
colormap: this._options.colormap,
|
|
36719
|
+
colorRange: this._options.colorRange,
|
|
36263
36720
|
hiddenClassifications: this._options.hiddenClassifications
|
|
36264
36721
|
});
|
|
36722
|
+
if (result.bounds) {
|
|
36723
|
+
this._lastComputedBounds = result.bounds;
|
|
36724
|
+
}
|
|
36265
36725
|
this._pointClouds.set(id, {
|
|
36266
36726
|
id,
|
|
36267
36727
|
data,
|
|
36268
|
-
colors,
|
|
36728
|
+
colors: result.colors,
|
|
36269
36729
|
coordinateOrigin: data.coordinateOrigin,
|
|
36270
36730
|
visible: existing.visible,
|
|
36271
36731
|
opacityOverride: existing.opacityOverride
|
|
@@ -36336,17 +36796,24 @@ class PointCloudManager {
|
|
|
36336
36796
|
updateStyle(options) {
|
|
36337
36797
|
const colorSchemeChanged = options.colorScheme !== void 0 && options.colorScheme !== this._options.colorScheme;
|
|
36338
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;
|
|
36339
36801
|
const hiddenClassificationsChanged = options.hiddenClassifications !== void 0;
|
|
36340
36802
|
this._options = { ...this._options, ...options };
|
|
36341
|
-
if (colorSchemeChanged || percentileChanged || hiddenClassificationsChanged) {
|
|
36803
|
+
if (colorSchemeChanged || percentileChanged || colormapChanged || colorRangeChanged || hiddenClassificationsChanged) {
|
|
36342
36804
|
for (const [id, pc] of this._pointClouds) {
|
|
36343
|
-
const
|
|
36805
|
+
const result = this._colorProcessor.getColorsWithBounds(pc.data, this._options.colorScheme, {
|
|
36344
36806
|
usePercentile: this._options.usePercentile,
|
|
36807
|
+
colormap: this._options.colormap,
|
|
36808
|
+
colorRange: this._options.colorRange,
|
|
36345
36809
|
hiddenClassifications: this._options.hiddenClassifications
|
|
36346
36810
|
});
|
|
36811
|
+
if (result.bounds) {
|
|
36812
|
+
this._lastComputedBounds = result.bounds;
|
|
36813
|
+
}
|
|
36347
36814
|
this._pointClouds.set(id, {
|
|
36348
36815
|
...pc,
|
|
36349
|
-
colors,
|
|
36816
|
+
colors: result.colors,
|
|
36350
36817
|
coordinateOrigin: pc.coordinateOrigin,
|
|
36351
36818
|
visible: pc.visible,
|
|
36352
36819
|
opacityOverride: pc.opacityOverride
|
|
@@ -36393,6 +36860,22 @@ class PointCloudManager {
|
|
|
36393
36860
|
setUsePercentile(usePercentile) {
|
|
36394
36861
|
this.updateStyle({ usePercentile });
|
|
36395
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
|
+
}
|
|
36396
36879
|
/**
|
|
36397
36880
|
* Sets the elevation range filter.
|
|
36398
36881
|
*
|
|
@@ -36491,6 +36974,13 @@ class PointCloudManager {
|
|
|
36491
36974
|
getOptions() {
|
|
36492
36975
|
return { ...this._options };
|
|
36493
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
|
+
}
|
|
36494
36984
|
/**
|
|
36495
36985
|
* Creates a deck.gl layer for a point cloud.
|
|
36496
36986
|
* Chunks large point clouds into multiple layers to avoid WebGL buffer limits.
|
|
@@ -36979,6 +37469,7 @@ class DualRangeSlider {
|
|
|
36979
37469
|
__publicField(this, "_sliderLow");
|
|
36980
37470
|
__publicField(this, "_sliderHigh");
|
|
36981
37471
|
__publicField(this, "_valueDisplay");
|
|
37472
|
+
__publicField(this, "_rangeHighlight");
|
|
36982
37473
|
this._options = options;
|
|
36983
37474
|
}
|
|
36984
37475
|
/**
|
|
@@ -37026,6 +37517,7 @@ class DualRangeSlider {
|
|
|
37026
37517
|
background: #159895;
|
|
37027
37518
|
border-radius: 2px;
|
|
37028
37519
|
`;
|
|
37520
|
+
this._rangeHighlight = range;
|
|
37029
37521
|
sliderContainer.appendChild(range);
|
|
37030
37522
|
const sliderLow = document.createElement("input");
|
|
37031
37523
|
sliderLow.type = "range";
|
|
@@ -37143,6 +37635,7 @@ class DualRangeSlider {
|
|
|
37143
37635
|
if (this._valueDisplay) {
|
|
37144
37636
|
this._valueDisplay.textContent = this._formatRange(low, high);
|
|
37145
37637
|
}
|
|
37638
|
+
this._updateRangeHighlight();
|
|
37146
37639
|
}
|
|
37147
37640
|
/**
|
|
37148
37641
|
* Updates the min/max bounds of the slider.
|
|
@@ -37158,6 +37651,33 @@ class DualRangeSlider {
|
|
|
37158
37651
|
this._sliderHigh.min = String(min);
|
|
37159
37652
|
this._sliderHigh.max = String(max);
|
|
37160
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}%`;
|
|
37161
37681
|
}
|
|
37162
37682
|
/**
|
|
37163
37683
|
* Gets the current range values.
|
|
@@ -37310,6 +37830,457 @@ class ClassificationLegend {
|
|
|
37310
37830
|
return this._container;
|
|
37311
37831
|
}
|
|
37312
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
|
+
}
|
|
37313
38284
|
class PanelBuilder {
|
|
37314
38285
|
constructor(callbacks, initialState) {
|
|
37315
38286
|
__publicField(this, "_callbacks");
|
|
@@ -37320,6 +38291,12 @@ class PanelBuilder {
|
|
|
37320
38291
|
__publicField(this, "_urlInput");
|
|
37321
38292
|
__publicField(this, "_loadButton");
|
|
37322
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");
|
|
37323
38300
|
__publicField(this, "_percentileCheckbox");
|
|
37324
38301
|
__publicField(this, "_percentileGroup");
|
|
37325
38302
|
__publicField(this, "_pointSizeSlider");
|
|
@@ -37395,6 +38372,25 @@ class PanelBuilder {
|
|
|
37395
38372
|
this._colorSelect.value = state.colorScheme;
|
|
37396
38373
|
this._updatePercentileVisibility(state.colorScheme);
|
|
37397
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
|
+
}
|
|
37398
38394
|
if (this._percentileCheckbox) {
|
|
37399
38395
|
this._percentileCheckbox.checked = state.usePercentile ?? true;
|
|
37400
38396
|
}
|
|
@@ -37535,8 +38531,10 @@ class PanelBuilder {
|
|
|
37535
38531
|
this._colorSelect = colorSelect;
|
|
37536
38532
|
colorGroup.appendChild(colorSelect);
|
|
37537
38533
|
section.appendChild(colorGroup);
|
|
38534
|
+
section.appendChild(this._buildColormapSelector());
|
|
38535
|
+
section.appendChild(this._buildColorbar());
|
|
37538
38536
|
section.appendChild(this._buildClassificationLegend());
|
|
37539
|
-
section.appendChild(this.
|
|
38537
|
+
section.appendChild(this._buildColorRangeControl());
|
|
37540
38538
|
this._pointSizeSlider = new RangeSlider({
|
|
37541
38539
|
label: "Point Size",
|
|
37542
38540
|
min: 1,
|
|
@@ -37721,46 +38719,120 @@ class PanelBuilder {
|
|
|
37721
38719
|
return { min: minZ, max: maxZ };
|
|
37722
38720
|
}
|
|
37723
38721
|
/**
|
|
37724
|
-
*
|
|
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.
|
|
37725
38730
|
*/
|
|
37726
|
-
|
|
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.
|
|
38740
|
+
*/
|
|
38741
|
+
_buildColormapSelector() {
|
|
37727
38742
|
const group = document.createElement("div");
|
|
37728
|
-
group.className = "lidar-
|
|
37729
|
-
this.
|
|
37730
|
-
const
|
|
37731
|
-
|
|
37732
|
-
|
|
37733
|
-
const checkbox = document.createElement("input");
|
|
37734
|
-
checkbox.type = "checkbox";
|
|
37735
|
-
checkbox.id = "lidar-percentile-checkbox";
|
|
37736
|
-
checkbox.checked = this._state.usePercentile ?? true;
|
|
37737
|
-
checkbox.style.marginRight = "6px";
|
|
37738
|
-
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";
|
|
37739
38748
|
const label = document.createElement("label");
|
|
37740
38749
|
label.className = "lidar-control-label";
|
|
37741
|
-
label.
|
|
37742
|
-
label
|
|
37743
|
-
|
|
37744
|
-
|
|
37745
|
-
|
|
37746
|
-
|
|
37747
|
-
|
|
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);
|
|
37748
38765
|
});
|
|
37749
|
-
|
|
37750
|
-
labelRow.appendChild(label);
|
|
37751
|
-
group.appendChild(labelRow);
|
|
37752
|
-
const currentScheme = typeof this._state.colorScheme === "string" ? this._state.colorScheme : "elevation";
|
|
37753
|
-
this._updatePercentileVisibility(currentScheme);
|
|
38766
|
+
group.appendChild(select);
|
|
37754
38767
|
return group;
|
|
37755
38768
|
}
|
|
37756
38769
|
/**
|
|
37757
|
-
*
|
|
37758
|
-
|
|
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.
|
|
37759
38818
|
*/
|
|
37760
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
|
+
}
|
|
37761
38834
|
if (this._percentileGroup) {
|
|
37762
|
-
|
|
37763
|
-
this._percentileGroup.style.display = showPercentile ? "block" : "none";
|
|
38835
|
+
this._percentileGroup.style.display = "none";
|
|
37764
38836
|
}
|
|
37765
38837
|
if (this._classificationLegendContainer) {
|
|
37766
38838
|
this._classificationLegendContainer.style.display = colorScheme === "classification" ? "block" : "none";
|
|
@@ -37927,6 +38999,9 @@ const DEFAULT_OPTIONS = {
|
|
|
37927
38999
|
opacity: 1,
|
|
37928
39000
|
colorScheme: "elevation",
|
|
37929
39001
|
usePercentile: true,
|
|
39002
|
+
colormap: "viridis",
|
|
39003
|
+
colorRange: { mode: "percentile", percentileLow: 2, percentileHigh: 98 },
|
|
39004
|
+
showColorbar: true,
|
|
37930
39005
|
pointBudget: 1e6,
|
|
37931
39006
|
elevationRange: null,
|
|
37932
39007
|
pickable: false,
|
|
@@ -37973,7 +39048,14 @@ class LidarControl {
|
|
|
37973
39048
|
__publicField(this, "_streamingLoaders", /* @__PURE__ */ new Map());
|
|
37974
39049
|
__publicField(this, "_eptStreamingLoaders", /* @__PURE__ */ new Map());
|
|
37975
39050
|
__publicField(this, "_viewportManagers", /* @__PURE__ */ new Map());
|
|
39051
|
+
__publicField(this, "_eptViewportRequestIds", /* @__PURE__ */ new Map());
|
|
39052
|
+
__publicField(this, "_eptLastViewport", /* @__PURE__ */ new Map());
|
|
37976
39053
|
this._options = { ...DEFAULT_OPTIONS, ...options };
|
|
39054
|
+
const defaultColorRange = this._options.colorRange ?? {
|
|
39055
|
+
mode: "percentile",
|
|
39056
|
+
percentileLow: 2,
|
|
39057
|
+
percentileHigh: 98
|
|
39058
|
+
};
|
|
37977
39059
|
this._state = {
|
|
37978
39060
|
collapsed: this._options.collapsed,
|
|
37979
39061
|
panelWidth: this._options.panelWidth,
|
|
@@ -37983,6 +39065,9 @@ class LidarControl {
|
|
|
37983
39065
|
pointSize: this._options.pointSize,
|
|
37984
39066
|
opacity: this._options.opacity,
|
|
37985
39067
|
colorScheme: this._options.colorScheme,
|
|
39068
|
+
colormap: this._options.colormap ?? "viridis",
|
|
39069
|
+
colorRange: defaultColorRange,
|
|
39070
|
+
showColorbar: this._options.showColorbar ?? true,
|
|
37986
39071
|
usePercentile: this._options.usePercentile,
|
|
37987
39072
|
elevationRange: this._options.elevationRange,
|
|
37988
39073
|
pointBudget: this._options.pointBudget,
|
|
@@ -38178,7 +39263,7 @@ class LidarControl {
|
|
|
38178
39263
|
* @returns Promise resolving to the point cloud info
|
|
38179
39264
|
*/
|
|
38180
39265
|
async loadPointCloud(source, options) {
|
|
38181
|
-
var _a, _b, _c;
|
|
39266
|
+
var _a, _b, _c, _d;
|
|
38182
39267
|
const isEptUrl = typeof source === "string" && (source.endsWith("/ept.json") || source.includes("/ept.json?"));
|
|
38183
39268
|
if (isEptUrl) {
|
|
38184
39269
|
return this.loadPointCloudEptStreaming(source);
|
|
@@ -38253,6 +39338,8 @@ class LidarControl {
|
|
|
38253
39338
|
zOffset: zOffset ?? this._state.zOffset,
|
|
38254
39339
|
zOffsetEnabled
|
|
38255
39340
|
});
|
|
39341
|
+
this._updateComputedColorBounds();
|
|
39342
|
+
(_c = this._panelBuilder) == null ? void 0 : _c.updateState(this._state);
|
|
38256
39343
|
this._emitWithData("load", { pointCloud: info2 });
|
|
38257
39344
|
if (this._options.autoZoom) {
|
|
38258
39345
|
this.flyToPointCloud(id);
|
|
@@ -38266,7 +39353,7 @@ class LidarControl {
|
|
|
38266
39353
|
console.warn(
|
|
38267
39354
|
`CORS error detected for ${source}. Falling back to download mode...`
|
|
38268
39355
|
);
|
|
38269
|
-
(
|
|
39356
|
+
(_d = this._panelBuilder) == null ? void 0 : _d.updateLoadingProgress(5, "CORS blocked - downloading file...");
|
|
38270
39357
|
return this._loadPointCloudFullDownload(source);
|
|
38271
39358
|
}
|
|
38272
39359
|
this.setState({
|
|
@@ -38324,7 +39411,7 @@ class LidarControl {
|
|
|
38324
39411
|
* @returns Promise resolving to initial point cloud info
|
|
38325
39412
|
*/
|
|
38326
39413
|
async loadPointCloudStreaming(source, options) {
|
|
38327
|
-
var _a, _b, _c, _d;
|
|
39414
|
+
var _a, _b, _c, _d, _e;
|
|
38328
39415
|
const id = generateId("pc-stream");
|
|
38329
39416
|
let name;
|
|
38330
39417
|
if (typeof source === "string") {
|
|
@@ -38429,12 +39516,18 @@ class LidarControl {
|
|
|
38429
39516
|
pointClouds,
|
|
38430
39517
|
activePointCloudId: id
|
|
38431
39518
|
});
|
|
39519
|
+
this._updateComputedColorBounds();
|
|
39520
|
+
(_c = this._panelBuilder) == null ? void 0 : _c.updateState(this._state);
|
|
38432
39521
|
viewportManager.start();
|
|
38433
39522
|
if (this._options.autoZoom) {
|
|
38434
|
-
|
|
39523
|
+
const clampedMinY = Math.max(-90, Math.min(90, bounds2.minY));
|
|
39524
|
+
const clampedMaxY = Math.max(-90, Math.min(90, bounds2.maxY));
|
|
39525
|
+
const clampedMinX = Math.max(-180, Math.min(180, bounds2.minX));
|
|
39526
|
+
const clampedMaxX = Math.max(-180, Math.min(180, bounds2.maxX));
|
|
39527
|
+
(_d = this._map) == null ? void 0 : _d.fitBounds(
|
|
38435
39528
|
[
|
|
38436
|
-
[
|
|
38437
|
-
[
|
|
39529
|
+
[clampedMinX, clampedMinY],
|
|
39530
|
+
[clampedMaxX, clampedMaxY]
|
|
38438
39531
|
],
|
|
38439
39532
|
{
|
|
38440
39533
|
padding: 50,
|
|
@@ -38470,7 +39563,7 @@ class LidarControl {
|
|
|
38470
39563
|
streamingActive: hasActiveStreaming,
|
|
38471
39564
|
error: null
|
|
38472
39565
|
});
|
|
38473
|
-
(
|
|
39566
|
+
(_e = this._panelBuilder) == null ? void 0 : _e.updateLoadingProgress(5, "CORS blocked - downloading file...");
|
|
38474
39567
|
return this._loadPointCloudFullDownload(source);
|
|
38475
39568
|
}
|
|
38476
39569
|
this.setState({
|
|
@@ -38491,7 +39584,7 @@ class LidarControl {
|
|
|
38491
39584
|
* @returns Promise resolving to initial point cloud info
|
|
38492
39585
|
*/
|
|
38493
39586
|
async loadPointCloudEptStreaming(eptUrl, options) {
|
|
38494
|
-
var _a, _b, _c, _d;
|
|
39587
|
+
var _a, _b, _c, _d, _e;
|
|
38495
39588
|
const id = generateId("ept-stream");
|
|
38496
39589
|
const name = getFilename(eptUrl.replace("/ept.json", ""));
|
|
38497
39590
|
this.setState({ loading: true, error: null, streamingActive: true });
|
|
@@ -38589,12 +39682,18 @@ class LidarControl {
|
|
|
38589
39682
|
pointClouds,
|
|
38590
39683
|
activePointCloudId: id
|
|
38591
39684
|
});
|
|
39685
|
+
this._updateComputedColorBounds();
|
|
39686
|
+
(_d = this._panelBuilder) == null ? void 0 : _d.updateState(this._state);
|
|
38592
39687
|
viewportManager.start();
|
|
38593
39688
|
if (this._options.autoZoom) {
|
|
38594
|
-
|
|
39689
|
+
const clampedMinY = Math.max(-90, Math.min(90, bounds2.minY));
|
|
39690
|
+
const clampedMaxY = Math.max(-90, Math.min(90, bounds2.maxY));
|
|
39691
|
+
const clampedMinX = Math.max(-180, Math.min(180, bounds2.minX));
|
|
39692
|
+
const clampedMaxX = Math.max(-180, Math.min(180, bounds2.maxX));
|
|
39693
|
+
(_e = this._map) == null ? void 0 : _e.fitBounds(
|
|
38595
39694
|
[
|
|
38596
|
-
[
|
|
38597
|
-
[
|
|
39695
|
+
[clampedMinX, clampedMinY],
|
|
39696
|
+
[clampedMaxX, clampedMaxY]
|
|
38598
39697
|
],
|
|
38599
39698
|
{
|
|
38600
39699
|
padding: 50,
|
|
@@ -38614,6 +39713,8 @@ class LidarControl {
|
|
|
38614
39713
|
eptLoader.destroy();
|
|
38615
39714
|
this._eptStreamingLoaders.delete(id);
|
|
38616
39715
|
}
|
|
39716
|
+
this._eptViewportRequestIds.delete(id);
|
|
39717
|
+
this._eptLastViewport.delete(id);
|
|
38617
39718
|
const viewportManager = this._viewportManagers.get(id);
|
|
38618
39719
|
if (viewportManager) {
|
|
38619
39720
|
viewportManager.destroy();
|
|
@@ -38635,15 +39736,77 @@ class LidarControl {
|
|
|
38635
39736
|
* @param viewport - Current viewport information
|
|
38636
39737
|
* @param datasetId - ID of the EPT dataset
|
|
38637
39738
|
*/
|
|
38638
|
-
|
|
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) {
|
|
38639
39754
|
const eptLoader = this._eptStreamingLoaders.get(datasetId);
|
|
38640
39755
|
if (!eptLoader) return;
|
|
38641
39756
|
try {
|
|
38642
|
-
const
|
|
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
|
+
}
|
|
38643
39794
|
for (const node of nodesToLoad) {
|
|
38644
39795
|
eptLoader.queueNode(node);
|
|
38645
39796
|
}
|
|
38646
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
|
+
}
|
|
38647
39810
|
} catch (err) {
|
|
38648
39811
|
console.warn("Failed to load EPT nodes for viewport:", err);
|
|
38649
39812
|
}
|
|
@@ -38653,7 +39816,7 @@ class LidarControl {
|
|
|
38653
39816
|
* Used as fallback when streaming fails due to CORS.
|
|
38654
39817
|
*/
|
|
38655
39818
|
async _loadPointCloudFullDownload(url) {
|
|
38656
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
39819
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
38657
39820
|
const id = generateId("pc");
|
|
38658
39821
|
const name = getFilename(url);
|
|
38659
39822
|
try {
|
|
@@ -38740,6 +39903,8 @@ class LidarControl {
|
|
|
38740
39903
|
zOffset: zOffset ?? this._state.zOffset,
|
|
38741
39904
|
zOffsetEnabled
|
|
38742
39905
|
});
|
|
39906
|
+
this._updateComputedColorBounds();
|
|
39907
|
+
(_i = this._panelBuilder) == null ? void 0 : _i.updateState(this._state);
|
|
38743
39908
|
this._emitWithData("load", { pointCloud: info2 });
|
|
38744
39909
|
if (this._options.autoZoom) {
|
|
38745
39910
|
this.flyToPointCloud(id);
|
|
@@ -38836,6 +40001,8 @@ class LidarControl {
|
|
|
38836
40001
|
eptLoader.destroy();
|
|
38837
40002
|
}
|
|
38838
40003
|
this._eptStreamingLoaders.clear();
|
|
40004
|
+
this._eptViewportRequestIds.clear();
|
|
40005
|
+
this._eptLastViewport.clear();
|
|
38839
40006
|
for (const streamingId of streamingIds) {
|
|
38840
40007
|
(_c = this._pointCloudManager) == null ? void 0 : _c.removePointCloud(streamingId);
|
|
38841
40008
|
}
|
|
@@ -38904,16 +40071,84 @@ class LidarControl {
|
|
|
38904
40071
|
}
|
|
38905
40072
|
/**
|
|
38906
40073
|
* Sets the color scheme.
|
|
40074
|
+
* Automatically switches colormap and resets color range when changing between elevation and intensity.
|
|
38907
40075
|
*
|
|
38908
40076
|
* @param scheme - Color scheme to apply
|
|
38909
40077
|
*/
|
|
38910
40078
|
setColorScheme(scheme) {
|
|
38911
|
-
var _a;
|
|
40079
|
+
var _a, _b, _c, _d, _e;
|
|
40080
|
+
const previousScheme = this._state.colorScheme;
|
|
38912
40081
|
this._state.colorScheme = scheme;
|
|
38913
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);
|
|
38914
40119
|
this._emit("stylechange");
|
|
38915
40120
|
this._emit("statechange");
|
|
38916
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);
|
|
40141
|
+
this._emit("stylechange");
|
|
40142
|
+
this._emit("statechange");
|
|
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
|
+
}
|
|
38917
40152
|
/**
|
|
38918
40153
|
* Sets whether to use percentile range for elevation/intensity coloring.
|
|
38919
40154
|
*
|
|
@@ -39123,6 +40358,50 @@ class LidarControl {
|
|
|
39123
40358
|
handlers.forEach((handler) => handler(eventData));
|
|
39124
40359
|
}
|
|
39125
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
|
+
}
|
|
39126
40405
|
/**
|
|
39127
40406
|
* Creates the main container element for the control.
|
|
39128
40407
|
*
|
|
@@ -39183,6 +40462,8 @@ class LidarControl {
|
|
|
39183
40462
|
onPointSizeChange: (size) => this.setPointSize(size),
|
|
39184
40463
|
onOpacityChange: (opacity) => this.setOpacity(opacity),
|
|
39185
40464
|
onColorSchemeChange: (scheme) => this.setColorScheme(scheme),
|
|
40465
|
+
onColormapChange: (colormap) => this.setColormap(colormap),
|
|
40466
|
+
onColorRangeChange: (config) => this.setColorRange(config),
|
|
39186
40467
|
onUsePercentileChange: (usePercentile) => this.setUsePercentile(usePercentile),
|
|
39187
40468
|
onElevationRangeChange: (range) => {
|
|
39188
40469
|
if (range) {
|
|
@@ -39685,6 +40966,9 @@ class LidarLayerAdapter {
|
|
|
39685
40966
|
this._changeCallbacks = [];
|
|
39686
40967
|
}
|
|
39687
40968
|
}
|
|
40969
|
+
exports.COLORMAPS = COLORMAPS;
|
|
40970
|
+
exports.COLORMAP_LABELS = COLORMAP_LABELS;
|
|
40971
|
+
exports.COLORMAP_NAMES = COLORMAP_NAMES;
|
|
39688
40972
|
exports.ColorSchemeProcessor = ColorSchemeProcessor;
|
|
39689
40973
|
exports.CopcStreamingLoader = CopcStreamingLoader;
|
|
39690
40974
|
exports.DeckOverlay = DeckOverlay;
|
|
@@ -39702,6 +40986,7 @@ exports.formatNumber = formatNumber;
|
|
|
39702
40986
|
exports.formatNumericValue = formatNumericValue;
|
|
39703
40987
|
exports.generateId = generateId;
|
|
39704
40988
|
exports.getClassificationName = getClassificationName;
|
|
40989
|
+
exports.getColormap = getColormap;
|
|
39705
40990
|
exports.getFilename = getFilename;
|
|
39706
40991
|
exports.throttle = throttle;
|
|
39707
|
-
//# sourceMappingURL=LidarLayerAdapter-
|
|
40992
|
+
//# sourceMappingURL=LidarLayerAdapter-eh59KEMT.cjs.map
|