@loaders.gl/tile-converter 4.0.0-alpha.10 → 4.0.0-alpha.12

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 (79) hide show
  1. package/bin/slpk-extractor.js +4 -0
  2. package/dist/3d-tiles-attributes-worker.js +1 -1
  3. package/dist/converter-cli.js +18 -84
  4. package/dist/converter.min.js +50 -50
  5. package/dist/dist.min.js +21 -18
  6. package/dist/es5/3d-tiles-attributes-worker.js +1 -1
  7. package/dist/es5/converter-cli.js +18 -63
  8. package/dist/es5/converter-cli.js.map +1 -1
  9. package/dist/es5/deps-installer/deps-installer.js +1 -1
  10. package/dist/es5/i3s-attributes-worker.js +1 -1
  11. package/dist/es5/i3s-converter/helpers/coordinate-converter.js.map +1 -1
  12. package/dist/es5/i3s-converter/helpers/gltf-attributes.js.map +1 -1
  13. package/dist/es5/i3s-converter/helpers/preprocess-3d-tiles.js +2 -2
  14. package/dist/es5/i3s-converter/helpers/preprocess-3d-tiles.js.map +1 -1
  15. package/dist/es5/i3s-converter/i3s-converter.js +59 -38
  16. package/dist/es5/i3s-converter/i3s-converter.js.map +1 -1
  17. package/dist/es5/lib/utils/cli-utils.js +57 -0
  18. package/dist/es5/lib/utils/cli-utils.js.map +1 -0
  19. package/dist/es5/pgm-loader.js +1 -1
  20. package/dist/es5/slpk-extractor/helpers/file-handle-provider.js +181 -0
  21. package/dist/es5/slpk-extractor/helpers/file-handle-provider.js.map +1 -0
  22. package/dist/es5/slpk-extractor/slpk-extractor.js +172 -0
  23. package/dist/es5/slpk-extractor/slpk-extractor.js.map +1 -0
  24. package/dist/es5/slpk-extractor-cli.js +117 -0
  25. package/dist/es5/slpk-extractor-cli.js.map +1 -0
  26. package/dist/esm/3d-tiles-attributes-worker.js +1 -1
  27. package/dist/esm/converter-cli.js +1 -46
  28. package/dist/esm/converter-cli.js.map +1 -1
  29. package/dist/esm/deps-installer/deps-installer.js +1 -1
  30. package/dist/esm/i3s-attributes-worker.js +1 -1
  31. package/dist/esm/i3s-converter/helpers/coordinate-converter.js.map +1 -1
  32. package/dist/esm/i3s-converter/helpers/gltf-attributes.js.map +1 -1
  33. package/dist/esm/i3s-converter/helpers/preprocess-3d-tiles.js +1 -1
  34. package/dist/esm/i3s-converter/helpers/preprocess-3d-tiles.js.map +1 -1
  35. package/dist/esm/i3s-converter/i3s-converter.js +22 -9
  36. package/dist/esm/i3s-converter/i3s-converter.js.map +1 -1
  37. package/dist/esm/lib/utils/cli-utils.js +47 -0
  38. package/dist/esm/lib/utils/cli-utils.js.map +1 -0
  39. package/dist/esm/pgm-loader.js +1 -1
  40. package/dist/esm/slpk-extractor/helpers/file-handle-provider.js +43 -0
  41. package/dist/esm/slpk-extractor/helpers/file-handle-provider.js.map +1 -0
  42. package/dist/esm/slpk-extractor/slpk-extractor.js +63 -0
  43. package/dist/esm/slpk-extractor/slpk-extractor.js.map +1 -0
  44. package/dist/esm/slpk-extractor-cli.js +74 -0
  45. package/dist/esm/slpk-extractor-cli.js.map +1 -0
  46. package/dist/i3s-converter/helpers/coordinate-converter.d.ts +1 -2
  47. package/dist/i3s-converter/helpers/coordinate-converter.d.ts.map +1 -1
  48. package/dist/i3s-converter/helpers/coordinate-converter.js +1 -2
  49. package/dist/i3s-converter/helpers/gltf-attributes.d.ts +1 -1
  50. package/dist/i3s-converter/helpers/gltf-attributes.d.ts.map +1 -1
  51. package/dist/i3s-converter/helpers/gltf-attributes.js +2 -1
  52. package/dist/i3s-converter/helpers/preprocess-3d-tiles.d.ts +2 -3
  53. package/dist/i3s-converter/helpers/preprocess-3d-tiles.d.ts.map +1 -1
  54. package/dist/i3s-converter/helpers/preprocess-3d-tiles.js +1 -2
  55. package/dist/i3s-converter/i3s-converter.d.ts.map +1 -1
  56. package/dist/i3s-converter/i3s-converter.js +21 -6
  57. package/dist/lib/utils/cli-utils.d.ts +34 -0
  58. package/dist/lib/utils/cli-utils.d.ts.map +1 -0
  59. package/dist/lib/utils/cli-utils.js +82 -0
  60. package/dist/slpk-extractor/helpers/file-handle-provider.d.ts +48 -0
  61. package/dist/slpk-extractor/helpers/file-handle-provider.d.ts.map +1 -0
  62. package/dist/slpk-extractor/helpers/file-handle-provider.js +71 -0
  63. package/dist/slpk-extractor/slpk-extractor.d.ts +23 -0
  64. package/dist/slpk-extractor/slpk-extractor.d.ts.map +1 -0
  65. package/dist/slpk-extractor/slpk-extractor.js +78 -0
  66. package/dist/slpk-extractor-cli.d.ts +17 -0
  67. package/dist/slpk-extractor-cli.d.ts.map +1 -0
  68. package/dist/slpk-extractor-cli.js +102 -0
  69. package/dist/slpk-extractor.min.js +189 -0
  70. package/package.json +17 -15
  71. package/src/converter-cli.ts +7 -72
  72. package/src/i3s-converter/helpers/coordinate-converter.ts +1 -2
  73. package/src/i3s-converter/helpers/gltf-attributes.ts +7 -8
  74. package/src/i3s-converter/helpers/preprocess-3d-tiles.ts +1 -3
  75. package/src/i3s-converter/i3s-converter.ts +24 -7
  76. package/src/lib/utils/cli-utils.ts +78 -0
  77. package/src/slpk-extractor/helpers/file-handle-provider.ts +91 -0
  78. package/src/slpk-extractor/slpk-extractor.ts +102 -0
  79. package/src/slpk-extractor-cli.ts +128 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loaders.gl/tile-converter",
