@loaders.gl/tile-converter 4.1.1 → 4.2.0-alpha.2

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.
Files changed (55) hide show
  1. package/dist/3d-tiles-converter/3d-tiles-converter.d.ts +11 -0
  2. package/dist/3d-tiles-converter/3d-tiles-converter.d.ts.map +1 -1
  3. package/dist/3d-tiles-converter/3d-tiles-converter.js +77 -27
  4. package/dist/3d-tiles-converter/3d-tiles-converter.js.map +1 -1
  5. package/dist/3d-tiles-converter/helpers/load-i3s.d.ts +22 -1
  6. package/dist/3d-tiles-converter/helpers/load-i3s.d.ts.map +1 -1
  7. package/dist/3d-tiles-converter/helpers/load-i3s.js +49 -4
  8. package/dist/3d-tiles-converter/helpers/load-i3s.js.map +1 -1
  9. package/dist/converter-cli.js +2 -1
  10. package/dist/converter-cli.js.map +1 -1
  11. package/dist/converter.min.cjs +137 -130
  12. package/dist/deps-installer/deps-installer.js +1 -1
  13. package/dist/deps-installer/deps-installer.js.map +1 -1
  14. package/dist/i3s-converter/helpers/attribute-metadata-info.d.ts +10 -0
  15. package/dist/i3s-converter/helpers/attribute-metadata-info.d.ts.map +1 -1
  16. package/dist/i3s-converter/helpers/attribute-metadata-info.js +5 -0
  17. package/dist/i3s-converter/helpers/attribute-metadata-info.js.map +1 -1
  18. package/dist/i3s-converter/helpers/load-3d-tiles.d.ts.map +1 -1
  19. package/dist/i3s-converter/helpers/load-3d-tiles.js +22 -2
  20. package/dist/i3s-converter/helpers/load-3d-tiles.js.map +1 -1
  21. package/dist/i3s-converter/helpers/node-index-document.d.ts +2 -1
  22. package/dist/i3s-converter/helpers/node-index-document.d.ts.map +1 -1
  23. package/dist/i3s-converter/helpers/node-index-document.js +6 -8
  24. package/dist/i3s-converter/helpers/node-index-document.js.map +1 -1
  25. package/dist/i3s-converter/i3s-converter.d.ts +18 -0
  26. package/dist/i3s-converter/i3s-converter.d.ts.map +1 -1
  27. package/dist/i3s-converter/i3s-converter.js +121 -24
  28. package/dist/i3s-converter/i3s-converter.js.map +1 -1
  29. package/dist/i3s-server/bin/i3s-server.min.cjs +86 -86
  30. package/dist/index.cjs +792 -101
  31. package/dist/lib/json-schemas/conversion-dump-json-schema.d.ts +463 -0
  32. package/dist/lib/json-schemas/conversion-dump-json-schema.d.ts.map +1 -0
  33. package/dist/lib/json-schemas/conversion-dump-json-schema.js +463 -0
  34. package/dist/lib/json-schemas/conversion-dump-json-schema.js.map +1 -0
  35. package/dist/lib/utils/conversion-dump.d.ts +65 -8
  36. package/dist/lib/utils/conversion-dump.d.ts.map +1 -1
  37. package/dist/lib/utils/conversion-dump.js +98 -18
  38. package/dist/lib/utils/conversion-dump.js.map +1 -1
  39. package/dist/lib/utils/file-utils.d.ts +6 -0
  40. package/dist/lib/utils/file-utils.d.ts.map +1 -1
  41. package/dist/lib/utils/file-utils.js +7 -0
  42. package/dist/lib/utils/file-utils.js.map +1 -1
  43. package/dist/pgm-loader.js +1 -1
  44. package/dist/pgm-loader.js.map +1 -1
  45. package/package.json +15 -14
  46. package/src/3d-tiles-converter/3d-tiles-converter.ts +104 -31
  47. package/src/3d-tiles-converter/helpers/load-i3s.ts +86 -7
  48. package/src/converter-cli.ts +2 -1
  49. package/src/i3s-converter/helpers/attribute-metadata-info.ts +16 -0
  50. package/src/i3s-converter/helpers/load-3d-tiles.ts +52 -2
  51. package/src/i3s-converter/helpers/node-index-document.ts +18 -8
  52. package/src/i3s-converter/i3s-converter.ts +198 -41
  53. package/src/lib/json-schemas/conversion-dump-json-schema.ts +285 -0
  54. package/src/lib/utils/conversion-dump.ts +200 -26
  55. package/src/lib/utils/file-utils.ts +13 -0
