@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 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
- const meshTerrain = isKernel ? this.extractMeshRaster(terrain) : terrain;
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
- switch (options.tesselator) {
6349
- case 'martini':
6350
- mesh = this.getMartiniTileMesh(meshMaxError, meshWidth, meshTerrain);
6351
- break;
6352
- case 'delatin':
6353
- mesh = this.getDelatinTileMesh(meshMaxError, meshWidth, meshHeight, meshTerrain);
6354
- break;
6355
- default:
6356
- // Intentional: default to Martini for any unspecified or unrecognized tesselator.
6357
- mesh = this.getMartiniTileMesh(meshMaxError, meshWidth, meshTerrain);
6358
- break;
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, meshTerrain, meshWidth, meshHeight, input.bounds, verticalExaggeration);
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
- this.setState({ terrainCogTiles: props.cogTiles });
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 minZ = Math.min(...ranges.map((x) => x?.[0] ?? 0).filter((n) => Number.isFinite(n)));
8261
- const maxZ = Math.max(...ranges.map((x) => x?.[1] ?? 0).filter((n) => Number.isFinite(n)));
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
- this.props.onZRangeUpdate?.(newZRange);
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
- // color,
8271
- // material,
8272
- elevationData,
8273
- // texture,
8274
- // wireframe,
8275
- meshMaxError, elevationDecoder, tileSize, extent, maxRequests, onTileLoad, onTileUnload, onTileError, maxCacheSize, maxCacheByteSize, refinementStrategy, } = this.props;
8276
- if (this.state.isTiled && this.state.initialized) {
8277
- return new geoLayers.TileLayer(this.getSubLayerProps({
8278
- id: 'tiles',
8279
- }), {
8280
- getTileData: this.getTiledTerrainData.bind(this),
8281
- renderSubLayers: this.renderSubLayers.bind(this),
8282
- pickable: this.props.pickable,
8283
- onClick: this.props.onClick,
8284
- updateTriggers: {
8285
- getTileData: {
8286
- elevationData: urlTemplateToUpdateTrigger(elevationData),
8287
- meshMaxError,
8288
- elevationDecoder,
8289
- terrainCogTiles: this.state.terrainCogTiles,
8290
- skipTexture: !!(this.props.wireframe || this.props.operation === 'terrain' || this.props.disableTexture),
8291
- useChannel: this.props.terrainOptions?.useChannel,
8292
- },
8293
- renderSubLayers: {
8294
- disableTexture: this.props.disableTexture,
8295
- terrainOptions: this.props.terrainOptions,
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
- onViewportLoad: this.onViewportLoad.bind(this),
8299
- zRange: this.state.zRange || null,
8300
- tileSize,
8301
- minZoom: this.state.minZoom,
8302
- maxZoom: this.state.maxZoom,
8303
- extent,
8304
- maxRequests,
8305
- onTileLoad,
8306
- onTileUnload,
8307
- onTileError,
8308
- maxCacheSize,
8309
- maxCacheByteSize,
8310
- refinementStrategy,
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
- return null;
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