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