@@ -4,7 +4,7 @@ import type {
4
4
  NodeReference,
5
5
  I3STilesetHeader
6
6
  } from '@loaders.gl/i3s';
7
- import type {Tiles3DTileJSON} from '@loaders.gl/3d-tiles';
7
+ import type {Tile3DBoundingVolume, Tiles3DTileJSON} from '@loaders.gl/3d-tiles';
8
8
 
9
9
  import {join} from 'path';
10
10
  import process from 'process';
@@ -24,8 +24,10 @@ import {WorkerFarm} from '@loaders.gl/worker-utils';
24
24
  import {BROWSER_ERROR_MESSAGE} from '../constants';
25
25
  import B3dmConverter, {I3SAttributesData} from './helpers/b3dm-converter';
26
26
  import {I3STileHeader} from '@loaders.gl/i3s/src/types';
27
- import {loadI3SContent} from './helpers/load-i3s';
27
+ import {loadFromArchive, loadI3SContent, openSLPK} from './helpers/load-i3s';
28
28
  import {I3SLoaderOptions} from '@loaders.gl/i3s/src/i3s-loader';
29
+ import {ZipFileSystem} from '../../../zip/src';
30
+ import {ConversionDump, ConversionDumpOptions} from '../lib/utils/conversion-dump';
29
31
 
30
32
  const I3S = 'I3S';
31
33
 
