@gisatcz/deckgl-geolib 2.6.0-dev.4 → 2.6.0-dev.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +424 -70
- 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 +424 -71
- 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 +1 -0
- package/dist/esm/types/core/GeoImage.d.ts +4 -2
- package/dist/esm/types/core/lib/TerrainGenerator.d.ts +2 -1
- package/dist/esm/types/core/types.d.ts +2 -0
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/esm/types/layers/CogTerrainLayer.d.ts +15 -0
- package/dist/esm/types/workers/TerrainWorkerPool.d.ts +58 -0
- package/dist/esm/types/workers/terrain.worker.d.ts +5 -0
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -4971,6 +4971,7 @@ const DefaultGeoImageOptions = {
|
|
|
4971
4971
|
disableLighting: false,
|
|
4972
4972
|
// --- Animation / Caching ---
|
|
4973
4973
|
cacheAllBands: false,
|
|
4974
|
+
disableWorkerPool: false,
|
|
4974
4975
|
};
|
|
4975
4976
|
|
|
4976
4977
|
class Martini {
|
|
@@ -6331,7 +6332,8 @@ class ReliefCompositor {
|
|
|
6331
6332
|
}
|
|
6332
6333
|
|
|
6333
6334
|
class TerrainGenerator {
|
|
6334
|
-
static async generate(input, options, meshMaxError)
|
|
6335
|
+
static async generate(input, options, meshMaxError, workerPool, // TerrainWorkerPool (optional for flexibility)
|
|
6336
|
+
signal) {
|
|
6335
6337
|
const { width, height } = input;
|
|
6336
6338
|
const isKernel = width === 258;
|
|
6337
6339
|
// 1. Compute Terrain Data (Extract Elevation)
|
|
@@ -6339,27 +6341,47 @@ class TerrainGenerator {
|
|
|
6339
6341
|
// For kernel tiles, the mesh uses the inner 257×257 sub-grid (rows 1–257, cols 1–257)
|
|
6340
6342
|
// so that row 0 / col 0 (kernel padding) is dropped while the bottom/right stitching
|
|
6341
6343
|
// overlap is preserved.
|
|
6342
|
-
|
|
6344
|
+
let meshTerrain = isKernel ? this.extractMeshRaster(terrain) : terrain;
|
|
6343
6345
|
const meshWidth = isKernel ? 257 : width;
|
|
6344
6346
|
const meshHeight = isKernel ? 257 : height;
|
|
6345
6347
|
// 2. Tesselate (Generate Mesh)
|
|
6346
6348
|
const { terrainSkirtHeight, verticalExaggeration = 1.0 } = options;
|
|
6347
6349
|
let mesh;
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6350
|
+
let meshTerrainForAttributes; // ← Will hold terrain for getMeshAttributes()
|
|
6351
|
+
if (workerPool) {
|
|
6352
|
+
// ✅ NEW: Offload to Web Worker with ZERO-COPY ROUNDTRIP
|
|
6353
|
+
// Transfer meshTerrain to worker; worker transfers it back alongside mesh
|
|
6354
|
+
// This avoids meshTerrain.slice() allocation on main thread
|
|
6355
|
+
const result = await workerPool.computeMesh({
|
|
6356
|
+
terrain: meshTerrain, // ← Transferred to worker (detached here)
|
|
6357
|
+
meshMaxError,
|
|
6358
|
+
tesselator: options.tesselator || 'martini',
|
|
6359
|
+
width: meshWidth,
|
|
6360
|
+
height: meshHeight,
|
|
6361
|
+
signal, // ← Thread cancellation signal to worker
|
|
6362
|
+
});
|
|
6363
|
+
mesh = { vertices: result.vertices, triangles: result.triangles };
|
|
6364
|
+
meshTerrainForAttributes = result.terrain; // ← Transferred back from worker
|
|
6365
|
+
meshTerrain = result.terrain; // ← Reassign for downstream use (tileResult.raw, etc.)
|
|
6366
|
+
}
|
|
6367
|
+
else {
|
|
6368
|
+
// ❌ FALLBACK: Synchronous (old behavior, kept for safety)
|
|
6369
|
+
switch (options.tesselator) {
|
|
6370
|
+
case 'martini':
|
|
6371
|
+
mesh = this.getMartiniTileMesh(meshMaxError, meshWidth, meshTerrain);
|
|
6372
|
+
break;
|
|
6373
|
+
case 'delatin':
|
|
6374
|
+
mesh = this.getDelatinTileMesh(meshMaxError, meshWidth, meshHeight, meshTerrain);
|
|
6375
|
+
break;
|
|
6376
|
+
default:
|
|
6377
|
+
mesh = this.getMartiniTileMesh(meshMaxError, meshWidth, meshTerrain);
|
|
6378
|
+
break;
|
|
6379
|
+
}
|
|
6380
|
+
meshTerrainForAttributes = meshTerrain; // ← Use original
|
|
6359
6381
|
}
|
|
6360
6382
|
const { vertices } = mesh;
|
|
6361
6383
|
let { triangles } = mesh;
|
|
6362
|
-
let attributes = this.getMeshAttributes(vertices,
|
|
6384
|
+
let attributes = this.getMeshAttributes(vertices, meshTerrainForAttributes, meshWidth, meshHeight, input.bounds, verticalExaggeration);
|
|
6363
6385
|
// Compute bounding box before adding skirt so that z values are not skewed
|
|
6364
6386
|
const boundingBox = schema.getMeshBoundingBox(attributes);
|
|
6365
6387
|
if (terrainSkirtHeight) {
|
|
@@ -6626,13 +6648,14 @@ class GeoImage {
|
|
|
6626
6648
|
const data = await tiff.getImage(0);
|
|
6627
6649
|
this.data = data;
|
|
6628
6650
|
}
|
|
6629
|
-
async getMap(input, options, meshMaxError)
|
|
6651
|
+
async getMap(input, options, meshMaxError, workerPool, // TerrainWorkerPool (optional)
|
|
6652
|
+
signal) {
|
|
6630
6653
|
const mergedOptions = GeoImage.resolveVisualizationMode({ ...DefaultGeoImageOptions, ...options }, options);
|
|
6631
6654
|
switch (mergedOptions.type) {
|
|
6632
6655
|
case 'image':
|
|
6633
6656
|
return this.getBitmap(input, mergedOptions);
|
|
6634
6657
|
case 'terrain':
|
|
6635
|
-
return this.getHeightmap(input, mergedOptions, meshMaxError);
|
|
6658
|
+
return this.getHeightmap(input, mergedOptions, meshMaxError, workerPool, signal);
|
|
6636
6659
|
default:
|
|
6637
6660
|
return null;
|
|
6638
6661
|
}
|
|
@@ -6689,7 +6712,8 @@ class GeoImage {
|
|
|
6689
6712
|
return resolved;
|
|
6690
6713
|
}
|
|
6691
6714
|
// GetHeightmap uses only "useChannel" and "multiplier" options
|
|
6692
|
-
async getHeightmap(input, options, meshMaxError)
|
|
6715
|
+
async getHeightmap(input, options, meshMaxError, workerPool, // TerrainWorkerPool (optional)
|
|
6716
|
+
signal) {
|
|
6693
6717
|
let rasters = [];
|
|
6694
6718
|
let width;
|
|
6695
6719
|
let height;
|
|
@@ -6711,8 +6735,8 @@ class GeoImage {
|
|
|
6711
6735
|
bounds = input.bounds;
|
|
6712
6736
|
cellSizeMeters = input.cellSizeMeters;
|
|
6713
6737
|
}
|
|
6714
|
-
// Delegate to TerrainGenerator
|
|
6715
|
-
return await TerrainGenerator.generate({ width, height, rasters, bounds, cellSizeMeters }, options, meshMaxError);
|
|
6738
|
+
// Delegate to TerrainGenerator with worker pool and cancellation signal
|
|
6739
|
+
return await TerrainGenerator.generate({ width, height, rasters, bounds, cellSizeMeters }, options, meshMaxError, workerPool, signal);
|
|
6716
6740
|
}
|
|
6717
6741
|
async getBitmap(input, options) {
|
|
6718
6742
|
let rasters = [];
|
|
@@ -6736,6 +6760,218 @@ class GeoImage {
|
|
|
6736
6760
|
}
|
|
6737
6761
|
}
|
|
6738
6762
|
|
|
6763
|
+
function decodeBase64(base64, enableUnicode) {
|
|
6764
|
+
var binaryString = atob(base64);
|
|
6765
|
+
return binaryString;
|
|
6766
|
+
}
|
|
6767
|
+
|
|
6768
|
+
function createURL(base64, sourcemapArg, enableUnicodeArg) {
|
|
6769
|
+
var source = decodeBase64(base64);
|
|
6770
|
+
var start = source.indexOf('\n', 10) + 1;
|
|
6771
|
+
var body = source.substring(start) + ('');
|
|
6772
|
+
var blob = new Blob([body], { type: 'application/javascript' });
|
|
6773
|
+
return URL.createObjectURL(blob);
|
|
6774
|
+
}
|
|
6775
|
+
|
|
6776
|
+
function createBase64WorkerFactory(base64, sourcemapArg, enableUnicodeArg) {
|
|
6777
|
+
var url;
|
|
6778
|
+
return function WorkerFactory(options) {
|
|
6779
|
+
url = url || createURL(base64);
|
|
6780
|
+
return new Worker(url, options);
|
|
6781
|
+
};
|
|
6782
|
+
}
|
|
6783
|
+
|
|
6784
|
+
var WorkerFactory = /*#__PURE__*/createBase64WorkerFactory('Lyogcm9sbHVwLXBsdWdpbi13ZWItd29ya2VyLWxvYWRlciAqLwooZnVuY3Rpb24gKCkgewogICAgJ3VzZSBzdHJpY3QnOwoKICAgIGNsYXNzIE1hcnRpbmkgewogICAgICAgIGNvbnN0cnVjdG9yKGdyaWRTaXplID0gMjU3KSB7CiAgICAgICAgICAgIHRoaXMuZ3JpZFNpemUgPSBncmlkU2l6ZTsKICAgICAgICAgICAgY29uc3QgdGlsZVNpemUgPSBncmlkU2l6ZSAtIDE7CiAgICAgICAgICAgIGlmICh0aWxlU2l6ZSAmICh0aWxlU2l6ZSAtIDEpKSB0aHJvdyBuZXcgRXJyb3IoCiAgICAgICAgICAgICAgICBgRXhwZWN0ZWQgZ3JpZCBzaXplIHRvIGJlIDJebisxLCBnb3QgJHtncmlkU2l6ZX0uYCk7CgogICAgICAgICAgICB0aGlzLm51bVRyaWFuZ2xlcyA9IHRpbGVTaXplICogdGlsZVNpemUgKiAyIC0gMjsKICAgICAgICAgICAgdGhpcy5udW1QYXJlbnRUcmlhbmdsZXMgPSB0aGlzLm51bVRyaWFuZ2xlcyAtIHRpbGVTaXplICogdGlsZVNpemU7CgogICAgICAgICAgICB0aGlzLmluZGljZXMgPSBuZXcgVWludDMyQXJyYXkodGhpcy5ncmlkU2l6ZSAqIHRoaXMuZ3JpZFNpemUpOwoKICAgICAgICAgICAgLy8gY29vcmRpbmF0ZXMgZm9yIGFsbCBwb3NzaWJsZSB0cmlhbmdsZXMgaW4gYW4gUlRJTiB0aWxlCiAgICAgICAgICAgIHRoaXMuY29vcmRzID0gbmV3IFVpbnQxNkFycmF5KHRoaXMubnVtVHJpYW5nbGVzICogNCk7CgogICAgICAgICAgICAvLyBnZXQgdHJpYW5nbGUgY29vcmRpbmF0ZXMgZnJvbSBpdHMgaW5kZXggaW4gYW4gaW1wbGljaXQgYmluYXJ5IHRyZWUKICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0aGlzLm51bVRyaWFuZ2xlczsgaSsrKSB7CiAgICAgICAgICAgICAgICBsZXQgaWQgPSBpICsgMjsKICAgICAgICAgICAgICAgIGxldCBheCA9IDAsIGF5ID0gMCwgYnggPSAwLCBieSA9IDAsIGN4ID0gMCwgY3kgPSAwOwogICAgICAgICAgICAgICAgaWYgKGlkICYgMSkgewogICAgICAgICAgICAgICAgICAgIGJ4ID0gYnkgPSBjeCA9IHRpbGVTaXplOyAvLyBib3R0b20tbGVmdCB0cmlhbmdsZQogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICBheCA9IGF5ID0gY3kgPSB0aWxlU2l6ZTsgLy8gdG9wLXJpZ2h0IHRyaWFuZ2xlCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB3aGlsZSAoKGlkID4+PSAxKSA+IDEpIHsKICAgICAgICAgICAgICAgICAgICBjb25zdCBteCA9IChheCArIGJ4KSA+PiAxOwogICAgICAgICAgICAgICAgICAgIGNvbnN0IG15ID0gKGF5ICsgYnkpID4+IDE7CgogICAgICAgICAgICAgICAgICAgIGlmIChpZCAmIDEpIHsgLy8gbGVmdCBoYWxmCiAgICAgICAgICAgICAgICAgICAgICAgIGJ4ID0gYXg7IGJ5ID0gYXk7CiAgICAgICAgICAgICAgICAgICAgICAgIGF4ID0gY3g7IGF5ID0gY3k7CiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHsgLy8gcmlnaHQgaGFsZgogICAgICAgICAgICAgICAgICAgICAgICBheCA9IGJ4OyBheSA9IGJ5OwogICAgICAgICAgICAgICAgICAgICAgICBieCA9IGN4OyBieSA9IGN5OwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICBjeCA9IG14OyBjeSA9IG15OwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgY29uc3QgayA9IGkgKiA0OwogICAgICAgICAgICAgICAgdGhpcy5jb29yZHNbayArIDBdID0gYXg7CiAgICAgICAgICAgICAgICB0aGlzLmNvb3Jkc1trICsgMV0gPSBheTsKICAgICAgICAgICAgICAgIHRoaXMuY29vcmRzW2sgKyAyXSA9IGJ4OwogICAgICAgICAgICAgICAgdGhpcy5jb29yZHNbayArIDNdID0gYnk7CiAgICAgICAgICAgIH0KICAgICAgICB9CgogICAgICAgIGNyZWF0ZVRpbGUodGVycmFpbikgewogICAgICAgICAgICByZXR1cm4gbmV3IFRpbGUodGVycmFpbiwgdGhpcyk7CiAgICAgICAgfQogICAgfQoKICAgIGNsYXNzIFRpbGUgewogICAgICAgIGNvbnN0cnVjdG9yKHRlcnJhaW4sIG1hcnRpbmkpIHsKICAgICAgICAgICAgY29uc3Qgc2l6ZSA9IG1hcnRpbmkuZ3JpZFNpemU7CiAgICAgICAgICAgIGlmICh0ZXJyYWluLmxlbmd0aCAhPT0gc2l6ZSAqIHNpemUpIHRocm93IG5ldyBFcnJvcigKICAgICAgICAgICAgICAgIGBFeHBlY3RlZCB0ZXJyYWluIGRhdGEgb2YgbGVuZ3RoICR7c2l6ZSAqIHNpemV9ICgke3NpemV9IHggJHtzaXplfSksIGdvdCAke3RlcnJhaW4ubGVuZ3RofS5gKTsKCiAgICAgICAgICAgIHRoaXMudGVycmFpbiA9IHRlcnJhaW47CiAgICAgICAgICAgIHRoaXMubWFydGluaSA9IG1hcnRpbmk7CiAgICAgICAgICAgIHRoaXMuZXJyb3JzID0gbmV3IEZsb2F0MzJBcnJheSh0ZXJyYWluLmxlbmd0aCk7CiAgICAgICAgICAgIHRoaXMudXBkYXRlKCk7CiAgICAgICAgfQoKICAgICAgICB1cGRhdGUoKSB7CiAgICAgICAgICAgIGNvbnN0IHtudW1UcmlhbmdsZXMsIG51bVBhcmVudFRyaWFuZ2xlcywgY29vcmRzLCBncmlkU2l6ZTogc2l6ZX0gPSB0aGlzLm1hcnRpbmk7CiAgICAgICAgICAgIGNvbnN0IHt0ZXJyYWluLCBlcnJvcnN9ID0gdGhpczsKCiAgICAgICAgICAgIC8vIGl0ZXJhdGUgb3ZlciBhbGwgcG9zc2libGUgdHJpYW5nbGVzLCBzdGFydGluZyBmcm9tIHRoZSBzbWFsbGVzdCBsZXZlbAogICAgICAgICAgICBmb3IgKGxldCBpID0gbnVtVHJpYW5nbGVzIC0gMTsgaSA+PSAwOyBpLS0pIHsKICAgICAgICAgICAgICAgIGNvbnN0IGsgPSBpICogNDsKICAgICAgICAgICAgICAgIGNvbnN0IGF4ID0gY29vcmRzW2sgKyAwXTsKICAgICAgICAgICAgICAgIGNvbnN0IGF5ID0gY29vcmRzW2sgKyAxXTsKICAgICAgICAgICAgICAgIGNvbnN0IGJ4ID0gY29vcmRzW2sgKyAyXTsKICAgICAgICAgICAgICAgIGNvbnN0IGJ5ID0gY29vcmRzW2sgKyAzXTsKICAgICAgICAgICAgICAgIGNvbnN0IG14ID0gKGF4ICsgYngpID4+IDE7CiAgICAgICAgICAgICAgICBjb25zdCBteSA9IChheSArIGJ5KSA+PiAxOwogICAgICAgICAgICAgICAgY29uc3QgY3ggPSBteCArIG15IC0gYXk7CiAgICAgICAgICAgICAgICBjb25zdCBjeSA9IG15ICsgYXggLSBteDsKCiAgICAgICAgICAgICAgICAvLyBjYWxjdWxhdGUgZXJyb3IgaW4gdGhlIG1pZGRsZSBvZiB0aGUgbG9uZyBlZGdlIG9mIHRoZSB0cmlhbmdsZQogICAgICAgICAgICAgICAgY29uc3QgaW50ZXJwb2xhdGVkSGVpZ2h0ID0gKHRlcnJhaW5bYXkgKiBzaXplICsgYXhdICsgdGVycmFpbltieSAqIHNpemUgKyBieF0pIC8gMjsKICAgICAgICAgICAgICAgIGNvbnN0IG1pZGRsZUluZGV4ID0gbXkgKiBzaXplICsgbXg7CiAgICAgICAgICAgICAgICBjb25zdCBtaWRkbGVFcnJvciA9IE1hdGguYWJzKGludGVycG9sYXRlZEhlaWdodCAtIHRlcnJhaW5bbWlkZGxlSW5kZXhdKTsKCiAgICAgICAgICAgICAgICBlcnJvcnNbbWlkZGxlSW5kZXhdID0gTWF0aC5tYXgoZXJyb3JzW21pZGRsZUluZGV4XSwgbWlkZGxlRXJyb3IpOwoKICAgICAgICAgICAgICAgIGlmIChpIDwgbnVtUGFyZW50VHJpYW5nbGVzKSB7IC8vIGJpZ2dlciB0cmlhbmdsZXM7IGFjY3VtdWxhdGUgZXJyb3Igd2l0aCBjaGlsZHJlbgogICAgICAgICAgICAgICAgICAgIGNvbnN0IGxlZnRDaGlsZEluZGV4ID0gKChheSArIGN5KSA+PiAxKSAqIHNpemUgKyAoKGF4ICsgY3gpID4+IDEpOwogICAgICAgICAgICAgICAgICAgIGNvbnN0IHJpZ2h0Q2hpbGRJbmRleCA9ICgoYnkgKyBjeSkgPj4gMSkgKiBzaXplICsgKChieCArIGN4KSA+PiAxKTsKICAgICAgICAgICAgICAgICAgICBlcnJvcnNbbWlkZGxlSW5kZXhdID0gTWF0aC5tYXgoZXJyb3JzW21pZGRsZUluZGV4XSwgZXJyb3JzW2xlZnRDaGlsZEluZGV4XSwgZXJyb3JzW3JpZ2h0Q2hpbGRJbmRleF0pOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBnZXRNZXNoKG1heEVycm9yID0gMCkgewogICAgICAgICAgICBjb25zdCB7Z3JpZFNpemU6IHNpemUsIGluZGljZXN9ID0gdGhpcy5tYXJ0aW5pOwogICAgICAgICAgICBjb25zdCB7ZXJyb3JzfSA9IHRoaXM7CiAgICAgICAgICAgIGxldCBudW1WZXJ0aWNlcyA9IDA7CiAgICAgICAgICAgIGxldCBudW1UcmlhbmdsZXMgPSAwOwogICAgICAgICAgICBjb25zdCBtYXggPSBzaXplIC0gMTsKCiAgICAgICAgICAgIC8vIHVzZSBhbiBpbmRleCBncmlkIHRvIGtlZXAgdHJhY2sgb2YgdmVydGljZXMgdGhhdCB3ZXJlIGFscmVhZHkgdXNlZCB0byBhdm9pZCBkdXBsaWNhdGlvbgogICAgICAgICAgICBpbmRpY2VzLmZpbGwoMCk7CgogICAgICAgICAgICAvLyByZXRyaWV2ZSBtZXNoIGluIHR3byBzdGFnZXMgdGhhdCBib3RoIHRyYXZlcnNlIHRoZSBlcnJvciBtYXA6CiAgICAgICAgICAgIC8vIC0gY291bnRFbGVtZW50czogZmluZCB1c2VkIHZlcnRpY2VzIChhbmQgYXNzaWduIGVhY2ggYW4gaW5kZXgpLCBhbmQgY291bnQgdHJpYW5nbGVzIChmb3IgbWluaW11bSBhbGxvY2F0aW9uKQogICAgICAgICAgICAvLyAtIHByb2Nlc3NUcmlhbmdsZTogZmlsbCB0aGUgYWxsb2NhdGVkIHZlcnRpY2VzICYgdHJpYW5nbGVzIHR5cGVkIGFycmF5cwoKICAgICAgICAgICAgZnVuY3Rpb24gY291bnRFbGVtZW50cyhheCwgYXksIGJ4LCBieSwgY3gsIGN5KSB7CiAgICAgICAgICAgICAgICBjb25zdCBteCA9IChheCArIGJ4KSA+PiAxOwogICAgICAgICAgICAgICAgY29uc3QgbXkgPSAoYXkgKyBieSkgPj4gMTsKCiAgICAgICAgICAgICAgICBpZiAoTWF0aC5hYnMoYXggLSBjeCkgKyBNYXRoLmFicyhheSAtIGN5KSA+IDEgJiYgZXJyb3JzW215ICogc2l6ZSArIG14XSA+IG1heEVycm9yKSB7CiAgICAgICAgICAgICAgICAgICAgY291bnRFbGVtZW50cyhjeCwgY3ksIGF4LCBheSwgbXgsIG15KTsKICAgICAgICAgICAgICAgICAgICBjb3VudEVsZW1lbnRzKGJ4LCBieSwgY3gsIGN5LCBteCwgbXkpOwogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICBpbmRpY2VzW2F5ICogc2l6ZSArIGF4XSA9IGluZGljZXNbYXkgKiBzaXplICsgYXhdIHx8ICsrbnVtVmVydGljZXM7CiAgICAgICAgICAgICAgICAgICAgaW5kaWNlc1tieSAqIHNpemUgKyBieF0gPSBpbmRpY2VzW2J5ICogc2l6ZSArIGJ4XSB8fCArK251bVZlcnRpY2VzOwogICAgICAgICAgICAgICAgICAgIGluZGljZXNbY3kgKiBzaXplICsgY3hdID0gaW5kaWNlc1tjeSAqIHNpemUgKyBjeF0gfHwgKytudW1WZXJ0aWNlczsKICAgICAgICAgICAgICAgICAgICBudW1UcmlhbmdsZXMrKzsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgICBjb3VudEVsZW1lbnRzKDAsIDAsIG1heCwgbWF4LCBtYXgsIDApOwogICAgICAgICAgICBjb3VudEVsZW1lbnRzKG1heCwgbWF4LCAwLCAwLCAwLCBtYXgpOwoKICAgICAgICAgICAgY29uc3QgdmVydGljZXMgPSBuZXcgVWludDE2QXJyYXkobnVtVmVydGljZXMgKiAyKTsKICAgICAgICAgICAgY29uc3QgdHJpYW5nbGVzID0gbmV3IFVpbnQzMkFycmF5KG51bVRyaWFuZ2xlcyAqIDMpOwogICAgICAgICAgICBsZXQgdHJpSW5kZXggPSAwOwoKICAgICAgICAgICAgZnVuY3Rpb24gcHJvY2Vzc1RyaWFuZ2xlKGF4LCBheSwgYngsIGJ5LCBjeCwgY3kpIHsKICAgICAgICAgICAgICAgIGNvbnN0IG14ID0gKGF4ICsgYngpID4+IDE7CiAgICAgICAgICAgICAgICBjb25zdCBteSA9IChheSArIGJ5KSA+PiAxOwoKICAgICAgICAgICAgICAgIGlmIChNYXRoLmFicyhheCAtIGN4KSArIE1hdGguYWJzKGF5IC0gY3kpID4gMSAmJiBlcnJvcnNbbXkgKiBzaXplICsgbXhdID4gbWF4RXJyb3IpIHsKICAgICAgICAgICAgICAgICAgICAvLyB0cmlhbmdsZSBkb2Vzbid0IGFwcHJveGltYXRlIHRoZSBzdXJmYWNlIHdlbGwgZW5vdWdoOyBkcmlsbCBkb3duIGZ1cnRoZXIKICAgICAgICAgICAgICAgICAgICBwcm9jZXNzVHJpYW5nbGUoY3gsIGN5LCBheCwgYXksIG14LCBteSk7CiAgICAgICAgICAgICAgICAgICAgcHJvY2Vzc1RyaWFuZ2xlKGJ4LCBieSwgY3gsIGN5LCBteCwgbXkpOwoKICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgLy8gYWRkIGEgdHJpYW5nbGUKICAgICAgICAgICAgICAgICAgICBjb25zdCBhID0gaW5kaWNlc1theSAqIHNpemUgKyBheF0gLSAxOwogICAgICAgICAgICAgICAgICAgIGNvbnN0IGIgPSBpbmRpY2VzW2J5ICogc2l6ZSArIGJ4XSAtIDE7CiAgICAgICAgICAgICAgICAgICAgY29uc3QgYyA9IGluZGljZXNbY3kgKiBzaXplICsgY3hdIC0gMTsKCiAgICAgICAgICAgICAgICAgICAgdmVydGljZXNbMiAqIGFdID0gYXg7CiAgICAgICAgICAgICAgICAgICAgdmVydGljZXNbMiAqIGEgKyAxXSA9IGF5OwoKICAgICAgICAgICAgICAgICAgICB2ZXJ0aWNlc1syICogYl0gPSBieDsKICAgICAgICAgICAgICAgICAgICB2ZXJ0aWNlc1syICogYiArIDFdID0gYnk7CgogICAgICAgICAgICAgICAgICAgIHZlcnRpY2VzWzIgKiBjXSA9IGN4OwogICAgICAgICAgICAgICAgICAgIHZlcnRpY2VzWzIgKiBjICsgMV0gPSBjeTsKCiAgICAgICAgICAgICAgICAgICAgdHJpYW5nbGVzW3RyaUluZGV4KytdID0gYTsKICAgICAgICAgICAgICAgICAgICB0cmlhbmdsZXNbdHJpSW5kZXgrK10gPSBiOwogICAgICAgICAgICAgICAgICAgIHRyaWFuZ2xlc1t0cmlJbmRleCsrXSA9IGM7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgcHJvY2Vzc1RyaWFuZ2xlKDAsIDAsIG1heCwgbWF4LCBtYXgsIDApOwogICAgICAgICAgICBwcm9jZXNzVHJpYW5nbGUobWF4LCBtYXgsIDAsIDAsIDAsIG1heCk7CgogICAgICAgICAgICByZXR1cm4ge3ZlcnRpY2VzLCB0cmlhbmdsZXN9OwogICAgICAgIH0KICAgIH0KCiAgICAvLyBsb2FkZXJzLmdsCiAgICAvLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogTUlUCiAgICAvLyBDb3B5cmlnaHQgKGMpIHZpcy5nbCBjb250cmlidXRvcnMKICAgIC8vIElTQyBMaWNlbnNlCiAgICAvLyBDb3B5cmlnaHQoYykgMjAxOSwgTWljaGFlbCBGb2dsZW1hbiwgVmxhZGltaXIgQWdhZm9ua2luCiAgICAvKiBlc2xpbnQtZGlzYWJsZSBAdHlwZXNjcmlwdC1lc2xpbnQvYmFuLXRzLWNvbW1lbnQgKi8KICAgIC8vIEB0cy1ub2NoZWNrCiAgICAvKiBlc2xpbnQtZW5hYmxlIEB0eXBlc2NyaXB0LWVzbGludC9iYW4tdHMtY29tbWVudCAqLwogICAgZnVuY3Rpb24gb3JpZW50KGF4LCBheSwgYngsIGJ5LCBjeCwgY3kpIHsKICAgICAgICByZXR1cm4gKGJ4IC0gY3gpICogKGF5IC0gY3kpIC0gKGJ5IC0gY3kpICogKGF4IC0gY3gpOwogICAgfQogICAgY2xhc3MgRGVsYXRpbiB7CiAgICAgICAgY29uc3RydWN0b3IoZGF0YSwgd2lkdGgsIGhlaWdodCA9IHdpZHRoKSB7CiAgICAgICAgICAgIHRoaXMuZGF0YSA9IGRhdGE7IC8vIGhlaWdodCBkYXRhCiAgICAgICAgICAgIHRoaXMud2lkdGggPSB3aWR0aDsKICAgICAgICAgICAgdGhpcy5oZWlnaHQgPSBoZWlnaHQ7CiAgICAgICAgICAgIHRoaXMuY29vcmRzID0gW107IC8vIHZlcnRleCBjb29yZGluYXRlcyAoeCwgeSkKICAgICAgICAgICAgdGhpcy50cmlhbmdsZXMgPSBbXTsgLy8gbWVzaCB0cmlhbmdsZSBpbmRpY2VzCiAgICAgICAgICAgIC8vIGFkZGl0aW9uYWwgdHJpYW5nbGUgZGF0YQogICAgICAgICAgICB0aGlzLl9oYWxmZWRnZXMgPSBbXTsKICAgICAgICAgICAgdGhpcy5fY2FuZGlkYXRlcyA9IFtdOwogICAgICAgICAgICB0aGlzLl9xdWV1ZUluZGljZXMgPSBbXTsKICAgICAgICAgICAgdGhpcy5fcXVldWUgPSBbXTsgLy8gcXVldWUgb2YgYWRkZWQgdHJpYW5nbGVzCiAgICAgICAgICAgIHRoaXMuX2Vycm9ycyA9IFtdOwogICAgICAgICAgICB0aGlzLl9ybXMgPSBbXTsKICAgICAgICAgICAgdGhpcy5fcGVuZGluZyA9IFtdOyAvLyB0cmlhbmdsZXMgcGVuZGluZyBhZGRpdGlvbiB0byBxdWV1ZQogICAgICAgICAgICB0aGlzLl9wZW5kaW5nTGVuID0gMDsKICAgICAgICAgICAgdGhpcy5fcm1zU3VtID0gMDsKICAgICAgICAgICAgY29uc3QgeDEgPSB3aWR0aCAtIDE7CiAgICAgICAgICAgIGNvbnN0IHkxID0gaGVpZ2h0IC0gMTsKICAgICAgICAgICAgY29uc3QgcDAgPSB0aGlzLl9hZGRQb2ludCgwLCAwKTsKICAgICAgICAgICAgY29uc3QgcDEgPSB0aGlzLl9hZGRQb2ludCh4MSwgMCk7CiAgICAgICAgICAgIGNvbnN0IHAyID0gdGhpcy5fYWRkUG9pbnQoMCwgeTEpOwogICAgICAgICAgICBjb25zdCBwMyA9IHRoaXMuX2FkZFBvaW50KHgxLCB5MSk7CiAgICAgICAgICAgIC8vIGFkZCBpbml0aWFsIHR3byB0cmlhbmdsZXMKICAgICAgICAgICAgY29uc3QgdDAgPSB0aGlzLl9hZGRUcmlhbmdsZShwMywgcDAsIHAyLCAtMSwgLTEsIC0xKTsKICAgICAgICAgICAgdGhpcy5fYWRkVHJpYW5nbGUocDAsIHAzLCBwMSwgdDAsIC0xLCAtMSk7CiAgICAgICAgICAgIHRoaXMuX2ZsdXNoKCk7CiAgICAgICAgfQogICAgICAgIC8vIHJlZmluZSB0aGUgbWVzaCB1bnRpbCBpdHMgbWF4aW11bSBlcnJvciBnZXRzIGJlbG93IHRoZSBnaXZlbiBvbmUKICAgICAgICBydW4obWF4RXJyb3IgPSAxKSB7CiAgICAgICAgICAgIHdoaWxlICh0aGlzLmdldE1heEVycm9yKCkgPiBtYXhFcnJvcikgewogICAgICAgICAgICAgICAgdGhpcy5yZWZpbmUoKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICAvLyByZWZpbmUgdGhlIG1lc2ggd2l0aCBhIHNpbmdsZSBwb2ludAogICAgICAgIHJlZmluZSgpIHsKICAgICAgICAgICAgdGhpcy5fc3RlcCgpOwogICAgICAgICAgICB0aGlzLl9mbHVzaCgpOwogICAgICAgIH0KICAgICAgICAvLyBtYXggZXJyb3Igb2YgdGhlIGN1cnJlbnQgbWVzaAogICAgICAgIGdldE1heEVycm9yKCkgewogICAgICAgICAgICByZXR1cm4gdGhpcy5fZXJyb3JzWzBdOwogICAgICAgIH0KICAgICAgICAvLyByb290LW1lYW4tc3F1YXJlIGRldmlhdGlvbiBvZiB0aGUgY3VycmVudCBtZXNoCiAgICAgICAgZ2V0Uk1TRCgpIHsKICAgICAgICAgICAgcmV0dXJuIHRoaXMuX3Jtc1N1bSA+IDAgPyBNYXRoLnNxcnQodGhpcy5fcm1zU3VtIC8gKHRoaXMud2lkdGggKiB0aGlzLmhlaWdodCkpIDogMDsKICAgICAgICB9CiAgICAgICAgLy8gaGVpZ2h0IHZhbHVlIGF0IGEgZ2l2ZW4gcG9zaXRpb24KICAgICAgICBoZWlnaHRBdCh4LCB5KSB7CiAgICAgICAgICAgIHJldHVybiB0aGlzLmRhdGFbdGhpcy53aWR0aCAqIHkgKyB4XTsKICAgICAgICB9CiAgICAgICAgLy8gcmFzdGVyaXplIGFuZCBxdWV1ZSBhbGwgdHJpYW5nbGVzIHRoYXQgZ290IGFkZGVkIG9yIHVwZGF0ZWQgaW4gX3N0ZXAKICAgICAgICBfZmx1c2goKSB7CiAgICAgICAgICAgIGNvbnN0IHsgY29vcmRzIH0gPSB0aGlzOwogICAgICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHRoaXMuX3BlbmRpbmdMZW47IGkrKykgewogICAgICAgICAgICAgICAgY29uc3QgdCA9IHRoaXMuX3BlbmRpbmdbaV07CiAgICAgICAgICAgICAgICAvLyByYXN0ZXJpemUgdHJpYW5nbGUgdG8gZmluZCBtYXhpbXVtIHBpeGVsIGVycm9yCiAgICAgICAgICAgICAgICBjb25zdCBhID0gMiAqIHRoaXMudHJpYW5nbGVzW3QgKiAzICsgMF07CiAgICAgICAgICAgICAgICBjb25zdCBiID0gMiAqIHRoaXMudHJpYW5nbGVzW3QgKiAzICsgMV07CiAgICAgICAgICAgICAgICBjb25zdCBjID0gMiAqIHRoaXMudHJpYW5nbGVzW3QgKiAzICsgMl07CiAgICAgICAgICAgICAgICB0aGlzLl9maW5kQ2FuZGlkYXRlKGNvb3Jkc1thXSwgY29vcmRzW2EgKyAxXSwgY29vcmRzW2JdLCBjb29yZHNbYiArIDFdLCBjb29yZHNbY10sIGNvb3Jkc1tjICsgMV0sIHQpOwogICAgICAgICAgICB9CiAgICAgICAgICAgIHRoaXMuX3BlbmRpbmdMZW4gPSAwOwogICAgICAgIH0KICAgICAgICAvLyByYXN0ZXJpemUgYSB0cmlhbmdsZSwgZmluZCBpdHMgbWF4IGVycm9yLCBhbmQgcXVldWUgaXQgZm9yIHByb2Nlc3NpbmcKICAgICAgICBfZmluZENhbmRpZGF0ZShwMHgsIHAweSwgcDF4LCBwMXksIHAyeCwgcDJ5LCB0KSB7CiAgICAgICAgICAgIC8vIHRyaWFuZ2xlIGJvdW5kaW5nIGJveAogICAgICAgICAgICBjb25zdCBtaW5YID0gTWF0aC5taW4ocDB4LCBwMXgsIHAyeCk7CiAgICAgICAgICAgIGNvbnN0IG1pblkgPSBNYXRoLm1pbihwMHksIHAxeSwgcDJ5KTsKICAgICAgICAgICAgY29uc3QgbWF4WCA9IE1hdGgubWF4KHAweCwgcDF4LCBwMngpOwogICAgICAgICAgICBjb25zdCBtYXhZID0gTWF0aC5tYXgocDB5LCBwMXksIHAyeSk7CiAgICAgICAgICAgIC8vIGZvcndhcmQgZGlmZmVyZW5jaW5nIHZhcmlhYmxlcwogICAgICAgICAgICBsZXQgdzAwID0gb3JpZW50KHAxeCwgcDF5LCBwMngsIHAyeSwgbWluWCwgbWluWSk7CiAgICAgICAgICAgIGxldCB3MDEgPSBvcmllbnQocDJ4LCBwMnksIHAweCwgcDB5LCBtaW5YLCBtaW5ZKTsKICAgICAgICAgICAgbGV0IHcwMiA9IG9yaWVudChwMHgsIHAweSwgcDF4LCBwMXksIG1pblgsIG1pblkpOwogICAgICAgICAgICBjb25zdCBhMDEgPSBwMXkgLSBwMHk7CiAgICAgICAgICAgIGNvbnN0IGIwMSA9IHAweCAtIHAxeDsKICAgICAgICAgICAgY29uc3QgYTEyID0gcDJ5IC0gcDF5OwogICAgICAgICAgICBjb25zdCBiMTIgPSBwMXggLSBwMng7CiAgICAgICAgICAgIGNvbnN0IGEyMCA9IHAweSAtIHAyeTsKICAgICAgICAgICAgY29uc3QgYjIwID0gcDJ4IC0gcDB4OwogICAgICAgICAgICAvLyBwcmUtbXVsdGlwbGllZCB6IHZhbHVlcyBhdCB2ZXJ0aWNlcwogICAgICAgICAgICBjb25zdCBhID0gb3JpZW50KHAweCwgcDB5LCBwMXgsIHAxeSwgcDJ4LCBwMnkpOwogICAgICAgICAgICBjb25zdCB6MCA9IHRoaXMuaGVpZ2h0QXQocDB4LCBwMHkpIC8gYTsKICAgICAgICAgICAgY29uc3QgejEgPSB0aGlzLmhlaWdodEF0KHAxeCwgcDF5KSAvIGE7CiAgICAgICAgICAgIGNvbnN0IHoyID0gdGhpcy5oZWlnaHRBdChwMngsIHAyeSkgLyBhOwogICAgICAgICAgICAvLyBpdGVyYXRlIG92ZXIgcGl4ZWxzIGluIGJvdW5kaW5nIGJveAogICAgICAgICAgICBsZXQgbWF4RXJyb3IgPSAwOwogICAgICAgICAgICBsZXQgbXggPSAwOwogICAgICAgICAgICBsZXQgbXkgPSAwOwogICAgICAgICAgICBsZXQgcm1zID0gMDsKICAgICAgICAgICAgZm9yIChsZXQgeSA9IG1pblk7IHkgPD0gbWF4WTsgeSsrKSB7CiAgICAgICAgICAgICAgICAvLyBjb21wdXRlIHN0YXJ0aW5nIG9mZnNldAogICAgICAgICAgICAgICAgbGV0IGR4ID0gMDsKICAgICAgICAgICAgICAgIGlmICh3MDAgPCAwICYmIGExMiAhPT0gMCkgewogICAgICAgICAgICAgICAgICAgIGR4ID0gTWF0aC5tYXgoZHgsIE1hdGguZmxvb3IoLXcwMCAvIGExMikpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgaWYgKHcwMSA8IDAgJiYgYTIwICE9PSAwKSB7CiAgICAgICAgICAgICAgICAgICAgZHggPSBNYXRoLm1heChkeCwgTWF0aC5mbG9vcigtdzAxIC8gYTIwKSk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBpZiAodzAyIDwgMCAmJiBhMDEgIT09IDApIHsKICAgICAgICAgICAgICAgICAgICBkeCA9IE1hdGgubWF4KGR4LCBNYXRoLmZsb29yKC13MDIgLyBhMDEpKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIGxldCB3MCA9IHcwMCArIGExMiAqIGR4OwogICAgICAgICAgICAgICAgbGV0IHcxID0gdzAxICsgYTIwICogZHg7CiAgICAgICAgICAgICAgICBsZXQgdzIgPSB3MDIgKyBhMDEgKiBkeDsKICAgICAgICAgICAgICAgIGxldCB3YXNJbnNpZGUgPSBmYWxzZTsKICAgICAgICAgICAgICAgIGZvciAobGV0IHggPSBtaW5YICsgZHg7IHggPD0gbWF4WDsgeCsrKSB7CiAgICAgICAgICAgICAgICAgICAgLy8gY2hlY2sgaWYgaW5zaWRlIHRyaWFuZ2xlCiAgICAgICAgICAgICAgICAgICAgaWYgKHcwID49IDAgJiYgdzEgPj0gMCAmJiB3MiA+PSAwKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHdhc0luc2lkZSA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGNvbXB1dGUgeiB1c2luZyBiYXJ5Y2VudHJpYyBjb29yZGluYXRlcwogICAgICAgICAgICAgICAgICAgICAgICBjb25zdCB6ID0gejAgKiB3MCArIHoxICogdzEgKyB6MiAqIHcyOwogICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBkeiA9IE1hdGguYWJzKHogLSB0aGlzLmhlaWdodEF0KHgsIHkpKTsKICAgICAgICAgICAgICAgICAgICAgICAgcm1zICs9IGR6ICogZHo7CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChkeiA+IG1heEVycm9yKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhFcnJvciA9IGR6OwogICAgICAgICAgICAgICAgICAgICAgICAgICAgbXggPSB4OwogICAgICAgICAgICAgICAgICAgICAgICAgICAgbXkgPSB5OwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgKHdhc0luc2lkZSkgewogICAgICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgdzAgKz0gYTEyOwogICAgICAgICAgICAgICAgICAgIHcxICs9IGEyMDsKICAgICAgICAgICAgICAgICAgICB3MiArPSBhMDE7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB3MDAgKz0gYjEyOwogICAgICAgICAgICAgICAgdzAxICs9IGIyMDsKICAgICAgICAgICAgICAgIHcwMiArPSBiMDE7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgaWYgKChteCA9PT0gcDB4ICYmIG15ID09PSBwMHkpIHx8IChteCA9PT0gcDF4ICYmIG15ID09PSBwMXkpIHx8IChteCA9PT0gcDJ4ICYmIG15ID09PSBwMnkpKSB7CiAgICAgICAgICAgICAgICBtYXhFcnJvciA9IDA7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgLy8gdXBkYXRlIHRyaWFuZ2xlIG1ldGFkYXRhCiAgICAgICAgICAgIHRoaXMuX2NhbmRpZGF0ZXNbMiAqIHRdID0gbXg7CiAgICAgICAgICAgIHRoaXMuX2NhbmRpZGF0ZXNbMiAqIHQgKyAxXSA9IG15OwogICAgICAgICAgICB0aGlzLl9ybXNbdF0gPSBybXM7CiAgICAgICAgICAgIC8vIGFkZCB0cmlhbmdsZSB0byBwcmlvcml0eSBxdWV1ZQogICAgICAgICAgICB0aGlzLl9xdWV1ZVB1c2godCwgbWF4RXJyb3IsIHJtcyk7CiAgICAgICAgfQogICAgICAgIC8vIHByb2Nlc3MgdGhlIG5leHQgdHJpYW5nbGUgaW4gdGhlIHF1ZXVlLCBzcGxpdHRpbmcgaXQgd2l0aCBhIG5ldyBwb2ludAogICAgICAgIF9zdGVwKCkgewogICAgICAgICAgICAvLyBwb3AgdHJpYW5nbGUgd2l0aCBoaWdoZXN0IGVycm9yIGZyb20gcHJpb3JpdHkgcXVldWUKICAgICAgICAgICAgY29uc3QgdCA9IHRoaXMuX3F1ZXVlUG9wKCk7CiAgICAgICAgICAgIGNvbnN0IGUwID0gdCAqIDMgKyAwOwogICAgICAgICAgICBjb25zdCBlMSA9IHQgKiAzICsgMTsKICAgICAgICAgICAgY29uc3QgZTIgPSB0ICogMyArIDI7CiAgICAgICAgICAgIGNvbnN0IHAwID0gdGhpcy50cmlhbmdsZXNbZTBdOwogICAgICAgICAgICBjb25zdCBwMSA9IHRoaXMudHJpYW5nbGVzW2UxXTsKICAgICAgICAgICAgY29uc3QgcDIgPSB0aGlzLnRyaWFuZ2xlc1tlMl07CiAgICAgICAgICAgIGNvbnN0IGF4ID0gdGhpcy5jb29yZHNbMiAqIHAwXTsKICAgICAgICAgICAgY29uc3QgYXkgPSB0aGlzLmNvb3Jkc1syICogcDAgKyAxXTsKICAgICAgICAgICAgY29uc3QgYnggPSB0aGlzLmNvb3Jkc1syICogcDFdOwogICAgICAgICAgICBjb25zdCBieSA9IHRoaXMuY29vcmRzWzIgKiBwMSArIDFdOwogICAgICAgICAgICBjb25zdCBjeCA9IHRoaXMuY29vcmRzWzIgKiBwMl07CiAgICAgICAgICAgIGNvbnN0IGN5ID0gdGhpcy5jb29yZHNbMiAqIHAyICsgMV07CiAgICAgICAgICAgIGNvbnN0IHB4ID0gdGhpcy5fY2FuZGlkYXRlc1syICogdF07CiAgICAgICAgICAgIGNvbnN0IHB5ID0gdGhpcy5fY2FuZGlkYXRlc1syICogdCArIDFdOwogICAgICAgICAgICBjb25zdCBwbiA9IHRoaXMuX2FkZFBvaW50KHB4LCBweSk7CiAgICAgICAgICAgIGlmIChvcmllbnQoYXgsIGF5LCBieCwgYnksIHB4LCBweSkgPT09IDApIHsKICAgICAgICAgICAgICAgIHRoaXMuX2hhbmRsZUNvbGxpbmVhcihwbiwgZTApOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGVsc2UgaWYgKG9yaWVudChieCwgYnksIGN4LCBjeSwgcHgsIHB5KSA9PT0gMCkgewogICAgICAgICAgICAgICAgdGhpcy5faGFuZGxlQ29sbGluZWFyKHBuLCBlMSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgZWxzZSBpZiAob3JpZW50KGN4LCBjeSwgYXgsIGF5LCBweCwgcHkpID09PSAwKSB7CiAgICAgICAgICAgICAgICB0aGlzLl9oYW5kbGVDb2xsaW5lYXIocG4sIGUyKTsKICAgICAgICAgICAgfQogICAgICAgICAgICBlbHNlIHsKICAgICAgICAgICAgICAgIGNvbnN0IGgwID0gdGhpcy5faGFsZmVkZ2VzW2UwXTsKICAgICAgICAgICAgICAgIGNvbnN0IGgxID0gdGhpcy5faGFsZmVkZ2VzW2UxXTsKICAgICAgICAgICAgICAgIGNvbnN0IGgyID0gdGhpcy5faGFsZmVkZ2VzW2UyXTsKICAgICAgICAgICAgICAgIGNvbnN0IHQwID0gdGhpcy5fYWRkVHJpYW5nbGUocDAsIHAxLCBwbiwgaDAsIC0xLCAtMSwgZTApOwogICAgICAgICAgICAgICAgY29uc3QgdDEgPSB0aGlzLl9hZGRUcmlhbmdsZShwMSwgcDIsIHBuLCBoMSwgLTEsIHQwICsgMSk7CiAgICAgICAgICAgICAgICBjb25zdCB0MiA9IHRoaXMuX2FkZFRyaWFuZ2xlKHAyLCBwMCwgcG4sIGgyLCB0MCArIDIsIHQxICsgMSk7CiAgICAgICAgICAgICAgICB0aGlzLl9sZWdhbGl6ZSh0MCk7CiAgICAgICAgICAgICAgICB0aGlzLl9sZWdhbGl6ZSh0MSk7CiAgICAgICAgICAgICAgICB0aGlzLl9sZWdhbGl6ZSh0Mik7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgLy8gYWRkIGNvb3JkaW5hdGVzIGZvciBhIG5ldyB2ZXJ0ZXgKICAgICAgICBfYWRkUG9pbnQoeCwgeSkgewogICAgICAgICAgICBjb25zdCBpID0gdGhpcy5jb29yZHMubGVuZ3RoID4+IDE7CiAgICAgICAgICAgIHRoaXMuY29vcmRzLnB1c2goeCwgeSk7CiAgICAgICAgICAgIHJldHVybiBpOwogICAgICAgIH0KICAgICAgICAvLyBhZGQgb3IgdXBkYXRlIGEgdHJpYW5nbGUgaW4gdGhlIG1lc2gKICAgICAgICBfYWRkVHJpYW5nbGUoYSwgYiwgYywgYWIsIGJjLCBjYSwgZSA9IHRoaXMudHJpYW5nbGVzLmxlbmd0aCkgewogICAgICAgICAgICBjb25zdCB0ID0gZSAvIDM7IC8vIG5ldyB0cmlhbmdsZSBpbmRleAogICAgICAgICAgICAvLyBhZGQgdHJpYW5nbGUgdmVydGljZXMKICAgICAgICAgICAgdGhpcy50cmlhbmdsZXNbZSArIDBdID0gYTsKICAgICAgICAgICAgdGhpcy50cmlhbmdsZXNbZSArIDFdID0gYjsKICAgICAgICAgICAgdGhpcy50cmlhbmdsZXNbZSArIDJdID0gYzsKICAgICAgICAgICAgLy8gYWRkIHRyaWFuZ2xlIGhhbGZlZGdlcwogICAgICAgICAgICB0aGlzLl9oYWxmZWRnZXNbZSArIDBdID0gYWI7CiAgICAgICAgICAgIHRoaXMuX2hhbGZlZGdlc1tlICsgMV0gPSBiYzsKICAgICAgICAgICAgdGhpcy5faGFsZmVkZ2VzW2UgKyAyXSA9IGNhOwogICAgICAgICAgICAvLyBsaW5rIG5laWdoYm9yaW5nIGhhbGZlZGdlcwogICAgICAgICAgICBpZiAoYWIgPj0gMCkgewogICAgICAgICAgICAgICAgdGhpcy5faGFsZmVkZ2VzW2FiXSA9IGUgKyAwOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGlmIChiYyA+PSAwKSB7CiAgICAgICAgICAgICAgICB0aGlzLl9oYWxmZWRnZXNbYmNdID0gZSArIDE7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgaWYgKGNhID49IDApIHsKICAgICAgICAgICAgICAgIHRoaXMuX2hhbGZlZGdlc1tjYV0gPSBlICsgMjsKICAgICAgICAgICAgfQogICAgICAgICAgICAvLyBpbml0IHRyaWFuZ2xlIG1ldGFkYXRhCiAgICAgICAgICAgIHRoaXMuX2NhbmRpZGF0ZXNbMiAqIHQgKyAwXSA9IDA7CiAgICAgICAgICAgIHRoaXMuX2NhbmRpZGF0ZXNbMiAqIHQgKyAxXSA9IDA7CiAgICAgICAgICAgIHRoaXMuX3F1ZXVlSW5kaWNlc1t0XSA9IC0xOwogICAgICAgICAgICB0aGlzLl9ybXNbdF0gPSAwOwogICAgICAgICAgICAvLyBhZGQgdHJpYW5nbGUgdG8gcGVuZGluZyBxdWV1ZSBmb3IgbGF0ZXIgcmFzdGVyaXphdGlvbgogICAgICAgICAgICB0aGlzLl9wZW5kaW5nW3RoaXMuX3BlbmRpbmdMZW4rK10gPSB0OwogICAgICAgICAgICAvLyByZXR1cm4gZmlyc3QgaGFsZmVkZ2UgaW5kZXgKICAgICAgICAgICAgcmV0dXJuIGU7CiAgICAgICAgfQogICAgICAgIF9sZWdhbGl6ZShhKSB7CiAgICAgICAgICAgIC8vIGlmIHRoZSBwYWlyIG9mIHRyaWFuZ2xlcyBkb2Vzbid0IHNhdGlzZnkgdGhlIERlbGF1bmF5IGNvbmRpdGlvbgogICAgICAgICAgICAvLyAocDEgaXMgaW5zaWRlIHRoZSBjaXJjdW1jaXJjbGUgb2YgW3AwLCBwbCwgcHJdKSwgZmxpcCB0aGVtLAogICAgICAgICAgICAvLyB0aGVuIGRvIHRoZSBzYW1lIGNoZWNrL2ZsaXAgcmVjdXJzaXZlbHkgZm9yIHRoZSBuZXcgcGFpciBvZiB0cmlhbmdsZXMKICAgICAgICAgICAgLy8KICAgICAgICAgICAgLy8gICAgICAgICAgIHBsICAgICAgICAgICAgICAgICAgICBwbAogICAgICAgICAgICAvLyAgICAgICAgICAvfHxcICAgICAgICAgICAgICAgICAgLyAgXAogICAgICAgICAgICAvLyAgICAgICBhbC8gfHwgXGJsICAgICAgICAgICAgYWwvICAgIFxhCiAgICAgICAgICAgIC8vICAgICAgICAvICB8fCAgXCAgICAgICAgICAgICAgLyAgICAgIFwKICAgICAgICAgICAgLy8gICAgICAgLyAgYXx8YiAgXCAgICBmbGlwICAgIC9fX19hcl9fX1wKICAgICAgICAgICAgLy8gICAgIHAwXCAgIHx8ICAgL3AxICAgPT4gICBwMFwtLS1ibC0tLS9wMQogICAgICAgICAgICAvLyAgICAgICAgXCAgfHwgIC8gICAgICAgICAgICAgIFwgICAgICAvCiAgICAgICAgICAgIC8vICAgICAgIGFyXCB8fCAvYnIgICAgICAgICAgICAgYlwgICAgL2JyCiAgICAgICAgICAgIC8vICAgICAgICAgIFx8fC8gICAgICAgICAgICAgICAgICBcICAvCiAgICAgICAgICAgIC8vICAgICAgICAgICBwciAgICAgICAgICAgICAgICAgICAgcHIKICAgICAgICAgICAgY29uc3QgYiA9IHRoaXMuX2hhbGZlZGdlc1thXTsKICAgICAgICAgICAgaWYgKGIgPCAwKSB7CiAgICAgICAgICAgICAgICByZXR1cm47CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY29uc3QgYTAgPSBhIC0gKGEgJSAzKTsKICAgICAgICAgICAgY29uc3QgYjAgPSBiIC0gKGIgJSAzKTsKICAgICAgICAgICAgY29uc3QgYWwgPSBhMCArICgoYSArIDEpICUgMyk7CiAgICAgICAgICAgIGNvbnN0IGFyID0gYTAgKyAoKGEgKyAyKSAlIDMpOwogICAgICAgICAgICBjb25zdCBibCA9IGIwICsgKChiICsgMikgJSAzKTsKICAgICAgICAgICAgY29uc3QgYnIgPSBiMCArICgoYiArIDEpICUgMyk7CiAgICAgICAgICAgIGNvbnN0IHAwID0gdGhpcy50cmlhbmdsZXNbYXJdOwogICAgICAgICAgICBjb25zdCBwciA9IHRoaXMudHJpYW5nbGVzW2FdOwogICAgICAgICAgICBjb25zdCBwbCA9IHRoaXMudHJpYW5nbGVzW2FsXTsKICAgICAgICAgICAgY29uc3QgcDEgPSB0aGlzLnRyaWFuZ2xlc1tibF07CiAgICAgICAgICAgIGNvbnN0IHsgY29vcmRzIH0gPSB0aGlzOwogICAgICAgICAgICBpZiAoIWluQ2lyY2xlKGNvb3Jkc1syICogcDBdLCBjb29yZHNbMiAqIHAwICsgMV0sIGNvb3Jkc1syICogcHJdLCBjb29yZHNbMiAqIHByICsgMV0sIGNvb3Jkc1syICogcGxdLCBjb29yZHNbMiAqIHBsICsgMV0sIGNvb3Jkc1syICogcDFdLCBjb29yZHNbMiAqIHAxICsgMV0pKSB7CiAgICAgICAgICAgICAgICByZXR1cm47CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY29uc3QgaGFsID0gdGhpcy5faGFsZmVkZ2VzW2FsXTsKICAgICAgICAgICAgY29uc3QgaGFyID0gdGhpcy5faGFsZmVkZ2VzW2FyXTsKICAgICAgICAgICAgY29uc3QgaGJsID0gdGhpcy5faGFsZmVkZ2VzW2JsXTsKICAgICAgICAgICAgY29uc3QgaGJyID0gdGhpcy5faGFsZmVkZ2VzW2JyXTsKICAgICAgICAgICAgdGhpcy5fcXVldWVSZW1vdmUoYTAgLyAzKTsKICAgICAgICAgICAgdGhpcy5fcXVldWVSZW1vdmUoYjAgLyAzKTsKICAgICAgICAgICAgY29uc3QgdDAgPSB0aGlzLl9hZGRUcmlhbmdsZShwMCwgcDEsIHBsLCAtMSwgaGJsLCBoYWwsIGEwKTsKICAgICAgICAgICAgY29uc3QgdDEgPSB0aGlzLl9hZGRUcmlhbmdsZShwMSwgcDAsIHByLCB0MCwgaGFyLCBoYnIsIGIwKTsKICAgICAgICAgICAgdGhpcy5fbGVnYWxpemUodDAgKyAxKTsKICAgICAgICAgICAgdGhpcy5fbGVnYWxpemUodDEgKyAyKTsKICAgICAgICB9CiAgICAgICAgLy8gaGFuZGxlIGEgY2FzZSB3aGVyZSBuZXcgdmVydGV4IGlzIG9uIHRoZSBlZGdlIG9mIGEgdHJpYW5nbGUKICAgICAgICBfaGFuZGxlQ29sbGluZWFyKHBuLCBhKSB7CiAgICAgICAgICAgIGNvbnN0IGEwID0gYSAtIChhICUgMyk7CiAgICAgICAgICAgIGNvbnN0IGFsID0gYTAgKyAoKGEgKyAxKSAlIDMpOwogICAgICAgICAgICBjb25zdCBhciA9IGEwICsgKChhICsgMikgJSAzKTsKICAgICAgICAgICAgY29uc3QgcDAgPSB0aGlzLnRyaWFuZ2xlc1thcl07CiAgICAgICAgICAgIGNvbnN0IHByID0gdGhpcy50cmlhbmdsZXNbYV07CiAgICAgICAgICAgIGNvbnN0IHBsID0gdGhpcy50cmlhbmdsZXNbYWxdOwogICAgICAgICAgICBjb25zdCBoYWwgPSB0aGlzLl9oYWxmZWRnZXNbYWxdOwogICAgICAgICAgICBjb25zdCBoYXIgPSB0aGlzLl9oYWxmZWRnZXNbYXJdOwogICAgICAgICAgICBjb25zdCBiID0gdGhpcy5faGFsZmVkZ2VzW2FdOwogICAgICAgICAgICBpZiAoYiA8IDApIHsKICAgICAgICAgICAgICAgIGNvbnN0IHQwID0gdGhpcy5fYWRkVHJpYW5nbGUocG4sIHAwLCBwciwgLTEsIGhhciwgLTEsIGEwKTsKICAgICAgICAgICAgICAgIGNvbnN0IHQxID0gdGhpcy5fYWRkVHJpYW5nbGUocDAsIHBuLCBwbCwgdDAsIC0xLCBoYWwpOwogICAgICAgICAgICAgICAgdGhpcy5fbGVnYWxpemUodDAgKyAxKTsKICAgICAgICAgICAgICAgIHRoaXMuX2xlZ2FsaXplKHQxICsgMik7CiAgICAgICAgICAgICAgICByZXR1cm47CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY29uc3QgYjAgPSBiIC0gKGIgJSAzKTsKICAgICAgICAgICAgY29uc3QgYmwgPSBiMCArICgoYiArIDIpICUgMyk7CiAgICAgICAgICAgIGNvbnN0IGJyID0gYjAgKyAoKGIgKyAxKSAlIDMpOwogICAgICAgICAgICBjb25zdCBwMSA9IHRoaXMudHJpYW5nbGVzW2JsXTsKICAgICAgICAgICAgY29uc3QgaGJsID0gdGhpcy5faGFsZmVkZ2VzW2JsXTsKICAgICAgICAgICAgY29uc3QgaGJyID0gdGhpcy5faGFsZmVkZ2VzW2JyXTsKICAgICAgICAgICAgdGhpcy5fcXVldWVSZW1vdmUoYjAgLyAzKTsKICAgICAgICAgICAgY29uc3QgdDAgPSB0aGlzLl9hZGRUcmlhbmdsZShwMCwgcHIsIHBuLCBoYXIsIC0xLCAtMSwgYTApOwogICAgICAgICAgICBjb25zdCB0MSA9IHRoaXMuX2FkZFRyaWFuZ2xlKHByLCBwMSwgcG4sIGhiciwgLTEsIHQwICsgMSwgYjApOwogICAgICAgICAgICBjb25zdCB0MiA9IHRoaXMuX2FkZFRyaWFuZ2xlKHAxLCBwbCwgcG4sIGhibCwgLTEsIHQxICsgMSk7CiAgICAgICAgICAgIGNvbnN0IHQzID0gdGhpcy5fYWRkVHJpYW5nbGUocGwsIHAwLCBwbiwgaGFsLCB0MCArIDIsIHQyICsgMSk7CiAgICAgICAgICAgIHRoaXMuX2xlZ2FsaXplKHQwKTsKICAgICAgICAgICAgdGhpcy5fbGVnYWxpemUodDEpOwogICAgICAgICAgICB0aGlzLl9sZWdhbGl6ZSh0Mik7CiAgICAgICAgICAgIHRoaXMuX2xlZ2FsaXplKHQzKTsKICAgICAgICB9CiAgICAgICAgLy8gcHJpb3JpdHkgcXVldWUgbWV0aG9kcwogICAgICAgIF9xdWV1ZVB1c2godCwgZXJyb3IsIHJtcykgewogICAgICAgICAgICBjb25zdCBpID0gdGhpcy5fcXVldWUubGVuZ3RoOwogICAgICAgICAgICB0aGlzLl9xdWV1ZUluZGljZXNbdF0gPSBpOwogICAgICAgICAgICB0aGlzLl9xdWV1ZS5wdXNoKHQpOwogICAgICAgICAgICB0aGlzLl9lcnJvcnMucHVzaChlcnJvcik7CiAgICAgICAgICAgIHRoaXMuX3Jtc1N1bSArPSBybXM7CiAgICAgICAgICAgIHRoaXMuX3F1ZXVlVXAoaSk7CiAgICAgICAgfQogICAgICAgIF9xdWV1ZVBvcCgpIHsKICAgICAgICAgICAgY29uc3QgbiA9IHRoaXMuX3F1ZXVlLmxlbmd0aCAtIDE7CiAgICAgICAgICAgIHRoaXMuX3F1ZXVlU3dhcCgwLCBuKTsKICAgICAgICAgICAgdGhpcy5fcXVldWVEb3duKDAsIG4pOwogICAgICAgICAgICByZXR1cm4gdGhpcy5fcXVldWVQb3BCYWNrKCk7CiAgICAgICAgfQogICAgICAgIF9xdWV1ZVBvcEJhY2soKSB7CiAgICAgICAgICAgIGNvbnN0IHQgPSB0aGlzLl9xdWV1ZS5wb3AoKTsKICAgICAgICAgICAgdGhpcy5fZXJyb3JzLnBvcCgpOwogICAgICAgICAgICB0aGlzLl9ybXNTdW0gLT0gdGhpcy5fcm1zW3RdOwogICAgICAgICAgICB0aGlzLl9xdWV1ZUluZGljZXNbdF0gPSAtMTsKICAgICAgICAgICAgcmV0dXJuIHQ7CiAgICAgICAgfQogICAgICAgIF9xdWV1ZVJlbW92ZSh0KSB7CiAgICAgICAgICAgIGNvbnN0IGkgPSB0aGlzLl9xdWV1ZUluZGljZXNbdF07CiAgICAgICAgICAgIGlmIChpIDwgMCkgewogICAgICAgICAgICAgICAgY29uc3QgaXQgPSB0aGlzLl9wZW5kaW5nLmluZGV4T2YodCk7CiAgICAgICAgICAgICAgICBpZiAoaXQgIT09IC0xKSB7CiAgICAgICAgICAgICAgICAgICAgdGhpcy5fcGVuZGluZ1tpdF0gPSB0aGlzLl9wZW5kaW5nWy0tdGhpcy5fcGVuZGluZ0xlbl07CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBlbHNlIHsKICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0Jyb2tlbiB0cmlhbmd1bGF0aW9uIChzb21ldGhpbmcgd2VudCB3cm9uZykuJyk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICByZXR1cm47CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY29uc3QgbiA9IHRoaXMuX3F1ZXVlLmxlbmd0aCAtIDE7CiAgICAgICAgICAgIGlmIChuICE9PSBpKSB7CiAgICAgICAgICAgICAgICB0aGlzLl9xdWV1ZVN3YXAoaSwgbik7CiAgICAgICAgICAgICAgICBpZiAoIXRoaXMuX3F1ZXVlRG93bihpLCBuKSkgewogICAgICAgICAgICAgICAgICAgIHRoaXMuX3F1ZXVlVXAoaSk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgdGhpcy5fcXVldWVQb3BCYWNrKCk7CiAgICAgICAgfQogICAgICAgIF9xdWV1ZUxlc3MoaSwgaikgewogICAgICAgICAgICByZXR1cm4gdGhpcy5fZXJyb3JzW2ldID4gdGhpcy5fZXJyb3JzW2pdOwogICAgICAgIH0KICAgICAgICBfcXVldWVTd2FwKGksIGopIHsKICAgICAgICAgICAgY29uc3QgcGkgPSB0aGlzLl9xdWV1ZVtpXTsKICAgICAgICAgICAgY29uc3QgcGogPSB0aGlzLl9xdWV1ZVtqXTsKICAgICAgICAgICAgdGhpcy5fcXVldWVbaV0gPSBwajsKICAgICAgICAgICAgdGhpcy5fcXVldWVbal0gPSBwaTsKICAgICAgICAgICAgdGhpcy5fcXVldWVJbmRpY2VzW3BpXSA9IGo7CiAgICAgICAgICAgIHRoaXMuX3F1ZXVlSW5kaWNlc1twal0gPSBpOwogICAgICAgICAgICBjb25zdCBlID0gdGhpcy5fZXJyb3JzW2ldOwogICAgICAgICAgICB0aGlzLl9lcnJvcnNbaV0gPSB0aGlzLl9lcnJvcnNbal07CiAgICAgICAgICAgIHRoaXMuX2Vycm9yc1tqXSA9IGU7CiAgICAgICAgfQogICAgICAgIF9xdWV1ZVVwKGowKSB7CiAgICAgICAgICAgIGxldCBqID0gajA7CiAgICAgICAgICAgIHdoaWxlICh0cnVlKSB7CiAgICAgICAgICAgICAgICBjb25zdCBpID0gKGogLSAxKSA+PiAxOwogICAgICAgICAgICAgICAgaWYgKGkgPT09IGogfHwgIXRoaXMuX3F1ZXVlTGVzcyhqLCBpKSkgewogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgdGhpcy5fcXVldWVTd2FwKGksIGopOwogICAgICAgICAgICAgICAgaiA9IGk7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgX3F1ZXVlRG93bihpMCwgbikgewogICAgICAgICAgICBsZXQgaSA9IGkwOwogICAgICAgICAgICB3aGlsZSAodHJ1ZSkgewogICAgICAgICAgICAgICAgY29uc3QgajEgPSAyICogaSArIDE7CiAgICAgICAgICAgICAgICBpZiAoajEgPj0gbiB8fCBqMSA8IDApIHsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIGNvbnN0IGoyID0gajEgKyAxOwogICAgICAgICAgICAgICAgbGV0IGogPSBqMTsKICAgICAgICAgICAgICAgIGlmIChqMiA8IG4gJiYgdGhpcy5fcXVldWVMZXNzKGoyLCBqMSkpIHsKICAgICAgICAgICAgICAgICAgICBqID0gajI7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBpZiAoIXRoaXMuX3F1ZXVlTGVzcyhqLCBpKSkgewogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgdGhpcy5fcXVldWVTd2FwKGksIGopOwogICAgICAgICAgICAgICAgaSA9IGo7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgcmV0dXJuIGkgPiBpMDsKICAgICAgICB9CiAgICB9CiAgICBmdW5jdGlvbiBpbkNpcmNsZShheCwgYXksIGJ4LCBieSwgY3gsIGN5LCBweCwgcHkpIHsKICAgICAgICBjb25zdCBkeCA9IGF4IC0gcHg7CiAgICAgICAgY29uc3QgZHkgPSBheSAtIHB5OwogICAgICAgIGNvbnN0IGV4ID0gYnggLSBweDsKICAgICAgICBjb25zdCBleSA9IGJ5IC0gcHk7CiAgICAgICAgY29uc3QgZnggPSBjeCAtIHB4OwogICAgICAgIGNvbnN0IGZ5ID0gY3kgLSBweTsKICAgICAgICBjb25zdCBhcCA9IGR4ICogZHggKyBkeSAqIGR5OwogICAgICAgIGNvbnN0IGJwID0gZXggKiBleCArIGV5ICogZXk7CiAgICAgICAgY29uc3QgY3AgPSBmeCAqIGZ4ICsgZnkgKiBmeTsKICAgICAgICByZXR1cm4gZHggKiAoZXkgKiBjcCAtIGJwICogZnkpIC0gZHkgKiAoZXggKiBjcCAtIGJwICogZngpICsgYXAgKiAoZXggKiBmeSAtIGV5ICogZngpIDwgMDsKICAgIH0KCiAgICAvKioKICAgICAqIFdlYiBXb3JrZXIgZm9yIHRlcnJhaW4gbWVzaCB0ZXNzZWxsYXRpb24uCiAgICAgKiBSdW5zIE1hcnRpbmkvRGVsYXRpbiBhbGdvcml0aG1zIG9uIGEgYmFja2dyb3VuZCB0aHJlYWQgdG8gYXZvaWQgYmxvY2tpbmcgdGhlIG1haW4gdGhyZWFkLgogICAgICovCiAgICAvLyBUcmFjayBhYm9ydGVkIHRhc2tzIHRvIGF2b2lkIHNlbmRpbmcgcmVzdWx0cyBmb3IgY2FuY2VsbGVkIHdvcmsKICAgIGNvbnN0IGFib3J0ZWRUYXNrcyA9IG5ldyBNYXAoKTsKICAgIHNlbGYub25tZXNzYWdlID0gKGUpID0+IHsKICAgICAgICBjb25zdCBkYXRhID0gZS5kYXRhOwogICAgICAgIGlmIChkYXRhLnR5cGUgPT09ICdhYm9ydCcpIHsKICAgICAgICAgICAgLy8gTWFyayB0YXNrIGFzIGFib3J0ZWQ7IGlmIGl0J3Mgc3RpbGwgY29tcHV0aW5nLCB3ZSdsbCBza2lwIHRoZSByZXNwb25zZQogICAgICAgICAgICBhYm9ydGVkVGFza3Muc2V0KGRhdGEudGFza0lkLCB0cnVlKTsKICAgICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KICAgICAgICBpZiAoZGF0YS50eXBlID09PSAnY29tcHV0ZU1lc2gnKSB7CiAgICAgICAgICAgIGNvbnN0IHsgdGFza0lkLCB0ZXJyYWluLCBtZXNoTWF4RXJyb3IsIHRlc3NlbGF0b3IsIHdpZHRoLCBoZWlnaHQgfSA9IGRhdGE7CiAgICAgICAgICAgIGxldCBtZXNoOwogICAgICAgICAgICB0cnkgewogICAgICAgICAgICAgICAgaWYgKHRlc3NlbGF0b3IgPT09ICdkZWxhdGluJykgewogICAgICAgICAgICAgICAgICAgIC8vIERlbGF0aW4gdGVzc2VsbGF0aW9uCiAgICAgICAgICAgICAgICAgICAgY29uc3Qgd2lkdGhQbHVzID0gd2lkdGggPT09IDI1NyA/IDI1NyA6IHdpZHRoICsgMTsKICAgICAgICAgICAgICAgICAgICBjb25zdCBoZWlnaHRQbHVzID0gaGVpZ2h0ID09PSAyNTcgPyAyNTcgOiBoZWlnaHQgKyAxOwogICAgICAgICAgICAgICAgICAgIGNvbnN0IHRpbiA9IG5ldyBEZWxhdGluKHRlcnJhaW4sIHdpZHRoUGx1cywgaGVpZ2h0UGx1cyk7CiAgICAgICAgICAgICAgICAgICAgdGluLnJ1bihtZXNoTWF4RXJyb3IpOwogICAgICAgICAgICAgICAgICAgIC8vIEB0cy1leHBlY3QtZXJyb3I6IERlbGF0aW4gaW5zdGFuY2UgcHJvcGVydGllcyAnY29vcmRzJyBhbmQgJ3RyaWFuZ2xlcycgYXJlIG5vdCBleHBsaWNpdGx5IHR5cGVkIGluIHRoZSBsaWJyYXJ5IHBvcnQKICAgICAgICAgICAgICAgICAgICBjb25zdCB7IGNvb3JkcywgdHJpYW5nbGVzIH0gPSB0aW47CiAgICAgICAgICAgICAgICAgICAgLy8gY29vcmRzIGlzIGEgcGxhaW4gYXJyYXkg4oCUIGNvbnZlcnQgdG8gRmxvYXQ2NEFycmF5IHNvIGl0IGhhcyAuYnVmZmVyIGZvciB0cmFuc2ZlcgogICAgICAgICAgICAgICAgICAgIGNvbnN0IHZlcnRpY2VzVHlwZWQgPSBGbG9hdDY0QXJyYXkuZnJvbShjb29yZHMpOwogICAgICAgICAgICAgICAgICAgIGNvbnN0IHRyaWFuZ2xlc1R5cGVkID0gVWludDMyQXJyYXkuZnJvbSh0cmlhbmdsZXMpOwogICAgICAgICAgICAgICAgICAgIG1lc2ggPSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHZlcnRpY2VzOiB2ZXJ0aWNlc1R5cGVkLAogICAgICAgICAgICAgICAgICAgICAgICB0cmlhbmdsZXM6IHRyaWFuZ2xlc1R5cGVkLAogICAgICAgICAgICAgICAgICAgIH07CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAvLyBNYXJ0aW5pIHRlc3NlbGxhdGlvbiAoZGVmYXVsdCkKICAgICAgICAgICAgICAgICAgICBjb25zdCBncmlkU2l6ZSA9IHdpZHRoID09PSAyNTcgPyAyNTcgOiB3aWR0aCArIDE7IC8vIE9ubHkgYWRkIDEgaWYgd2lkdGggaXMgbm90IGFscmVhZHkgMl5uKzEKICAgICAgICAgICAgICAgICAgICBjb25zdCBtYXJ0aW5pID0gbmV3IE1hcnRpbmkoZ3JpZFNpemUpOwogICAgICAgICAgICAgICAgICAgIGNvbnN0IHRpbGUgPSBtYXJ0aW5pLmNyZWF0ZVRpbGUodGVycmFpbik7CiAgICAgICAgICAgICAgICAgICAgbWVzaCA9IHRpbGUuZ2V0TWVzaChtZXNoTWF4RXJyb3IpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgLy8gT25seSBzZW5kIHJlc3VsdCBpZiBub3QgYWJvcnRlZAogICAgICAgICAgICAgICAgaWYgKCFhYm9ydGVkVGFza3MuZ2V0KHRhc2tJZCkpIHsKICAgICAgICAgICAgICAgICAgICBjb25zdCByZXNwb25zZSA9IHsKICAgICAgICAgICAgICAgICAgICAgICAgdHlwZTogJ21lc2hSZXN1bHQnLAogICAgICAgICAgICAgICAgICAgICAgICB0YXNrSWQsCiAgICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdDogbWVzaCwKICAgICAgICAgICAgICAgICAgICAgICAgdGVycmFpbiwgLy8g4oaQIENSSVRJQ0FMOiBSZXR1cm4gdGVycmFpbiBidWZmZXIgdG8gbWFpbiB0aHJlYWQKICAgICAgICAgICAgICAgICAgICB9OwogICAgICAgICAgICAgICAgICAgIC8vIFRyYW5zZmVyIG93bmVyc2hpcCBvZiBBTEwgYnVmZmVycyBiYWNrIHRvIG1haW4gdGhyZWFkICh6ZXJvLWNvcHkgcm91bmR0cmlwKQogICAgICAgICAgICAgICAgICAgIC8vIFRoaXMgYXZvaWRzIG1lc2hUZXJyYWluLnNsaWNlKCkgYWxsb2NhdGlvbiBvbiBtYWluIHRocmVhZAogICAgICAgICAgICAgICAgICAgIHNlbGYucG9zdE1lc3NhZ2UocmVzcG9uc2UsIHsKICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnNmZXI6IFsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lc2gudmVydGljZXMuYnVmZmVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVzaC50cmlhbmdsZXMuYnVmZmVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVycmFpbi5idWZmZXIsIC8vIOKGkCBUcmFuc2ZlciB0ZXJyYWluIGJhY2sKICAgICAgICAgICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgICBjYXRjaCAoZXJyb3IpIHsKICAgICAgICAgICAgICAgIC8vIE9ubHkgcmVwb3J0IGVycm9ycyBmb3Igbm9uLWFib3J0ZWQgdGFza3MKICAgICAgICAgICAgICAgIGlmICghYWJvcnRlZFRhc2tzLmdldCh0YXNrSWQpKSB7CiAgICAgICAgICAgICAgICAgICAgc2VsZi5wb3N0TWVzc2FnZSh7CiAgICAgICAgICAgICAgICAgICAgICAgIHR5cGU6ICdlcnJvcicsCiAgICAgICAgICAgICAgICAgICAgICAgIHRhc2tJZCwKICAgICAgICAgICAgICAgICAgICAgICAgZXJyb3I6IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKSwKICAgICAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgICBmaW5hbGx5IHsKICAgICAgICAgICAgICAgIC8vIENsZWFuIHVwIGFib3J0IHRyYWNraW5nCiAgICAgICAgICAgICAgICBhYm9ydGVkVGFza3MuZGVsZXRlKHRhc2tJZCk7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICB9OwogICAgLy8gU2lnbmFsIHRoYXQgd29ya2VyIGlzIHJlYWR5CiAgICBzZWxmLnBvc3RNZXNzYWdlKHsgdHlwZTogJ3JlYWR5JyB9KTsKCn0pKCk7Ci8vIyBzb3VyY2VNYXBwaW5nVVJMPXRlcnJhaW4ud29ya2VyLmpzLm1hcAoK');
|
|
6785
|
+
/* eslint-enable */
|
|
6786
|
+
|
|
6787
|
+
/**
|
|
6788
|
+
* Pool of Web Workers for parallel terrain tessellation.
|
|
6789
|
+
* Distributes work across multiple workers based on CPU core count.
|
|
6790
|
+
*
|
|
6791
|
+
* SINGLETON PATTERN: One global pool is shared across all CogTiles instances
|
|
6792
|
+
* to avoid expensive worker creation/destruction during deck.gl layer recreations.
|
|
6793
|
+
*/
|
|
6794
|
+
// @ts-expect-error - The import statement will be handled by both Rollup (web-worker: prefix)
|
|
6795
|
+
// and our Vite plugin (web-worker: → ?worker conversion)
|
|
6796
|
+
/**
|
|
6797
|
+
* Manages a pool of terrain tessellation workers.
|
|
6798
|
+
* Automatically scales to CPU core count (capped at 8 for memory safety).
|
|
6799
|
+
*/
|
|
6800
|
+
class TerrainWorkerPool {
|
|
6801
|
+
workers = [];
|
|
6802
|
+
pendingTasks = new Map();
|
|
6803
|
+
taskCounter = 0;
|
|
6804
|
+
roundRobinIndex = 0;
|
|
6805
|
+
constructor(poolSize) {
|
|
6806
|
+
// Detect browser environment; default to 1 worker in non-browser contexts (SSR/Node/tests)
|
|
6807
|
+
let defaultSize = 1;
|
|
6808
|
+
if (typeof navigator !== 'undefined') {
|
|
6809
|
+
// Default to CPU core count, fallback to 4, cap at 8 to prevent memory exhaustion
|
|
6810
|
+
defaultSize = Math.min(navigator.hardwareConcurrency || 4, 8);
|
|
6811
|
+
// On low-memory devices (<4GB), use only 2 workers to avoid OOM
|
|
6812
|
+
if (navigator.deviceMemory && navigator.deviceMemory < 4) {
|
|
6813
|
+
defaultSize = 2;
|
|
6814
|
+
}
|
|
6815
|
+
}
|
|
6816
|
+
const size = Math.max(1, poolSize ?? defaultSize);
|
|
6817
|
+
for (let i = 0; i < size; i++) {
|
|
6818
|
+
const worker = new WorkerFactory();
|
|
6819
|
+
worker.onmessage = this.handleWorkerMessage.bind(this);
|
|
6820
|
+
worker.onerror = this.handleWorkerError.bind(this);
|
|
6821
|
+
this.workers.push(worker);
|
|
6822
|
+
}
|
|
6823
|
+
}
|
|
6824
|
+
/**
|
|
6825
|
+
* Compute terrain mesh using the worker pool.
|
|
6826
|
+
* Returns a Promise that resolves with the mesh data.
|
|
6827
|
+
* Supports cancellation via AbortSignal.
|
|
6828
|
+
*/
|
|
6829
|
+
async computeMesh(options) {
|
|
6830
|
+
const { terrain, meshMaxError, tesselator, width, height, signal } = options;
|
|
6831
|
+
// Check if already aborted
|
|
6832
|
+
if (signal?.aborted) {
|
|
6833
|
+
throw new DOMException('Aborted', 'AbortError');
|
|
6834
|
+
}
|
|
6835
|
+
const taskId = `task_${++this.taskCounter}`;
|
|
6836
|
+
return new Promise((resolve, reject) => {
|
|
6837
|
+
// Pick worker once — used for both dispatch and abort to ensure correct routing
|
|
6838
|
+
const worker = this.getNextWorker();
|
|
6839
|
+
const task = { resolve, reject, aborted: false, worker };
|
|
6840
|
+
this.pendingTasks.set(taskId, task);
|
|
6841
|
+
// Handle abort signal
|
|
6842
|
+
if (signal) {
|
|
6843
|
+
signal.addEventListener('abort', () => {
|
|
6844
|
+
// Guard against late aborts: only process if task still exists
|
|
6845
|
+
if (!this.pendingTasks.has(taskId)) {
|
|
6846
|
+
return;
|
|
6847
|
+
}
|
|
6848
|
+
task.aborted = true;
|
|
6849
|
+
this.pendingTasks.delete(taskId);
|
|
6850
|
+
// Send abort to the same worker that owns this task
|
|
6851
|
+
worker.postMessage({ type: 'abort', taskId });
|
|
6852
|
+
reject(new DOMException('Aborted', 'AbortError'));
|
|
6853
|
+
}, { once: true });
|
|
6854
|
+
}
|
|
6855
|
+
// Transfer terrain buffer ownership to avoid copy
|
|
6856
|
+
// NOTE: After this, `terrain` becomes detached on main thread
|
|
6857
|
+
worker.postMessage({
|
|
6858
|
+
type: 'computeMesh',
|
|
6859
|
+
taskId,
|
|
6860
|
+
terrain,
|
|
6861
|
+
meshMaxError,
|
|
6862
|
+
tesselator,
|
|
6863
|
+
width,
|
|
6864
|
+
height,
|
|
6865
|
+
}, [terrain.buffer] // ← Transferable
|
|
6866
|
+
);
|
|
6867
|
+
});
|
|
6868
|
+
}
|
|
6869
|
+
getNextWorker() {
|
|
6870
|
+
const worker = this.workers[this.roundRobinIndex];
|
|
6871
|
+
this.roundRobinIndex = (this.roundRobinIndex + 1) % this.workers.length;
|
|
6872
|
+
return worker;
|
|
6873
|
+
}
|
|
6874
|
+
handleWorkerMessage(e) {
|
|
6875
|
+
const { type, taskId, result, terrain, error } = e.data;
|
|
6876
|
+
if (type === 'ready') {
|
|
6877
|
+
// Worker initialization complete (can be ignored)
|
|
6878
|
+
return;
|
|
6879
|
+
}
|
|
6880
|
+
const task = this.pendingTasks.get(taskId);
|
|
6881
|
+
if (!task) {
|
|
6882
|
+
// Task was aborted or already resolved
|
|
6883
|
+
return;
|
|
6884
|
+
}
|
|
6885
|
+
this.pendingTasks.delete(taskId);
|
|
6886
|
+
if (task.aborted) {
|
|
6887
|
+
// Ignore result for aborted task
|
|
6888
|
+
return;
|
|
6889
|
+
}
|
|
6890
|
+
if (type === 'meshResult') {
|
|
6891
|
+
// Combine result with terrain that was transferred back
|
|
6892
|
+
task.resolve({
|
|
6893
|
+
vertices: result.vertices,
|
|
6894
|
+
triangles: result.triangles,
|
|
6895
|
+
terrain, // ← Include terrain from message
|
|
6896
|
+
});
|
|
6897
|
+
}
|
|
6898
|
+
else if (type === 'error') {
|
|
6899
|
+
task.reject(new Error(`Worker error: ${error}`));
|
|
6900
|
+
}
|
|
6901
|
+
}
|
|
6902
|
+
handleWorkerError(e) {
|
|
6903
|
+
// Find which worker failed (e.target is the Worker instance)
|
|
6904
|
+
const failedWorker = e.target;
|
|
6905
|
+
// Find all pending tasks assigned to this worker
|
|
6906
|
+
const failedTaskIds = [];
|
|
6907
|
+
this.pendingTasks.forEach((task, taskId) => {
|
|
6908
|
+
if (task.worker === failedWorker) {
|
|
6909
|
+
failedTaskIds.push(taskId);
|
|
6910
|
+
}
|
|
6911
|
+
});
|
|
6912
|
+
// Reject all tasks for the failed worker
|
|
6913
|
+
for (const taskId of failedTaskIds) {
|
|
6914
|
+
const task = this.pendingTasks.get(taskId);
|
|
6915
|
+
if (task) {
|
|
6916
|
+
this.pendingTasks.delete(taskId);
|
|
6917
|
+
task.reject(new Error(`Worker crashed: ${e.message || 'Unknown error'}`));
|
|
6918
|
+
}
|
|
6919
|
+
}
|
|
6920
|
+
// Respawn the failed worker to maintain pool capacity
|
|
6921
|
+
const workerIndex = this.workers.indexOf(failedWorker);
|
|
6922
|
+
if (workerIndex !== -1) {
|
|
6923
|
+
try {
|
|
6924
|
+
const newWorker = new WorkerFactory();
|
|
6925
|
+
newWorker.onmessage = this.handleWorkerMessage.bind(this);
|
|
6926
|
+
newWorker.onerror = this.handleWorkerError.bind(this);
|
|
6927
|
+
this.workers[workerIndex] = newWorker;
|
|
6928
|
+
}
|
|
6929
|
+
catch (spawnError) {
|
|
6930
|
+
// eslint-disable-next-line no-console
|
|
6931
|
+
console.error('[TerrainWorkerPool] Failed to respawn worker:', spawnError);
|
|
6932
|
+
}
|
|
6933
|
+
}
|
|
6934
|
+
}
|
|
6935
|
+
/**
|
|
6936
|
+
* Terminate all workers.
|
|
6937
|
+
* ⚠️ NOTE: Because this is a singleton, terminate() should only be called
|
|
6938
|
+
* on app shutdown, not on individual layer unmount.
|
|
6939
|
+
*/
|
|
6940
|
+
terminate() {
|
|
6941
|
+
// Reject all pending tasks before terminating workers
|
|
6942
|
+
this.pendingTasks.forEach(task => {
|
|
6943
|
+
task.reject(new DOMException('Worker pool terminated', 'AbortError'));
|
|
6944
|
+
});
|
|
6945
|
+
this.pendingTasks.clear();
|
|
6946
|
+
this.workers.forEach(w => w.terminate());
|
|
6947
|
+
this.workers = [];
|
|
6948
|
+
}
|
|
6949
|
+
}
|
|
6950
|
+
// ─── SINGLETON INSTANCE ───
|
|
6951
|
+
// Lazily initialized on first use; shared across all CogTiles instances
|
|
6952
|
+
let globalWorkerPool = null;
|
|
6953
|
+
/**
|
|
6954
|
+
* Gets the global terrain worker pool, creating it on first use.
|
|
6955
|
+
* All CogTiles instances share this pool to avoid expensive worker churn
|
|
6956
|
+
* during deck.gl layer recreations.
|
|
6957
|
+
*/
|
|
6958
|
+
function getGlobalTerrainWorkerPool() {
|
|
6959
|
+
if (!globalWorkerPool) {
|
|
6960
|
+
globalWorkerPool = new TerrainWorkerPool();
|
|
6961
|
+
}
|
|
6962
|
+
return globalWorkerPool;
|
|
6963
|
+
}
|
|
6964
|
+
/**
|
|
6965
|
+
* Terminates the global worker pool.
|
|
6966
|
+
* Only call this on app shutdown, not on layer unmount.
|
|
6967
|
+
*/
|
|
6968
|
+
function terminateGlobalTerrainWorkerPool() {
|
|
6969
|
+
if (globalWorkerPool) {
|
|
6970
|
+
globalWorkerPool.terminate();
|
|
6971
|
+
globalWorkerPool = null;
|
|
6972
|
+
}
|
|
6973
|
+
}
|
|
6974
|
+
|
|
6739
6975
|
const EARTH_CIRCUMFERENCE = 2 * Math.PI * 6378137;
|
|
6740
6976
|
const EARTH_HALF_CIRCUMFERENCE = EARTH_CIRCUMFERENCE / 2;
|
|
6741
6977
|
const webMercatorOrigin = [-20037508.342789244, 20037508.342789244];
|
|
@@ -7089,12 +7325,19 @@ class CogTiles {
|
|
|
7089
7325
|
// future cache hits just await the already-resolved promise directly.
|
|
7090
7326
|
cache = new TileCacheManager();
|
|
7091
7327
|
tileReader;
|
|
7328
|
+
workerPool; // TerrainWorkerPool (for terrain tiles)
|
|
7092
7329
|
// Store initialization promise to prevent concurrent duplicate initializations
|
|
7093
7330
|
initializePromise;
|
|
7094
7331
|
// Track the last successfully initialized URL to detect URL changes
|
|
7095
7332
|
lastInitializedUrl;
|
|
7096
7333
|
constructor(options) {
|
|
7097
7334
|
this.options = { ...CogTilesGeoImageOptionsDefaults, ...options };
|
|
7335
|
+
// Get reference to global worker pool for terrain tiles
|
|
7336
|
+
// Do NOT create a new pool per instance — reuse the singleton
|
|
7337
|
+
// Skip if disableWorkerPool is true (e.g., for smooth animation during rapid band changes)
|
|
7338
|
+
if (options.type === 'terrain' && typeof Worker !== 'undefined' && !options.disableWorkerPool) {
|
|
7339
|
+
this.workerPool = getGlobalTerrainWorkerPool();
|
|
7340
|
+
}
|
|
7098
7341
|
}
|
|
7099
7342
|
async initializeCog(url) {
|
|
7100
7343
|
// Reuse existing initialization while it is in progress, or when the same URL
|
|
@@ -7626,7 +7869,7 @@ class CogTiles {
|
|
|
7626
7869
|
height: requiredSize,
|
|
7627
7870
|
bounds: bounds ?? [0, 0, 0, 0],
|
|
7628
7871
|
cellSizeMeters,
|
|
7629
|
-
}, generatorOptions, resolvedMeshMaxError);
|
|
7872
|
+
}, generatorOptions, resolvedMeshMaxError, this.workerPool, controller.signal);
|
|
7630
7873
|
})();
|
|
7631
7874
|
const entry = {
|
|
7632
7875
|
promise: pipeline,
|
|
@@ -7680,7 +7923,7 @@ class CogTiles {
|
|
|
7680
7923
|
height: this.tileSize,
|
|
7681
7924
|
bounds: bounds ?? [0, 0, 0, 0],
|
|
7682
7925
|
cellSizeMeters,
|
|
7683
|
-
}, this.options, meshMaxError ?? 4.0);
|
|
7926
|
+
}, this.options, meshMaxError ?? 4.0, this.workerPool, signal);
|
|
7684
7927
|
}
|
|
7685
7928
|
async getBitmapTile(x, y, z, bounds, cellSizeMeters, meshMaxError, signal) {
|
|
7686
7929
|
const rasterKey = this.cache.getTileCacheKey(x, y, z);
|
|
@@ -7702,7 +7945,7 @@ class CogTiles {
|
|
|
7702
7945
|
height: this.tileSize,
|
|
7703
7946
|
bounds: bounds ?? [0, 0, 0, 0],
|
|
7704
7947
|
cellSizeMeters,
|
|
7705
|
-
}, this.options, meshMaxError ?? 4.0);
|
|
7948
|
+
}, this.options, meshMaxError ?? 4.0, this.workerPool);
|
|
7706
7949
|
}
|
|
7707
7950
|
async getTileAllBands(x, y, z, meshMaxError, signal, bounds) {
|
|
7708
7951
|
if (!this.cog) {
|
|
@@ -7799,7 +8042,7 @@ class CogTiles {
|
|
|
7799
8042
|
height: FETCH_SIZE,
|
|
7800
8043
|
bounds: bounds ?? [0, 0, 0, 0],
|
|
7801
8044
|
cellSizeMeters,
|
|
7802
|
-
}, generatorOptions, resolvedMeshMaxError);
|
|
8045
|
+
}, generatorOptions, resolvedMeshMaxError, this.workerPool, signal);
|
|
7803
8046
|
if (tileResult)
|
|
7804
8047
|
results.push(tileResult);
|
|
7805
8048
|
}
|
|
@@ -8062,6 +8305,8 @@ const defaultProps = {
|
|
|
8062
8305
|
// Same as SimpleMeshLayer wireframe
|
|
8063
8306
|
wireframe: false,
|
|
8064
8307
|
material: true,
|
|
8308
|
+
// Enable progressive loading by default (overview tiles first, then detail)
|
|
8309
|
+
enableProgressiveLoading: true,
|
|
8065
8310
|
// loaders: [TerrainLoader],
|
|
8066
8311
|
};
|
|
8067
8312
|
// Turns array of templates into a single string to work around shallow change
|
|
@@ -8079,12 +8324,15 @@ class CogTerrainLayer extends core.CompositeLayer {
|
|
|
8079
8324
|
static layerName = 'CogTerrainLayer';
|
|
8080
8325
|
// terrainCogTiles: CogTiles;
|
|
8081
8326
|
terrainUrl = '';
|
|
8327
|
+
zRangeUpdateTimeoutId = null;
|
|
8328
|
+
lastZRangeValue = null;
|
|
8082
8329
|
async initializeState(context) {
|
|
8083
8330
|
super.initializeState(context);
|
|
8084
8331
|
const terrainCogTiles = this.props.cogTiles || new CogTiles(this.props.terrainOptions);
|
|
8085
8332
|
this.setState({
|
|
8086
8333
|
terrainCogTiles,
|
|
8087
8334
|
initialized: false,
|
|
8335
|
+
overviewLoaded: false,
|
|
8088
8336
|
});
|
|
8089
8337
|
// Only initialize if not already done (e.g., provided cogTiles instance may be pre-initialized)
|
|
8090
8338
|
if (!terrainCogTiles.cog) {
|
|
@@ -8130,6 +8378,26 @@ class CogTerrainLayer extends core.CompositeLayer {
|
|
|
8130
8378
|
this.state.terrainCogTiles.options.useChannelIndex = null; // Clear derived channel index
|
|
8131
8379
|
this.state.terrainCogTiles.clearTileResultCache(); // Invalidate cached tiles from previous channel
|
|
8132
8380
|
}
|
|
8381
|
+
// Update kernel visualization options when hillshade/slope/relief settings change.
|
|
8382
|
+
// These affect tile texture generation — the cache must be cleared and options synced
|
|
8383
|
+
// so the next getTileData call uses the updated kernel settings.
|
|
8384
|
+
const kernelOptionsChanged = props?.terrainOptions?.useHillshade !== oldProps.terrainOptions?.useHillshade ||
|
|
8385
|
+
props?.terrainOptions?.useSlope !== oldProps.terrainOptions?.useSlope ||
|
|
8386
|
+
props?.terrainOptions?.useSwissRelief !== oldProps.terrainOptions?.useSwissRelief ||
|
|
8387
|
+
props?.terrainOptions?.hillshadeAzimuth !== oldProps.terrainOptions?.hillshadeAzimuth ||
|
|
8388
|
+
props?.terrainOptions?.hillshadeAltitude !== oldProps.terrainOptions?.hillshadeAltitude ||
|
|
8389
|
+
props?.terrainOptions?.zFactor !== oldProps.terrainOptions?.zFactor;
|
|
8390
|
+
if (kernelOptionsChanged && this.state.terrainCogTiles) {
|
|
8391
|
+
// Sync updated options into the shared CogTiles instance
|
|
8392
|
+
this.state.terrainCogTiles.options.useHillshade = props.terrainOptions?.useHillshade;
|
|
8393
|
+
this.state.terrainCogTiles.options.useSlope = props.terrainOptions?.useSlope;
|
|
8394
|
+
this.state.terrainCogTiles.options.useSwissRelief = props.terrainOptions?.useSwissRelief;
|
|
8395
|
+
this.state.terrainCogTiles.options.hillshadeAzimuth = props.terrainOptions?.hillshadeAzimuth;
|
|
8396
|
+
this.state.terrainCogTiles.options.hillshadeAltitude = props.terrainOptions?.hillshadeAltitude;
|
|
8397
|
+
this.state.terrainCogTiles.options.zFactor = props.terrainOptions?.zFactor;
|
|
8398
|
+
// Invalidate cached tiles — kernel output is baked into the texture
|
|
8399
|
+
this.state.terrainCogTiles.clearTileResultCache();
|
|
8400
|
+
}
|
|
8133
8401
|
// Update skipTexture when wireframe/operation/disableTexture changes so cache keys are correct
|
|
8134
8402
|
const newSkipTexture = !!(props?.wireframe || props?.operation === 'terrain' || props?.disableTexture);
|
|
8135
8403
|
const oldSkipTexture = !!(oldProps?.wireframe || oldProps?.operation === 'terrain' || oldProps?.disableTexture);
|
|
@@ -8140,8 +8408,20 @@ class CogTerrainLayer extends core.CompositeLayer {
|
|
|
8140
8408
|
// When the external cogTiles instance is swapped (e.g. mode switch), update state so
|
|
8141
8409
|
// renderLayers picks up the new reference and the TileLayer updateTrigger fires a refetch
|
|
8142
8410
|
// while keeping old tile content visible until new tiles are ready.
|
|
8411
|
+
// Also reset progressive loading state for the new dataset.
|
|
8143
8412
|
if (props.cogTiles && props.cogTiles !== oldProps.cogTiles) {
|
|
8144
|
-
|
|
8413
|
+
const newState = { terrainCogTiles: props.cogTiles };
|
|
8414
|
+
// Only reset progressive loading state if it's actually enabled
|
|
8415
|
+
if (this.props.enableProgressiveLoading) {
|
|
8416
|
+
newState.overviewLoaded = false;
|
|
8417
|
+
}
|
|
8418
|
+
this.setState(newState);
|
|
8419
|
+
}
|
|
8420
|
+
else if (elevationDataChanged) {
|
|
8421
|
+
// Reset progressive loading state when dataset URL changes
|
|
8422
|
+
if (this.props.enableProgressiveLoading) {
|
|
8423
|
+
this.setState({ overviewLoaded: false });
|
|
8424
|
+
}
|
|
8145
8425
|
}
|
|
8146
8426
|
if (props.workerUrl) {
|
|
8147
8427
|
core.log.removed('workerUrl', 'loadOptions.terrain.workerUrl')();
|
|
@@ -8233,9 +8513,21 @@ class CogTerrainLayer extends core.CompositeLayer {
|
|
|
8233
8513
|
_instanced: false,
|
|
8234
8514
|
pickable: props.pickable,
|
|
8235
8515
|
coordinateSystem: core.COORDINATE_SYSTEM.CARTESIAN,
|
|
8516
|
+
// Dynamic polygon offset: pull higher zoom levels closer to camera to depth-test in front.
|
|
8517
|
+
// Uses tile.index.z from closure to avoid Z-fighting between ancestor tiles and high-res detail.
|
|
8518
|
+
// Formula: zoom 0 = offset 0, zoom 9 = offset -9000, zoom 12 = offset -12000, etc.
|
|
8519
|
+
// getPolygonOffset must be a function (deck.gl calls it as getPolygonOffset(uniforms)).
|
|
8520
|
+
// If the user supplied a custom override on the CogTerrainLayer, respect it;
|
|
8521
|
+
// otherwise apply the tile-zoom-based dynamic offset.
|
|
8522
|
+
getPolygonOffset: (this.props.getPolygonOffset != null
|
|
8523
|
+
&& this.props.getPolygonOffset !== ((CogTerrainLayer.defaultProps.getPolygonOffset?.value) ?? CogTerrainLayer.defaultProps.getPolygonOffset))
|
|
8524
|
+
? this.props.getPolygonOffset
|
|
8525
|
+
: () => [0, -((props.tile?.index?.z ?? 0) * 1000)],
|
|
8236
8526
|
// getPosition: (d) => [0, 0, 0],
|
|
8237
8527
|
getColor: tileTexture ? [255, 255, 255] : color,
|
|
8238
8528
|
wireframe,
|
|
8529
|
+
// ADDED: Forward parameters prop down to the SimpleMeshLayer for depthRange to work
|
|
8530
|
+
parameters: this.props.parameters,
|
|
8239
8531
|
});
|
|
8240
8532
|
}
|
|
8241
8533
|
// Update zRange of viewport
|
|
@@ -8257,60 +8549,121 @@ class CogTerrainLayer extends core.CompositeLayer {
|
|
|
8257
8549
|
if (ranges.length === 0) {
|
|
8258
8550
|
return;
|
|
8259
8551
|
}
|
|
8260
|
-
const
|
|
8261
|
-
|
|
8552
|
+
const minValues = ranges
|
|
8553
|
+
.map((x) => x?.[0])
|
|
8554
|
+
.filter((n) => n !== undefined && Number.isFinite(n));
|
|
8555
|
+
const maxValues = ranges
|
|
8556
|
+
.map((x) => x?.[1])
|
|
8557
|
+
.filter((n) => n !== undefined && Number.isFinite(n));
|
|
8558
|
+
if (minValues.length === 0 || maxValues.length === 0) {
|
|
8559
|
+
return;
|
|
8560
|
+
}
|
|
8561
|
+
const minZ = Math.min(...minValues);
|
|
8562
|
+
const maxZ = Math.max(...maxValues);
|
|
8262
8563
|
if (!zRange || minZ < zRange[0] || maxZ > zRange[1]) {
|
|
8263
8564
|
const newZRange = [Number.isFinite(minZ) ? minZ : 0, Number.isFinite(maxZ) ? maxZ : 0];
|
|
8264
8565
|
this.setState({ zRange: newZRange });
|
|
8265
|
-
|
|
8566
|
+
// Debounce onZRangeUpdate callback to avoid excessive React re-renders during rapid updates
|
|
8567
|
+
// (e.g., slider scrubbing in animation use cases). Wait 200ms of stable zRange before firing.
|
|
8568
|
+
if (this.zRangeUpdateTimeoutId !== null) {
|
|
8569
|
+
clearTimeout(this.zRangeUpdateTimeoutId);
|
|
8570
|
+
}
|
|
8571
|
+
this.lastZRangeValue = newZRange;
|
|
8572
|
+
this.zRangeUpdateTimeoutId = setTimeout(() => {
|
|
8573
|
+
this.props.onZRangeUpdate?.(this.lastZRangeValue);
|
|
8574
|
+
this.zRangeUpdateTimeoutId = null;
|
|
8575
|
+
}, 200);
|
|
8266
8576
|
}
|
|
8267
8577
|
}
|
|
8268
8578
|
renderLayers() {
|
|
8269
|
-
const {
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
//
|
|
8274
|
-
//
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8579
|
+
const { elevationData, meshMaxError, elevationDecoder, tileSize, extent, maxRequests, onTileUnload, onTileError, maxCacheSize, maxCacheByteSize, refinementStrategy, } = this.props;
|
|
8580
|
+
if (!this.state.isTiled || !this.state.initialized) {
|
|
8581
|
+
return null;
|
|
8582
|
+
}
|
|
8583
|
+
// Auto-enable LOD gate: lock at minZoom until overview tile loads, then release to full range.
|
|
8584
|
+
// User's explicit zoomOverride takes precedence over auto-gate.
|
|
8585
|
+
let effectiveMinZoom = this.state.minZoom;
|
|
8586
|
+
let effectiveMaxZoom = this.state.maxZoom;
|
|
8587
|
+
if (this.props.zoomOverride !== undefined) {
|
|
8588
|
+
effectiveMinZoom = this.props.zoomOverride;
|
|
8589
|
+
effectiveMaxZoom = this.props.zoomOverride;
|
|
8590
|
+
}
|
|
8591
|
+
else if (this.props.enableProgressiveLoading && !this.state.overviewLoaded) {
|
|
8592
|
+
// Gate: lock at minZoom until the overview viewport is fully covered
|
|
8593
|
+
effectiveMinZoom = this.state.minZoom;
|
|
8594
|
+
effectiveMaxZoom = this.state.minZoom;
|
|
8595
|
+
}
|
|
8596
|
+
return new geoLayers.TileLayer(this.getSubLayerProps({ id: 'tiles' }), {
|
|
8597
|
+
getTileData: this.getTiledTerrainData.bind(this),
|
|
8598
|
+
renderSubLayers: this.renderSubLayers.bind(this),
|
|
8599
|
+
pickable: this.props.pickable,
|
|
8600
|
+
onClick: this.props.onClick,
|
|
8601
|
+
updateTriggers: {
|
|
8602
|
+
getTileData: {
|
|
8603
|
+
elevationData: urlTemplateToUpdateTrigger(elevationData),
|
|
8604
|
+
meshMaxError,
|
|
8605
|
+
elevationDecoder,
|
|
8606
|
+
terrainCogTiles: this.state.terrainCogTiles,
|
|
8607
|
+
skipTexture: !!(this.props.wireframe || this.props.operation === 'terrain' || this.props.disableTexture),
|
|
8608
|
+
useChannel: this.props.terrainOptions?.useChannel,
|
|
8609
|
+
// Only include kernel visualization triggers if they're explicitly set to avoid false positives
|
|
8610
|
+
// when animating with different useChannel values
|
|
8611
|
+
...(this.props.terrainOptions?.useHillshade !== undefined && {
|
|
8612
|
+
useHillshade: this.props.terrainOptions.useHillshade,
|
|
8613
|
+
}),
|
|
8614
|
+
...(this.props.terrainOptions?.useSlope !== undefined && {
|
|
8615
|
+
useSlope: this.props.terrainOptions.useSlope,
|
|
8616
|
+
}),
|
|
8617
|
+
...(this.props.terrainOptions?.useSwissRelief !== undefined && {
|
|
8618
|
+
useSwissRelief: this.props.terrainOptions.useSwissRelief,
|
|
8619
|
+
}),
|
|
8620
|
+
...(this.props.terrainOptions?.hillshadeAzimuth !== undefined && {
|
|
8621
|
+
hillshadeAzimuth: this.props.terrainOptions.hillshadeAzimuth,
|
|
8622
|
+
}),
|
|
8623
|
+
...(this.props.terrainOptions?.hillshadeAltitude !== undefined && {
|
|
8624
|
+
hillshadeAltitude: this.props.terrainOptions.hillshadeAltitude,
|
|
8625
|
+
}),
|
|
8626
|
+
...(this.props.terrainOptions?.zFactor !== undefined && {
|
|
8627
|
+
zFactor: this.props.terrainOptions.zFactor,
|
|
8628
|
+
}),
|
|
8297
8629
|
},
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8630
|
+
renderSubLayers: {
|
|
8631
|
+
disableTexture: this.props.disableTexture,
|
|
8632
|
+
terrainOptions: this.props.terrainOptions,
|
|
8633
|
+
},
|
|
8634
|
+
},
|
|
8635
|
+
onViewportLoad: this.onViewportLoad.bind(this),
|
|
8636
|
+
zRange: this.state.zRange || null,
|
|
8637
|
+
tileSize,
|
|
8638
|
+
minZoom: effectiveMinZoom,
|
|
8639
|
+
maxZoom: effectiveMaxZoom,
|
|
8640
|
+
extent,
|
|
8641
|
+
maxRequests,
|
|
8642
|
+
onTileLoad: (tile) => {
|
|
8643
|
+
// Release progressive loading gate as soon as any minZoom tile finishes loading.
|
|
8644
|
+
// This fires mid-cycle so the TileLayer immediately re-selects high-res tiles
|
|
8645
|
+
// for the current viewport without requiring a zoom/pan interaction.
|
|
8646
|
+
if (this.props.enableProgressiveLoading &&
|
|
8647
|
+
tile.index.z === this.state.minZoom &&
|
|
8648
|
+
!this.state.overviewLoaded) {
|
|
8649
|
+
this.setState({ overviewLoaded: true });
|
|
8650
|
+
}
|
|
8651
|
+
this.props.onTileLoad?.(tile);
|
|
8652
|
+
},
|
|
8653
|
+
onTileUnload,
|
|
8654
|
+
onTileError,
|
|
8655
|
+
maxCacheSize,
|
|
8656
|
+
maxCacheByteSize,
|
|
8657
|
+
refinementStrategy,
|
|
8658
|
+
});
|
|
8659
|
+
}
|
|
8660
|
+
_finalize() {
|
|
8661
|
+
// Clean up pending zRangeUpdate callback timer on layer unmount
|
|
8662
|
+
if (this.zRangeUpdateTimeoutId !== null) {
|
|
8663
|
+
clearTimeout(this.zRangeUpdateTimeoutId);
|
|
8664
|
+
this.zRangeUpdateTimeoutId = null;
|
|
8312
8665
|
}
|
|
8313
|
-
|
|
8666
|
+
super._finalize?.();
|
|
8314
8667
|
}
|
|
8315
8668
|
}
|
|
8316
8669
|
|
|
@@ -15679,4 +16032,5 @@ exports.GeoImage = GeoImage;
|
|
|
15679
16032
|
exports.extractTerrainCoordinate = extractTerrainCoordinate;
|
|
15680
16033
|
exports.sampleTerrainTileCoordinates = sampleTerrainTileCoordinates;
|
|
15681
16034
|
exports.suppressGlobalAbortErrors = suppressGlobalAbortErrors;
|
|
16035
|
+
exports.terminateGlobalTerrainWorkerPool = terminateGlobalTerrainWorkerPool;
|
|
15682
16036
|
//# sourceMappingURL=index.js.map
|