3
- "version": "4.0.0-alpha.10",
3
+ "version": "4.0.0-alpha.12",
4
4
  "description": "Converter",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -21,6 +21,7 @@
21
21
  "sideEffects": false,
22
22
  "bin": {
23
23
  "tile-converter": "./bin/converter.js",
24
+ "slpk-extractor": "./bin/slpk-extractor.js",
24
25
  "i3s-server": "./src/i3s-server/bin/www"
25
26
  },
26
27
  "files": [
@@ -37,25 +38,26 @@
37
38
  "join-images": false
38
39
  },
39
40
  "scripts": {
40
- "pre-build": "npm run build-bundle && npm run build-converter-bundle && npm run build-i3s-attributes-worker && npm run build-3d-tiles-attributes-worker",
41
+ "pre-build": "npm run build-bundle && npm run build-converter-bundle && npm run build-i3s-attributes-worker && npm run build-slpk-extractor-bundle && npm run build-3d-tiles-attributes-worker",
41
42
  "build-bundle": "esbuild ./src/index.ts --bundle --outfile=dist/dist.min.js --platform=node --external:join-images",
42
43
  "build-converter-bundle": "esbuild src/converter-cli.ts --outfile=dist/converter.min.js --platform=node --target=esnext,node14 --external:join-images --minify --bundle --define:__VERSION__=\\\"$npm_package_version\\\"",
44
+ "build-slpk-extractor-bundle": "esbuild src/slpk-extractor-cli.ts --outfile=dist/slpk-extractor.min.js --platform=node --target=esnext,node14 --external:join-images --minify --bundle --define:__VERSION__=\\\"$npm_package_version\\\"",
43
45
  "build-i3s-attributes-worker": "esbuild src/workers/i3s-attributes-worker.ts --outfile=dist/i3s-attributes-worker.js --platform=node --target=esnext,node14 --external:join-images --minify --bundle --sourcemap --define:__VERSION__=\\\"$npm_package_version\\\"",
44
46
  "build-3d-tiles-attributes-worker": "esbuild src/workers/3d-tiles-attributes-worker.ts --outfile=dist/3d-tiles-attributes-worker.js --platform=node --target=esnext,node14 --external:join-images --minify --bundle --sourcemap --define:__VERSION__=\\\"$npm_package_version\\\""
45
47
  },
