@loaders.gl/tile-converter 4.0.2 → 4.0.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.
@@ -0,0 +1,166 @@
1
+ import process from 'process';
2
+ import {timeConverter} from '../../lib/utils/statistic-utills';
3
+
4
+ /** Defines a threshold that is used to check if the process velocity can be consifered trust. */
5
+ const THRESHOLD_DEFAULT = 0.2;
6
+
7
+ /**
8
+ * Implements methods to keep track on the progress of a long process.
9
+ */
10
+ export class Progress {
11
+ /** Total amount of work, e.g. number of files to save or number of bytes to send */
12
+ private _stepsTotal: number = 0;
13
+ /** Amount of work already done */
14
+ private _stepsDone: number = 0;
15
+ /** Time in milli-seconds when the process started */
16
+ private startTime: number = 0;
17
+ /** Time in milli-seconds when the process stopped */
18
+ private stopTime: number = 0;
19
+ /** Time in milli-seconds when stepsDone was updated */
20
+ private timeOfUpdatingStepsDone: number = 0;
21
+ /** Time in milli-seconds spent for performing one step*/
22
+ private milliSecForOneStep: number = 0;
23
+ private trust: boolean = false;
24
+ /**
25
+ * The number of digits to appear after decimal point in the string representation of the count of steps already done.
26
+ * It's calculated based on the total count of steps.
27
+ */
28
+ private numberOfDigitsInPercentage: number = 0;
29
+ /** Defines a threshold that is used to check if the process velocity can be consifered trust. */
30
+ private threshold: number;
31
+ /** Function that is used to get the time stamp */
32
+ private getTime: () => bigint;
33
+
34
+ constructor(options: {threshold?: number; getTime?: () => bigint} = {}) {
35
+ this.getTime = options.getTime || process.hrtime.bigint;
36
+ this.threshold = options.threshold || THRESHOLD_DEFAULT;
37
+ }
38
+
39
+ /** Total amount of work, e.g. number of files to save or number of bytes to send */
40
+ get stepsTotal() {
41
+ return this._stepsTotal;
42
+ }
43
+
44
+ set stepsTotal(stepsTotal) {
45
+ this._stepsTotal = stepsTotal;
46
+ this.numberOfDigitsInPercentage =
47
+ this.stepsTotal > 100 ? Math.ceil(Math.log10(this.stepsTotal)) - 2 : 0;
48
+ }
49
+
50
+ /** Amount of work already done */
51
+ get stepsDone() {
52
+ return this._stepsDone;
53
+ }
54
+
55
+ set stepsDone(stepsDone) {
56
+ this._stepsDone = stepsDone;
57
+ this.timeOfUpdatingStepsDone = this.getCurrentTimeInMilliSeconds();
58
+ if (this._stepsDone) {
59
+ const diff = this.timeOfUpdatingStepsDone - this.startTime;
60
+ const milliSecForOneStep = diff / this._stepsDone;
61
+
62
+ this.trust = this.isVelocityTrust(milliSecForOneStep, this.milliSecForOneStep);
63
+ this.milliSecForOneStep = milliSecForOneStep;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Saves the current time as we start monitoring the process.
69
+ */
70
+ startMonitoring() {
71
+ this.startTime = this.getCurrentTimeInMilliSeconds();
72
+ this.milliSecForOneStep = 0;
73
+ this.trust = false;
74
+ this.timeOfUpdatingStepsDone = 0;
75
+ this.stopTime = 0;
76
+ this.stepsDone = 0;
77
+ }
78
+
79
+ /**
80
+ * Saves the current time as we stop monitoring the process.
81
+ */
82
+ stopMonitoring() {
83
+ this.stopTime = this.getCurrentTimeInMilliSeconds();
84
+ }
85
+
86
+ /**
87
+ * Gets percentage of the work already done.
88
+ * @returns percentage of the work already done.
89
+ */
90
+ getPercent(): number | null {
91
+ if (!this._stepsTotal) {
92
+ return null;
93
+ }
94
+ const percent = (this._stepsDone / this._stepsTotal) * 100.0;
95
+ return percent;
96
+ }
97
+
98
+ /**
99
+ * Gets string representation of percentage of the work already done.
100
+ * @returns string representation of percentage or an empty string if the percetage value cannot be calculated.
101
+ */
102
+ getPercentString() {
103
+ const percent = this.getPercent();
104
+ return percent !== null ? percent.toFixed(this.numberOfDigitsInPercentage) : '';
105
+ }
106
+
107
+ /**
108
+ * Gets the time elapsed since the monitoring started
109
+ * @returns Number of milliseconds elapsed
110
+ */
111
+ getTimeCurrentlyElapsed(): number {
112
+ const currentTime = this.stopTime ? this.stopTime : this.getCurrentTimeInMilliSeconds();
113
+ const diff = currentTime - this.startTime;
114
+ return diff;
115
+ }
116
+
117
+ /**
118
+ * Gets the time remaining (expected at the moment of updating 'stepsDone') to complete the work.
119
+ * @returns Number of milliseconds remaining
120
+ */
121
+ getTimeRemaining(): {timeRemaining: number; trust: boolean} | null {
122
+ if (!this._stepsDone || !this.startTime) {
123
+ return null;
124
+ }
125
+
126
+ const timeRemainingInMilliSeconds =
127
+ (this._stepsTotal - this._stepsDone) * this.milliSecForOneStep;
128
+ return {timeRemaining: timeRemainingInMilliSeconds, trust: this.trust};
129
+ }
130
+
131
+ /**
132
+ * Gets the string representation of the time remaining (expected at the moment of updating 'stepsDone') to complete the work.
133
+ * @returns string representation of the time remaining.
134
+ * It's an empty string if the time cannot be pedicted or it's still being calculated.
135
+ */
136
+ getTimeRemainingString(): string {
137
+ const timeRemainingObject = this.getTimeRemaining();
138
+ return timeRemainingObject?.trust ? timeConverter(timeRemainingObject.timeRemaining) : '';
139
+ }
140
+
141
+ /**
142
+ * Check if the computed velociy of the process can be considered trust.
143
+ * At the beginning of the process the number of samples collected ('time necessary to perform one step' averaged) is too small,
144
+ * which results in huge deviation of the cumputed velocity of the process.
145
+ * It makes sense to perform the check before reporting the time remainig so the end user is not confused.
146
+ * @param current - current value
147
+ * @param previous - previous value
148
+ * @returns true if the computed velociy can be considered trust, or false otherwise
149
+ */
150
+ private isVelocityTrust(current: number, previous: number): boolean {
151
+ if (previous) {
152
+ const dev = Math.abs((current - previous) / previous);
153
+ return dev < this.threshold;
154
+ }
155
+ return false;
156
+ }
157
+
158
+ /**
159
+ * Gets current time in milliseconds.
160
+ * @returns current time in milliseconds.
161
+ */
162
+ private getCurrentTimeInMilliSeconds(): number {
163
+ // process.hrtime.bigint() returns the time in nanoseconds. We need the time in milliseconds.
164
+ return Number(this.getTime() / BigInt(1e6));
165
+ }
166
+ }
@@ -79,6 +79,7 @@ import {BoundingSphere, OrientedBoundingBox} from '@math.gl/culling';
79
79
  import {createBoundingVolume} from '@loaders.gl/tiles';
80
80
  import {TraversalConversionProps, traverseDatasetWith} from './helpers/tileset-traversal';
81
81
  import {analyzeTileContent, mergePreprocessData} from './helpers/preprocess-3d-tiles';
82
+ import {Progress} from './helpers/progress';
82
83
 
83
84
  const ION_DEFAULT_TOKEN = process.env?.IonToken;
84
85
  const HARDCODED_NODES_PER_PAGE = 64;
@@ -87,6 +88,7 @@ const _3D_OBJECT_LAYER_TYPE = '3DObject';
87
88
  const REFRESH_TOKEN_TIMEOUT = 1800; // 30 minutes in seconds
88
89
  const CESIUM_DATASET_PREFIX = 'https://';
89
90
  // const FS_FILE_TOO_LARGE = 'ERR_FS_FILE_TOO_LARGE';
91
+ const PROGRESS_PHASE1_COUNT = 'phase1-count';
90
92
 
91
93
  /**
92
94
  * Converter from 3d-tiles tileset to i3s layer
@@ -138,15 +140,7 @@ export default class I3SConverter {
138
140
  meshTopologyTypes: new Set(),
139
141
  metadataClasses: new Set()
140
142
  };
141
- /** Total count of tiles in tileset */
142
- tileCountTotal: number = 0;
143
- /** Count of tiles already converted plus one (refers to the tile currently being converted) */
144
- tileCountCurrentlyConverting: number = 0;
145
- /**
146
- * The number of digits to appear after decimal point in the string representation of the tile count.
147
- * It's calculated based on the total count of tiles.
148
- */
149
- numberOfDigitsInPercentage: number = 0;
143
+ progresses: Record<string, Progress> = {};
150
144
 
151
145
  constructor() {
152
146
  this.attributeMetadataInfo = new AttributeMetadataInfo();
@@ -244,6 +238,7 @@ export default class I3SConverter {
244
238
  inquirer,
245
239
  metadataClass
246
240
  };
241
+ this.progresses[PROGRESS_PHASE1_COUNT] = new Progress();
247
242
  this.compressList = (this.options.instantNodeWriting && []) || null;
248
243
  this.validate = Boolean(validate);
249
244
  this.Loader = inputUrl.indexOf(CESIUM_DATASET_PREFIX) !== -1 ? CesiumIonLoader : Tiles3DLoader;
@@ -310,12 +305,9 @@ export default class I3SConverter {
310
305
  );
311
306
  const {meshTopologyTypes, metadataClasses} = this.preprocessData;
312
307
 
313
- this.numberOfDigitsInPercentage =
314
- this.tileCountTotal > 100 ? Math.ceil(Math.log10(this.tileCountTotal)) - 2 : 0;
315
-
316
308
  console.log(`------------------------------------------------`);
317
309
  console.log(`Preprocess results:`);
318
- console.log(`Tile count: ${this.tileCountTotal}`);
310
+ console.log(`Tile count: ${this.progresses[PROGRESS_PHASE1_COUNT].stepsTotal}`);
319
311
  console.log(`glTF mesh topology types: ${Array.from(meshTopologyTypes).join(', ')}`);
320
312
 
321
313
  if (metadataClasses.size) {
@@ -357,7 +349,7 @@ export default class I3SConverter {
357
349
  return null;
358
350
  }
359
351
  if (sourceTile.id) {
360
- this.tileCountTotal++;
352
+ this.progresses[PROGRESS_PHASE1_COUNT].stepsTotal += 1;
361
353
  console.log(`[analyze]: ${sourceTile.id}`); // eslint-disable-line
362
354
  }
363
355
 
@@ -374,7 +366,6 @@ export default class I3SConverter {
374
366
  }
375
367
  const tilePreprocessData = await analyzeTileContent(tileContent);
376
368
  mergePreprocessData(this.preprocessData, tilePreprocessData);
377
-
378
369
  return null;
379
370
  }
380
371
 
@@ -451,7 +442,7 @@ export default class I3SConverter {
451
442
  obb: boundingVolumes.obb,
452
443
  children: []
453
444
  });
454
-
445
+ this.progresses[PROGRESS_PHASE1_COUNT].startMonitoring();
455
446
  const rootNode = await NodeIndexDocument.createRootNode(boundingVolumes, this);
456
447
  await traverseDatasetWith<TraversalConversionProps>(
457
448
  sourceRootTile,
@@ -463,6 +454,8 @@ export default class I3SConverter {
463
454
  this.finalizeTile.bind(this),
464
455
  this.options.maxDepth
465
456
  );
457
+ this.progresses[PROGRESS_PHASE1_COUNT].stopMonitoring();
458
+ console.log(`[finalizing conversion]`); // eslint-disable-line
466
459
 
467
460
  this.layers0!.attributeStorageInfo = this.attributeMetadataInfo.attributeStorageInfo;
468
461
  this.layers0!.fields = this.attributeMetadataInfo.fields;
@@ -618,16 +611,8 @@ export default class I3SConverter {
618
611
  return traversalProps;
619
612
  }
620
613
  if (sourceTile.id) {
621
- // Print the tile number that is currently being converted.
622
- this.tileCountCurrentlyConverting++;
623
- let percentString = '';
624
- if (this.tileCountTotal) {
625
- const percent = (this.tileCountCurrentlyConverting / this.tileCountTotal) * 100;
626
- percentString = ' ' + percent.toFixed(this.numberOfDigitsInPercentage);
627
- }
628
- console.log(`[convert${percentString}%]: ${sourceTile.id}`); // eslint-disable-line
614
+ console.log(`[convert]: ${sourceTile.id}`); // eslint-disable-line
629
615
  }
630
-
631
616
  const {parentNodes, transform} = traversalProps;
632
617
  let transformationMatrix: Matrix4 = transform.clone();
633
618
  if (sourceTile.transform) {
@@ -641,6 +626,20 @@ export default class I3SConverter {
641
626
  transform: transformationMatrix,
642
627
  parentNodes: childNodes
643
628
  };
629
+
630
+ if (sourceTile.id) {
631
+ this.progresses[PROGRESS_PHASE1_COUNT].stepsDone += 1;
632
+
633
+ let timeRemainingString = 'Calculating time left...';
634
+ const timeRemainingStringBasedOnCount =
635
+ this.progresses[PROGRESS_PHASE1_COUNT].getTimeRemainingString();
636
+ if (timeRemainingStringBasedOnCount) {
637
+ timeRemainingString = `${timeRemainingStringBasedOnCount} left`;
638
+ }
639
+
640
+ let percentString = this.progresses[PROGRESS_PHASE1_COUNT].getPercentString();
641
+ console.log(`[converted ${percentString}%, ${timeRemainingString}]: ${sourceTile.id}`); // eslint-disable-line
642
+ }
644
643
  return newTraversalProps;
645
644
  }
646
645
 
@@ -2,15 +2,33 @@ import {join} from 'path';
2
2
  import {promises as fs} from 'fs';
3
3
  import {getAbsoluteFilePath} from './file-utils';
4
4
 
5
- export function timeConverter(time) {
6
- const nanoSecondsInMillisecond = 1e6;
7
- let timeInSeconds = time[0];
5
+ /**
6
+ * Converts time value to string.
7
+ * @param time - high-resolution real time in a [seconds, nanoseconds] tuple Array, or a value on milliseconds.
8
+ * @returns string representation of the time
9
+ */
10
+ export function timeConverter(time: number | [number, number]): string {
11
+ if (typeof time === 'number') {
12
+ // time - real time in milli-seconds
13
+ const milliSecondsInSecond = 1e3;
14
+ const timeInSeconds = Math.floor(time / milliSecondsInSecond);
15
+ const milliseconds = time - timeInSeconds * milliSecondsInSecond;
16
+ return timeConverterFromSecondsAndMilliseconds(timeInSeconds, milliseconds);
17
+ } else {
18
+ // time - high-resolution real time in a [seconds, nanoseconds] tuple Array
19
+ const nanoSecondsInMillisecond = 1e6;
20
+ const timeInSeconds = time[0];
21
+ const milliseconds = time[1] / nanoSecondsInMillisecond;
22
+ return timeConverterFromSecondsAndMilliseconds(timeInSeconds, milliseconds);
23
+ }
24
+ }
25
+
26
+ function timeConverterFromSecondsAndMilliseconds(timeInSeconds: number, milliseconds: number) {
8
27
  const hours = Math.floor(timeInSeconds / 3600);
9
28
  timeInSeconds = timeInSeconds - hours * 3600;
10
29
  const minutes = Math.floor(timeInSeconds / 60);
11
30
  timeInSeconds = timeInSeconds - minutes * 60;
12
31
  const seconds = Math.floor(timeInSeconds);
13
- const milliseconds = time[1] / nanoSecondsInMillisecond;
14
32
  let result = '';
15
33
 
16
34
  if (hours) {