@@ -41,6 +43,7 @@ export default class Tiles3DConverter {
41
43
  sourceTileset: I3STilesetHeader | null;
42
44
  attributeStorageInfo?: AttributeStorageInfo[] | null;
43
45
  workerSource: {[key: string]: string} = {};
46
+ slpkFilesystem: ZipFileSystem | null = null;
44
47
  loaderOptions: I3SLoaderOptions = {
45
48
  _nodeWorkers: true,
46
49
  reuseWorkers: true,
@@ -52,6 +55,7 @@ export default class Tiles3DConverter {
52
55
  workerUrl: './modules/i3s/dist/i3s-content-worker-node.js'
53
56
  }
54
57
  };
58
+ conversionDump: ConversionDump;
55
59
 
56
60
  constructor() {
57
61
  this.options = {};
@@ -62,6 +66,7 @@ export default class Tiles3DConverter {
62
66
  this.sourceTileset = null;
63
67
  this.attributeStorageInfo = null;
64
68
  this.workerSource = {};
69
+ this.conversionDump = new ConversionDump();
65
70
  }
66
71
 
67
72
  /**
@@ -79,20 +84,32 @@ export default class Tiles3DConverter {
79
84
  tilesetName: string;
80
85
  maxDepth?: number;
81
86
  egmFilePath: string;
87
+ inquirer?: Promise<unknown>;
82
88
  }): Promise<any> {
83
89
  if (isBrowser) {
84
90
  console.log(BROWSER_ERROR_MESSAGE);
85
91
  return BROWSER_ERROR_MESSAGE;
86
92
  }
87
- const {inputUrl, outputPath, tilesetName, maxDepth, egmFilePath} = options;
93
+ const {inputUrl, outputPath, tilesetName, maxDepth, egmFilePath, inquirer} = options;
88
94
  this.conversionStartTime = process.hrtime();
89
- this.options = {maxDepth};
95
+ this.options = {maxDepth, inquirer};
90
96
 
91
97
  console.log('Loading egm file...'); // eslint-disable-line
92
98
  this.geoidHeightModel = await load(egmFilePath, PGMLoader);
93
99
  console.log('Loading egm file completed!'); // eslint-disable-line
94
100
 
95
- this.sourceTileset = await load(inputUrl, I3SLoader, this.loaderOptions);
101
+ this.slpkFilesystem = await openSLPK(inputUrl);
102
+
103
+ this.sourceTileset = await loadFromArchive(
104
+ inputUrl,
105
+ I3SLoader,
106
+ {
107
+ ...this.loaderOptions,
108
+ // @ts-expect-error `isTileset` can be boolean of 'auto' but TS expects a string
109
+ i3s: {...this.loaderOptions.i3s, isTileset: true}
110
+ },
111
+ this.slpkFilesystem
112
+ );
96
113
 
97
114
  if (!this.sourceTileset) {
98
115
  return;
@@ -105,11 +122,28 @@ export default class Tiles3DConverter {
105
122
 
106
123
  this.tilesetPath = join(`${outputPath}`, `${tilesetName}`);
107
124
  this.attributeStorageInfo = this.sourceTileset.attributeStorageInfo;
125
+
126
+ await this.conversionDump.createDump(options as ConversionDumpOptions);
127
+ if (this.conversionDump.restored && this.options.inquirer) {
128
+ const result = await this.options.inquirer.prompt([
129
+ {
130
+ name: 'resumeConversion',
131
+ type: 'confirm',
132
+ message:
133
+ 'Dump file of the previous conversion exists, do you want to resume that conversion?'
134
+ }
135
+ ]);
136
+ if (!result.resumeConversion) {
137
+ this.conversionDump.reset();
138
+ }
139
+ }
108
140
  // Removing the tilesetPath needed to exclude erroneous files after conversion
109
- try {
110
- await removeDir(this.tilesetPath);
111
- } catch (e) {
112
- // do nothing
141
+ if (!this.conversionDump.restored) {
142
+ try {
143
+ await removeDir(this.tilesetPath);
144
+ } catch (e) {
145
+ // do nothing
146
+ }
113
147
  }
114
148
 
115
149
  const rootTile: Tiles3DTileJSON = {
@@ -125,8 +159,13 @@ export default class Tiles3DConverter {
125
159
 
126
160
  const tileset = transform({root: rootTile}, tilesetTemplate());
127
161
  await writeFile(this.tilesetPath, JSON.stringify(tileset), 'tileset.json');
162
+ await this.conversionDump.deleteDumpFile();
128
163
 
129
- this._finishConversion({slpk: false, outputPath, tilesetName});
164
+ await this._finishConversion({slpk: false, outputPath, tilesetName});
165
+
166
+ if (this.slpkFilesystem) {
167
+ this.slpkFilesystem.destroy();
168
+ }
130
169
 
131
170
  // Clean up worker pools
132
171
  const workerFarm = WorkerFarm.getWorkerFarm({});
@@ -148,7 +187,22 @@ export default class Tiles3DConverter {
148
187
  ): Promise<void> {
149
188
  const sourceChild = await this._loadChildNode(parentSourceNode, childNodeInfo);
150
189
  if (sourceChild.contentUrl) {
151
- const content = await loadI3SContent(this.sourceTileset, sourceChild, this.loaderOptions);
190
+ if (
191
+ this.conversionDump.restored &&
192
+ this.conversionDump.isFileConversionComplete(`${sourceChild.id}.b3dm`) &&
193
+ (sourceChild.obb || sourceChild.mbs)
194
+ ) {
195
+ const {child} = this._createChildAndBoundingVolume(sourceChild);
196
+ parentNode.children.push(child);
197
+ await this._addChildren(sourceChild, child, level + 1);
198
+ return;
199
+ }
200
+ const content = await loadI3SContent(
201
+ this.sourceTileset,
202
+ sourceChild,
203
+ this.loaderOptions,
204
+ this.slpkFilesystem
205
+ );
152
206
 
153
207
  if (!content) {
154
208
  await this._addChildren(sourceChild, parentNode, level + 1);
@@ -162,33 +216,24 @@ export default class Tiles3DConverter {
162
216
  featureAttributes = await this._loadChildAttributes(sourceChild, this.attributeStorageInfo);
163
217
  }
164
218
 
165
- if (!sourceChild.obb) {
166
- sourceChild.obb = createObbFromMbs(sourceChild.mbs);
167
- }
168
-
169
- const boundingVolume = {
170
- box: i3sObbTo3dTilesObb(sourceChild.obb, this.geoidHeightModel)
171
- };
172
- const child: Tiles3DTileJSON = {
173
- boundingVolume,
174
- geometricError: convertScreenThresholdToGeometricError(sourceChild),
175
- children: []
176
- };
219
+ const {child, boundingVolume} = this._createChildAndBoundingVolume(sourceChild);
177
220
 
178
221
  const i3sAttributesData: I3SAttributesData = {
179
222
  tileContent: content,
180
- box: boundingVolume.box,
223
+ box: boundingVolume.box || [],
181
224
  textureFormat: sourceChild.textureFormat
182
225
  };
183
226
 
184
227
  const b3dmConverter = new B3dmConverter();
185
228
  const b3dm = await b3dmConverter.convert(i3sAttributesData, featureAttributes);
186
229
 
187
- child.content = {
188
- uri: `${sourceChild.id}.b3dm`,
189
- boundingVolume
190
- };
230
+ await this.conversionDump.addNode(`${sourceChild.id}.b3dm`, sourceChild.id);
191
231
  await writeFile(this.tilesetPath, new Uint8Array(b3dm), `${sourceChild.id}.b3dm`);
232
+ await this.conversionDump.updateConvertedNodesDumpFile(
233
+ `${sourceChild.id}.b3dm`,
234
+ sourceChild.id,
235
+ true
236
+ );
192
237
  parentNode.children.push(child);
193
238
 
194
239
  await this._addChildren(sourceChild, child, level + 1);
@@ -235,20 +280,48 @@ export default class Tiles3DConverter {
235
280
  } else {
236
281
  const nodeUrl = this._relativeUrlToFullUrl(parentNode.url, childNodeInfo.href!);
237
282
  // load metadata
238
- const options = {
283
+ const options: I3SLoaderOptions = {
239
284
  i3s: {
240
285
  ...this.loaderOptions,
286
+ // @ts-expect-error
241
287
  isTileHeader: true,
242
288
  loadContent: false
243
289
  }
244
290
  };
245
291
 
246
292
  console.log(`Node conversion: ${nodeUrl}`); // eslint-disable-line no-console,no-undef
247
- header = await load(nodeUrl, I3SLoader, options);
293
+ header = await loadFromArchive(nodeUrl, I3SLoader, options, this.slpkFilesystem);
248
294
  }
249
295
  return header;
250
296
  }
251
297
 
298
+ /**
299
+ * Create child and child's boundingVolume for the converted node
300
+ * @param sourceChild
301
+ * @returns child and child's boundingVolume
302
+ */
303
+ private _createChildAndBoundingVolume(sourceChild: I3STileHeader): {
304
+ boundingVolume: Tile3DBoundingVolume;
305
+ child: Tiles3DTileJSON;
306
+ } {
307
+ if (!sourceChild.obb) {
308
+ sourceChild.obb = createObbFromMbs(sourceChild.mbs);
309
+ }
310
+ const boundingVolume: Tile3DBoundingVolume = {
311
+ box: i3sObbTo3dTilesObb(sourceChild.obb, this.geoidHeightModel)
312
+ };
313
+ const child: Tiles3DTileJSON = {
314
+ boundingVolume,
315
+ geometricError: convertScreenThresholdToGeometricError(sourceChild),
316
+ children: [],
317
+ content: {
318
+ uri: `${sourceChild.id}.b3dm`,
319
+ boundingVolume
320
+ }
321
+ };
322
+ return {boundingVolume, child};
323
+ }
324
+
252
325
  /**
253
326
  * Make an url of a resource from its relative url having the base url
254
327
  * @param baseUrl the base url. A resulting url will be related from this url
@@ -292,7 +365,7 @@ export default class Tiles3DConverter {
292
365
  attributeType: this._getAttributeType(attribute)
293
366
  };
294
367
 
295
- promises.push(load(inputUrl, I3SAttributeLoader, options));
368
+ promises.push(loadFromArchive(inputUrl, I3SAttributeLoader, options, this.slpkFilesystem));
296
369
  }
297
370
  const attributesList = await Promise.all(promises);
298
371
  this._replaceNestedArrays(attributesList);
@@ -1,36 +1,45 @@
1
- import {load} from '@loaders.gl/core';
1
+ import {LoaderWithParser, load} from '@loaders.gl/core';
2
2
  import {
3
3
  I3STileContent,
4
4
  I3STileHeader,
5
5
  I3STilesetHeader,
6
6
  I3SLoader,
7
- I3SLoaderOptions
7
+ I3SLoaderOptions,
8
+ parseSLPKArchive
8
9
  } from '@loaders.gl/i3s';
10
+ import {FileHandleFile} from '@loaders.gl/loader-utils';
11
+ import {ZipFileSystem} from '@loaders.gl/zip';
12
+
13
+ export type SLPKUrlParts = {slpkFileName: string; internalFileName: string};
9
14
 
10
15
  /**
11
16
  * Load I3S node content
12
17
  * @param sourceTileset - source layer JSON
13
18
  * @param sourceTile - source I3S node metadata
14
19
  * @param tilesetLoadOptions - load options for Tiles3DLoader
20
+ * @param slpkFilesystem - loaded instance of ZipFileSystem for local convertion from SLPK file
15
21
  * @returns - 3DTiles tile content or null
16
22
  */
17
23
  export const loadI3SContent = async (
18
24
  sourceTileset: I3STilesetHeader | null,
19
25
  sourceTile: I3STileHeader,
20
- tilesetLoadOptions: I3SLoaderOptions
26
+ tilesetLoadOptions: I3SLoaderOptions,
27
+ slpkFilesystem: ZipFileSystem | null
21
28
  ): Promise<I3STileContent | null> => {
22
29
  if (!sourceTileset || !sourceTile.contentUrl) {
23
30
  return null;
24
31
  }
25
32
 
26
- const loadOptions = {
33
+ const loadOptions: I3SLoaderOptions = {
27
34
  ...tilesetLoadOptions,
28
35
  i3s: {
29
36
  ...tilesetLoadOptions.i3s,
37
+ // @ts-expect-error
30
38
  isTileset: false,
39
+ // @ts-expect-error
31
40
  isTileHeader: false,
32
41
  _tileOptions: {
33
- attributeUrls: sourceTile.attributeUrls,
42
+ attributeUrls: sourceTile.attributeUrls || [],
34
43
  textureUrl: sourceTile.textureUrl,
35
44
  textureFormat: sourceTile.textureFormat,
36
45
  textureLoaderOptions: sourceTile.textureLoaderOptions,
@@ -40,13 +49,83 @@ export const loadI3SContent = async (
40
49
  },
41
50
  _tilesetOptions: {
42
51
  store: sourceTileset.store,
52
+ // @ts-expect-error
43
53
  attributeStorageInfo: sourceTileset.attributeStorageInfo,
54
+ // @ts-expect-error
44
55
  fields: sourceTileset.fields
45
56
  }
46
57
  }
47
58
  };
48
- const tileContent = await load(sourceTile.contentUrl, I3SLoader, loadOptions);
59
+ const tileContent = await loadFromArchive(
60
+ sourceTile.contentUrl,
61
+ I3SLoader,
62
+ loadOptions,
63
+ slpkFilesystem
64
+ );
49
65
 
50
- // @ts-expect-error
51
66
  return tileContent;
52
67
  };
68
+
69
+ /**
70
+ * Load local SLPK file to ZipFileSystem instance
71
+ * @param url - path to SLPK file
72
+ * @returns instance of ZipFileSystem or null if url is not an SLPK file
73
+ */
74
+ export async function openSLPK(url: string): Promise<ZipFileSystem | null> {
75
+ const slpkUrlParts = url.split('.slpk');
76
+ const {slpkFileName} = getSlpkUrlParts(url) || {};
77
+ if (slpkFileName) {
78
+ const slpkFileName = `${slpkUrlParts[0]}.slpk`;
79
+ const fileProvider = new FileHandleFile(slpkFileName);
80
+ const archive = await parseSLPKArchive(fileProvider, undefined, slpkFileName);
81
+ const fileSystem = new ZipFileSystem(archive);
82
+ return fileSystem;
83
+ }
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Load a resource with load options and .3tz format support
89
+ * @param url - resource URL
90
+ * @param loader - loader to parse data (Tiles3DLoader / CesiumIonLoader)
91
+ * @param loadOptions - i3s loader options
92
+ * @returns i3s resource
93
+ */
94
+ export async function loadFromArchive(
95
+ url: string,
96
+ loader: LoaderWithParser,
97
+ loadOptions: I3SLoaderOptions,
98
+ fileSystem: ZipFileSystem | null
99
+ ) {
100
+ const slpkUrlParts = getSlpkUrlParts(url);
101
+ if (fileSystem !== null && slpkUrlParts !== null) {
102
+ const {internalFileName} = slpkUrlParts;
103
+ const content = await load(internalFileName, loader, {
104
+ ...loadOptions,
105
+ fetch: fileSystem.fetch.bind(fileSystem)
106
+ });
107
+ return content;
108
+ }
109
+ return await load(url, loader, loadOptions);
110
+ }
111
+
112
+ /**
113
+ * Extract slpk file path and internal from the url
114
+ * For example, for `./path/to/file.slpk/nodes/0` it returns
115
+ * {"slpkFileName": "./path/to/file.slpk", "internalFileName": "/nodes/0" }
116
+ * @param url full internal file path
117
+ * @returns object with internal slpk file parts
118
+ */
119
+ function getSlpkUrlParts(url: string): SLPKUrlParts | null {
120
+ const slpkUrlParts = url.split('.slpk');
121
+ let result: SLPKUrlParts | null;
122
+ // Not '.slpk'. The file will be loaded with global fetch function
123
+ if (slpkUrlParts.length === 1) {
124
+ result = null;
125
+ } else if (slpkUrlParts.length === 2) {
126
+ result = {slpkFileName: `${slpkUrlParts[0]}.slpk`, internalFileName: slpkUrlParts[1].slice(1)};
127
+ } else {
128
+ throw new Error('Unexpected URL format');
129
+ }
130
+ return result;
131
+ }
@@ -210,7 +210,8 @@ async function convert(options: ValidatedTileConversionOptions) {
210
210
  outputPath: options.output,
211
211
  tilesetName: options.name,
212
212
  maxDepth: options.maxDepth,
213
- egmFilePath: options.egm
213
+ egmFilePath: options.egm,
214
+ inquirer
214
215
  });
215
216
  break;
216
217
  case TILESET_TYPE._3DTILES:
@@ -9,6 +9,12 @@ import type {
9
9
 
10
10
  import {AttributeType} from '../types';
11
11
 
12
+ export type AttributeMetadataInfoObject = {
13
+ attributeStorageInfo: AttributeStorageInfo[];
14
+ fields: Field[];
15
+ popupInfo: PopupInfo | undefined;
16
+ };
17
+
12
18
  export class AttributeMetadataInfo {
13
19
  private _attributeStorageInfo: AttributeStorageInfo[];
14
20
  private _fields: Field[];
@@ -95,6 +101,16 @@ export class AttributeMetadataInfo {
95
101
  }
96
102
  }
97
103
 
104
+ /**
105
+ * Set AttributeMetadataInfo from object
106
+ * @param object - object with AttributeMetadataInfo props
107
+ */
108
+ fromObject(object: AttributeMetadataInfoObject) {
109
+ this._attributeStorageInfo = object.attributeStorageInfo;
110
+ this._fields = object.fields;
111
+ this._popupInfo = object.popupInfo;
112
+ }
113
+
98
114
  /**
99
115
  * Generates storage attribute for map segmentation.
100
116
  * @param attributeIndex - order index of attribute (f_0, f_1 ...).
@@ -4,8 +4,17 @@ import type {
4
4
  Tiles3DTileJSONPostprocessed,
5
5
  Tiles3DTilesetJSONPostprocessed
6
6
  } from '@loaders.gl/3d-tiles';
7
- import {Tiles3DArchiveFileSystem} from '@loaders.gl/3d-tiles';
7
+ import {Tiles3DArchive} from '@loaders.gl/3d-tiles';
8
8
  import {LoaderWithParser, load} from '@loaders.gl/core';
9
+ import {FileHandleFile, FileProvider} from '@loaders.gl/loader-utils';
10
+ import {
11
+ CD_HEADER_SIGNATURE,
12
+ ZipFileSystem,
13
+ parseHashTable,
14
+ parseZipCDFileHeader,
15
+ parseZipLocalFileHeader,
16
+ searchFromTheEnd
17
+ } from '@loaders.gl/zip';
9
18
 
10
19
  /**
11
20
  * Load nested 3DTiles tileset. If the sourceTile is not nested tileset - do nothing
@@ -104,7 +113,10 @@ export async function loadFromArchive(
104
113
  }
105
114
  if (filename) {
106
115
  const tz3Path = `${tz3UrlParts[0]}.3tz`;
107
- const fileSystem = new Tiles3DArchiveFileSystem(tz3Path);
116
+ const fileProvider = new FileHandleFile(tz3Path);
117
+ const hashTable = await loadHashTable(fileProvider);
118
+ const archive = new Tiles3DArchive(fileProvider, hashTable, tz3Path);
119
+ const fileSystem = new ZipFileSystem(archive);
108
120
  const content = await load(filename, loader, {
109
121
  ...loadOptions,
110
122
  fetch: fileSystem.fetch.bind(fileSystem)
@@ -123,3 +135,41 @@ export async function loadFromArchive(
123
135
  export function isNestedTileset(tile: Tiles3DTileJSONPostprocessed) {
124
136
  return tile?.type === 'json' || tile?.type === '3tz';
125
137
  }
138
+
139
+ /**
140
+ * Load hash file from 3TZ
141
+ * @param fileProvider - binary reader of 3TZ
142
+ * @returns hash table of the 3TZ file content or undefined if the hash file is not presented inside
143
+ */
144
+ async function loadHashTable(
145
+ fileProvider: FileProvider
146
+ ): Promise<undefined | Record<string, bigint>> {
147
+ let hashTable: undefined | Record<string, bigint>;
148
+
149
+ const hashCDOffset = await searchFromTheEnd(fileProvider, CD_HEADER_SIGNATURE);
150
+
151
+ const cdFileHeader = await parseZipCDFileHeader(hashCDOffset, fileProvider);
152
+
153
+ // '@3dtilesIndex1@' is index file that must be the last in the archive. It allows
154
+ // to improve load and read performance when the archive contains a very large number
155
+ // of files.
156
+ if (cdFileHeader?.fileName === '@3dtilesIndex1@') {
157
+ const localFileHeader = await parseZipLocalFileHeader(
158
+ cdFileHeader.localHeaderOffset,
159
+ fileProvider
160
+ );
161
+ if (!localFileHeader) {
162
+ throw new Error('corrupted 3tz');
163
+ }
164
+
165
+ const fileDataOffset = localFileHeader.fileDataOffset;
166
+ const hashFile = await fileProvider.slice(
167
+ fileDataOffset,
168
+ fileDataOffset + localFileHeader.compressedSize
169
+ );
170
+
171
+ hashTable = parseHashTable(hashFile);
172
+ }
173
+
174
+ return hashTable;
175
+ }
@@ -12,6 +12,7 @@ import {openJson, writeFile, writeFileForSlpk} from '../../lib/utils/file-utils'
12
12
  import I3SConverter from '../i3s-converter';
13
13
  import {NODE as nodeTemplate} from '../json-templates/node';
14
14
  import {I3SConvertedResources} from '../types';
15
+ import {DumpMetadata} from '../../lib/utils/conversion-dump';
15
16
 
16
17
  /**
17
18
  * Wrapper for https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DNodeIndexDocument.cmn.md data
@@ -287,9 +288,8 @@ export class NodeIndexDocument {
287
288
  boundingVolumes: BoundingVolumes,
288
289
  lodSelection: LodSelection[],
289
290
  nodeInPage: NodeInPage,
290
- resources: I3SConvertedResources
291
+ resources: I3SConvertedResources | DumpMetadata
291
292
  ): Promise<Node3DIndexDocument> {
292
- const {texture, attributes} = resources;
293
293
  const nodeId = nodeInPage.index!;
294
294
  const parentNodeData = await parentNode.load();
295
295
  const nodeData = {
@@ -313,19 +313,29 @@ export class NodeIndexDocument {
313
313
  node.geometryData = [{href: './geometries/0'}];
314
314
  node.sharedResource = {href: './shared'};
315
315
 
316
- if (texture) {
316
+ if (
317
+ ('texture' in resources && resources.texture) ||
318
+ ('texelCountHint' in resources && resources.texelCountHint)
319
+ ) {
317
320
  node.textureData = [{href: './textures/0'}, {href: './textures/1'}];
318
321
  }
319
322
 
320
323
  if (
321
- attributes &&
322
- attributes.length &&
323
- parentNode.converter.layers0?.attributeStorageInfo?.length
324
+ ('attributes' in resources &&
325
+ resources.attributes &&
326
+ resources.attributes.length &&
327
+ parentNode.converter.layers0?.attributeStorageInfo?.length) ||
328
+ ('attributesCount' in resources &&
329
+ resources.attributesCount &&
330
+ parentNode.converter.layers0?.attributeStorageInfo?.length)
324
331
  ) {
332
+ const attributesLength =
333
+ ('attributes' in resources ? resources.attributes?.length : resources.attributesCount) ||
334
+ 0;
325
335
  node.attributeData = [];
326
336
  const minimumLength =
327
- attributes.length < parentNode.converter.layers0.attributeStorageInfo.length
328
- ? attributes.length
337
+ attributesLength < parentNode.converter.layers0.attributeStorageInfo.length
338
+ ? attributesLength
329
339
  : parentNode.converter.layers0.attributeStorageInfo.length;
330
340
  for (let index = 0; index < minimumLength; index++) {
331
341
  const folderName = parentNode.converter.layers0.attributeStorageInfo[index].key;