46
48
  "dependencies": {
47
- "@loaders.gl/3d-tiles": "4.0.0-alpha.10",
48
- "@loaders.gl/crypto": "4.0.0-alpha.10",
49
- "@loaders.gl/draco": "4.0.0-alpha.10",
50
- "@loaders.gl/gltf": "4.0.0-alpha.10",
51
- "@loaders.gl/i3s": "4.0.0-alpha.10",
52
- "@loaders.gl/images": "4.0.0-alpha.10",
53
- "@loaders.gl/loader-utils": "4.0.0-alpha.10",
54
- "@loaders.gl/polyfills": "4.0.0-alpha.10",
55
- "@loaders.gl/textures": "4.0.0-alpha.10",
56
- "@loaders.gl/tiles": "4.0.0-alpha.10",
57
- "@loaders.gl/worker-utils": "4.0.0-alpha.10",
58
- "@loaders.gl/zip": "4.0.0-alpha.10",
49
+ "@loaders.gl/3d-tiles": "4.0.0-alpha.12",
50
+ "@loaders.gl/crypto": "4.0.0-alpha.12",
51
+ "@loaders.gl/draco": "4.0.0-alpha.12",
52
+ "@loaders.gl/gltf": "4.0.0-alpha.12",
53
+ "@loaders.gl/i3s": "4.0.0-alpha.12",
54
+ "@loaders.gl/images": "4.0.0-alpha.12",
55
+ "@loaders.gl/loader-utils": "4.0.0-alpha.12",
56
+ "@loaders.gl/polyfills": "4.0.0-alpha.12",
57
+ "@loaders.gl/textures": "4.0.0-alpha.12",
58
+ "@loaders.gl/tiles": "4.0.0-alpha.12",
59
+ "@loaders.gl/worker-utils": "4.0.0-alpha.12",
60
+ "@loaders.gl/zip": "4.0.0-alpha.12",
59
61
  "@math.gl/core": "^3.5.1",
60
62
  "@math.gl/culling": "^3.5.1",
61
63
  "@math.gl/geoid": "^3.5.1",
@@ -78,5 +80,5 @@
78
80
  "join-images": "^1.1.3",
79
81
  "sharp": "^0.31.3"
80
82
  },
81
- "gitHead": "7efdbe09e02098aad6d985e4d6465d08806e19a9"
83
+ "gitHead": "42dfc47a41e3e6089eec22a1e1d4f3387e0cb6e9"
82
84
  }
@@ -3,6 +3,13 @@ import '@loaders.gl/polyfills';
3
3
  import {join} from 'path';
4
4
  import {I3SConverter, Tiles3DConverter} from '@loaders.gl/tile-converter';
5
5
  import {DepsInstaller} from './deps-installer/deps-installer';
6
+ import {
7
+ getBooleanValue,
8
+ getIntegerValue,
9
+ getStringValue,
10
+ getURLValue,
11
+ validateOptionsWithEqual
12
+ } from './lib/utils/cli-utils';
6
13
 
7
14
  type TileConversionOptions = {
8
15
  /** "I3S" - for I3S to 3DTiles conversion, "3DTILES" for 3DTiles to I3S conversion */
@@ -213,19 +220,6 @@ function validateOptions(options: TileConversionOptions): ValidatedTileConversio
213
220
  return <ValidatedTileConversionOptions>options;
214
221
  }
215
222
 
