@gisatcz/deckgl-geolib 2.1.2-dev.1 → 2.1.4-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cjs/index.js +515 -457
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.min.js +2 -2
- package/dist/cjs/index.min.js.map +1 -1
- package/dist/esm/index.js +515 -457
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.min.js +2 -2
- package/dist/esm/index.min.js.map +1 -1
- package/dist/esm/types/core/CogTiles.d.ts +8 -6
- package/dist/esm/types/core/GeoImage.d.ts +5 -49
- package/dist/esm/types/core/lib/BitmapGenerator.d.ts +15 -0
- package/dist/esm/types/core/lib/DataUtils.d.ts +1 -0
- package/dist/esm/types/core/lib/TerrainGenerator.d.ts +55 -0
- package/dist/esm/types/core/types.d.ts +42 -0
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -4515,6 +4515,41 @@ async function fromArrayBuffer(arrayBuffer, signal) {
|
|
|
4515
4515
|
return GeoTIFF.fromSource(makeBufferSource(arrayBuffer), undefined, signal);
|
|
4516
4516
|
}
|
|
4517
4517
|
|
|
4518
|
+
// types.ts
|
|
4519
|
+
const DefaultGeoImageOptions = {
|
|
4520
|
+
type: 'image',
|
|
4521
|
+
tesselator: 'martini',
|
|
4522
|
+
format: undefined,
|
|
4523
|
+
useHeatMap: true,
|
|
4524
|
+
useColorsBasedOnValues: false,
|
|
4525
|
+
useAutoRange: false,
|
|
4526
|
+
useDataForOpacity: false,
|
|
4527
|
+
useSingleColor: false,
|
|
4528
|
+
useColorClasses: false,
|
|
4529
|
+
blurredTexture: true,
|
|
4530
|
+
clipLow: null,
|
|
4531
|
+
clipHigh: null,
|
|
4532
|
+
multiplier: 1.0,
|
|
4533
|
+
color: [255, 0, 255, 255],
|
|
4534
|
+
colorScale: chroma.brewer.YlOrRd,
|
|
4535
|
+
colorScaleValueRange: [0, 255],
|
|
4536
|
+
colorsBasedOnValues: undefined,
|
|
4537
|
+
colorClasses: undefined,
|
|
4538
|
+
alpha: 100,
|
|
4539
|
+
useChannel: null,
|
|
4540
|
+
useChannelIndex: null,
|
|
4541
|
+
noDataValue: undefined,
|
|
4542
|
+
numOfChannels: undefined,
|
|
4543
|
+
nullColor: [0, 0, 0, 0],
|
|
4544
|
+
unidentifiedColor: [0, 0, 0, 0],
|
|
4545
|
+
clippedColor: [0, 0, 0, 0],
|
|
4546
|
+
terrainColor: [133, 133, 133, 255],
|
|
4547
|
+
terrainSkirtHeight: 100,
|
|
4548
|
+
// Default fallback for invalid/nodata elevations. Should be configured based on the dataset's actual range.
|
|
4549
|
+
terrainMinValue: 0,
|
|
4550
|
+
planarConfig: undefined,
|
|
4551
|
+
};
|
|
4552
|
+
|
|
4518
4553
|
class Martini {
|
|
4519
4554
|
constructor(gridSize = 257) {
|
|
4520
4555
|
this.gridSize = gridSize;
|
|
@@ -4679,107 +4714,6 @@ class Tile {
|
|
|
4679
4714
|
}
|
|
4680
4715
|
}
|
|
4681
4716
|
|
|
4682
|
-
// loaders.gl
|
|
4683
|
-
// SPDX-License-Identifier: MIT
|
|
4684
|
-
// Copyright (c) vis.gl contributors
|
|
4685
|
-
/**
|
|
4686
|
-
* Add skirt to existing mesh
|
|
4687
|
-
* @param {object} attributes - POSITION and TEXCOOD_0 attributes data
|
|
4688
|
-
* @param {any} triangles - indices array of the mesh geometry
|
|
4689
|
-
* @param skirtHeight - height of the skirt geometry
|
|
4690
|
-
* @param outsideIndices - edge indices from quantized mesh data
|
|
4691
|
-
* @returns - geometry data with added skirt
|
|
4692
|
-
*/
|
|
4693
|
-
function addSkirt(attributes, triangles, skirtHeight, outsideIndices) {
|
|
4694
|
-
const outsideEdges = getOutsideEdgesFromTriangles(triangles);
|
|
4695
|
-
// 2 new vertices for each outside edge
|
|
4696
|
-
const newPosition = new attributes.POSITION.value.constructor(outsideEdges.length * 6);
|
|
4697
|
-
const newTexcoord0 = new attributes.TEXCOORD_0.value.constructor(outsideEdges.length * 4);
|
|
4698
|
-
// 2 new triangles for each outside edge
|
|
4699
|
-
const newTriangles = new triangles.constructor(outsideEdges.length * 6);
|
|
4700
|
-
for (let i = 0; i < outsideEdges.length; i++) {
|
|
4701
|
-
const edge = outsideEdges[i];
|
|
4702
|
-
updateAttributesForNewEdge({
|
|
4703
|
-
edge,
|
|
4704
|
-
edgeIndex: i,
|
|
4705
|
-
attributes,
|
|
4706
|
-
skirtHeight,
|
|
4707
|
-
newPosition,
|
|
4708
|
-
newTexcoord0,
|
|
4709
|
-
newTriangles,
|
|
4710
|
-
});
|
|
4711
|
-
}
|
|
4712
|
-
attributes.POSITION.value = loaderUtils.concatenateTypedArrays(attributes.POSITION.value, newPosition);
|
|
4713
|
-
attributes.TEXCOORD_0.value = loaderUtils.concatenateTypedArrays(attributes.TEXCOORD_0.value, newTexcoord0);
|
|
4714
|
-
const resultTriangles = triangles instanceof Array
|
|
4715
|
-
? triangles.concat(newTriangles)
|
|
4716
|
-
: loaderUtils.concatenateTypedArrays(triangles, newTriangles);
|
|
4717
|
-
return {
|
|
4718
|
-
attributes,
|
|
4719
|
-
triangles: resultTriangles,
|
|
4720
|
-
};
|
|
4721
|
-
}
|
|
4722
|
-
/**
|
|
4723
|
-
* Get geometry edges that located on a border of the mesh
|
|
4724
|
-
* @param {any} triangles - indices array of the mesh geometry
|
|
4725
|
-
* @returns {number[][]} - outside edges data
|
|
4726
|
-
*/
|
|
4727
|
-
function getOutsideEdgesFromTriangles(triangles) {
|
|
4728
|
-
const edges = [];
|
|
4729
|
-
for (let i = 0; i < triangles.length; i += 3) {
|
|
4730
|
-
edges.push([triangles[i], triangles[i + 1]]);
|
|
4731
|
-
edges.push([triangles[i + 1], triangles[i + 2]]);
|
|
4732
|
-
edges.push([triangles[i + 2], triangles[i]]);
|
|
4733
|
-
}
|
|
4734
|
-
edges.sort((a, b) => Math.min(...a) - Math.min(...b) || Math.max(...a) - Math.max(...b));
|
|
4735
|
-
const outsideEdges = [];
|
|
4736
|
-
let index = 0;
|
|
4737
|
-
while (index < edges.length) {
|
|
4738
|
-
if (edges[index][0] === edges[index + 1]?.[1] && edges[index][1] === edges[index + 1]?.[0]) {
|
|
4739
|
-
index += 2;
|
|
4740
|
-
}
|
|
4741
|
-
else {
|
|
4742
|
-
outsideEdges.push(edges[index]);
|
|
4743
|
-
index++;
|
|
4744
|
-
}
|
|
4745
|
-
}
|
|
4746
|
-
return outsideEdges;
|
|
4747
|
-
}
|
|
4748
|
-
/**
|
|
4749
|
-
* Get geometry edges that located on a border of the mesh
|
|
4750
|
-
* @param {object} args
|
|
4751
|
-
* @param {number[]} args.edge - edge indices in geometry
|
|
4752
|
-
* @param {number} args.edgeIndex - edge index in outsideEdges array
|
|
4753
|
-
* @param {object} args.attributes - POSITION and TEXCOORD_0 attributes
|
|
4754
|
-
* @param {number} args.skirtHeight - height of the skirt geometry
|
|
4755
|
-
* @param {TypedArray} args.newPosition - POSITION array for skirt data
|
|
4756
|
-
* @param {TypedArray} args.newTexcoord0 - TEXCOORD_0 array for skirt data
|
|
4757
|
-
* @param {TypedArray | Array} args.newTriangles - trinagle indices array for skirt data
|
|
4758
|
-
* @returns {void}
|
|
4759
|
-
*/
|
|
4760
|
-
function updateAttributesForNewEdge({ edge, edgeIndex, attributes, skirtHeight, newPosition, newTexcoord0, newTriangles, }) {
|
|
4761
|
-
const positionsLength = attributes.POSITION.value.length;
|
|
4762
|
-
const vertex1Offset = edgeIndex * 2;
|
|
4763
|
-
const vertex2Offset = edgeIndex * 2 + 1;
|
|
4764
|
-
// Define POSITION for new 1st vertex
|
|
4765
|
-
newPosition.set(attributes.POSITION.value.subarray(edge[0] * 3, edge[0] * 3 + 3), vertex1Offset * 3);
|
|
4766
|
-
newPosition[vertex1Offset * 3 + 2] = newPosition[vertex1Offset * 3 + 2] - skirtHeight; // put down elevation on the skirt height
|
|
4767
|
-
// Define POSITION for new 2nd vertex
|
|
4768
|
-
newPosition.set(attributes.POSITION.value.subarray(edge[1] * 3, edge[1] * 3 + 3), vertex2Offset * 3);
|
|
4769
|
-
newPosition[vertex2Offset * 3 + 2] = newPosition[vertex2Offset * 3 + 2] - skirtHeight; // put down elevation on the skirt height
|
|
4770
|
-
// Use same TEXCOORDS for skirt vertices
|
|
4771
|
-
newTexcoord0.set(attributes.TEXCOORD_0.value.subarray(edge[0] * 2, edge[0] * 2 + 2), vertex1Offset * 2);
|
|
4772
|
-
newTexcoord0.set(attributes.TEXCOORD_0.value.subarray(edge[1] * 2, edge[1] * 2 + 2), vertex2Offset * 2);
|
|
4773
|
-
// Define new triangles
|
|
4774
|
-
const triangle1Offset = edgeIndex * 2 * 3;
|
|
4775
|
-
newTriangles[triangle1Offset] = edge[0];
|
|
4776
|
-
newTriangles[triangle1Offset + 1] = positionsLength / 3 + vertex2Offset;
|
|
4777
|
-
newTriangles[triangle1Offset + 2] = edge[1];
|
|
4778
|
-
newTriangles[triangle1Offset + 3] = positionsLength / 3 + vertex2Offset;
|
|
4779
|
-
newTriangles[triangle1Offset + 4] = edge[0];
|
|
4780
|
-
newTriangles[triangle1Offset + 5] = positionsLength / 3 + vertex1Offset;
|
|
4781
|
-
}
|
|
4782
|
-
|
|
4783
4717
|
// loaders.gl
|
|
4784
4718
|
// SPDX-License-Identifier: MIT
|
|
4785
4719
|
// Copyright (c) vis.gl contributors
|
|
@@ -5190,141 +5124,130 @@ function inCircle(ax, ay, bx, by, cx, cy, px, py) {
|
|
|
5190
5124
|
return dx * (ey * cp - bp * fy) - dy * (ex * cp - bp * fx) + ap * (ex * fy - ey * fx) < 0;
|
|
5191
5125
|
}
|
|
5192
5126
|
|
|
5193
|
-
//
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
terrainSkirtHeight: 100,
|
|
5223
|
-
terrainMinValue: 0,
|
|
5224
|
-
planarConfig: undefined,
|
|
5225
|
-
};
|
|
5226
|
-
class GeoImage {
|
|
5227
|
-
data;
|
|
5228
|
-
scale = (num, inMin, inMax, outMin, outMax) => ((num - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
|
|
5229
|
-
async setUrl(url) {
|
|
5230
|
-
// TODO - not tested
|
|
5231
|
-
const response = await fetch(url);
|
|
5232
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
5233
|
-
const tiff = await fromArrayBuffer(arrayBuffer);
|
|
5234
|
-
const data = await tiff.getImage(0);
|
|
5235
|
-
this.data = data;
|
|
5127
|
+
// loaders.gl
|
|
5128
|
+
// SPDX-License-Identifier: MIT
|
|
5129
|
+
// Copyright (c) vis.gl contributors
|
|
5130
|
+
/**
|
|
5131
|
+
* Add skirt to existing mesh
|
|
5132
|
+
* @param {object} attributes - POSITION and TEXCOOD_0 attributes data
|
|
5133
|
+
* @param {any} triangles - indices array of the mesh geometry
|
|
5134
|
+
* @param skirtHeight - height of the skirt geometry
|
|
5135
|
+
* @param outsideIndices - edge indices from quantized mesh data
|
|
5136
|
+
* @returns - geometry data with added skirt
|
|
5137
|
+
*/
|
|
5138
|
+
function addSkirt(attributes, triangles, skirtHeight, outsideIndices) {
|
|
5139
|
+
const outsideEdges = getOutsideEdgesFromTriangles(triangles);
|
|
5140
|
+
// 2 new vertices for each outside edge
|
|
5141
|
+
const newPosition = new attributes.POSITION.value.constructor(outsideEdges.length * 6);
|
|
5142
|
+
const newTexcoord0 = new attributes.TEXCOORD_0.value.constructor(outsideEdges.length * 4);
|
|
5143
|
+
// 2 new triangles for each outside edge
|
|
5144
|
+
const newTriangles = new triangles.constructor(outsideEdges.length * 6);
|
|
5145
|
+
for (let i = 0; i < outsideEdges.length; i++) {
|
|
5146
|
+
const edge = outsideEdges[i];
|
|
5147
|
+
updateAttributesForNewEdge({
|
|
5148
|
+
edge,
|
|
5149
|
+
edgeIndex: i,
|
|
5150
|
+
attributes,
|
|
5151
|
+
skirtHeight,
|
|
5152
|
+
newPosition,
|
|
5153
|
+
newTexcoord0,
|
|
5154
|
+
newTriangles,
|
|
5155
|
+
});
|
|
5236
5156
|
}
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5157
|
+
attributes.POSITION.value = loaderUtils.concatenateTypedArrays(attributes.POSITION.value, newPosition);
|
|
5158
|
+
attributes.TEXCOORD_0.value = loaderUtils.concatenateTypedArrays(attributes.TEXCOORD_0.value, newTexcoord0);
|
|
5159
|
+
const resultTriangles = triangles instanceof Array
|
|
5160
|
+
? triangles.concat(newTriangles)
|
|
5161
|
+
: loaderUtils.concatenateTypedArrays(triangles, newTriangles);
|
|
5162
|
+
return {
|
|
5163
|
+
attributes,
|
|
5164
|
+
triangles: resultTriangles,
|
|
5165
|
+
};
|
|
5166
|
+
}
|
|
5167
|
+
/**
|
|
5168
|
+
* Get geometry edges that located on a border of the mesh
|
|
5169
|
+
* @param {any} triangles - indices array of the mesh geometry
|
|
5170
|
+
* @returns {number[][]} - outside edges data
|
|
5171
|
+
*/
|
|
5172
|
+
function getOutsideEdgesFromTriangles(triangles) {
|
|
5173
|
+
const edges = [];
|
|
5174
|
+
for (let i = 0; i < triangles.length; i += 3) {
|
|
5175
|
+
edges.push([triangles[i], triangles[i + 1]]);
|
|
5176
|
+
edges.push([triangles[i + 1], triangles[i + 2]]);
|
|
5177
|
+
edges.push([triangles[i + 2], triangles[i]]);
|
|
5247
5178
|
}
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
// TODO not tested
|
|
5255
|
-
// input is type of object
|
|
5256
|
-
await this.setUrl(input);
|
|
5257
|
-
rasters = (await this.data.readRasters());
|
|
5258
|
-
width = this.data.getWidth();
|
|
5259
|
-
height = this.data.getHeight();
|
|
5179
|
+
edges.sort((a, b) => Math.min(...a) - Math.min(...b) || Math.max(...a) - Math.max(...b));
|
|
5180
|
+
const outsideEdges = [];
|
|
5181
|
+
let index = 0;
|
|
5182
|
+
while (index < edges.length) {
|
|
5183
|
+
if (edges[index][0] === edges[index + 1]?.[1] && edges[index][1] === edges[index + 1]?.[0]) {
|
|
5184
|
+
index += 2;
|
|
5260
5185
|
}
|
|
5261
5186
|
else {
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
height = input.height;
|
|
5265
|
-
}
|
|
5266
|
-
const optionsLocal = { ...options };
|
|
5267
|
-
let channel = rasters[0];
|
|
5268
|
-
optionsLocal.useChannelIndex ??= optionsLocal.useChannel == null ? null : optionsLocal.useChannel - 1;
|
|
5269
|
-
if (options.useChannelIndex != null) {
|
|
5270
|
-
if (rasters[optionsLocal.useChannelIndex]) {
|
|
5271
|
-
channel = rasters[optionsLocal.useChannelIndex];
|
|
5272
|
-
}
|
|
5273
|
-
}
|
|
5274
|
-
const terrain = new Float32Array((width === 257 ? width : width + 1) * (height === 257 ? height : height + 1));
|
|
5275
|
-
const numOfChannels = channel.length / (width * height);
|
|
5276
|
-
let pixel = options.useChannelIndex === null ? 0 : options.useChannelIndex;
|
|
5277
|
-
const isStitched = width === 257;
|
|
5278
|
-
for (let y = 0; y < height; y++) {
|
|
5279
|
-
for (let x = 0; x < width; x++) {
|
|
5280
|
-
let elevationValue = (options.noDataValue && channel[pixel] === options.noDataValue) ? options.terrainMinValue : channel[pixel] * options.multiplier;
|
|
5281
|
-
// Validate that the elevation value is within the valid range for Float32.
|
|
5282
|
-
// Extreme values (like -1.79e308) can become -Infinity when cast, causing WebGL errors.
|
|
5283
|
-
if (Number.isNaN(elevationValue) || elevationValue < -34e37 || elevationValue > 3.4e38) {
|
|
5284
|
-
elevationValue = options.terrainMinValue;
|
|
5285
|
-
}
|
|
5286
|
-
// If stitched (257), fill linearly. If 256, fill with stride for padding.
|
|
5287
|
-
const index = isStitched ? (y * width + x) : (y * (width + 1) + x);
|
|
5288
|
-
terrain[index] = elevationValue;
|
|
5289
|
-
pixel += numOfChannels;
|
|
5290
|
-
}
|
|
5291
|
-
}
|
|
5292
|
-
if (!isStitched) {
|
|
5293
|
-
// backfill bottom border
|
|
5294
|
-
for (let i = (width + 1) * width, x = 0; x < width; x++, i++) {
|
|
5295
|
-
terrain[i] = terrain[i - width - 1];
|
|
5296
|
-
}
|
|
5297
|
-
// backfill right border
|
|
5298
|
-
for (let i = height, y = 0; y < height + 1; y++, i += height + 1) {
|
|
5299
|
-
terrain[i] = terrain[i - 1];
|
|
5300
|
-
}
|
|
5187
|
+
outsideEdges.push(edges[index]);
|
|
5188
|
+
index++;
|
|
5301
5189
|
}
|
|
5302
|
-
|
|
5190
|
+
}
|
|
5191
|
+
return outsideEdges;
|
|
5192
|
+
}
|
|
5193
|
+
/**
|
|
5194
|
+
* Get geometry edges that located on a border of the mesh
|
|
5195
|
+
* @param {object} args
|
|
5196
|
+
* @param {number[]} args.edge - edge indices in geometry
|
|
5197
|
+
* @param {number} args.edgeIndex - edge index in outsideEdges array
|
|
5198
|
+
* @param {object} args.attributes - POSITION and TEXCOORD_0 attributes
|
|
5199
|
+
* @param {number} args.skirtHeight - height of the skirt geometry
|
|
5200
|
+
* @param {TypedArray} args.newPosition - POSITION array for skirt data
|
|
5201
|
+
* @param {TypedArray} args.newTexcoord0 - TEXCOORD_0 array for skirt data
|
|
5202
|
+
* @param {TypedArray | Array} args.newTriangles - trinagle indices array for skirt data
|
|
5203
|
+
* @returns {void}
|
|
5204
|
+
*/
|
|
5205
|
+
function updateAttributesForNewEdge({ edge, edgeIndex, attributes, skirtHeight, newPosition, newTexcoord0, newTriangles, }) {
|
|
5206
|
+
const positionsLength = attributes.POSITION.value.length;
|
|
5207
|
+
const vertex1Offset = edgeIndex * 2;
|
|
5208
|
+
const vertex2Offset = edgeIndex * 2 + 1;
|
|
5209
|
+
// Define POSITION for new 1st vertex
|
|
5210
|
+
newPosition.set(attributes.POSITION.value.subarray(edge[0] * 3, edge[0] * 3 + 3), vertex1Offset * 3);
|
|
5211
|
+
newPosition[vertex1Offset * 3 + 2] = newPosition[vertex1Offset * 3 + 2] - skirtHeight; // put down elevation on the skirt height
|
|
5212
|
+
// Define POSITION for new 2nd vertex
|
|
5213
|
+
newPosition.set(attributes.POSITION.value.subarray(edge[1] * 3, edge[1] * 3 + 3), vertex2Offset * 3);
|
|
5214
|
+
newPosition[vertex2Offset * 3 + 2] = newPosition[vertex2Offset * 3 + 2] - skirtHeight; // put down elevation on the skirt height
|
|
5215
|
+
// Use same TEXCOORDS for skirt vertices
|
|
5216
|
+
newTexcoord0.set(attributes.TEXCOORD_0.value.subarray(edge[0] * 2, edge[0] * 2 + 2), vertex1Offset * 2);
|
|
5217
|
+
newTexcoord0.set(attributes.TEXCOORD_0.value.subarray(edge[1] * 2, edge[1] * 2 + 2), vertex2Offset * 2);
|
|
5218
|
+
// Define new triangles
|
|
5219
|
+
const triangle1Offset = edgeIndex * 2 * 3;
|
|
5220
|
+
newTriangles[triangle1Offset] = edge[0];
|
|
5221
|
+
newTriangles[triangle1Offset + 1] = positionsLength / 3 + vertex2Offset;
|
|
5222
|
+
newTriangles[triangle1Offset + 2] = edge[1];
|
|
5223
|
+
newTriangles[triangle1Offset + 3] = positionsLength / 3 + vertex2Offset;
|
|
5224
|
+
newTriangles[triangle1Offset + 4] = edge[0];
|
|
5225
|
+
newTriangles[triangle1Offset + 5] = positionsLength / 3 + vertex1Offset;
|
|
5226
|
+
}
|
|
5227
|
+
|
|
5228
|
+
class TerrainGenerator {
|
|
5229
|
+
static generate(input, options, meshMaxError) {
|
|
5230
|
+
const { width, height } = input;
|
|
5231
|
+
// 1. Compute Terrain Data (Extract Elevation)
|
|
5232
|
+
const terrain = this.computeTerrainData(input, options);
|
|
5233
|
+
// 2. Tesselate (Generate Mesh)
|
|
5303
5234
|
const { terrainSkirtHeight } = options;
|
|
5304
5235
|
let mesh;
|
|
5305
5236
|
switch (options.tesselator) {
|
|
5306
5237
|
case 'martini':
|
|
5307
|
-
mesh = getMartiniTileMesh(meshMaxError, width, terrain);
|
|
5238
|
+
mesh = this.getMartiniTileMesh(meshMaxError, width, terrain);
|
|
5308
5239
|
break;
|
|
5309
5240
|
case 'delatin':
|
|
5310
|
-
mesh = getDelatinTileMesh(meshMaxError, width, height, terrain);
|
|
5241
|
+
mesh = this.getDelatinTileMesh(meshMaxError, width, height, terrain);
|
|
5311
5242
|
break;
|
|
5312
5243
|
default:
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
// terrain = getTerrain(data, width, height, elevationDecoder, 'martini');
|
|
5316
|
-
mesh = getMartiniTileMesh(meshMaxError, width, terrain);
|
|
5317
|
-
}
|
|
5318
|
-
else {
|
|
5319
|
-
// fixme get terrain to separate method
|
|
5320
|
-
// terrain = getTerrain(data, width, height, elevationDecoder, 'delatin');
|
|
5321
|
-
mesh = getDelatinTileMesh(meshMaxError, width, height, terrain);
|
|
5322
|
-
}
|
|
5244
|
+
// Intentional: default to Martini for any unspecified or unrecognized tesselator.
|
|
5245
|
+
mesh = this.getMartiniTileMesh(meshMaxError, width, terrain);
|
|
5323
5246
|
break;
|
|
5324
5247
|
}
|
|
5325
5248
|
const { vertices } = mesh;
|
|
5326
5249
|
let { triangles } = mesh;
|
|
5327
|
-
let attributes = getMeshAttributes(vertices, terrain, width, height, input.bounds);
|
|
5250
|
+
let attributes = this.getMeshAttributes(vertices, terrain, width, height, input.bounds);
|
|
5328
5251
|
// Compute bounding box before adding skirt so that z values are not skewed
|
|
5329
5252
|
const boundingBox = schema.getMeshBoundingBox(attributes);
|
|
5330
5253
|
if (terrainSkirtHeight) {
|
|
@@ -5346,27 +5269,119 @@ class GeoImage {
|
|
|
5346
5269
|
attributes,
|
|
5347
5270
|
};
|
|
5348
5271
|
}
|
|
5349
|
-
|
|
5272
|
+
/**
|
|
5273
|
+
* Decodes raw raster data into a Float32Array of elevation values.
|
|
5274
|
+
* Handles channel selection, value scaling, data type validation, and border stitching.
|
|
5275
|
+
*/
|
|
5276
|
+
static computeTerrainData(input, options) {
|
|
5277
|
+
const { width, height, rasters } = input;
|
|
5350
5278
|
const optionsLocal = { ...options };
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5279
|
+
optionsLocal.useChannelIndex ??= optionsLocal.useChannel == null ? null : optionsLocal.useChannel - 1;
|
|
5280
|
+
// Detect if data is planar (multiple arrays) or interleaved (one array with multiple samples per pixel)
|
|
5281
|
+
const isPlanar = rasters.length > 1;
|
|
5282
|
+
const channel = isPlanar
|
|
5283
|
+
? (rasters[optionsLocal.useChannelIndex ?? 0] ?? rasters[0])
|
|
5284
|
+
: rasters[0];
|
|
5285
|
+
const terrain = new Float32Array((width === 257 ? width : width + 1) * (height === 257 ? height : height + 1));
|
|
5286
|
+
const samplesPerPixel = isPlanar ? 1 : (channel.length / (width * height));
|
|
5287
|
+
// If planar, we already selected the correct array, so start at index 0.
|
|
5288
|
+
// If interleaved, start at the index of the desired channel.
|
|
5289
|
+
let pixel = isPlanar ? 0 : (optionsLocal.useChannelIndex ?? 0);
|
|
5290
|
+
const isStitched = width === 257;
|
|
5291
|
+
const fallbackValue = options.terrainMinValue ?? 0;
|
|
5292
|
+
for (let y = 0; y < height; y++) {
|
|
5293
|
+
for (let x = 0; x < width; x++) {
|
|
5294
|
+
const multiplier = options.multiplier ?? 1;
|
|
5295
|
+
let elevationValue = (options.noDataValue !== undefined &&
|
|
5296
|
+
options.noDataValue !== null &&
|
|
5297
|
+
channel[pixel] === options.noDataValue)
|
|
5298
|
+
? fallbackValue
|
|
5299
|
+
: channel[pixel] * multiplier;
|
|
5300
|
+
// Validate that the elevation value is within the valid range for Float32.
|
|
5301
|
+
// Extreme values (like -1.79e308) can become -Infinity when cast, causing WebGL errors.
|
|
5302
|
+
if (Number.isNaN(elevationValue) || elevationValue < -34e37 || elevationValue > 3.4e38) {
|
|
5303
|
+
elevationValue = fallbackValue;
|
|
5304
|
+
}
|
|
5305
|
+
// If stitched (257), fill linearly. If 256, fill with stride for padding.
|
|
5306
|
+
const index = isStitched ? (y * width + x) : (y * (width + 1) + x);
|
|
5307
|
+
terrain[index] = elevationValue;
|
|
5308
|
+
pixel += samplesPerPixel;
|
|
5309
|
+
}
|
|
5363
5310
|
}
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5311
|
+
if (!isStitched) {
|
|
5312
|
+
// backfill bottom border
|
|
5313
|
+
for (let i = (width + 1) * width, x = 0; x < width; x++, i++) {
|
|
5314
|
+
terrain[i] = terrain[i - width - 1];
|
|
5315
|
+
}
|
|
5316
|
+
// backfill right border
|
|
5317
|
+
for (let i = height, y = 0; y < height + 1; y++, i += height + 1) {
|
|
5318
|
+
terrain[i] = terrain[i - 1];
|
|
5319
|
+
}
|
|
5369
5320
|
}
|
|
5321
|
+
return terrain;
|
|
5322
|
+
}
|
|
5323
|
+
static getMartiniTileMesh(meshMaxError, width, terrain) {
|
|
5324
|
+
const gridSize = width === 257 ? 257 : width + 1;
|
|
5325
|
+
const martini = new Martini(gridSize);
|
|
5326
|
+
const tile = martini.createTile(terrain);
|
|
5327
|
+
const { vertices, triangles } = tile.getMesh(meshMaxError);
|
|
5328
|
+
return { vertices, triangles };
|
|
5329
|
+
}
|
|
5330
|
+
static getDelatinTileMesh(meshMaxError, width, height, terrain) {
|
|
5331
|
+
const widthPlus = width === 257 ? 257 : width + 1;
|
|
5332
|
+
const heightPlus = height === 257 ? 257 : height + 1;
|
|
5333
|
+
const tin = new Delatin(terrain, widthPlus, heightPlus);
|
|
5334
|
+
tin.run(meshMaxError);
|
|
5335
|
+
// @ts-expect-error: Delatin instance properties 'coords' and 'triangles' are not explicitly typed in the library port
|
|
5336
|
+
const { coords, triangles } = tin;
|
|
5337
|
+
const vertices = coords;
|
|
5338
|
+
return { vertices, triangles };
|
|
5339
|
+
}
|
|
5340
|
+
static getMeshAttributes(vertices, terrain, width, height, bounds) {
|
|
5341
|
+
const gridSize = width === 257 ? 257 : width + 1;
|
|
5342
|
+
const numOfVerticies = vertices.length / 2;
|
|
5343
|
+
// vec3. x, y in pixels, z in meters
|
|
5344
|
+
const positions = new Float32Array(numOfVerticies * 3);
|
|
5345
|
+
// vec2. 1 to 1 relationship with position. represents the uv on the texture image. 0,0 to 1,1.
|
|
5346
|
+
const texCoords = new Float32Array(numOfVerticies * 2);
|
|
5347
|
+
const [minX, minY, maxX, maxY] = bounds || [0, 0, width, height];
|
|
5348
|
+
// If stitched (257), the spatial extent covers 0..256 pixels, so we divide by 256.
|
|
5349
|
+
// If standard (256), the spatial extent covers 0..256 pixels (with backfill), so we divide by 256.
|
|
5350
|
+
const effectiveWidth = width === 257 ? width - 1 : width;
|
|
5351
|
+
const effectiveHeight = height === 257 ? height - 1 : height;
|
|
5352
|
+
const xScale = (maxX - minX) / effectiveWidth;
|
|
5353
|
+
const yScale = (maxY - minY) / effectiveHeight;
|
|
5354
|
+
for (let i = 0; i < numOfVerticies; i++) {
|
|
5355
|
+
const x = vertices[i * 2];
|
|
5356
|
+
const y = vertices[i * 2 + 1];
|
|
5357
|
+
const pixelIdx = y * gridSize + x;
|
|
5358
|
+
positions[3 * i] = x * xScale + minX;
|
|
5359
|
+
positions[3 * i + 1] = -y * yScale + maxY;
|
|
5360
|
+
positions[3 * i + 2] = terrain[pixelIdx];
|
|
5361
|
+
texCoords[2 * i] = x / effectiveWidth;
|
|
5362
|
+
texCoords[2 * i + 1] = y / effectiveHeight;
|
|
5363
|
+
}
|
|
5364
|
+
return {
|
|
5365
|
+
POSITION: { value: positions, size: 3 },
|
|
5366
|
+
TEXCOORD_0: { value: texCoords, size: 2 },
|
|
5367
|
+
// NORMAL: {}, - optional, but creates the high poly look with lighting
|
|
5368
|
+
};
|
|
5369
|
+
}
|
|
5370
|
+
}
|
|
5371
|
+
|
|
5372
|
+
// DataUtils.ts
|
|
5373
|
+
function scale(num, inMin, inMax, outMin, outMax) {
|
|
5374
|
+
if (inMax === inMin) {
|
|
5375
|
+
return outMin;
|
|
5376
|
+
}
|
|
5377
|
+
return ((num - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
|
|
5378
|
+
}
|
|
5379
|
+
|
|
5380
|
+
class BitmapGenerator {
|
|
5381
|
+
static async generate(input, options) {
|
|
5382
|
+
const optionsLocal = { ...options };
|
|
5383
|
+
const { rasters, width, height } = input;
|
|
5384
|
+
const channels = rasters.length;
|
|
5370
5385
|
const canvas = document.createElement('canvas');
|
|
5371
5386
|
canvas.width = width;
|
|
5372
5387
|
canvas.height = height;
|
|
@@ -5377,21 +5392,14 @@ class GeoImage {
|
|
|
5377
5392
|
let b;
|
|
5378
5393
|
let a;
|
|
5379
5394
|
const size = width * height * 4;
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
optionsLocal.
|
|
5385
|
-
optionsLocal.
|
|
5386
|
-
|
|
5387
|
-
optionsLocal.
|
|
5388
|
-
optionsLocal.useChannelIndex ??= options.useChannel === null ? null : options.useChannel - 1;
|
|
5389
|
-
// console.log(rasters[0])
|
|
5390
|
-
/* console.log("raster 0 length: " + rasters[0].length)
|
|
5391
|
-
console.log("image width: " + width)
|
|
5392
|
-
console.log("channels: " + channels)
|
|
5393
|
-
console.log("format: " + rasters[0].length / (width * height))
|
|
5394
|
-
*/
|
|
5395
|
+
const alpha255 = Math.floor(optionsLocal.alpha * 2.55);
|
|
5396
|
+
optionsLocal.unidentifiedColor = this.getColorFromChromaType(optionsLocal.unidentifiedColor, alpha255);
|
|
5397
|
+
optionsLocal.nullColor = this.getColorFromChromaType(optionsLocal.nullColor, alpha255);
|
|
5398
|
+
optionsLocal.clippedColor = this.getColorFromChromaType(optionsLocal.clippedColor, alpha255);
|
|
5399
|
+
optionsLocal.color = this.getColorFromChromaType(optionsLocal.color, alpha255);
|
|
5400
|
+
optionsLocal.useChannelIndex ??= optionsLocal.useChannel == null ? null : optionsLocal.useChannel - 1;
|
|
5401
|
+
// Derive channel count from data if not provided
|
|
5402
|
+
const numAvailableChannels = optionsLocal.numOfChannels ?? (rasters.length === 1 ? rasters[0].length / (width * height) : rasters.length);
|
|
5395
5403
|
if (optionsLocal.useChannelIndex == null) {
|
|
5396
5404
|
if (channels === 1) {
|
|
5397
5405
|
if (rasters[0].length / (width * height) === 1) {
|
|
@@ -5399,17 +5407,13 @@ class GeoImage {
|
|
|
5399
5407
|
// AUTO RANGE
|
|
5400
5408
|
if (optionsLocal.useAutoRange) {
|
|
5401
5409
|
optionsLocal.colorScaleValueRange = this.getMinMax(channel, optionsLocal);
|
|
5402
|
-
// console.log('data min: ' + optionsLocal.rangeMin + ', max: ' + optionsLocal.rangeMax);
|
|
5403
5410
|
}
|
|
5404
5411
|
// SINGLE CHANNEL
|
|
5405
5412
|
const colorData = this.getColorValue(channel, optionsLocal, size);
|
|
5406
|
-
|
|
5407
|
-
imageData.data[index] = value;
|
|
5408
|
-
});
|
|
5413
|
+
imageData.data.set(colorData);
|
|
5409
5414
|
}
|
|
5410
5415
|
// RGB values in one channel
|
|
5411
5416
|
if (rasters[0].length / (width * height) === 3) {
|
|
5412
|
-
// console.log("geoImage: " + "RGB 1 array of length: " + rasters[0].length);
|
|
5413
5417
|
let pixel = 0;
|
|
5414
5418
|
for (let idx = 0; idx < size; idx += 4) {
|
|
5415
5419
|
const rgbColor = [rasters[0][pixel], rasters[0][pixel + 1], rasters[0][pixel + 2]];
|
|
@@ -5421,7 +5425,6 @@ class GeoImage {
|
|
|
5421
5425
|
}
|
|
5422
5426
|
}
|
|
5423
5427
|
if (rasters[0].length / (width * height) === 4) {
|
|
5424
|
-
// console.log("geoImage: " + "RGBA 1 array");
|
|
5425
5428
|
rasters[0].forEach((value, index) => {
|
|
5426
5429
|
imageData.data[index] = value;
|
|
5427
5430
|
});
|
|
@@ -5430,11 +5433,12 @@ class GeoImage {
|
|
|
5430
5433
|
if (channels === 3) {
|
|
5431
5434
|
// RGB
|
|
5432
5435
|
let pixel = 0;
|
|
5436
|
+
const alphaConst = Math.floor(optionsLocal.alpha * 2.55);
|
|
5433
5437
|
for (let i = 0; i < size; i += 4) {
|
|
5434
5438
|
r = rasters[0][pixel];
|
|
5435
5439
|
g = rasters[1][pixel];
|
|
5436
5440
|
b = rasters[2][pixel];
|
|
5437
|
-
a =
|
|
5441
|
+
a = alphaConst;
|
|
5438
5442
|
imageData.data[i] = r;
|
|
5439
5443
|
imageData.data[i + 1] = g;
|
|
5440
5444
|
imageData.data[i + 2] = b;
|
|
@@ -5445,11 +5449,12 @@ class GeoImage {
|
|
|
5445
5449
|
if (channels === 4) {
|
|
5446
5450
|
// RGBA
|
|
5447
5451
|
let pixel = 0;
|
|
5452
|
+
const alphaConst = Math.floor(optionsLocal.alpha * 2.55);
|
|
5448
5453
|
for (let i = 0; i < size; i += 4) {
|
|
5449
5454
|
r = rasters[0][pixel];
|
|
5450
5455
|
g = rasters[1][pixel];
|
|
5451
5456
|
b = rasters[2][pixel];
|
|
5452
|
-
a =
|
|
5457
|
+
a = alphaConst;
|
|
5453
5458
|
imageData.data[i] = r;
|
|
5454
5459
|
imageData.data[i + 1] = g;
|
|
5455
5460
|
imageData.data[i + 2] = b;
|
|
@@ -5458,21 +5463,16 @@ class GeoImage {
|
|
|
5458
5463
|
}
|
|
5459
5464
|
}
|
|
5460
5465
|
}
|
|
5461
|
-
else if (optionsLocal.useChannelIndex <
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
}
|
|
5466
|
+
else if (optionsLocal.useChannelIndex < numAvailableChannels && optionsLocal.useChannelIndex >= 0) {
|
|
5467
|
+
const isInterleaved = rasters.length === 1 && numAvailableChannels > 1;
|
|
5468
|
+
const channel = isInterleaved ? rasters[0] : (rasters[optionsLocal.useChannelIndex] ?? rasters[0]);
|
|
5469
|
+
const samplesPerPixel = isInterleaved ? numAvailableChannels : 1;
|
|
5466
5470
|
// AUTO RANGE
|
|
5467
5471
|
if (optionsLocal.useAutoRange) {
|
|
5468
|
-
optionsLocal.colorScaleValueRange = this.getMinMax(channel, optionsLocal);
|
|
5469
|
-
// console.log('data min: ' + optionsLocal.rangeMin + ', max: ' + optionsLocal.rangeMax);
|
|
5472
|
+
optionsLocal.colorScaleValueRange = this.getMinMax(channel, optionsLocal, samplesPerPixel);
|
|
5470
5473
|
}
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
colorData.forEach((value, index) => {
|
|
5474
|
-
imageData.data[index] = value;
|
|
5475
|
-
});
|
|
5474
|
+
const colorData = this.getColorValue(channel, optionsLocal, size, samplesPerPixel);
|
|
5475
|
+
imageData.data.set(colorData);
|
|
5476
5476
|
}
|
|
5477
5477
|
else {
|
|
5478
5478
|
// if user defined channel does not exist
|
|
@@ -5483,92 +5483,162 @@ class GeoImage {
|
|
|
5483
5483
|
imageData.data[index] = value;
|
|
5484
5484
|
});
|
|
5485
5485
|
}
|
|
5486
|
-
//
|
|
5486
|
+
// Optimization: Skip Canvas -> PNG encoding -> Base64 string
|
|
5487
|
+
// Return raw GPU-ready ImageBitmap directly
|
|
5488
|
+
// Note: createImageBitmap(imageData) is cleaner, but using the canvas ensures broad compatibility
|
|
5487
5489
|
c.putImageData(imageData, 0, 0);
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
let
|
|
5494
|
-
let
|
|
5495
|
-
for (let idx =
|
|
5490
|
+
return createImageBitmap(canvas);
|
|
5491
|
+
}
|
|
5492
|
+
static getMinMax(array, options, samplesPerPixel = 1) {
|
|
5493
|
+
let maxValue = -Infinity;
|
|
5494
|
+
let minValue = Infinity;
|
|
5495
|
+
let foundValid = false;
|
|
5496
|
+
let pixel = samplesPerPixel === 1 ? 0 : (options.useChannelIndex ?? 0);
|
|
5497
|
+
for (let idx = pixel; idx < array.length; idx += samplesPerPixel) {
|
|
5496
5498
|
if (options.noDataValue === undefined || array[idx] !== options.noDataValue) {
|
|
5497
5499
|
if (array[idx] > maxValue)
|
|
5498
5500
|
maxValue = array[idx];
|
|
5499
5501
|
if (array[idx] < minValue)
|
|
5500
5502
|
minValue = array[idx];
|
|
5503
|
+
foundValid = true;
|
|
5501
5504
|
}
|
|
5502
5505
|
}
|
|
5506
|
+
if (!foundValid) {
|
|
5507
|
+
return options.colorScaleValueRange || [0, 255];
|
|
5508
|
+
}
|
|
5503
5509
|
return [minValue, maxValue];
|
|
5504
5510
|
}
|
|
5505
|
-
getColorValue(dataArray, options, arrayLength,
|
|
5506
|
-
// const rgb = chroma.random().rgb(); // [R, G, B]
|
|
5507
|
-
// const randomColor = [...rgb, 120];
|
|
5511
|
+
static getColorValue(dataArray, options, arrayLength, samplesPerPixel = 1) {
|
|
5508
5512
|
const colorScale = chroma.scale(options.colorScale).domain(options.colorScaleValueRange);
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
const
|
|
5512
|
-
|
|
5513
|
-
const
|
|
5514
|
-
const
|
|
5515
|
-
|
|
5516
|
-
const
|
|
5517
|
-
const
|
|
5518
|
-
const
|
|
5513
|
+
let pixel = samplesPerPixel === 1 ? 0 : (options.useChannelIndex ?? 0);
|
|
5514
|
+
const colorsArray = new Uint8ClampedArray(arrayLength);
|
|
5515
|
+
const cbvInput = options.colorsBasedOnValues ?? [];
|
|
5516
|
+
const classesInput = options.colorClasses ?? [];
|
|
5517
|
+
const optUseColorsBasedOnValues = options.useColorsBasedOnValues && cbvInput.length > 0;
|
|
5518
|
+
const optUseColorClasses = options.useColorClasses && classesInput.length > 0;
|
|
5519
|
+
const dataValues = optUseColorsBasedOnValues ? cbvInput.map(([first]) => first) : [];
|
|
5520
|
+
const colorValues = optUseColorsBasedOnValues ? cbvInput.map(([, second]) => [...chroma(second).rgb(), Math.floor(options.alpha * 2.55)]) : [];
|
|
5521
|
+
const colorClasses = optUseColorClasses ? classesInput.map(([color]) => [...chroma(color).rgb(), Math.floor(options.alpha * 2.55)]) : [];
|
|
5522
|
+
const dataIntervals = optUseColorClasses ? classesInput.map(([, interval]) => interval) : [];
|
|
5523
|
+
const dataIntervalBounds = optUseColorClasses ? classesInput.map(([, , bounds], index) => {
|
|
5519
5524
|
if (bounds !== undefined)
|
|
5520
5525
|
return bounds;
|
|
5521
|
-
if (index ===
|
|
5526
|
+
if (index === classesInput.length - 1)
|
|
5522
5527
|
return [true, true];
|
|
5523
5528
|
return [true, false];
|
|
5524
|
-
}) :
|
|
5529
|
+
}) : [];
|
|
5530
|
+
// Pre-calculate Loop Variables to avoid object lookup in loop
|
|
5531
|
+
const optNoData = options.noDataValue;
|
|
5532
|
+
const optClipLow = options.clipLow;
|
|
5533
|
+
const optClipHigh = options.clipHigh;
|
|
5534
|
+
const optClippedColor = options.clippedColor;
|
|
5535
|
+
const optUseHeatMap = options.useHeatMap;
|
|
5536
|
+
const optUseSingleColor = options.useSingleColor;
|
|
5537
|
+
const optUseDataForOpacity = options.useDataForOpacity;
|
|
5538
|
+
const optColor = options.color;
|
|
5539
|
+
const optUnidentifiedColor = options.unidentifiedColor;
|
|
5540
|
+
const optNullColor = options.nullColor;
|
|
5541
|
+
const optAlpha = Math.floor(options.alpha * 2.55);
|
|
5542
|
+
const rangeMin = options.colorScaleValueRange[0];
|
|
5543
|
+
const rangeMax = options.colorScaleValueRange.slice(-1)[0];
|
|
5544
|
+
// LOOKUP TABLE OPTIMIZATION (for 8-bit data)
|
|
5545
|
+
// If the data is Uint8 (0-255), we can pre-calculate the result for every possible value.
|
|
5546
|
+
const is8Bit = dataArray instanceof Uint8Array || dataArray instanceof Uint8ClampedArray;
|
|
5547
|
+
// The LUT optimization is only applied for 8-bit data when `useDataForOpacity` is false.
|
|
5548
|
+
// `useDataForOpacity` is excluded because it requires the raw data value for
|
|
5549
|
+
// dynamic opacity scaling. All other visualization modes (HeatMap, Categorical,
|
|
5550
|
+
// Classes, Single Color) are pre-calculated into the LUT for maximum performance.
|
|
5551
|
+
if (is8Bit && !optUseDataForOpacity) {
|
|
5552
|
+
// Create LUT: 256 values * 4 channels (RGBA)
|
|
5553
|
+
const lut = new Uint8ClampedArray(256 * 4);
|
|
5554
|
+
for (let i = 0; i < 256; i++) {
|
|
5555
|
+
let r = optNullColor[0], g = optNullColor[1], b = optNullColor[2], a = optNullColor[3];
|
|
5556
|
+
// Logic mirroring the pixel loop
|
|
5557
|
+
if (optNoData === undefined || i !== optNoData) {
|
|
5558
|
+
if ((optClipLow != null && i <= optClipLow) || (optClipHigh != null && i >= optClipHigh)) {
|
|
5559
|
+
[r, g, b, a] = optClippedColor;
|
|
5560
|
+
}
|
|
5561
|
+
else {
|
|
5562
|
+
let c = [r, g, b, a];
|
|
5563
|
+
if (optUseHeatMap) {
|
|
5564
|
+
const rgb = colorScale(i).rgb();
|
|
5565
|
+
c = [rgb[0], rgb[1], rgb[2], optAlpha];
|
|
5566
|
+
}
|
|
5567
|
+
else if (optUseColorsBasedOnValues) {
|
|
5568
|
+
const index = dataValues.indexOf(i);
|
|
5569
|
+
c = (index > -1) ? colorValues[index] : optUnidentifiedColor;
|
|
5570
|
+
}
|
|
5571
|
+
else if (optUseColorClasses) {
|
|
5572
|
+
const index = this.findClassIndex(i, dataIntervals, dataIntervalBounds);
|
|
5573
|
+
c = (index > -1) ? colorClasses[index] : optUnidentifiedColor;
|
|
5574
|
+
}
|
|
5575
|
+
else if (optUseSingleColor) {
|
|
5576
|
+
c = optColor;
|
|
5577
|
+
}
|
|
5578
|
+
[r, g, b, a] = c;
|
|
5579
|
+
}
|
|
5580
|
+
}
|
|
5581
|
+
lut[i * 4] = r;
|
|
5582
|
+
lut[i * 4 + 1] = g;
|
|
5583
|
+
lut[i * 4 + 2] = b;
|
|
5584
|
+
lut[i * 4 + 3] = a;
|
|
5585
|
+
}
|
|
5586
|
+
// Fast Apply Loop
|
|
5587
|
+
let outIdx = 0;
|
|
5588
|
+
const numPixels = arrayLength / 4;
|
|
5589
|
+
for (let i = 0; i < numPixels; i++) {
|
|
5590
|
+
const val = dataArray[pixel];
|
|
5591
|
+
const lutIdx = Math.min(255, Math.max(0, val)) * 4;
|
|
5592
|
+
colorsArray[outIdx++] = lut[lutIdx];
|
|
5593
|
+
colorsArray[outIdx++] = lut[lutIdx + 1];
|
|
5594
|
+
colorsArray[outIdx++] = lut[lutIdx + 2];
|
|
5595
|
+
colorsArray[outIdx++] = lut[lutIdx + 3];
|
|
5596
|
+
pixel += samplesPerPixel;
|
|
5597
|
+
}
|
|
5598
|
+
return colorsArray;
|
|
5599
|
+
}
|
|
5600
|
+
// Standard Loop (Float or non-optimized)
|
|
5525
5601
|
for (let i = 0; i < arrayLength; i += 4) {
|
|
5526
|
-
let
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|| (options.clipHigh != null && dataArray[pixel] >= options.clipHigh)) {
|
|
5532
|
-
pixelColor = options.clippedColor;
|
|
5602
|
+
let r = optNullColor[0], g = optNullColor[1], b = optNullColor[2], a = optNullColor[3];
|
|
5603
|
+
const val = dataArray[pixel];
|
|
5604
|
+
if ((!Number.isNaN(val)) && (optNoData === undefined || val !== optNoData)) {
|
|
5605
|
+
if ((optClipLow != null && val <= optClipLow) || (optClipHigh != null && val >= optClipHigh)) {
|
|
5606
|
+
[r, g, b, a] = optClippedColor;
|
|
5533
5607
|
}
|
|
5534
5608
|
else {
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5609
|
+
let c;
|
|
5610
|
+
if (optUseHeatMap) {
|
|
5611
|
+
const rgb = colorScale(val).rgb();
|
|
5612
|
+
c = [rgb[0], rgb[1], rgb[2], optAlpha];
|
|
5538
5613
|
}
|
|
5539
|
-
if (
|
|
5540
|
-
const index = dataValues.indexOf(
|
|
5541
|
-
|
|
5542
|
-
pixelColor = colorValues[index];
|
|
5543
|
-
}
|
|
5544
|
-
else
|
|
5545
|
-
pixelColor = options.unidentifiedColor;
|
|
5614
|
+
else if (optUseColorsBasedOnValues) {
|
|
5615
|
+
const index = dataValues.indexOf(val);
|
|
5616
|
+
c = (index > -1) ? colorValues[index] : optUnidentifiedColor;
|
|
5546
5617
|
}
|
|
5547
|
-
if (
|
|
5548
|
-
const index = this.findClassIndex(
|
|
5549
|
-
|
|
5550
|
-
pixelColor = colorClasses[index];
|
|
5551
|
-
}
|
|
5552
|
-
else
|
|
5553
|
-
pixelColor = options.unidentifiedColor;
|
|
5618
|
+
else if (optUseColorClasses) {
|
|
5619
|
+
const index = this.findClassIndex(val, dataIntervals, dataIntervalBounds);
|
|
5620
|
+
c = (index > -1) ? colorClasses[index] : optUnidentifiedColor;
|
|
5554
5621
|
}
|
|
5555
|
-
if (
|
|
5556
|
-
|
|
5557
|
-
pixelColor = options.color;
|
|
5622
|
+
else if (optUseSingleColor) {
|
|
5623
|
+
c = optColor;
|
|
5558
5624
|
}
|
|
5559
|
-
if (
|
|
5560
|
-
|
|
5625
|
+
if (c) {
|
|
5626
|
+
[r, g, b, a] = c;
|
|
5627
|
+
}
|
|
5628
|
+
if (optUseDataForOpacity) {
|
|
5629
|
+
a = scale(val, rangeMin, rangeMax, 0, 255);
|
|
5561
5630
|
}
|
|
5562
5631
|
}
|
|
5563
5632
|
}
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5633
|
+
colorsArray[i] = r;
|
|
5634
|
+
colorsArray[i + 1] = g;
|
|
5635
|
+
colorsArray[i + 2] = b;
|
|
5636
|
+
colorsArray[i + 3] = a;
|
|
5637
|
+
pixel += samplesPerPixel;
|
|
5567
5638
|
}
|
|
5568
5639
|
return colorsArray;
|
|
5569
5640
|
}
|
|
5570
|
-
findClassIndex(number, intervals, bounds) {
|
|
5571
|
-
// returns index of the first class to which the number belongs
|
|
5641
|
+
static findClassIndex(number, intervals, bounds) {
|
|
5572
5642
|
for (let idx = 0; idx < intervals.length; idx += 1) {
|
|
5573
5643
|
const [min, max] = intervals[idx];
|
|
5574
5644
|
const [includeEqualMin, includeEqualMax] = bounds[idx];
|
|
@@ -5579,89 +5649,89 @@ class GeoImage {
|
|
|
5579
5649
|
}
|
|
5580
5650
|
return -1;
|
|
5581
5651
|
}
|
|
5582
|
-
getDefaultColor(size, nullColor) {
|
|
5583
|
-
const colorsArray = new
|
|
5652
|
+
static getDefaultColor(size, nullColor) {
|
|
5653
|
+
const colorsArray = new Uint8ClampedArray(size);
|
|
5584
5654
|
for (let i = 0; i < size; i += 4) {
|
|
5585
5655
|
[colorsArray[i], colorsArray[i + 1], colorsArray[i + 2], colorsArray[i + 3]] = nullColor;
|
|
5586
5656
|
}
|
|
5587
5657
|
return colorsArray;
|
|
5588
5658
|
}
|
|
5589
|
-
getColorFromChromaType(colorDefinition) {
|
|
5659
|
+
static getColorFromChromaType(colorDefinition, alpha = 255) {
|
|
5590
5660
|
if (!Array.isArray(colorDefinition) || colorDefinition.length !== 4) {
|
|
5591
|
-
return [...chroma(colorDefinition).rgb(),
|
|
5661
|
+
return [...chroma(colorDefinition).rgb(), alpha];
|
|
5592
5662
|
}
|
|
5593
5663
|
return colorDefinition;
|
|
5594
5664
|
}
|
|
5595
|
-
hasPixelsNoData(pixels, noDataValue) {
|
|
5665
|
+
static hasPixelsNoData(pixels, noDataValue) {
|
|
5596
5666
|
return noDataValue !== undefined && pixels.every((pixel) => pixel === noDataValue);
|
|
5597
5667
|
}
|
|
5598
5668
|
}
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5669
|
+
|
|
5670
|
+
class GeoImage {
|
|
5671
|
+
data;
|
|
5672
|
+
async setUrl(url) {
|
|
5673
|
+
// TODO - not tested
|
|
5674
|
+
const response = await fetch(url);
|
|
5675
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
5676
|
+
const tiff = await fromArrayBuffer(arrayBuffer);
|
|
5677
|
+
const data = await tiff.getImage(0);
|
|
5678
|
+
this.data = data;
|
|
5679
|
+
}
|
|
5680
|
+
async getMap(input, options, meshMaxError) {
|
|
5681
|
+
const mergedOptions = { ...DefaultGeoImageOptions, ...options };
|
|
5682
|
+
switch (mergedOptions.type) {
|
|
5683
|
+
case 'image':
|
|
5684
|
+
return this.getBitmap(input, mergedOptions);
|
|
5685
|
+
case 'terrain':
|
|
5686
|
+
return this.getHeightmap(input, mergedOptions, meshMaxError);
|
|
5687
|
+
default:
|
|
5688
|
+
return null;
|
|
5689
|
+
}
|
|
5690
|
+
}
|
|
5691
|
+
// GetHeightmap uses only "useChannel" and "multiplier" options
|
|
5692
|
+
async getHeightmap(input, options, meshMaxError) {
|
|
5693
|
+
let rasters = [];
|
|
5694
|
+
let width;
|
|
5695
|
+
let height;
|
|
5696
|
+
let bounds;
|
|
5697
|
+
if (typeof (input) === 'string') {
|
|
5698
|
+
// TODO not tested
|
|
5699
|
+
// input is type of object
|
|
5700
|
+
await this.setUrl(input);
|
|
5701
|
+
rasters = (await this.data.readRasters());
|
|
5702
|
+
width = this.data.getWidth();
|
|
5703
|
+
height = this.data.getHeight();
|
|
5704
|
+
bounds = this.data.getBoundingBox();
|
|
5705
|
+
}
|
|
5706
|
+
else {
|
|
5707
|
+
rasters = input.rasters;
|
|
5708
|
+
width = input.width;
|
|
5709
|
+
height = input.height;
|
|
5710
|
+
bounds = input.bounds;
|
|
5711
|
+
}
|
|
5712
|
+
// Delegate to TerrainGenerator
|
|
5713
|
+
return TerrainGenerator.generate({ width, height, rasters, bounds }, options, meshMaxError);
|
|
5714
|
+
}
|
|
5715
|
+
async getBitmap(input, options) {
|
|
5716
|
+
let rasters = [];
|
|
5717
|
+
let width;
|
|
5718
|
+
let height;
|
|
5719
|
+
if (typeof (input) === 'string') {
|
|
5720
|
+
// TODO not tested
|
|
5721
|
+
// input is type of object
|
|
5722
|
+
await this.setUrl(input);
|
|
5723
|
+
rasters = (await this.data.readRasters());
|
|
5724
|
+
width = this.data.getWidth();
|
|
5725
|
+
height = this.data.getHeight();
|
|
5726
|
+
}
|
|
5727
|
+
else {
|
|
5728
|
+
rasters = input.rasters;
|
|
5729
|
+
width = input.width;
|
|
5730
|
+
height = input.height;
|
|
5731
|
+
}
|
|
5732
|
+
// Delegate to BitmapGenerator
|
|
5733
|
+
return BitmapGenerator.generate({ width, height, rasters }, options);
|
|
5640
5734
|
}
|
|
5641
|
-
return {
|
|
5642
|
-
POSITION: { value: positions, size: 3 },
|
|
5643
|
-
TEXCOORD_0: { value: texCoords, size: 2 },
|
|
5644
|
-
// NORMAL: {}, - optional, but creates the high poly look with lighting
|
|
5645
|
-
};
|
|
5646
|
-
}
|
|
5647
|
-
/**
|
|
5648
|
-
* Get Delatin generated vertices and triangles
|
|
5649
|
-
*
|
|
5650
|
-
* @param {number} meshMaxError threshold for simplifying mesh
|
|
5651
|
-
* @param {number} width width of the input data array
|
|
5652
|
-
* @param {number} height height of the input data array
|
|
5653
|
-
* @param {number[] | Float32Array} terrain elevation data
|
|
5654
|
-
* @returns {{vertices: number[], triangles: number[]}} vertices and triangles data
|
|
5655
|
-
*/
|
|
5656
|
-
function getDelatinTileMesh(meshMaxError, width, height, terrain) {
|
|
5657
|
-
const widthPlus = width === 257 ? 257 : width + 1;
|
|
5658
|
-
const heightPlus = height === 257 ? 257 : height + 1;
|
|
5659
|
-
const tin = new Delatin(terrain, widthPlus, heightPlus);
|
|
5660
|
-
tin.run(meshMaxError);
|
|
5661
|
-
// @ts-expect-error: Delatin instance properties 'coords' and 'triangles' are not explicitly typed in the library port
|
|
5662
|
-
const { coords, triangles } = tin;
|
|
5663
|
-
const vertices = coords;
|
|
5664
|
-
return { vertices, triangles };
|
|
5665
5735
|
}
|
|
5666
5736
|
|
|
5667
5737
|
const EARTH_CIRCUMFERENCE = 2 * Math.PI * 6378137;
|
|
@@ -5686,19 +5756,31 @@ class CogTiles {
|
|
|
5686
5756
|
}
|
|
5687
5757
|
async initializeCog(url) {
|
|
5688
5758
|
if (this.cog)
|
|
5689
|
-
return; // Prevent re-initialization
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5759
|
+
return; // Prevent re-initialization on the same instance
|
|
5760
|
+
try {
|
|
5761
|
+
this.cog = await fromUrl(url);
|
|
5762
|
+
const image = await this.cog.getImage();
|
|
5763
|
+
const fileDirectory = image.fileDirectory;
|
|
5764
|
+
this.cogOrigin = image.getOrigin();
|
|
5765
|
+
this.options.noDataValue ??= await this.getNoDataValue(image);
|
|
5766
|
+
this.options.format ??= await this.getDataTypeFromTags(fileDirectory);
|
|
5767
|
+
this.options.numOfChannels = fileDirectory.getValue('SamplesPerPixel');
|
|
5768
|
+
this.options.planarConfig = fileDirectory.getValue('PlanarConfiguration');
|
|
5769
|
+
[this.cogZoomLookup, this.cogResolutionLookup] = await this.buildCogZoomResolutionLookup(this.cog);
|
|
5770
|
+
this.tileSize = image.getTileWidth();
|
|
5771
|
+
// 1. Validation: Ensure the image is tiled
|
|
5772
|
+
if (!this.tileSize || !image.getTileHeight()) {
|
|
5773
|
+
throw new Error('GeoTIFF Error: The provided image is not tiled. '
|
|
5774
|
+
+ 'Please use "rio cogeo create --web-optimized" to fix this.');
|
|
5775
|
+
}
|
|
5776
|
+
this.zoomRange = this.calculateZoomRange(this.tileSize, image.getResolution()[0], await this.cog.getImageCount());
|
|
5777
|
+
this.bounds = this.calculateBoundsAsLatLon(image.getBoundingBox());
|
|
5778
|
+
}
|
|
5779
|
+
catch (error) {
|
|
5780
|
+
/* eslint-disable no-console */
|
|
5781
|
+
console.error(`[CogTiles] Failed to initialize COG from ${url}:`, error);
|
|
5782
|
+
throw error;
|
|
5783
|
+
}
|
|
5702
5784
|
}
|
|
5703
5785
|
getZoomRange() {
|
|
5704
5786
|
return this.zoomRange;
|
|
@@ -5804,13 +5886,6 @@ class CogTiles {
|
|
|
5804
5886
|
async getTileFromImage(tileX, tileY, zoom, fetchSize) {
|
|
5805
5887
|
const imageIndex = this.getImageIndexForZoomLevel(zoom);
|
|
5806
5888
|
const targetImage = await this.cog.getImage(imageIndex);
|
|
5807
|
-
// 1. Validation: Ensure the image is tiled
|
|
5808
|
-
const tileWidth = targetImage.getTileWidth();
|
|
5809
|
-
const tileHeight = targetImage.getTileHeight();
|
|
5810
|
-
if (!tileWidth || !tileHeight) {
|
|
5811
|
-
throw new Error('GeoTIFF Error: The provided image is not tiled. '
|
|
5812
|
-
+ 'Please use "rio cogeo create --web-optimized" to fix this.');
|
|
5813
|
-
}
|
|
5814
5889
|
// --- STEP 1: CALCULATE BOUNDS IN METERS ---
|
|
5815
5890
|
// 2. Get COG Metadata (image = COG)
|
|
5816
5891
|
const imageResolution = this.cogResolutionLookup[imageIndex];
|
|
@@ -5865,16 +5940,14 @@ class CogTiles {
|
|
|
5865
5940
|
// If the tile is hanging off the edge, we need to manually reconstruct it.
|
|
5866
5941
|
// We strictly compare against FETCH_SIZE because that is our target buffer dimension.
|
|
5867
5942
|
if (missingLeft > 0 || missingTop > 0 || readWidth < FETCH_SIZE || readHeight < FETCH_SIZE) {
|
|
5868
|
-
|
|
5869
|
-
//
|
|
5870
|
-
const
|
|
5871
|
-
|
|
5943
|
+
const numChannels = this.options.numOfChannels || 1;
|
|
5944
|
+
// Initialize with a TypedArray of the full target size and correct data type
|
|
5945
|
+
const validImageData = this.createTileBuffer(this.options.format, FETCH_SIZE, numChannels);
|
|
5946
|
+
if (this.options.noDataValue !== undefined) {
|
|
5947
|
+
validImageData.fill(this.options.noDataValue);
|
|
5948
|
+
}
|
|
5872
5949
|
// if the valid window is smaller than the tile size, it gets the image size width and height, thus validRasterData.width must be used as below
|
|
5873
5950
|
const validRasterData = await targetImage.readRasters({ window });
|
|
5874
|
-
// FOR MULTI-BAND - the result is one array with sequentially typed bands, firstly all data for the band 0, then for band 1
|
|
5875
|
-
// I think this is less practical then the commented solution above, but I do it so it works with the code in GeoImage.ts in deck.gl-geoimage in function getColorValue.
|
|
5876
|
-
const validImageData = Array(validRasterData.length * validRasterData[0].length);
|
|
5877
|
-
validImageData.fill(this.options.noDataValue);
|
|
5878
5951
|
// Place the valid pixel data into the tile buffer.
|
|
5879
5952
|
for (let band = 0; band < validRasterData.length; band += 1) {
|
|
5880
5953
|
// We must reset the buffer for each band, otherwise data from previous band persists in padding areas
|
|
@@ -5888,7 +5961,6 @@ class CogTiles {
|
|
|
5888
5961
|
const srcRowOffset = row * validRasterData.width;
|
|
5889
5962
|
for (let col = 0; col < readWidth; col += 1) {
|
|
5890
5963
|
// Compute the destination position in the tile buffer.
|
|
5891
|
-
// We shift by the number of missing pixels (if any) at the top/left.
|
|
5892
5964
|
const destCol = missingLeft + col;
|
|
5893
5965
|
// Bounds Check: Ensure we don't write outside the allocated buffer
|
|
5894
5966
|
if (destRow < FETCH_SIZE && destCol < FETCH_SIZE) {
|
|
@@ -5901,7 +5973,7 @@ class CogTiles {
|
|
|
5901
5973
|
}
|
|
5902
5974
|
}
|
|
5903
5975
|
for (let i = 0; i < tileBuffer.length; i += 1) {
|
|
5904
|
-
validImageData[i *
|
|
5976
|
+
validImageData[i * numChannels + band] = tileBuffer[i];
|
|
5905
5977
|
}
|
|
5906
5978
|
}
|
|
5907
5979
|
return [validImageData];
|
|
@@ -6019,10 +6091,11 @@ class CogTiles {
|
|
|
6019
6091
|
*
|
|
6020
6092
|
* @param {string} dataType - A string specifying the data type (e.g., "Int32", "Float64", "UInt16", etc.).
|
|
6021
6093
|
* @param {number} tileSize - The width/height of the square tile.
|
|
6022
|
-
* @
|
|
6094
|
+
* @param {number} multiplier - Optional multiplier for interleaved buffers (e.g., numChannels).
|
|
6095
|
+
* @returns {TypedArray} A typed array buffer of length (tileSize * tileSize * multiplier).
|
|
6023
6096
|
*/
|
|
6024
|
-
createTileBuffer(dataType, tileSize) {
|
|
6025
|
-
const length = tileSize * tileSize;
|
|
6097
|
+
createTileBuffer(dataType, tileSize, multiplier = 1) {
|
|
6098
|
+
const length = tileSize * tileSize * multiplier;
|
|
6026
6099
|
switch (dataType) {
|
|
6027
6100
|
case 'UInt8':
|
|
6028
6101
|
return new Uint8Array(length);
|
|
@@ -6137,12 +6210,10 @@ class CogBitmapLayer extends core.CompositeLayer {
|
|
|
6137
6210
|
|| props.bounds !== oldProps.bounds;
|
|
6138
6211
|
if (!this.state.isTiled && shouldReload) ;
|
|
6139
6212
|
// Update the useChannel option for bitmapCogTiles when cogBitmapOptions.useChannel changes.
|
|
6140
|
-
|
|
6141
|
-
// object in this way is not ideal and may need refactoring in the future to follow a more
|
|
6142
|
-
// declarative state management approach. Consider revisiting this if additional properties
|
|
6143
|
-
// need to be synchronized or if the state structure changes.
|
|
6144
|
-
if (props?.cogBitmapOptions?.useChannel && (props.cogBitmapOptions?.useChannel !== oldProps.cogBitmapOptions?.useChannel)) {
|
|
6213
|
+
if (props?.cogBitmapOptions?.useChannel !== oldProps.cogBitmapOptions?.useChannel) {
|
|
6145
6214
|
this.state.bitmapCogTiles.options.useChannel = props.cogBitmapOptions.useChannel;
|
|
6215
|
+
// Trigger a refresh of the tiles
|
|
6216
|
+
this.state.bitmapCogTiles.options.useChannelIndex = null; // Clear cached index
|
|
6146
6217
|
}
|
|
6147
6218
|
if (props.workerUrl) {
|
|
6148
6219
|
core.log.removed('workerUrl', 'loadOptions.terrain.workerUrl')();
|
|
@@ -6320,6 +6391,12 @@ class CogTerrainLayer extends core.CompositeLayer {
|
|
|
6320
6391
|
|| props.elevationDecoder !== oldProps.elevationDecoder
|
|
6321
6392
|
|| props.bounds !== oldProps.bounds;
|
|
6322
6393
|
if (!this.state.isTiled && shouldReload) ;
|
|
6394
|
+
// Update the useChannel option for terrainCogTiles when terrainOptions.useChannel changes.
|
|
6395
|
+
if (props?.terrainOptions?.useChannel !== oldProps.terrainOptions?.useChannel) {
|
|
6396
|
+
this.state.terrainCogTiles.options.useChannel = props.terrainOptions.useChannel;
|
|
6397
|
+
// Trigger a refresh of the tiles
|
|
6398
|
+
this.state.terrainCogTiles.options.useChannelIndex = null; // Clear cached index
|
|
6399
|
+
}
|
|
6323
6400
|
if (props.workerUrl) {
|
|
6324
6401
|
core.log.removed('workerUrl', 'loadOptions.terrain.workerUrl')();
|
|
6325
6402
|
}
|
|
@@ -6452,25 +6529,6 @@ class CogTerrainLayer extends core.CompositeLayer {
|
|
|
6452
6529
|
refinementStrategy,
|
|
6453
6530
|
});
|
|
6454
6531
|
}
|
|
6455
|
-
// if (!elevationData) {
|
|
6456
|
-
// return null;
|
|
6457
|
-
// }
|
|
6458
|
-
// const SubLayerClass = this.getSubLayerClass('mesh', SimpleMeshLayer);
|
|
6459
|
-
// return new SubLayerClass(
|
|
6460
|
-
// this.getSubLayerProps({
|
|
6461
|
-
// id: 'mesh',
|
|
6462
|
-
// }),
|
|
6463
|
-
// {
|
|
6464
|
-
// data: DUMMY_DATA,
|
|
6465
|
-
// mesh: this.state.terrain,
|
|
6466
|
-
// texture,
|
|
6467
|
-
// _instanced: false,
|
|
6468
|
-
// getPosition: (d) => [0, 0, 0],
|
|
6469
|
-
// getColor: color,
|
|
6470
|
-
// material,
|
|
6471
|
-
// wireframe,
|
|
6472
|
-
// },
|
|
6473
|
-
// );
|
|
6474
6532
|
}
|
|
6475
6533
|
}
|
|
6476
6534
|
|