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

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/index.cjs CHANGED
@@ -4676,7 +4676,6 @@ var I3SConverter = class {
4676
4676
  } else {
4677
4677
  console.log("Feature metadata classes have not been found");
4678
4678
  }
4679
- console.log(`------------------------------------------------`);
4680
4679
  if (!meshTopologyTypes.has("TRIANGLES" /* TRIANGLES */) && !meshTopologyTypes.has("TRIANGLE_STRIP" /* TRIANGLE_STRIP */)) {
4681
4680
  console.log(
4682
4681
  "The tileset is of unsupported mesh topology types. The conversion will be interrupted."
@@ -4684,6 +4683,7 @@ var I3SConverter = class {
4684
4683
  console.log(`------------------------------------------------`);
4685
4684
  return false;
4686
4685
  }
4686
+ console.log(`------------------------------------------------`);
4687
4687
  return true;
4688
4688
  }
4689
4689
  /**
@@ -6191,6 +6191,20 @@ function getSlpkUrlParts(url) {
6191
6191
  }
6192
6192
  return result;
6193
6193
  }
6194
+ async function getNodeCount(fileSystem) {
6195
+ if (!(fileSystem == null ? void 0 : fileSystem.fileProvider)) {
6196
+ return 0;
6197
+ }
6198
+ let count = 0;
6199
+ const filesIterator = (0, import_zip3.makeZipCDHeaderIterator)(fileSystem.fileProvider);
6200
+ for await (const file of filesIterator) {
6201
+ const filename = file.fileName;
6202
+ if (filename.indexOf("3dNodeIndexDocument.json.gz") >= 0) {
6203
+ count++;
6204
+ }
6205
+ }
6206
+ return count;
6207
+ }
6194
6208
 
6195
6209
  // src/3d-tiles-converter/3d-tiles-converter.ts
6196
6210
  var I3S = "I3S";
@@ -6218,6 +6232,7 @@ var Tiles3DConverter = class {
6218
6232
  this.attributeStorageInfo = null;
6219
6233
  this.workerSource = {};
6220
6234
  this.conversionDump = new ConversionDump();
6235
+ this.progress = new Progress();
6221
6236
  }
6222
6237
  /**
6223
6238
  * Convert i3s format data to 3dTiles
@@ -6234,13 +6249,21 @@ var Tiles3DConverter = class {
6234
6249
  console.log(BROWSER_ERROR_MESSAGE);
6235
6250
  return BROWSER_ERROR_MESSAGE;
6236
6251
  }
6237
- const { inputUrl, outputPath, tilesetName, maxDepth, egmFilePath, inquirer } = options;
6252
+ const { inputUrl, outputPath, tilesetName, maxDepth, egmFilePath, inquirer, analyze } = options;
6238
6253
  this.conversionStartTime = import_process5.default.hrtime();
6239
6254
  this.options = { maxDepth, inquirer };
6240
6255
  console.log("Loading egm file...");
6241
6256
  this.geoidHeightModel = await (0, import_core15.load)(egmFilePath, PGMLoader);
6242
6257
  console.log("Loading egm file completed!");
6243
6258
  this.slpkFilesystem = await openSLPK(inputUrl);
6259
+ let preprocessResult = true;
6260
+ if (analyze || this.slpkFilesystem) {
6261
+ preprocessResult = await this.preprocessConversion();
6262
+ if (!preprocessResult || analyze) {
6263
+ return;
6264
+ }
6265
+ }
6266
+ this.progress.startMonitoring();
6244
6267
  this.sourceTileset = await loadFromArchive2(
6245
6268
  inputUrl,
6246
6269
  import_i3s2.I3SLoader,
@@ -6291,6 +6314,7 @@ var Tiles3DConverter = class {
6291
6314
  const tileset = (0, import_json_map_transform10.default)({ root: rootTile }, TILESET());
6292
6315
  await writeFile(this.tilesetPath, JSON.stringify(tileset), "tileset.json");
6293
6316
  await this.conversionDump.deleteDumpFile();
6317
+ this.progress.stopMonitoring();
6294
6318
  await this._finishConversion({ slpk: false, outputPath, tilesetName });
6295
6319
  if (this.slpkFilesystem) {
6296
6320
  this.slpkFilesystem.destroy();
@@ -6298,6 +6322,29 @@ var Tiles3DConverter = class {
6298
6322
  const workerFarm = import_worker_utils2.WorkerFarm.getWorkerFarm({});
6299
6323
  workerFarm.destroy();
6300
6324
  }
6325
+ /**
6326
+ * Preprocess stage of the tile converter. Calculate number of nodes
6327
+ * @returns true - the conversion is possible, false - the tileset's content is not supported
6328
+ */
6329
+ async preprocessConversion() {
6330
+ console.log(`Analyze source layer`);
6331
+ const nodesCount = await getNodeCount(this.slpkFilesystem);
6332
+ this.progress.stepsTotal = nodesCount;
6333
+ console.log(`------------------------------------------------`);
6334
+ console.log(`Preprocess results:`);
6335
+ if (this.slpkFilesystem) {
6336
+ console.log(`Node count: ${nodesCount}`);
6337
+ if (nodesCount === 0) {
6338
+ console.log("Node count is 0. The conversion will be interrupted.");
6339
+ console.log(`------------------------------------------------`);
6340
+ return false;
6341
+ }
6342
+ } else {
6343
+ console.log(`Node count cannot be calculated for the remote dataset`);
6344
+ }
6345
+ console.log(`------------------------------------------------`);
6346
+ return true;
6347
+ }
6301
6348
  /**
6302
6349
  * Convert particular I3S Node
6303
6350
  * @param parentSourceNode the parent node tile object (@loaders.gl/tiles/Tile3D)
@@ -6306,6 +6353,7 @@ var Tiles3DConverter = class {
6306
6353
  * @param childNodeInfo child node to convert
6307
6354
  */
6308
6355
  async convertChildNode(parentSourceNode, parentNode, level, childNodeInfo) {
6356
+ let nextParentNode = parentNode;
6309
6357
  const sourceChild = await this._loadChildNode(parentSourceNode, childNodeInfo);
6310
6358
  if (sourceChild.contentUrl) {
6311
6359
  if (this.conversionDump.restored && this.conversionDump.isFileConversionComplete(`${sourceChild.id}.b3dm`) && (sourceChild.obb || sourceChild.mbs)) {
@@ -6345,10 +6393,18 @@ var Tiles3DConverter = class {
6345
6393
  true
6346
6394
  );
6347
6395
  parentNode.children.push(child);
6348
- await this._addChildren(sourceChild, child, level + 1);
6349
- } else {
6350
- await this._addChildren(sourceChild, parentNode, level + 1);
6351
- }
6396
+ nextParentNode = child;
6397
+ }
6398
+ this.progress.stepsDone += 1;
6399
+ let timeRemainingString = "Calculating time left...";
6400
+ const timeRemaining = this.progress.getTimeRemainingString();
6401
+ if (timeRemaining) {
6402
+ timeRemainingString = `${timeRemaining} left`;
6403
+ }
6404
+ const percentString = this.progress.getPercentString();
6405
+ const progressString = percentString ? ` ${percentString}%, ${timeRemainingString}` : "";
6406
+ console.log(`[converted${progressString}]: ${childNodeInfo.id}`);
6407
+ await this._addChildren(sourceChild, nextParentNode, level + 1);
6352
6408
  }
6353
6409
  /**
6354
6410
  * The recursive function of traversal of a nodes tree
@@ -1,5 +1,5 @@
1
1
  import { Geoid, parsePGM } from '@math.gl/geoid';
2
- const VERSION = typeof "4.2.0-alpha.2" !== 'undefined' ? "4.2.0-alpha.2" : 'latest';
2
+ const VERSION = typeof "4.2.0-alpha.4" !== 'undefined' ? "4.2.0-alpha.4" : 'latest';
3
3
  export { Geoid };
4
4
  export const PGMLoader = {
5
5
  name: 'PGM - Netpbm grayscale image format',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loaders.gl/tile-converter",
3
- "version": "4.2.0-alpha.2",
3
+ "version": "4.2.0-alpha.4",
4
4
  "description": "Converter",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -52,18 +52,18 @@
52
52
  "build-i3s-server-bundle": "esbuild src/i3s-server/bin/www.ts --outfile=dist/i3s-server/bin/i3s-server.min.cjs --platform=node --target=esnext,node14 --minify --bundle --define:__VERSION__=\\\"$npm_package_version\\\""
53
53
  },
54
54
  "dependencies": {
55
- "@loaders.gl/3d-tiles": "4.2.0-alpha.2",
56
- "@loaders.gl/crypto": "4.2.0-alpha.2",
57
- "@loaders.gl/draco": "4.2.0-alpha.2",
58
- "@loaders.gl/gltf": "4.2.0-alpha.2",
59
- "@loaders.gl/i3s": "4.2.0-alpha.2",
60
- "@loaders.gl/images": "4.2.0-alpha.2",
61
- "@loaders.gl/loader-utils": "4.2.0-alpha.2",
62
- "@loaders.gl/polyfills": "4.2.0-alpha.2",
63
- "@loaders.gl/textures": "4.2.0-alpha.2",
64
- "@loaders.gl/tiles": "4.2.0-alpha.2",
65
- "@loaders.gl/worker-utils": "4.2.0-alpha.2",
66
- "@loaders.gl/zip": "4.2.0-alpha.2",
55
+ "@loaders.gl/3d-tiles": "4.2.0-alpha.4",
56
+ "@loaders.gl/crypto": "4.2.0-alpha.4",
57
+ "@loaders.gl/draco": "4.2.0-alpha.4",
58
+ "@loaders.gl/gltf": "4.2.0-alpha.4",
59
+ "@loaders.gl/i3s": "4.2.0-alpha.4",
60
+ "@loaders.gl/images": "4.2.0-alpha.4",
61
+ "@loaders.gl/loader-utils": "4.2.0-alpha.4",
62
+ "@loaders.gl/polyfills": "4.2.0-alpha.4",
63
+ "@loaders.gl/textures": "4.2.0-alpha.4",
64
+ "@loaders.gl/tiles": "4.2.0-alpha.4",
65
+ "@loaders.gl/worker-utils": "4.2.0-alpha.4",
66
+ "@loaders.gl/zip": "4.2.0-alpha.4",
67
67
  "@math.gl/core": "^4.0.0",
68
68
  "@math.gl/culling": "^4.0.0",
69
69
  "@math.gl/geoid": "^4.0.0",
@@ -88,7 +88,7 @@
88
88
  "join-images": "^1.1.3",
89
89
  "sharp": "^0.31.3"
90
90
  },
91
- "gitHead": "d66a6a4626ea84c5f2cad5fa5cf7ebb6943c57c8",
91
+ "gitHead": "6c52dee5c3f005648a394cc4aee7fc37005c8e83",
92
92
  "devDependencies": {
93
93
  "@types/express": "^4.17.17",
94
94
  "@types/node": "^20.4.2"
@@ -24,10 +24,11 @@ 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 {loadFromArchive, loadI3SContent, openSLPK} from './helpers/load-i3s';
27
+ import {getNodeCount, loadFromArchive, loadI3SContent, openSLPK} from './helpers/load-i3s';
28
28
  import {I3SLoaderOptions} from '@loaders.gl/i3s/src/i3s-loader';
29
29
  import {ZipFileSystem} from '../../../zip/src';
30
30
  import {ConversionDump, ConversionDumpOptions} from '../lib/utils/conversion-dump';
31
+ import {Progress} from '../i3s-converter/helpers/progress';
31
32
 
32
33
  const I3S = 'I3S';
33
34
 
@@ -56,6 +57,7 @@ export default class Tiles3DConverter {
56
57
  }
57
58
  };
58
59
  conversionDump: ConversionDump;
60
+ progress: Progress;
59
61
 
60
62
  constructor() {
61
63
  this.options = {};
@@ -67,6 +69,7 @@ export default class Tiles3DConverter {
67
69
  this.attributeStorageInfo = null;
68
70
  this.workerSource = {};
69
71
  this.conversionDump = new ConversionDump();
72
+ this.progress = new Progress();
70
73
  }
71
74
 
72
75
  /**
@@ -85,12 +88,13 @@ export default class Tiles3DConverter {
85
88
  maxDepth?: number;
86
89
  egmFilePath: string;
87
90
  inquirer?: Promise<unknown>;
91
+ analyze?: boolean;
88
92
  }): Promise<any> {
89
93
  if (isBrowser) {
90
94
  console.log(BROWSER_ERROR_MESSAGE);
91
95
  return BROWSER_ERROR_MESSAGE;
92
96
  }
93
- const {inputUrl, outputPath, tilesetName, maxDepth, egmFilePath, inquirer} = options;
97
+ const {inputUrl, outputPath, tilesetName, maxDepth, egmFilePath, inquirer, analyze} = options;
94
98
  this.conversionStartTime = process.hrtime();
95
99
  this.options = {maxDepth, inquirer};
96
100
 
@@ -100,6 +104,16 @@ export default class Tiles3DConverter {
100
104
 
101
105
  this.slpkFilesystem = await openSLPK(inputUrl);
102
106
 
107
+ let preprocessResult = true;
108
+ if (analyze || this.slpkFilesystem) {
109
+ preprocessResult = await this.preprocessConversion();
110
+ if (!preprocessResult || analyze) {
111
+ return;
112
+ }
113
+ }
114
+
115
+ this.progress.startMonitoring();
116
+
103
117
  this.sourceTileset = await loadFromArchive(
104
118
  inputUrl,
105
119
  I3SLoader,
@@ -161,6 +175,8 @@ export default class Tiles3DConverter {
161
175
  await writeFile(this.tilesetPath, JSON.stringify(tileset), 'tileset.json');
162
176
  await this.conversionDump.deleteDumpFile();
163
177
 
178
+ this.progress.stopMonitoring();
179
+
164
180
  await this._finishConversion({slpk: false, outputPath, tilesetName});
165
181
 
166
182
  if (this.slpkFilesystem) {
@@ -172,6 +188,32 @@ export default class Tiles3DConverter {
172
188
  workerFarm.destroy();
173
189
  }
174
190
 
191
+ /**
192
+ * Preprocess stage of the tile converter. Calculate number of nodes
193
+ * @returns true - the conversion is possible, false - the tileset's content is not supported
194
+ */
195
+ private async preprocessConversion(): Promise<boolean> {
196
+ console.log(`Analyze source layer`);
197
+ const nodesCount = await getNodeCount(this.slpkFilesystem);
198
+ this.progress.stepsTotal = nodesCount;
199
+
200
+ console.log(`------------------------------------------------`);
201
+ console.log(`Preprocess results:`);
202
+ if (this.slpkFilesystem) {
203
+ console.log(`Node count: ${nodesCount}`);
204
+ if (nodesCount === 0) {
205
+ console.log('Node count is 0. The conversion will be interrupted.');
206
+ console.log(`------------------------------------------------`);
207
+ return false;
208
+ }
209
+ } else {
210
+ console.log(`Node count cannot be calculated for the remote dataset`);
211
+ }
212
+
213
+ console.log(`------------------------------------------------`);
214
+ return true;
215
+ }
216
+
175
217
  /**
176
218
  * Convert particular I3S Node
177
219
  * @param parentSourceNode the parent node tile object (@loaders.gl/tiles/Tile3D)
@@ -185,6 +227,7 @@ export default class Tiles3DConverter {
185
227
  level: number,
186
228
  childNodeInfo: NodeReference
187
229
  ): Promise<void> {
230
+ let nextParentNode = parentNode;
188
231
  const sourceChild = await this._loadChildNode(parentSourceNode, childNodeInfo);
189
232
  if (sourceChild.contentUrl) {
190
233
  if (
@@ -235,11 +278,20 @@ export default class Tiles3DConverter {
235
278
  true
236
279
  );
237
280
  parentNode.children.push(child);
281
+ nextParentNode = child;
282
+ }
238
283
 
239
- await this._addChildren(sourceChild, child, level + 1);
240
- } else {
241
- await this._addChildren(sourceChild, parentNode, level + 1);
284
+ this.progress.stepsDone += 1;
285
+ let timeRemainingString = 'Calculating time left...';
286
+ const timeRemaining = this.progress.getTimeRemainingString();
287
+ if (timeRemaining) {
288
+ timeRemainingString = `${timeRemaining} left`;
242
289
  }
290
+ const percentString = this.progress.getPercentString();
291
+ const progressString = percentString ? ` ${percentString}%, ${timeRemainingString}` : '';
292
+ console.log(`[converted${progressString}]: ${childNodeInfo.id}`); // eslint-disable-line
293
+
294
+ await this._addChildren(sourceChild, nextParentNode, level + 1);
243
295
  }
244
296
 
245
297
  /**
@@ -8,7 +8,7 @@ import {
8
8
  parseSLPKArchive
9
9
  } from '@loaders.gl/i3s';
10
10
  import {FileHandleFile} from '@loaders.gl/loader-utils';
11
- import {ZipFileSystem} from '@loaders.gl/zip';
11
+ import {ZipFileSystem, makeZipCDHeaderIterator} from '@loaders.gl/zip';
12
12
 
13
13
  export type SLPKUrlParts = {slpkFileName: string; internalFileName: string};
14
14
 
@@ -129,3 +129,23 @@ function getSlpkUrlParts(url: string): SLPKUrlParts | null {
129
129
  }
130
130
  return result;
131
131
  }
132
+
133
+ /**
134
+ * Get nodes count inside SLPK
135
+ * @param fileSystem - file system of SLPK
136
+ * @returns number of nodes
137
+ */
138
+ export async function getNodeCount(fileSystem: ZipFileSystem | null): Promise<number> {
139
+ if (!fileSystem?.fileProvider) {
140
+ return 0;
141
+ }
142
+ let count = 0;
143
+ const filesIterator = makeZipCDHeaderIterator(fileSystem.fileProvider);
144
+ for await (const file of filesIterator) {
145
+ const filename = file.fileName;
146
+ if (filename.indexOf('3dNodeIndexDocument.json.gz') >= 0) {
147
+ count++;
148
+ }
149
+ }
150
+ return count;
151
+ }
@@ -59,6 +59,8 @@ type TileConversionOptions = {
59
59
  metadataClass?: string;
60
60
  /** With this options the tileset content will be analyzed without conversion */
61
61
  analyze?: boolean;
62
+ /** Skip all prompts that stop conversion and wait for a user input */
63
+ quiet?: boolean;
62
64
  };
63
65
 
64
66
  /* During validation we check that particular options are defined so they can't be undefined */
@@ -100,36 +102,40 @@ async function main() {
100
102
  if (options.addHash) {
101
103
  const validatedOptions = validateOptions(options, true);
102
104
  let finalPath = validatedOptions.tileset;
103
- if (validatedOptions.output === 'data') {
104
- const nameWithoutExt = validatedOptions.tileset.substring(
105
- 0,
106
- validatedOptions.tileset.length - 5
107
- );
108
105
 
109
- const result = await inquirer.prompt<{isNewFileRequired: boolean}>([
110
- {
111
- name: 'isNewFileRequired',
112
- type: 'list',
113
- message: 'What would you like to do?',
114
- choices: [
115
- {
116
- name: 'Add hash file to the current SLPK file',
117
- value: false
118
- },
119
- {
120
- name: `Create a new file ${nameWithoutExt}-hash.slpk with hash file inside`,
121
- value: true
122
- }
123
- ]
124
- }
125
- ]);
106
+ if (!options.quiet) {
107
+ if (validatedOptions.output === 'data') {
108
+ const nameWithoutExt = validatedOptions.tileset.substring(
109
+ 0,
110
+ validatedOptions.tileset.length - 5
111
+ );
112
+
113
+ const result = await inquirer.prompt<{isNewFileRequired: boolean}>([
114
+ {
115
+ name: 'isNewFileRequired',
116
+ type: 'list',
117
+ message: 'What would you like to do?',
118
+ choices: [
119
+ {
120
+ name: 'Add hash file to the current SLPK file',
121
+ value: false
122
+ },
123
+ {
124
+ name: `Create a new file ${nameWithoutExt}-hash.slpk with hash file inside`,
125
+ value: true
126
+ }
127
+ ]
128
+ }
129
+ ]);
126
130
 
127
- if (result.isNewFileRequired) {
128
- finalPath = `${nameWithoutExt}-hash.slpk`;
131
+ if (result.isNewFileRequired) {
132
+ finalPath = `${nameWithoutExt}-hash.slpk`;
133
+ }
134
+ } else {
135
+ finalPath = validatedOptions.output;
129
136
  }
130
- } else {
131
- finalPath = validatedOptions.output;
132
137
  }
138
+
133
139
  if (finalPath !== validatedOptions.tileset) {
134
140
  await copyFile(validatedOptions.tileset, finalPath);
135
141
  }
@@ -190,6 +196,9 @@ function printHelp(): void {
190
196
  '--metadata-class [One of the list of feature metadata classes, detected by converter on "analyze" stage, default: not set]'
191
197
  );
192
198
  console.log('--validate [Enable validation]');
199
+ console.log(
200
+ '--quiet [Skip all prompts that stop conversion and wait for a user input: default: false]'
201
+ );
193
202
  process.exit(0); // eslint-disable-line
194
203
  }
195
204
 
@@ -211,7 +220,8 @@ async function convert(options: ValidatedTileConversionOptions) {
211
220
  tilesetName: options.name,
212
221
  maxDepth: options.maxDepth,
213
222
  egmFilePath: options.egm,
214
- inquirer
223
+ analyze: options.analyze,
224
+ inquirer: options.quiet ? undefined : inquirer
215
225
  });
216
226
  break;
217
227
  case TILESET_TYPE._3DTILES:
@@ -233,7 +243,7 @@ async function convert(options: ValidatedTileConversionOptions) {
233
243
  instantNodeWriting: options.instantNodeWriting,
234
244
  metadataClass: options.metadataClass,
235
245
  analyze: options.analyze,
236
- inquirer
246
+ inquirer: options.quiet ? undefined : inquirer
237
247
  });
238
248
  break;
239
249
  default:
@@ -308,7 +318,8 @@ function parseOptions(args: string[]): TileConversionOptions {
308
318
  generateBoundingVolumes: false,
309
319
  validate: false,
310
320
  slpk: false,
311
- addHash: false
321
+ addHash: false,
322
+ quiet: false
312
323
  };
313
324
 
314
325
  // eslint-disable-next-line complexity
@@ -369,6 +380,9 @@ function parseOptions(args: string[]): TileConversionOptions {
369
380
  case '--analyze':
370
381
  opts.analyze = getBooleanValue(index, args);
371
382
  break;
383
+ case '--quiet':
384
+ opts.quiet = getBooleanValue(index, args);
385
+ break;
372
386
  case '--metadata-class':
373
387
  opts.metadataClass = getStringValue(index, args);
374
388
  break;
@@ -322,7 +322,6 @@ export default class I3SConverter {
322
322
  console.log('Feature metadata classes have not been found');
323
323
  }
324
324
 
325
- console.log(`------------------------------------------------`);
326
325
  if (
327
326
  !meshTopologyTypes.has(GLTFPrimitiveModeString.TRIANGLES) &&
328
327
  !meshTopologyTypes.has(GLTFPrimitiveModeString.TRIANGLE_STRIP)
@@ -334,6 +333,7 @@ export default class I3SConverter {
334
333
  return false;
335
334
  }
336
335
 
336
+ console.log(`------------------------------------------------`);
337
337
  return true;
338
338
  }
339
339