216
- function validateOptionsWithEqual(args: string[]): string[] {
217
- return args.reduce((acc: string[], curr) => {
218
- const equalSignIndex = curr.indexOf('=');
219
- const beforeEqual = curr.slice(0, equalSignIndex);
220
- const afterEqual = curr.slice(equalSignIndex + 1, curr.length);
221
- const condition = curr.includes('=') && curr.startsWith('--') && afterEqual;
222
- if (condition) {
223
- return acc.concat(beforeEqual, afterEqual);
224
- }
225
- return acc.concat(curr);
226
- }, []);
227
- }
228
-
229
223
  /**
230
224
  * Parse option from the cli arguments array
231
225
  * @param args
@@ -309,62 +303,3 @@ function parseOptions(args: string[]): TileConversionOptions {
309
303
  });
310
304
  return opts;
311
305
  }
312
-
313
- /**
314
- * Get string option value from cli arguments
315
- * @param index - option's name index in the argument's array.
316
- * The value of the option should be next to name of the option.
317
- * @param args - cli arguments array
318
- * @returns - string value of the option
319
- */
320
- function getStringValue(index: number, args: string[]): string {
321
- if (index + 1 >= args.length) {
322
- return '';
323
- }
324
- const value = args[index + 1];
325
- if (value.indexOf('--') === 0) {
326
- return '';
327
- }
328
- return value;
329
- }
330
-
331
- /**
332
- * Modyfy URL path to be compatible with fetch
333
- * @param index - option's name index in the argument's array.
334
- * The value of the option should be next to name of the option.
335
- * @param args - cli arguments array
336
- * @returns - string value of the option
337
- */
338
- function getURLValue(index: number, args: string[]): string {
339
- const value = getStringValue(index, args);
340
- console.log(`Input tileset value: ${value}`);
341
- console.log(`Modified tileset value: ${value.replace(/\\/g, '/')}`);
342
- return value.replace(/\\/g, '/');
343
- }
344
-
345
- /**
346
- * Get integer option value from cli arguments
347
- * @param index - option's name index in the argument's array
348
- * The value of the option should be next to name of the option.
349
- * @param args - cli arguments array
350
- * @returns - number value of the option
351
- */
352
- function getIntegerValue(index: number, args: string[]): number {
353
- const stringValue: string = getStringValue(index, args);
354
- const result: number = Number.parseInt(stringValue);
355
- if (isFinite(result)) {
356
- return result;
357
- }
358
- return NaN;
359
- }
360
-
361
- function getBooleanValue(index: number, args: string[]): boolean {
362
- const stringValue: string = getStringValue(index, args).toLowerCase().trim();
363
- if (['--no-draco', '--split-nodes'].includes(args[index]) && !stringValue) {
364
- return false;
365
- }
366
- if (!stringValue || stringValue === 'true') {
367
- return true;
368
- }
369
- return false;
370
- }
@@ -103,7 +103,6 @@ export function convertPositionsToVectors(positions: Float32Array): Vector3[] {
103
103
 
104
104
  /**
105
105
  * Convert common coordinate to fullExtent https://github.com/Esri/i3s-spec/blob/master/docs/1.8/fullExtent.cmn.md
106
- * @param
107
106
  * @param boundingVolume
108
107
  * @returns - fullExtent object
109
108
  */
@@ -142,7 +141,7 @@ export function convertBoundingVolumeToI3SFullExtent(
142
141
  /**
143
142
  * Creates oriented boundinb box from mbs.
144
143
  * @param mbs - Minimum Bounding Sphere
145
- * @returns - Oriented BOunding Box
144
+ * @returns - Oriented Bounding Box
146
145
  */
147
146
  export function createObbFromMbs(mbs: Mbs): Obb {
148
147
  const radius = mbs[3];
@@ -1,21 +1,17 @@
1
1
  import type {Tiles3DTileContent} from '@loaders.gl/3d-tiles';
2
2
  import type {GLTFAccessorPostprocessed, GLTFNodePostprocessed} from '@loaders.gl/gltf';
3
3
  import type {B3DMAttributesData} from '../../i3s-attributes-worker';
4
- import {Matrix4, Vector3} from '@math.gl/core';
4
+ import {Matrix4, TypedArray, Vector3} from '@math.gl/core';
5
5
  import {BoundingSphere, OrientedBoundingBox} from '@math.gl/culling';
6
6
  import {Ellipsoid} from '@math.gl/geospatial';
7
7
 
8
- type AttributesObject = {
9
- [k: string]: GLTFAccessorPostprocessed;
10
- };
11
-
12
8
  /**
13
9
  * Prepare attributes for conversion to avoid binary data breaking in worker thread.
14
10
  * @param tileContent - 3DTiles tile content
15
11
  * @param tileTransform - transformation matrix of the tile, calculated recursively multiplying
16
12
  * transform of all parent tiles and transform of the current tile
17
13
  * @param boundingVolume - initialized bounding volume of the source tile
18
- * @returns
14
+ * @returns 3DTiles content data, prepared for conversion
19
15
  */
20
16
  export function prepareDataForAttributesConversion(
21
17
  tileContent: Tiles3DTileContent,
@@ -67,9 +63,12 @@ export function prepareDataForAttributesConversion(
67
63
  /**
68
64
  * Keep only values for glTF attributes to pass data to worker thread.
69
65
  * @param attributes - geometry attributes
66
+ * @returns attributes with only `value` item
70
67
  */
71
- function getB3DMAttributesWithoutBufferView(attributes: AttributesObject): AttributesObject {
72
- const attributesWithoutBufferView = {};
68
+ function getB3DMAttributesWithoutBufferView(
69
+ attributes: Record<string, GLTFAccessorPostprocessed>
70
+ ): Record<string, {value: TypedArray}> {
71
+ const attributesWithoutBufferView: Record<string, {value: TypedArray}> = {};
73
72
 
74
73
  for (const attributeName in attributes) {
75
74
  attributesWithoutBufferView[attributeName] = {
@@ -1,4 +1,4 @@
1
- import {Tiles3DTileContent, Tiles3DTileJSONPostprocessed} from '@loaders.gl/3d-tiles';
1
+ import {Tiles3DTileContent} from '@loaders.gl/3d-tiles';
2
2
  import {GltfPrimitiveModeString, PreprocessData} from '../types';
3
3
  import {GLTF, GLTFLoader} from '@loaders.gl/gltf';
4
4
  import {parse} from '@loaders.gl/core';
@@ -20,12 +20,10 @@ export const GLTF_PRIMITIVE_MODES = [
20
20
  /**
21
21
  * Analyze tile content. This function is used during preprocess stage of
22
22
  * conversion
23
- * @param tile - 3DTiles tile JSON metadata
24
23
  * @param tileContent - 3DTiles tile content ArrayBuffer
25
24
  * @returns
26
25
  */
27
26
  export const analyzeTileContent = async (
28
- tile: Tiles3DTileJSONPostprocessed,
29
27
  tileContent: Tiles3DTileContent | null
30
28
  ): Promise<PreprocessData> => {
31
29
  const result: PreprocessData = {
@@ -313,11 +313,19 @@ export default class I3SConverter {
313
313
  if (sourceTile.id) {
314
314
  console.log(`[analyze]: ${sourceTile.id}`); // eslint-disable-line
315
315
  }
316
- const tileContent = await loadTile3DContent(this.sourceTileset, sourceTile, {
317
- ...this.loadOptions,
318
- '3d-tiles': {...this.loadOptions['3d-tiles'], loadGLTF: false}
319
- });
320
- const tilePreprocessData = await analyzeTileContent(sourceTile, tileContent);
316
+
317
+ let tileContent: Tiles3DTileContent | null = null;
318
+ try {
319
+ tileContent = await loadTile3DContent(this.sourceTileset, sourceTile, {
320
+ ...this.loadOptions,
321
+ '3d-tiles': {...this.loadOptions['3d-tiles'], loadGLTF: false}
322
+ });
323
+ } catch (error) {
324
+ console.log(
325
+ `[warning]: Failed to load ${sourceTile.contentUrl}. An I3S tile with empty content will be added to the output tileset`
326
+ );
327
+ }
328
+ const tilePreprocessData = await analyzeTileContent(tileContent);
321
329
  mergePreprocessData(this.preprocessData, tilePreprocessData);
322
330
 
323
331
  return null;
@@ -576,7 +584,12 @@ export default class I3SConverter {
576
584
 
577
585
  await this._updateTilesetOptions();
578
586
 
579
- const tileContent = await loadTile3DContent(this.sourceTileset, sourceTile, this.loadOptions);
587
+ let tileContent: Tiles3DTileContent | null = null;
588
+ try {
589
+ tileContent = await loadTile3DContent(this.sourceTileset, sourceTile, this.loadOptions);
590
+ } catch (error) {
591
+ console.log(`[warning]: Failed to load ${sourceTile.contentUrl}`);
592
+ }
580
593
  const sourceBoundingVolume = createBoundingVolume(
581
594
  sourceTile.boundingVolume,
582
595
  transformationMatrix,
@@ -753,13 +766,17 @@ export default class I3SConverter {
753
766
  }
754
767
 
755
768
  let nodeId = resources.nodeId;
756
- let node;
769
+ let node: NodeInPage;
757
770
  if (!nodeId) {
758
771
  node = await this.nodePages.push(nodeInPage, parentId);
759
772
  } else {
760
773
  node = await this.nodePages.getNodeById(nodeId);
761
774
  }
762
775
 
776
+ if (!nodeInPage.mesh) {
777
+ console.log(`[warning]: node ${node.index} is created with empty content`);
778
+ }
779
+
763
780
  NodePages.updateAll(node, nodeInPage);
764
781
  if (meshMaterial) {
765
782
  NodePages.updateMaterialByNodeId(node, this._findOrCreateMaterial(meshMaterial));
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Get string option value from cli arguments
3
+ * @param index - option's name index in the argument's array.
4
+ * The value of the option should be next to name of the option.
5
+ * @param args - cli arguments array
6
+ * @returns - string value of the option
7
+ */
8
+ export function getStringValue(index: number, args: string[]): string {
9
+ if (index + 1 >= args.length) {
10
+ return '';
11
+ }
12
+ const value = args[index + 1];
13
+ if (value.indexOf('--') === 0) {
14
+ return '';
15
+ }
16
+ return value;
17
+ }
18
+
19
+ /**
20
+ * Modyfy URL path to be compatible with fetch
21
+ * @param index - option's name index in the argument's array.
22
+ * The value of the option should be next to name of the option.
23
+ * @param args - cli arguments array
24
+ * @returns - string value of the option
25
+ */
26
+ export function getURLValue(index: number, args: string[]): string {
27
+ const value = getStringValue(index, args);
28
+ console.log(`Input tileset value: ${value}`);
29
+ console.log(`Modified tileset value: ${value.replace(/\\/g, '/')}`);
30
+ return value.replace(/\\/g, '/');
31
+ }
32
+
33
+ export function validateOptionsWithEqual(args: string[]): string[] {
34
+ return args.reduce((acc: string[], curr) => {
35
+ const equalSignIndex = curr.indexOf('=');
36
+ const beforeEqual = curr.slice(0, equalSignIndex);
37
+ const afterEqual = curr.slice(equalSignIndex + 1, curr.length);
38
+ const condition = curr.includes('=') && curr.startsWith('--') && afterEqual;
39
+ if (condition) {
40
+ return acc.concat(beforeEqual, afterEqual);
41
+ }
42
+ return acc.concat(curr);
43
+ }, []);
44
+ }
45
+
46
+ /**
47
+ * Get integer option value from cli arguments
48
+ * @param index - option's name index in the argument's array
49
+ * The value of the option should be next to name of the option.
50
+ * @param args - cli arguments array
51
+ * @returns - number value of the option
52
+ */
53
+ export function getIntegerValue(index: number, args: string[]): number {
54
+ const stringValue: string = getStringValue(index, args);
55
+ const result: number = Number.parseInt(stringValue);
56
+ if (isFinite(result)) {
57
+ return result;
58
+ }
59
+ return NaN;
60
+ }
61
+
62
+ /**
63
+ * Get boolean option value from cli arguments
64
+ * @param index - option's name index in the argument's array
65
+ * The value of the option should be next to name of the option.
66
+ * @param args - cli arguments array
67
+ * @returns - boolean value of the option
68
+ */
69
+ export function getBooleanValue(index: number, args: string[]): boolean {
70
+ const stringValue: string = getStringValue(index, args).toLowerCase().trim();
71
+ if (['--no-draco', '--split-nodes'].includes(args[index]) && !stringValue) {
72
+ return false;
73
+ }
74
+ if (!stringValue || stringValue === 'true') {
75
+ return true;
76
+ }
77
+ return false;
78
+ }
@@ -0,0 +1,91 @@
1
+ import {FileProvider} from '@loaders.gl/i3s';
2
+ import {promises as fsPromises, PathLike} from 'fs';
3
+
4
+ /**
5
+ * Provides file data using node fs library
6
+ */
7
+ export class FileHandleProvider implements FileProvider {
8
+ /**
9
+ * Returns a new copy of FileHandleProvider
10
+ * @param path The path to the file in file system
11
+ */
12
+ static async from(path: PathLike): Promise<FileHandleProvider> {
13
+ const fileDescriptor = await fsPromises.open(path);
14
+ return new FileHandleProvider(fileDescriptor, (await fileDescriptor.stat()).size);
15
+ }
16
+
17
+ /**
18
+ * The FileHandle from which data is provided
19
+ */
20
+ private fileDescriptor: fsPromises.FileHandle;
21
+
22
+ /**
23
+ * The file length in bytes
24
+ */
25
+ private size: number;
26
+
27
+ private constructor(fileDescriptor: fsPromises.FileHandle, size: number) {
28
+ this.fileDescriptor = fileDescriptor;
29
+ this.size = size;
30
+ }
31
+
32
+ /**
33
+ * Gets an unsigned 8-bit integer (unsigned byte) at the specified byte offset from the start of the file.
34
+ * @param offset The offset, in bytes, from the start of the file where to read the data.
35
+ */
36
+ async getUint8(offset: number): Promise<number> {
37
+ const val = new Uint8Array(
38
+ (await this.fileDescriptor.read(Buffer.alloc(1), 0, 1, offset)).buffer.buffer
39
+ ).at(0);
40
+ if (val === undefined) {
41
+ throw new Error('something went wrong');
42
+ }
43
+ return val;
44
+ }
45
+
46
+ /**
47
+ * Gets an unsigned 16-bit integer (unsigned byte) at the specified byte offset from the start of the file.
48
+ * @param offset The offset, in bytes, from the start of the file where to read the data.
49
+ */
50
+ async getUint16(offset: number): Promise<number> {
51
+ const val = new Uint16Array(
52
+ (await this.fileDescriptor.read(Buffer.alloc(2), 0, 2, offset)).buffer.buffer
53
+ ).at(0);
54
+ if (val === undefined) {
55
+ throw new Error('something went wrong');
56
+ }
57
+ return val;
58
+ }
59
+
60
+ /**
61
+ * Gets an unsigned 32-bit integer (unsigned byte) at the specified byte offset from the start of the file.
62
+ * @param offset The offset, in bytes, from the start of the file where to read the data.
63
+ */
64
+ async getUint32(offset: number): Promise<number> {
65
+ const val = new Uint32Array(
66
+ (await this.fileDescriptor.read(Buffer.alloc(4), 0, 4, offset)).buffer.buffer
67
+ ).at(0);
68
+ if (val === undefined) {
69
+ throw new Error('something went wrong');
70
+ }
71
+ return val;
72
+ }
73
+
74
+ /**
75
+ * returns an ArrayBuffer whose contents are a copy of this file bytes from startOffset, inclusive, up to endOffset, exclusive.
76
+ * @param startOffset The offset, in bytes, from the start of the file where to start reading the data.
77
+ * @param endOffset The offset, in bytes, from the start of the file where to end reading the data.
78
+ */
79
+ async slice(startOffset: number, endOffset: number): Promise<ArrayBuffer> {
80
+ const length = endOffset - startOffset;
81
+ return (await this.fileDescriptor.read(Buffer.alloc(length), 0, length, startOffset)).buffer
82
+ .buffer;
83
+ }
84
+
85
+ /**
86
+ * the length (in bytes) of the data.
87
+ */
88
+ get length(): number {
89
+ return this.size;
90
+ }
91
+ }
@@ -0,0 +1,102 @@
1
+ import {isBrowser} from '@loaders.gl/core';
2
+
3
+ import {BROWSER_ERROR_MESSAGE} from '../constants';
4
+ import {FileHandleProvider} from './helpers/file-handle-provider';
5
+ import {parseZipLocalFileHeader} from '@loaders.gl/i3s';
6
+ import {path} from '@loaders.gl/loader-utils';
7
+ import {GZipCompression} from '@loaders.gl/compression';
8
+ import {writeFile} from '../lib/utils/file-utils';
9
+
10
+ /**
11
+ * names of files that should be changed to index
12
+ */
13
+ const indexNames = [
14
+ '3dSceneLayer.json.gz',
15
+ '3dNodeIndexDocument.json.gz',
16
+ 'sharedResource.json.gz'
17
+ ];
18
+
19
+ /**
20
+ * Description of the file in the SLPK
21
+ */
22
+ type File = {
23
+ name: string | null;
24
+ data: ArrayBuffer;
25
+ };
26
+
27
+ /**
28
+ * Converter from slpk to i3s
29
+ */
30
+ export default class SLPKExtractor {
31
+ /**
32
+ * extract slpk to i3s
33
+ * @param options
34
+ * @param options.inputUrl the url to read SLPK file
35
+ * @param options.outputPath the output filename
36
+ */
37
+ public async extract(options: {inputUrl: string; outputPath: string}): Promise<string> {
38
+ if (isBrowser) {
39
+ console.log(BROWSER_ERROR_MESSAGE);
40
+ return BROWSER_ERROR_MESSAGE;
41
+ }
42
+ const {inputUrl} = options;
43
+
44
+ const provider = await FileHandleProvider.from(inputUrl);
45
+
46
+ let localHeader = await parseZipLocalFileHeader(0, provider);
47
+ while (localHeader) {
48
+ await this.writeFile(
49
+ await this.unGzip({
50
+ name: this.correctIndexNames(localHeader.fileName),
51
+ data: await provider.slice(
52
+ localHeader.fileDataOffset,
53
+ localHeader.fileDataOffset + localHeader.compressedSize
54
+ )
55
+ }),
56
+ options.outputPath
57
+ );
58
+ localHeader = await parseZipLocalFileHeader(
59
+ localHeader?.fileDataOffset + localHeader?.compressedSize,
60
+ provider
61
+ );
62
+ }
63
+
64
+ return 'success';
65
+ }
66
+
67
+ /**
68
+ * Defines file name and path for i3s format
69
+ * @param fileName initial file name and path
70
+ */
71
+ private correctIndexNames(fileName: string): string | null {
72
+ if (indexNames.includes(path.filename(path.join('/', fileName)))) {
73
+ return path.join(path.dirname(fileName), 'index.json.gz');
74
+ }
75
+ // finds path with name part and extention part
76
+ let parts = /^(.*\/[^\/\.]*)(\..+)$/.exec(fileName);
77
+ if (!parts) {
78
+ return null;
79
+ }
80
+ return `${parts?.at(1)}/index${parts?.at(2)}`;
81
+ }
82
+
83
+ private async unGzip(file: File): Promise<File> {
84
+ if (/\.gz$/.test(file.name ?? '')) {
85
+ const compression = new GZipCompression();
86
+
87
+ const decompressedData = await compression.decompress(file.data);
88
+ return {data: decompressedData, name: (file.name ?? '').slice(0, -3)};
89
+ }
90
+ return Promise.resolve(file);
91
+ }
92
+
93
+ private async writeFile(options: File, outputPath: string): Promise<void> {
94
+ if (!options.name) {
95
+ return;
96
+ }
97
+ const finalPath = path.join(outputPath, options.name);
98
+ const dirName = path.dirname(finalPath);
99
+ const fileName = path.filename(finalPath);
100
+ await writeFile(dirName, options.data, fileName);
101
+ }
102
+ }