@loaders.gl/tile-converter 4.4.0-alpha.1 → 4.4.0-alpha.9

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 (166) hide show
  1. package/dist/converter.min.cjs +89 -98
  2. package/dist/i3s-server/bin/i3s-server.min.cjs +78 -78
  3. package/package.json +17 -17
  4. package/src/3d-tiles-converter/3d-tiles-converter.ts +11 -7
  5. package/src/3d-tiles-converter/helpers/load-i3s.ts +5 -5
  6. package/src/converter-cli.ts +2 -2
  7. package/src/i3s-converter/helpers/geometry-converter.ts +17 -1
  8. package/src/i3s-converter/helpers/load-3d-tiles.ts +24 -9
  9. package/src/i3s-converter/i3s-converter.ts +25 -9
  10. package/src/i3s-server/controllers/slpk-controller.ts +2 -2
  11. package/src/lib/utils/compress-util.ts +1 -0
  12. package/dist/3d-tiles-converter/3d-tiles-converter.d.ts +0 -115
  13. package/dist/3d-tiles-converter/3d-tiles-converter.d.ts.map +0 -1
  14. package/dist/3d-tiles-converter/3d-tiles-converter.js +0 -388
  15. package/dist/3d-tiles-converter/helpers/3d-tiles-content-converter.d.ts +0 -88
  16. package/dist/3d-tiles-converter/helpers/3d-tiles-content-converter.d.ts.map +0 -1
  17. package/dist/3d-tiles-converter/helpers/3d-tiles-content-converter.js +0 -380
  18. package/dist/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.d.ts +0 -13
  19. package/dist/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.d.ts.map +0 -1
  20. package/dist/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.js +0 -19
  21. package/dist/3d-tiles-converter/helpers/load-i3s.d.ts +0 -37
  22. package/dist/3d-tiles-converter/helpers/load-i3s.d.ts.map +0 -1
  23. package/dist/3d-tiles-converter/helpers/load-i3s.js +0 -98
  24. package/dist/3d-tiles-converter/helpers/texture-atlas.d.ts +0 -10
  25. package/dist/3d-tiles-converter/helpers/texture-atlas.d.ts.map +0 -1
  26. package/dist/3d-tiles-converter/helpers/texture-atlas.js +0 -50
  27. package/dist/3d-tiles-converter/json-templates/tileset.d.ts +0 -15
  28. package/dist/3d-tiles-converter/json-templates/tileset.d.ts.map +0 -1
  29. package/dist/3d-tiles-converter/json-templates/tileset.js +0 -39
  30. package/dist/constants.d.ts +0 -3
  31. package/dist/constants.d.ts.map +0 -1
  32. package/dist/constants.js +0 -2
  33. package/dist/converter-cli.d.ts +0 -2
  34. package/dist/converter-cli.d.ts.map +0 -1
  35. package/dist/converter-cli.js +0 -290
  36. package/dist/deps-installer/deps-installer.d.ts +0 -19
  37. package/dist/deps-installer/deps-installer.d.ts.map +0 -1
  38. package/dist/deps-installer/deps-installer.js +0 -95
  39. package/dist/i3s-converter/helpers/attribute-metadata-info.d.ts +0 -84
  40. package/dist/i3s-converter/helpers/attribute-metadata-info.d.ts.map +0 -1
  41. package/dist/i3s-converter/helpers/attribute-metadata-info.js +0 -219
  42. package/dist/i3s-converter/helpers/batch-ids-extensions.d.ts +0 -26
  43. package/dist/i3s-converter/helpers/batch-ids-extensions.d.ts.map +0 -1
  44. package/dist/i3s-converter/helpers/batch-ids-extensions.js +0 -165
  45. package/dist/i3s-converter/helpers/coordinate-converter.d.ts +0 -39
  46. package/dist/i3s-converter/helpers/coordinate-converter.d.ts.map +0 -1
  47. package/dist/i3s-converter/helpers/coordinate-converter.js +0 -118
  48. package/dist/i3s-converter/helpers/create-scene-server-path.d.ts +0 -9
  49. package/dist/i3s-converter/helpers/create-scene-server-path.d.ts.map +0 -1
  50. package/dist/i3s-converter/helpers/create-scene-server-path.js +0 -21
  51. package/dist/i3s-converter/helpers/feature-attributes.d.ts +0 -54
  52. package/dist/i3s-converter/helpers/feature-attributes.d.ts.map +0 -1
  53. package/dist/i3s-converter/helpers/feature-attributes.js +0 -193
  54. package/dist/i3s-converter/helpers/geometry-attributes.d.ts +0 -8
  55. package/dist/i3s-converter/helpers/geometry-attributes.d.ts.map +0 -1
  56. package/dist/i3s-converter/helpers/geometry-attributes.js +0 -226
  57. package/dist/i3s-converter/helpers/geometry-converter.d.ts +0 -60
  58. package/dist/i3s-converter/helpers/geometry-converter.d.ts.map +0 -1
  59. package/dist/i3s-converter/helpers/geometry-converter.js +0 -1309
  60. package/dist/i3s-converter/helpers/gltf-attributes.d.ts +0 -28
  61. package/dist/i3s-converter/helpers/gltf-attributes.d.ts.map +0 -1
  62. package/dist/i3s-converter/helpers/gltf-attributes.js +0 -122
  63. package/dist/i3s-converter/helpers/load-3d-tiles.d.ts +0 -33
  64. package/dist/i3s-converter/helpers/load-3d-tiles.d.ts.map +0 -1
  65. package/dist/i3s-converter/helpers/load-3d-tiles.js +0 -121
  66. package/dist/i3s-converter/helpers/node-debug.d.ts +0 -8
  67. package/dist/i3s-converter/helpers/node-debug.d.ts.map +0 -1
  68. package/dist/i3s-converter/helpers/node-debug.js +0 -116
  69. package/dist/i3s-converter/helpers/node-index-document.d.ts +0 -111
  70. package/dist/i3s-converter/helpers/node-index-document.d.ts.map +0 -1
  71. package/dist/i3s-converter/helpers/node-index-document.js +0 -269
  72. package/dist/i3s-converter/helpers/node-pages.d.ts +0 -156
  73. package/dist/i3s-converter/helpers/node-pages.d.ts.map +0 -1
  74. package/dist/i3s-converter/helpers/node-pages.js +0 -285
  75. package/dist/i3s-converter/helpers/preprocess-3d-tiles.d.ts +0 -22
  76. package/dist/i3s-converter/helpers/preprocess-3d-tiles.d.ts.map +0 -1
  77. package/dist/i3s-converter/helpers/preprocess-3d-tiles.js +0 -104
  78. package/dist/i3s-converter/helpers/progress.d.ts +0 -90
  79. package/dist/i3s-converter/helpers/progress.d.ts.map +0 -1
  80. package/dist/i3s-converter/helpers/progress.js +0 -148
  81. package/dist/i3s-converter/helpers/tileset-traversal.d.ts +0 -32
  82. package/dist/i3s-converter/helpers/tileset-traversal.d.ts.map +0 -1
  83. package/dist/i3s-converter/helpers/tileset-traversal.js +0 -34
  84. package/dist/i3s-converter/i3s-converter.d.ts +0 -312
  85. package/dist/i3s-converter/i3s-converter.d.ts.map +0 -1
  86. package/dist/i3s-converter/i3s-converter.js +0 -1191
  87. package/dist/i3s-converter/json-templates/geometry-definitions.d.ts +0 -7
  88. package/dist/i3s-converter/json-templates/geometry-definitions.d.ts.map +0 -1
  89. package/dist/i3s-converter/json-templates/geometry-definitions.js +0 -80
  90. package/dist/i3s-converter/json-templates/layers.d.ts +0 -70
  91. package/dist/i3s-converter/json-templates/layers.d.ts.map +0 -1
  92. package/dist/i3s-converter/json-templates/layers.js +0 -132
  93. package/dist/i3s-converter/json-templates/metadata.d.ts +0 -22
  94. package/dist/i3s-converter/json-templates/metadata.d.ts.map +0 -1
  95. package/dist/i3s-converter/json-templates/metadata.js +0 -21
  96. package/dist/i3s-converter/json-templates/node.d.ts +0 -61
  97. package/dist/i3s-converter/json-templates/node.d.ts.map +0 -1
  98. package/dist/i3s-converter/json-templates/node.js +0 -82
  99. package/dist/i3s-converter/json-templates/scene-server.d.ts +0 -28
  100. package/dist/i3s-converter/json-templates/scene-server.d.ts.map +0 -1
  101. package/dist/i3s-converter/json-templates/scene-server.js +0 -27
  102. package/dist/i3s-converter/json-templates/shared-resources.d.ts +0 -14
  103. package/dist/i3s-converter/json-templates/shared-resources.d.ts.map +0 -1
  104. package/dist/i3s-converter/json-templates/shared-resources.js +0 -122
  105. package/dist/i3s-converter/json-templates/store.d.ts +0 -95
  106. package/dist/i3s-converter/json-templates/store.d.ts.map +0 -1
  107. package/dist/i3s-converter/json-templates/store.js +0 -100
  108. package/dist/i3s-converter/types.d.ts +0 -226
  109. package/dist/i3s-converter/types.d.ts.map +0 -1
  110. package/dist/i3s-converter/types.js +0 -37
  111. package/dist/i3s-server/app.d.ts +0 -3
  112. package/dist/i3s-server/app.d.ts.map +0 -1
  113. package/dist/i3s-server/app.js +0 -31
  114. package/dist/i3s-server/bin/www.d.ts +0 -3
  115. package/dist/i3s-server/bin/www.d.ts.map +0 -1
  116. package/dist/i3s-server/bin/www.js +0 -46
  117. package/dist/i3s-server/controllers/slpk-controller.d.ts +0 -13
  118. package/dist/i3s-server/controllers/slpk-controller.d.ts.map +0 -1
  119. package/dist/i3s-server/controllers/slpk-controller.js +0 -32
  120. package/dist/i3s-server/routes/slpk-router.d.ts +0 -3
  121. package/dist/i3s-server/routes/slpk-router.d.ts.map +0 -1
  122. package/dist/i3s-server/routes/slpk-router.js +0 -40
  123. package/dist/i3s-server/utils/create-scene-server.d.ts +0 -17
  124. package/dist/i3s-server/utils/create-scene-server.d.ts.map +0 -1
  125. package/dist/i3s-server/utils/create-scene-server.js +0 -18
  126. package/dist/i3s-server/utils/server-utils.d.ts +0 -21
  127. package/dist/i3s-server/utils/server-utils.d.ts.map +0 -1
  128. package/dist/i3s-server/utils/server-utils.js +0 -60
  129. package/dist/index.cjs +0 -6228
  130. package/dist/index.cjs.map +0 -7
  131. package/dist/index.d.ts +0 -3
  132. package/dist/index.d.ts.map +0 -1
  133. package/dist/index.js +0 -2
  134. package/dist/lib/json-schemas/conversion-dump-json-schema.d.ts +0 -463
  135. package/dist/lib/json-schemas/conversion-dump-json-schema.d.ts.map +0 -1
  136. package/dist/lib/json-schemas/conversion-dump-json-schema.js +0 -285
  137. package/dist/lib/utils/cli-utils.d.ts +0 -34
  138. package/dist/lib/utils/cli-utils.d.ts.map +0 -1
  139. package/dist/lib/utils/cli-utils.js +0 -76
  140. package/dist/lib/utils/compress-util.d.ts +0 -8
  141. package/dist/lib/utils/compress-util.d.ts.map +0 -1
  142. package/dist/lib/utils/compress-util.js +0 -25
  143. package/dist/lib/utils/conversion-dump.d.ts +0 -147
  144. package/dist/lib/utils/conversion-dump.d.ts.map +0 -1
  145. package/dist/lib/utils/conversion-dump.js +0 -257
  146. package/dist/lib/utils/file-utils.d.ts +0 -57
  147. package/dist/lib/utils/file-utils.d.ts.map +0 -1
  148. package/dist/lib/utils/file-utils.js +0 -140
  149. package/dist/lib/utils/geometry-utils.d.ts +0 -9
  150. package/dist/lib/utils/geometry-utils.d.ts.map +0 -1
  151. package/dist/lib/utils/geometry-utils.js +0 -14
  152. package/dist/lib/utils/lod-conversion-utils.d.ts +0 -42
  153. package/dist/lib/utils/lod-conversion-utils.d.ts.map +0 -1
  154. package/dist/lib/utils/lod-conversion-utils.js +0 -71
  155. package/dist/lib/utils/queue.d.ts +0 -7
  156. package/dist/lib/utils/queue.d.ts.map +0 -1
  157. package/dist/lib/utils/queue.js +0 -14
  158. package/dist/lib/utils/statistic-utils.d.ts +0 -20
  159. package/dist/lib/utils/statistic-utils.d.ts.map +0 -1
  160. package/dist/lib/utils/statistic-utils.js +0 -88
  161. package/dist/lib/utils/write-queue.d.ts +0 -44
  162. package/dist/lib/utils/write-queue.d.ts.map +0 -1
  163. package/dist/lib/utils/write-queue.js +0 -82
  164. package/dist/pgm-loader.d.ts +0 -28
  165. package/dist/pgm-loader.d.ts.map +0 -1
  166. package/dist/pgm-loader.js +0 -24
@@ -1,1191 +0,0 @@
1
- // loaders.gl
2
- // SPDX-License-Identifier: MIT
3
- // Copyright (c) vis.gl contributors
4
- import { AttributeMetadataInfo } from "./helpers/attribute-metadata-info.js";
5
- import { load, encode, isBrowser } from '@loaders.gl/core';
6
- import { CesiumIonLoader, Tiles3DLoader } from '@loaders.gl/3d-tiles';
7
- import { join } from 'path';
8
- import { v4 as uuidv4 } from 'uuid';
9
- import process from 'process';
10
- import transform from 'json-map-transform';
11
- import md5 from 'md5';
12
- import NodePages from "./helpers/node-pages.js";
13
- import { writeFile, removeDir, writeFileForSlpk, removeFile } from "../lib/utils/file-utils.js";
14
- import { compressFileWithGzip } from "../lib/utils/compress-util.js";
15
- import { calculateDatasetSize, timeConverter } from "../lib/utils/statistic-utils.js";
16
- import convertB3dmToI3sGeometry, { getPropertyTable } from "./helpers/geometry-converter.js";
17
- import { createBoundingVolumes, convertBoundingVolumeToI3SFullExtent } from "./helpers/coordinate-converter.js";
18
- import { createSceneServerPath } from "./helpers/create-scene-server-path.js";
19
- import { convertGeometricErrorToScreenThreshold } from "../lib/utils/lod-conversion-utils.js";
20
- import { PGMLoader } from "../pgm-loader.js";
21
- import { LAYERS as layersTemplate } from "./json-templates/layers.js";
22
- import { GEOMETRY_DEFINITION as geometryDefinitionTemlate } from "./json-templates/geometry-definitions.js";
23
- import { SHARED_RESOURCES as sharedResourcesTemplate } from "./json-templates/shared-resources.js";
24
- import { validateNodeBoundingVolumes } from "./helpers/node-debug.js";
25
- import { KTX2BasisWriterWorker } from '@loaders.gl/textures';
26
- import { ImageWriter } from '@loaders.gl/images';
27
- import { GLTFPrimitiveModeString, ResourceType } from "./types.js";
28
- import { WorkerFarm } from '@loaders.gl/worker-utils';
29
- import WriteQueue from "../lib/utils/write-queue.js";
30
- import { BROWSER_ERROR_MESSAGE } from "../constants.js";
31
- import { getAttributeTypesMapFromPropertyTable, getAttributeTypesMapFromSchema } from "./helpers/feature-attributes.js";
32
- import { NodeIndexDocument } from "./helpers/node-index-document.js";
33
- import { isNestedTileset, loadNestedTileset, loadTile3DContent, loadFromArchive } from "./helpers/load-3d-tiles.js";
34
- import { Matrix4 } from '@math.gl/core';
35
- import { TILE_REFINEMENT, createBoundingVolume } from '@loaders.gl/tiles';
36
- import { traverseDatasetWith } from "./helpers/tileset-traversal.js";
37
- import { analyzeTileContent, mergePreprocessData } from "./helpers/preprocess-3d-tiles.js";
38
- import { Progress } from "./helpers/progress.js";
39
- import { composeHashFile, createZip } from '@loaders.gl/zip';
40
- import { ConversionDump } from "../lib/utils/conversion-dump.js";
41
- // eslint-disable-next-line no-process-env
42
- const ION_DEFAULT_TOKEN = process.env?.IonToken;
43
- const HARDCODED_NODES_PER_PAGE = 64;
44
- const _3D_TILES = '3DTILES';
45
- const _3D_OBJECT_LAYER_TYPE = '3DObject';
46
- const REFRESH_TOKEN_TIMEOUT = 1800; // 30 minutes in seconds
47
- const CESIUM_DATASET_PREFIX = 'https://';
48
- // const FS_FILE_TOO_LARGE = 'ERR_FS_FILE_TOO_LARGE';
49
- const PROGRESS_PHASE1_COUNT = 'phase1-count';
50
- /**
51
- * Converter from 3d-tiles tileset to i3s layer
52
- */
53
- export default class I3SConverter {
54
- attributeMetadataInfo;
55
- nodePages;
56
- options;
57
- layers0Path;
58
- materialMap;
59
- materialDefinitions;
60
- geometryMap;
61
- geometryConfigs;
62
- vertexCounter;
63
- layers0;
64
- featuresHashArray;
65
- refinementCounter;
66
- validate;
67
- boundingVolumeWarnings = [];
68
- conversionStartTime = [0, 0];
69
- refreshTokenTime = [0, 0];
70
- sourceTileset = null;
71
- loadOptions = {
72
- _nodeWorkers: true,
73
- reuseWorkers: true,
74
- useLocalLibraries: true,
75
- basis: {
76
- format: 'rgba32',
77
- // We need to load local fs workers because nodejs can't load workers from the Internet
78
- workerUrl: './modules/textures/dist/basis-worker-node.js'
79
- },
80
- // We need to load local fs workers because nodejs can't load workers from the Internet
81
- draco: { workerUrl: './modules/draco/dist/draco-worker-node.js' },
82
- fetch: {},
83
- modules: {}
84
- };
85
- geoidHeightModel = null;
86
- Loader = Tiles3DLoader;
87
- generateTextures;
88
- generateBoundingVolumes;
89
- layersHasTexture;
90
- workerSource = {};
91
- writeQueue = new WriteQueue(new ConversionDump());
92
- compressList = null;
93
- preprocessData = {
94
- meshTopologyTypes: new Set(),
95
- metadataClasses: new Set()
96
- };
97
- progresses = {};
98
- conversionDump;
99
- constructor() {
100
- this.attributeMetadataInfo = new AttributeMetadataInfo();
101
- this.nodePages = new NodePages(writeFile, HARDCODED_NODES_PER_PAGE, this);
102
- this.options = {};
103
- this.layers0Path = '';
104
- this.materialMap = new Map();
105
- this.materialDefinitions = [];
106
- this.geometryMap = new Map();
107
- this.geometryConfigs = [];
108
- this.vertexCounter = 0;
109
- this.layers0 = null;
110
- this.featuresHashArray = [];
111
- this.refinementCounter = {
112
- tilesCount: 0,
113
- tilesWithAddRefineCount: 0
114
- };
115
- this.validate = false;
116
- this.generateTextures = false;
117
- this.generateBoundingVolumes = false;
118
- this.layersHasTexture = false;
119
- this.compressList = null;
120
- this.conversionDump = new ConversionDump();
121
- }
122
- /**
123
- * Convert a 3d tileset
124
- * @param options
125
- * @param options.inputUrl the url to read the tileset from
126
- * @param options.outputPath the output filename
127
- * @param options.tilesetName the output name of the tileset
128
- * @param options.maxDepth The max tree depth of conversion
129
- * @param options.egmFilePath location of *.pgm file to convert heights from ellipsoidal to gravity-related format
130
- * @param options.token Token for Cesium ION tilesets authentication
131
- * @param options.draco Generate I3S 1.7 draco compressed geometries
132
- * @param options.validate -enable validation
133
- * @param options.generateTextures - generate alternative type of textures (to have non-compressed jpeg/png and compressed ktx2)
134
- * @param options.generateBoundingVolumes - generate bounding volumes from vertices coordinates instead of source tiles bounding volumes
135
- * @param options.instantNodeWriting - Keep created 3DNodeIndexDocument files on disk instead of memory. This option reduce memory usage but decelerates conversion speed
136
- */
137
- // eslint-disable-next-line max-statements, complexity
138
- async convert(options) {
139
- if (isBrowser) {
140
- console.log(BROWSER_ERROR_MESSAGE); // eslint-disable-line no-console
141
- return BROWSER_ERROR_MESSAGE;
142
- }
143
- this.conversionStartTime = process.hrtime();
144
- const { tilesetName, egmFilePath, inputUrl, validate, outputPath, draco = true, maxDepth, token, generateTextures, generateBoundingVolumes, instantNodeWriting = false, mergeMaterials = true, inquirer, metadataClass, analyze = false } = options;
145
- this.options = {
146
- outputPath,
147
- tilesetName,
148
- maxDepth,
149
- egmFilePath,
150
- draco,
151
- token,
152
- inputUrl,
153
- instantNodeWriting,
154
- mergeMaterials,
155
- inquirer,
156
- metadataClass
157
- };
158
- this.progresses[PROGRESS_PHASE1_COUNT] = new Progress();
159
- this.compressList = (this.options.instantNodeWriting && []) || null;
160
- this.validate = Boolean(validate);
161
- this.Loader = inputUrl.indexOf(CESIUM_DATASET_PREFIX) !== -1 ? CesiumIonLoader : Tiles3DLoader;
162
- this.generateTextures = Boolean(generateTextures);
163
- this.generateBoundingVolumes = Boolean(generateBoundingVolumes);
164
- this.writeQueue = new WriteQueue(this.conversionDump);
165
- this.writeQueue.startListening();
166
- if (egmFilePath.toLowerCase() === 'none') {
167
- console.log('--egm chousen to be "none", skip loading egm file'); // eslint-disable-line
168
- }
169
- else {
170
- console.log('Loading egm file...'); // eslint-disable-line
171
- this.geoidHeightModel = await load(egmFilePath, PGMLoader);
172
- console.log('Loading egm file completed!'); // eslint-disable-line
173
- }
174
- this.nodePages.useWriteFunction(writeFileForSlpk);
175
- try {
176
- const preloadOptions = await this._fetchPreloadOptions();
177
- let tilesetUrl = inputUrl;
178
- if (preloadOptions.url) {
179
- tilesetUrl = preloadOptions.url;
180
- }
181
- if (preloadOptions.headers) {
182
- this.loadOptions.fetch = { headers: preloadOptions.headers };
183
- }
184
- this.sourceTileset = await loadFromArchive(tilesetUrl, this.Loader, this.loadOptions);
185
- const preprocessResult = this.Loader === Tiles3DLoader || analyze ? await this.preprocessConversion() : true;
186
- if (preprocessResult && !analyze) {
187
- const selectMetadataClassResult = await this.selectMetadataClass();
188
- if (selectMetadataClassResult) {
189
- await this._createAndSaveTileset(outputPath, tilesetName);
190
- await this._finishConversion({ outputPath, tilesetName });
191
- }
192
- }
193
- }
194
- catch (error) {
195
- throw error;
196
- }
197
- finally {
198
- await this.writeQueue.finalize();
199
- // Clean up worker pools
200
- const workerFarm = WorkerFarm.getWorkerFarm({});
201
- workerFarm.destroy();
202
- }
203
- return 'success';
204
- }
205
- /**
206
- * Preprocess stage of the tile converter. Traverse all the tiles tree and
207
- * check a tile content to be sure that the data is supported
208
- * @returns true - the conversion is possible, false - the tileset's content is not supported
209
- */
210
- async preprocessConversion() {
211
- // eslint-disable-next-line no-console
212
- console.log('Analyze source tileset');
213
- const sourceRootTile = this.sourceTileset.root;
214
- await traverseDatasetWith({
215
- tile: sourceRootTile,
216
- traversalProps: null,
217
- processTile: this.analyzeTile.bind(this),
218
- postprocessTile: undefined,
219
- maxDepth: this.options.maxDepth
220
- });
221
- const { meshTopologyTypes, metadataClasses } = this.preprocessData;
222
- // eslint-disable-next-line no-console
223
- console.log('------------------------------------------------');
224
- // eslint-disable-next-line no-console
225
- console.log('Preprocess results:');
226
- // eslint-disable-next-line no-console
227
- console.log(`Tile count: ${this.progresses[PROGRESS_PHASE1_COUNT].stepsTotal}`);
228
- // eslint-disable-next-line no-console
229
- console.log(`glTF mesh topology types: ${Array.from(meshTopologyTypes).join(', ')}`);
230
- if (metadataClasses.size) {
231
- // eslint-disable-next-line no-console
232
- console.log(`Feature metadata classes have been found: ${Array.from(metadataClasses).join(', ')}`);
233
- }
234
- else {
235
- // eslint-disable-next-line no-console
236
- console.log('Feature metadata classes have not been found');
237
- }
238
- if (!meshTopologyTypes.has(GLTFPrimitiveModeString.TRIANGLES) &&
239
- !meshTopologyTypes.has(GLTFPrimitiveModeString.TRIANGLE_STRIP)) {
240
- // eslint-disable-next-line no-console
241
- console.log('The tileset is of unsupported mesh topology types. The conversion will be interrupted.');
242
- // eslint-disable-next-line no-console
243
- console.log('------------------------------------------------');
244
- return false;
245
- }
246
- // eslint-disable-next-line no-console
247
- console.log('------------------------------------------------');
248
- return true;
249
- }
250
- /**
251
- * Analyze a tile content. The callback for preprocess stage.
252
- * @param sourceTile - 3DTiles tile JSON metadata
253
- * @param traversalProps - mandatory argument but it is not used for the preprocess stage
254
- * @returns - nothing
255
- */
256
- async analyzeTile(sourceTile, traversalProps) {
257
- const isTileset = isNestedTileset(sourceTile);
258
- if (isTileset) {
259
- await loadNestedTileset(this.sourceTileset, sourceTile, this.loadOptions);
260
- return null;
261
- }
262
- if (sourceTile.id) {
263
- this.progresses[PROGRESS_PHASE1_COUNT].stepsTotal += 1;
264
- console.log(`[analyze]: ${sourceTile.id}`); // eslint-disable-line
265
- }
266
- let tileContent = null;
267
- try {
268
- tileContent = await loadTile3DContent(this.sourceTileset, sourceTile, {
269
- ...this.loadOptions,
270
- '3d-tiles': { ...this.loadOptions['3d-tiles'], loadGLTF: false }
271
- });
272
- }
273
- catch (error) {
274
- // eslint-disable-next-line no-console
275
- console.log(`[warning]: Failed to load ${sourceTile.contentUrl}. An I3S tile with empty content will be added to the output tileset`);
276
- }
277
- const tilePreprocessData = await analyzeTileContent(tileContent);
278
- mergePreprocessData(this.preprocessData, tilePreprocessData);
279
- return null;
280
- }
281
- /**
282
- * Select metadata class associated with the set of feature attributes
283
- * @returns true if the metadata class has been successfully selected
284
- */
285
- async selectMetadataClass() {
286
- const { metadataClasses } = this.preprocessData;
287
- if (metadataClasses.size > 1) {
288
- if (this.options.metadataClass?.length) {
289
- // eslint-disable-next-line no-console
290
- console.log(`${this.options.metadataClass} has been selected`);
291
- }
292
- else if (this.options.inquirer) {
293
- const result = await this.options.inquirer.prompt([
294
- {
295
- name: 'metadataClass',
296
- type: 'list',
297
- message: 'Select feature metadata data class to convert...',
298
- choices: Array.from(metadataClasses)
299
- }
300
- ]);
301
- this.options.metadataClass = result.metadataClass;
302
- // eslint-disable-next-line no-console
303
- console.log(`${result.metadataClass} has been selected`);
304
- }
305
- else {
306
- // eslint-disable-next-line no-console
307
- console.log(`A feature metadata class has not been selected. Start the converter with option "--metadata-class". For example, "npx tile-converter ... --metadata-class ${Array.from(metadataClasses)[0]}"`);
308
- // eslint-disable-next-line no-console
309
- console.log('------------------------------------------------');
310
- return false;
311
- }
312
- }
313
- return true;
314
- }
315
- /**
316
- * Convert and save the layer and embedded tiles
317
- * @param outputPath - path to save output data
318
- * @param tilesetName - new tileset path
319
- */
320
- // eslint-disable-next-line max-statements, complexity
321
- async _createAndSaveTileset(outputPath, tilesetName) {
322
- const tilesetPath = join(`${outputPath}`, `${tilesetName}`);
323
- await this.conversionDump.createDump(this.options);
324
- if (this.conversionDump.restored && this.options.inquirer) {
325
- const result = await this.options.inquirer.prompt([
326
- {
327
- name: 'resumeConversion',
328
- type: 'confirm',
329
- message: 'Dump file of the previous conversion exists, do you want to resume that conversion?'
330
- }
331
- ]);
332
- if (!result.resumeConversion) {
333
- this.conversionDump.reset();
334
- }
335
- }
336
- this.layers0Path = join(tilesetPath, 'SceneServer', 'layers', '0');
337
- // Removing the tilesetPath needed to exclude erroneous files after conversion
338
- const removePath = this.conversionDump.restored
339
- ? join(this.layers0Path, 'nodepages')
340
- : tilesetPath;
341
- try {
342
- await removeDir(removePath);
343
- }
344
- catch (e) {
345
- // do nothing
346
- }
347
- if (this.conversionDump.restored && this.conversionDump.attributeMetadataInfo) {
348
- this.attributeMetadataInfo.fromObject(this.conversionDump.attributeMetadataInfo);
349
- }
350
- this.materialDefinitions = [];
351
- this.materialMap = new Map();
352
- if (this.conversionDump.restored && this.conversionDump.materialDefinitions) {
353
- for (let i = 0; i < this.conversionDump.materialDefinitions.length; i++) {
354
- const hash = md5(JSON.stringify(this.conversionDump.materialDefinitions[i]));
355
- this.materialMap.set(hash, i);
356
- }
357
- this.materialDefinitions = this.conversionDump.materialDefinitions;
358
- }
359
- const sourceRootTile = this.sourceTileset.root;
360
- const sourceBoundingVolume = createBoundingVolume(sourceRootTile.boundingVolume, new Matrix4(sourceRootTile.transform), null);
361
- this._formLayers0(tilesetName, sourceBoundingVolume, this.sourceTileset?.root?.boundingVolume?.region);
362
- const boundingVolumes = createBoundingVolumes(sourceBoundingVolume, this.geoidHeightModel);
363
- await this.nodePages.push({
364
- index: 0,
365
- lodThreshold: 0,
366
- obb: boundingVolumes.obb,
367
- children: []
368
- });
369
- this.progresses[PROGRESS_PHASE1_COUNT].startMonitoring();
370
- const rootNode = await NodeIndexDocument.createRootNode(boundingVolumes, this);
371
- await traverseDatasetWith({
372
- tile: sourceRootTile,
373
- traversalProps: {
374
- transform: new Matrix4(sourceRootTile.transform),
375
- parentNodes: [rootNode]
376
- },
377
- processTile: this.convertTile.bind(this),
378
- postprocessTile: this.finalizeTile.bind(this),
379
- maxDepth: this.options.maxDepth
380
- });
381
- this.progresses[PROGRESS_PHASE1_COUNT].stopMonitoring();
382
- console.log(`[finalizing conversion]`); // eslint-disable-line
383
- this.layers0.attributeStorageInfo = this.attributeMetadataInfo.attributeStorageInfo;
384
- this.layers0.fields = this.attributeMetadataInfo.fields;
385
- this.layers0.popupInfo = this.attributeMetadataInfo.popupInfo;
386
- if (this.attributeMetadataInfo.attributeStorageInfo.length) {
387
- this.layers0.layerType = _3D_OBJECT_LAYER_TYPE;
388
- }
389
- if (this.conversionDump.restored && this.conversionDump.textureSetDefinitions) {
390
- this.layers0.textureSetDefinitions = this.conversionDump.textureSetDefinitions;
391
- }
392
- this.layers0.materialDefinitions = this.materialDefinitions;
393
- // @ts-ignore
394
- this.layers0.geometryDefinitions = transform(this.geometryConfigs.map((config) => ({
395
- geometryConfig: { ...config, draco: this.options.draco }
396
- })), geometryDefinitionTemlate());
397
- if (this.layersHasTexture === false) {
398
- this.layers0.store.defaultGeometrySchema.ordering =
399
- this.layers0.store.defaultGeometrySchema.ordering.filter((attribute) => attribute !== 'uv0');
400
- }
401
- await this._writeLayers0();
402
- createSceneServerPath(tilesetName, this.layers0, tilesetPath);
403
- for (const filePath of this.compressList || []) {
404
- await compressFileWithGzip(filePath);
405
- await removeFile(filePath);
406
- }
407
- await this.nodePages.save();
408
- await this.writeQueue.finalize();
409
- await this._createSlpk(tilesetPath);
410
- }
411
- /**
412
- * Form object of 3DSceneLayer https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DSceneLayer.cmn.md
413
- * @param tilesetName - Name of layer
414
- * @param sourceBoundingVolume - initialized bounding volume of the source root tile
415
- * @param boundingVolumeRegion - region bounding volume of the source root tile
416
- */
417
- _formLayers0(tilesetName, sourceBoundingVolume, boundingVolumeRegion) {
418
- if (!this.sourceTileset?.root) {
419
- return;
420
- }
421
- const fullExtent = convertBoundingVolumeToI3SFullExtent(sourceBoundingVolume);
422
- if (boundingVolumeRegion) {
423
- fullExtent.zmin = boundingVolumeRegion[4];
424
- fullExtent.zmax = boundingVolumeRegion[5];
425
- }
426
- const extent = [fullExtent.xmin, fullExtent.ymin, fullExtent.xmax, fullExtent.ymax];
427
- const layers0data = {
428
- version: `{${uuidv4().toUpperCase()}}`,
429
- id: 0,
430
- name: tilesetName,
431
- href: './layers/0',
432
- store: {
433
- id: `{${uuidv4().toUpperCase()}}`,
434
- extent
435
- },
436
- nodePages: {
437
- nodesPerPage: HARDCODED_NODES_PER_PAGE
438
- },
439
- compressGeometry: this.options.draco,
440
- fullExtent
441
- };
442
- this.layers0 = transform(layers0data, layersTemplate());
443
- }
444
- /**
445
- * Write 3DSceneLayer https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DSceneLayer.cmn.md in file
446
- */
447
- async _writeLayers0() {
448
- await this.writeQueue.enqueue({
449
- archiveKey: '3dSceneLayer.json.gz',
450
- writePromise: () => writeFileForSlpk(this.layers0Path, JSON.stringify(this.layers0), '3dSceneLayer.json')
451
- });
452
- }
453
- /**
454
- * Pack files into *.slpk archive
455
- * @param tilesetPath - Path to save file
456
- */
457
- async _createSlpk(tilesetPath) {
458
- await this.conversionDump.deleteDumpFile();
459
- const slpkTilesetPath = join(tilesetPath, 'SceneServer', 'layers', '0');
460
- const slpkFileName = `${tilesetPath}.slpk`;
461
- await createZip(slpkTilesetPath, slpkFileName, async (fileList) => ({
462
- path: '@specialIndexFileHASH128@',
463
- file: await composeHashFile(fileList)
464
- }));
465
- try {
466
- await removeDir(tilesetPath);
467
- }
468
- catch (e) {
469
- // do nothing
470
- }
471
- }
472
- /**
473
- * Convert the specific 3DTiles tile to I3S nodes.
474
- * This is callback function for the traversal generic function
475
- * @param sourceTile - current 3DTiles tile JSON metadata
476
- * @param traversalProps - traversal properties calculated recursively
477
- * @returns - traversal properties for the child tiles
478
- */
479
- // eslint-disable-next-line max-statements
480
- async convertTile(sourceTile, traversalProps) {
481
- const isTileset = isNestedTileset(sourceTile);
482
- if (isTileset || sourceTile.type === 'empty') {
483
- if (isTileset) {
484
- if (sourceTile.id) {
485
- console.log(`[load]: ${sourceTile.id}`); // eslint-disable-line
486
- }
487
- await loadNestedTileset(this.sourceTileset, sourceTile, this.loadOptions);
488
- }
489
- return traversalProps;
490
- }
491
- if (sourceTile.id) {
492
- console.log(`[convert]: ${sourceTile.id}`); // eslint-disable-line
493
- }
494
- const { parentNodes, transform } = traversalProps;
495
- let transformationMatrix = transform.clone();
496
- if (sourceTile.transform) {
497
- transformationMatrix = transformationMatrix.multiplyRight(sourceTile.transform);
498
- }
499
- const parentNode = parentNodes[0];
500
- const restoreResult = await this._restoreNode(parentNode, sourceTile, transformationMatrix);
501
- let childNodes;
502
- if (restoreResult === null) {
503
- childNodes = await this._createNode(parentNode, sourceTile, transformationMatrix);
504
- }
505
- else {
506
- childNodes = restoreResult;
507
- }
508
- await parentNode.addChildren(childNodes);
509
- const newTraversalProps = {
510
- transform: transformationMatrix,
511
- parentNodes: childNodes
512
- };
513
- if (sourceTile.id) {
514
- this.progresses[PROGRESS_PHASE1_COUNT].stepsDone += 1;
515
- let timeRemainingString = 'Calculating time left...';
516
- const timeRemainingStringBasedOnCount = this.progresses[PROGRESS_PHASE1_COUNT].getTimeRemainingString();
517
- if (timeRemainingStringBasedOnCount) {
518
- timeRemainingString = `${timeRemainingStringBasedOnCount} left`;
519
- }
520
- const percentString = this.progresses[PROGRESS_PHASE1_COUNT].getPercentString();
521
- const progressString = percentString ? ` ${percentString}%, ${timeRemainingString}` : '';
522
- console.log(`[converted${progressString}]: ${sourceTile.id}`); // eslint-disable-line
523
- }
524
- return newTraversalProps;
525
- }
526
- /**
527
- * Do final action with nodes after the current node and all child nodes been converted.
528
- * @param conversionResults - array of conversion results of the current node
529
- * @param currentTraversalProps - traversal properties of the current node
530
- */
531
- async finalizeTile(conversionResults, currentTraversalProps) {
532
- for (const result of conversionResults) {
533
- for (const node of result.parentNodes) {
534
- await node.addNeighbors();
535
- }
536
- }
537
- for (const node of currentTraversalProps.parentNodes) {
538
- await node.save();
539
- }
540
- }
541
- /**
542
- * Generate NodeIndexDocument
543
- * @param boundingVolumes - Bounding volumes
544
- * @param resources - converted or dumped node resources data
545
- * @param parentNode - 3DNodeIndexDocument of parent node
546
- * @param sourceTile - source 3DTile data
547
- * @param isDumped - indicator if the node is dumped
548
- * @return NodeIndexDocument, nodeInPage and node data
549
- */
550
- async _generateNodeIndexDocument(boundingVolumes, resources, parentNode, sourceTile, isDumped) {
551
- this.layersHasTexture =
552
- this.layersHasTexture ||
553
- Boolean(('texture' in resources && resources.texture) ||
554
- ('texelCountHint' in resources && resources.texelCountHint));
555
- if (this.generateBoundingVolumes && resources.boundingVolumes) {
556
- boundingVolumes = resources.boundingVolumes;
557
- }
558
- const lodSelection = convertGeometricErrorToScreenThreshold(sourceTile, boundingVolumes);
559
- const maxScreenThresholdSQ = lodSelection.find((val) => val.metricType === 'maxScreenThresholdSQ') || { maxError: 0 };
560
- if (isDumped) {
561
- const draftObb = {
562
- center: [],
563
- halfSize: [],
564
- quaternion: []
565
- };
566
- await this.nodePages.push({ index: 0, obb: draftObb }, parentNode.inPageId);
567
- }
568
- const nodeInPage = await this._updateNodeInNodePages(maxScreenThresholdSQ, boundingVolumes, sourceTile, parentNode.inPageId, resources);
569
- const nodeData = await NodeIndexDocument.createNodeIndexDocument(parentNode, boundingVolumes, lodSelection, nodeInPage, resources);
570
- const node = await new NodeIndexDocument(nodeInPage.index, this).addData(nodeData);
571
- return { node, nodeInPage, nodeData };
572
- }
573
- /**
574
- * Restore 3DNodeIndexDocument from a comversion dump file
575
- * @param parentNode - 3DNodeIndexDocument of parent node
576
- * @param sourceTile - source 3DTile data
577
- * @param transformationMatrix - transformation matrix of the current tile, calculated recursively multiplying
578
- * transform of all parent tiles and transform of the current tile
579
- */
580
- async _restoreNode(parentNode, sourceTile, transformationMatrix) {
581
- this._checkAddRefinementTypeForTile(sourceTile);
582
- await this._updateTilesetOptions();
583
- if (this.conversionDump.restored &&
584
- sourceTile.id &&
585
- this.conversionDump.isFileConversionComplete(sourceTile.id)) {
586
- const sourceBoundingVolume = createBoundingVolume(sourceTile.boundingVolume, transformationMatrix, null);
587
- const boundingVolumes = createBoundingVolumes(sourceBoundingVolume, this.geoidHeightModel);
588
- const nodes = [];
589
- for (const convertedNode of this.conversionDump.tilesConverted[sourceTile.id].nodes) {
590
- const { node } = await this._generateNodeIndexDocument(boundingVolumes, {
591
- ...convertedNode.dumpMetadata,
592
- nodeId: convertedNode.nodeId
593
- }, parentNode, sourceTile, true);
594
- nodes.push(node);
595
- }
596
- return nodes;
597
- }
598
- else if (this.conversionDump.restored && sourceTile.id) {
599
- // clear existing record in a dump
600
- this.conversionDump.clearDumpRecord(sourceTile.id);
601
- }
602
- return null;
603
- }
604
- /**
605
- * Convert tile to one or more I3S nodes
606
- * @param parentNode - 3DNodeIndexDocument of parent node
607
- * @param sourceTile - source 3DTile data
608
- * @param transformationMatrix - transformation matrix of the current tile, calculated recursively multiplying
609
- * transform of all parent tiles and transform of the current tile
610
- * @param level - tree level
611
- */
612
- // eslint-disable-next-line max-statements
613
- async _createNode(parentNode, sourceTile, transformationMatrix) {
614
- this._checkAddRefinementTypeForTile(sourceTile);
615
- await this._updateTilesetOptions();
616
- let tileContent = null;
617
- try {
618
- tileContent = await loadTile3DContent(this.sourceTileset, sourceTile, this.loadOptions);
619
- }
620
- catch (error) {
621
- // eslint-disable-next-line no-console
622
- console.log(`[warning]: Failed to load ${sourceTile.contentUrl}`);
623
- }
624
- const sourceBoundingVolume = createBoundingVolume(sourceTile.boundingVolume, transformationMatrix, null);
625
- const boundingVolumes = createBoundingVolumes(sourceBoundingVolume, this.geoidHeightModel);
626
- const propertyTable = getPropertyTable(tileContent, this.options.metadataClass);
627
- this.createAttributeStorageInfo(tileContent, propertyTable);
628
- this.conversionDump.attributeMetadataInfo = {
629
- attributeStorageInfo: this.attributeMetadataInfo.attributeStorageInfo,
630
- fields: this.attributeMetadataInfo.fields,
631
- popupInfo: this.attributeMetadataInfo.popupInfo
632
- };
633
- const resourcesData = await this._convertResources({
634
- sourceTile,
635
- transformationMatrix,
636
- boundingVolume: sourceBoundingVolume,
637
- tileContent,
638
- parentId: parentNode.inPageId,
639
- propertyTable
640
- });
641
- const nodes = [];
642
- const nodeIds = [];
643
- const nodesInPage = [];
644
- const emptyResources = {
645
- geometry: null,
646
- compressedGeometry: null,
647
- texture: null,
648
- hasUvRegions: false,
649
- sharedResources: null,
650
- meshMaterial: null,
651
- vertexCount: null,
652
- attributes: null,
653
- featureCount: null,
654
- boundingVolumes: null
655
- };
656
- for (const resources of resourcesData || [emptyResources]) {
657
- const { node, nodeInPage, nodeData } = await this._generateNodeIndexDocument(boundingVolumes, resources, parentNode, sourceTile, false);
658
- nodes.push(node);
659
- if (nodeInPage.mesh) {
660
- // update a record in a dump file
661
- if (sourceTile.id) {
662
- const dumpMetadata = {
663
- boundingVolumes: resources.boundingVolumes,
664
- attributesCount: resources.attributes?.length,
665
- featureCount: resources.featureCount,
666
- geometry: Boolean(resources.geometry),
667
- hasUvRegions: resources.hasUvRegions,
668
- materialId: nodeInPage.mesh.material.definition,
669
- texelCountHint: nodeInPage.mesh.material.texelCountHint,
670
- vertexCount: resources.vertexCount
671
- };
672
- this.conversionDump.setMaterialsDefinitions(this.materialDefinitions);
673
- await this.conversionDump.addNode(sourceTile.id, nodeInPage.index, dumpMetadata);
674
- }
675
- // write resources
676
- await this._writeResources(resources, node.id, sourceTile);
677
- }
678
- if (this.validate) {
679
- this.boundingVolumeWarnings = validateNodeBoundingVolumes(nodeData);
680
- if (this.boundingVolumeWarnings && this.boundingVolumeWarnings.length) {
681
- console.warn('Bounding Volume Warnings: ', ...this.boundingVolumeWarnings); //eslint-disable-line
682
- }
683
- }
684
- nodeIds.push(nodeInPage.index);
685
- nodesInPage.push(nodeInPage);
686
- }
687
- return nodes;
688
- }
689
- /**
690
- * Convert tile to one or more I3S nodes
691
- * @param sourceTile - source tile (3DTile)
692
- * @param transformationMatrix - transformation matrix of the current tile, calculated recursively multiplying
693
- * transform of all parent tiles and transform of the current tile
694
- * @param boundingVolume - initialized bounding volume of the source tile
695
- * @param tileContent - content of the source tile
696
- * @param parentId - id of parent node in node pages
697
- * @param propertyTable - batch table from b3dm / feature properties from EXT_FEATURE_METADATA, EXT_MESH_FEATURES or EXT_STRUCTURAL_METADATA
698
- * @returns - converted node resources
699
- */
700
- async _convertResources({ sourceTile, transformationMatrix, boundingVolume, tileContent, parentId, propertyTable }) {
701
- if (!this.isContentSupported(sourceTile) || !tileContent) {
702
- return null;
703
- }
704
- const draftObb = {
705
- center: [],
706
- halfSize: [],
707
- quaternion: []
708
- };
709
- const resourcesData = await convertB3dmToI3sGeometry({
710
- tileContent,
711
- tileTransform: transformationMatrix,
712
- tileBoundingVolume: boundingVolume,
713
- addNodeToNodePage: async () => (await this.nodePages.push({ index: 0, obb: draftObb }, parentId)).index,
714
- propertyTable,
715
- featuresHashArray: this.featuresHashArray,
716
- attributeStorageInfo: this.attributeMetadataInfo.attributeStorageInfo,
717
- draco: this.options.draco ?? false,
718
- generateBoundingVolumes: this.generateBoundingVolumes,
719
- shouldMergeMaterials: this.options.mergeMaterials ?? false,
720
- geoidHeightModel: this.geoidHeightModel,
721
- libraries: this.loadOptions.modules,
722
- metadataClass: this.options.metadataClass
723
- });
724
- return resourcesData;
725
- }
726
- /**
727
- * Update node object (https://github.com/Esri/i3s-spec/blob/master/docs/1.7/node.cmn.md)
728
- * in node pages (https://github.com/Esri/i3s-spec/blob/master/docs/1.7/nodePage.cmn.md)
729
- * @param maxScreenThresholdSQ - Level of Details (LOD) metric
730
- * @param boundingVolumes - Bounding volumes
731
- * @param sourceTile - source tile (3DTile)
732
- * @param parentId - id of parent node in node pages
733
- * @param resources - the node resources data
734
- * @param resources.meshMaterial - PBR-like material object
735
- * @param resources.texture - texture image
736
- * @param resources.vertexCount - number of vertices in geometry
737
- * @param resources.featureCount - number of features
738
- * @param resources.geometry - Uint8Array with geometry attributes
739
- * @return the node object in node pages
740
- */
741
- // eslint-disable-next-line max-statements, complexity
742
- async _updateNodeInNodePages(maxScreenThresholdSQ, boundingVolumes, sourceTile, parentId, resources) {
743
- const { vertexCount, featureCount, geometry, hasUvRegions } = resources;
744
- const nodeInPage = {
745
- index: 0,
746
- lodThreshold: maxScreenThresholdSQ.maxError,
747
- obb: boundingVolumes.obb,
748
- children: []
749
- };
750
- if (geometry && this.isContentSupported(sourceTile)) {
751
- nodeInPage.mesh = {
752
- geometry: {
753
- definition: this.findOrCreateGeometryDefinition(Boolean(('texture' in resources && resources.texture) ||
754
- ('texelCountHint' in resources && resources.texelCountHint)), hasUvRegions),
755
- resource: 0
756
- },
757
- attribute: {
758
- resource: 0
759
- },
760
- material: {
761
- definition: 0
762
- }
763
- };
764
- }
765
- const nodeId = 'nodeId' in resources ? resources.nodeId : undefined;
766
- let node;
767
- if (!nodeId) {
768
- node = await this.nodePages.push(nodeInPage, parentId);
769
- }
770
- else {
771
- node = await this.nodePages.getNodeById(nodeId);
772
- }
773
- if (!nodeInPage.mesh) {
774
- // eslint-disable-next-line no-console
775
- console.log(`[warning]: node ${node.index} is created with empty content`);
776
- }
777
- NodePages.updateAll(node, nodeInPage);
778
- if ('meshMaterial' in resources && resources.meshMaterial) {
779
- NodePages.updateMaterialByNodeId(node, this._findOrCreateMaterial(resources.meshMaterial));
780
- }
781
- else if ('materialId' in resources && resources.materialId !== null) {
782
- NodePages.updateMaterialByNodeId(node, resources.materialId);
783
- }
784
- if ('texture' in resources && resources.texture) {
785
- const texelCountHint = resources.texture.image.height * resources.texture.image.width;
786
- NodePages.updateTexelCountHintByNodeId(node, texelCountHint);
787
- }
788
- else if ('texelCountHint' in resources && resources.texelCountHint) {
789
- NodePages.updateTexelCountHintByNodeId(node, resources.texelCountHint);
790
- }
791
- if (vertexCount) {
792
- this.vertexCounter += vertexCount;
793
- NodePages.updateVertexCountByNodeId(node, vertexCount);
794
- }
795
- NodePages.updateNodeAttributeByNodeId(node);
796
- if (featureCount) {
797
- NodePages.updateFeatureCountByNodeId(node, featureCount);
798
- }
799
- this.nodePages.saveNode(node);
800
- return node;
801
- }
802
- /**
803
- * Write node resources in files
804
- * @param resources - source tile (3DTile)
805
- * @param resources.geometry - Uint8Array with geometry attributes
806
- * @param resources.compressedGeometry - Uint8Array with compressed (draco) geometry
807
- * @param resources.texture - texture image
808
- * @param resources.sharedResources - shared resource data object
809
- * @param resources.attributes - feature attributes
810
- * @param nodePath - node path
811
- * @param sourceTile - source tile (3DTile)
812
- * @return {Promise<void>}
813
- */
814
- async _writeResources(resources, nodePath, sourceTile) {
815
- const { geometry: geometryBuffer, compressedGeometry, texture, sharedResources, attributes } = resources;
816
- const childPath = join(this.layers0Path, 'nodes', nodePath);
817
- const slpkChildPath = join('nodes', nodePath);
818
- await this._writeGeometries({
819
- geometryBuffer,
820
- compressedGeometry,
821
- childPath,
822
- slpkChildPath,
823
- sourceId: sourceTile.id || '',
824
- nodeId: parseInt(nodePath)
825
- });
826
- await this._writeShared({
827
- sharedResources,
828
- childPath,
829
- slpkChildPath,
830
- nodePath,
831
- sourceId: sourceTile.id || '',
832
- nodeId: parseInt(nodePath)
833
- });
834
- await this._writeTexture(texture, childPath, slpkChildPath, sourceTile.id || '', parseInt(nodePath));
835
- await this._writeAttributes(attributes, childPath, slpkChildPath, sourceTile.id || '', parseInt(nodePath));
836
- }
837
- /**
838
- * Write non-compressed and compressed geometries in files
839
- * @param geometryBuffer - Uint8Array with geometry attributes
840
- * @param compressedGeometry - Uint8Array with compressed (draco) geometry
841
- * @param childPath - a child path to write resources
842
- * @param slpkChildPath - resource path inside *slpk file
843
- * @param sourceId - source filename
844
- * @param nodeId - nodeId of a converted node for the writing
845
- */
846
- async _writeGeometries({ geometryBuffer, compressedGeometry, childPath, slpkChildPath, sourceId, nodeId }) {
847
- if (!geometryBuffer) {
848
- return;
849
- }
850
- this.conversionDump.updateDoneStatus(sourceId, nodeId, ResourceType.GEOMETRY, false);
851
- const slpkGeometryPath = join(childPath, 'geometries');
852
- await this.writeQueue.enqueue({
853
- archiveKey: `${slpkChildPath}/geometries/0.bin.gz`,
854
- sourceId,
855
- outputId: nodeId,
856
- resourceType: ResourceType.GEOMETRY,
857
- writePromise: () => writeFileForSlpk(slpkGeometryPath, geometryBuffer, '0.bin')
858
- });
859
- if (this.options.draco && compressedGeometry) {
860
- this.conversionDump.updateDoneStatus(sourceId, nodeId, ResourceType.DRACO_GEOMETRY, false);
861
- const slpkCompressedGeometryPath = join(childPath, 'geometries');
862
- await this.writeQueue.enqueue({
863
- archiveKey: `${slpkChildPath}/geometries/1.bin.gz`,
864
- sourceId,
865
- outputId: nodeId,
866
- resourceType: ResourceType.DRACO_GEOMETRY,
867
- writePromise: () => writeFileForSlpk(slpkCompressedGeometryPath, compressedGeometry, '1.bin')
868
- });
869
- }
870
- }
871
- /**
872
- * Write shared resources in a file
873
- * @param sharedResources - shared resource data object
874
- * @param childPath - a child path to write resources
875
- * @param slpkChildPath - resource path inside *slpk file
876
- * @param nodePath - a node path
877
- * @param sourceId - source filename
878
- * @param nodeId - nodeId of a converted node for the writing
879
- */
880
- async _writeShared({ sharedResources, childPath, slpkChildPath, nodePath, sourceId, nodeId }) {
881
- if (!sharedResources) {
882
- return;
883
- }
884
- sharedResources.nodePath = nodePath;
885
- const sharedData = transform(sharedResources, sharedResourcesTemplate());
886
- const sharedDataStr = JSON.stringify(sharedData);
887
- this.conversionDump.updateDoneStatus(sourceId, nodeId, ResourceType.SHARED, false);
888
- const slpkSharedPath = join(childPath, 'shared');
889
- await this.writeQueue.enqueue({
890
- archiveKey: `${slpkChildPath}/shared/sharedResource.json.gz`,
891
- sourceId,
892
- outputId: nodeId,
893
- resourceType: ResourceType.SHARED,
894
- writePromise: () => writeFileForSlpk(slpkSharedPath, sharedDataStr, 'sharedResource.json')
895
- });
896
- }
897
- /**
898
- * Generates textures based on texture mime type and fill in textureSetDefinitions data.
899
- * @param texture - the texture image
900
- * @param childPath - a child path to write resources
901
- * @param slpkChildPath - the resource path inside *slpk file
902
- * @param sourceId - source filename
903
- * @param nodeId - nodeId of a converted node for the writing
904
- */
905
- // eslint-disable-next-line max-statements
906
- async _writeTexture(texture, childPath, slpkChildPath, sourceId, nodeId) {
907
- if (texture) {
908
- const format = this._getFormatByMimeType(texture?.mimeType);
909
- const formats = [];
910
- const textureData = texture.bufferView.data;
911
- switch (format) {
912
- case 'jpg':
913
- case 'png': {
914
- formats.push({ name: '0', format });
915
- this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.TEXTURE}/${format}`, false);
916
- await this.writeTextureFile({
917
- textureData,
918
- name: '0',
919
- format,
920
- childPath,
921
- slpkChildPath,
922
- sourceId,
923
- nodeId
924
- });
925
- if (this.generateTextures) {
926
- formats.push({ name: '1', format: 'ktx2' });
927
- // For Node.js texture.image.data is type of Buffer
928
- const copyArrayBuffer = texture.image.data.subarray();
929
- const arrayToEncode = new Uint8Array(copyArrayBuffer);
930
- const ktx2TextureData = encode({ ...texture.image, data: arrayToEncode },
931
- // @ts-expect-error - Worker encoder typing is still WIP
932
- KTX2BasisWriterWorker, {
933
- ...KTX2BasisWriterWorker.options,
934
- ['ktx2-basis-writer']: {
935
- // We need to load local fs workers because nodejs can't load workers from the Internet
936
- workerUrl: './modules/textures/dist/ktx2-basis-writer-worker-node.js'
937
- },
938
- reuseWorkers: true,
939
- _nodeWorkers: true,
940
- useLocalLibraries: true
941
- });
942
- this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.TEXTURE}/ktx2`, false);
943
- await this.writeTextureFile({
944
- textureData: ktx2TextureData,
945
- name: '1',
946
- format: 'ktx2',
947
- childPath,
948
- slpkChildPath,
949
- sourceId,
950
- nodeId
951
- });
952
- }
953
- break;
954
- }
955
- case 'ktx2': {
956
- formats.push({ name: '1', format });
957
- this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.TEXTURE}/${format}`, false);
958
- await this.writeTextureFile({
959
- textureData,
960
- name: '1',
961
- format,
962
- childPath,
963
- slpkChildPath,
964
- sourceId,
965
- nodeId
966
- });
967
- if (this.generateTextures) {
968
- formats.push({ name: '0', format: 'jpg' });
969
- const decodedFromKTX2TextureData = encode(texture.image.data[0], ImageWriter);
970
- this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.TEXTURE}/jpg`, false);
971
- await this.writeTextureFile({
972
- textureData: decodedFromKTX2TextureData,
973
- name: '0',
974
- format: 'jpg',
975
- childPath,
976
- slpkChildPath,
977
- sourceId,
978
- nodeId
979
- });
980
- }
981
- break;
982
- }
983
- default:
984
- }
985
- if (!this.layers0.textureSetDefinitions.length) {
986
- this.layers0.textureSetDefinitions.push({ formats });
987
- this.layers0.textureSetDefinitions.push({ formats, atlas: true });
988
- if (this.layers0.textureSetDefinitions) {
989
- this.conversionDump.addTexturesDefinitions(this.layers0.textureSetDefinitions);
990
- }
991
- }
992
- }
993
- }
994
- /**
995
- * Write the texture image in a file
996
- * @param textureData
997
- * @param name
998
- * @param format
999
- * @param childPath
1000
- * @param slpkChildPath
1001
- * @param sourceId
1002
- * @param nodeId
1003
- */
1004
- async writeTextureFile({ textureData, name, format, childPath, slpkChildPath, sourceId, nodeId }) {
1005
- const slpkTexturePath = join(childPath, 'textures');
1006
- const compress = false;
1007
- await this.writeQueue.enqueue({
1008
- archiveKey: `${slpkChildPath}/textures/${name}.${format}`,
1009
- sourceId,
1010
- outputId: nodeId,
1011
- resourceType: `${ResourceType.TEXTURE}/${format}`,
1012
- writePromise: () => writeFileForSlpk(slpkTexturePath, textureData, `${name}.${format}`, compress)
1013
- });
1014
- }
1015
- /**
1016
- * Write feature attributes in files
1017
- * @param attributes - feature attributes
1018
- * @param childPath - a child path to write resources
1019
- * @param slpkChildPath - the resource path inside *slpk file
1020
- * @param sourceId - source filename
1021
- * @param nodeId - nodeId of a converted node for the writing
1022
- */
1023
- async _writeAttributes(attributes = [], childPath, slpkChildPath, sourceId, nodeId) {
1024
- if (attributes?.length && this.attributeMetadataInfo.attributeStorageInfo.length) {
1025
- const minimumLength = attributes.length < this.attributeMetadataInfo.attributeStorageInfo.length
1026
- ? attributes.length
1027
- : this.attributeMetadataInfo.attributeStorageInfo.length;
1028
- for (let index = 0; index < minimumLength; index++) {
1029
- const folderName = this.attributeMetadataInfo.attributeStorageInfo[index].key;
1030
- const fileBuffer = new Uint8Array(attributes[index]);
1031
- this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.ATTRIBUTES}/${folderName}`, false);
1032
- const slpkAttributesPath = join(childPath, 'attributes', folderName);
1033
- await this.writeQueue.enqueue({
1034
- archiveKey: `${slpkChildPath}/attributes/${folderName}.bin.gz`,
1035
- sourceId,
1036
- outputId: nodeId,
1037
- resourceType: `${ResourceType.ATTRIBUTES}/${folderName}`,
1038
- writePromise: () => writeFileForSlpk(slpkAttributesPath, fileBuffer, '0.bin')
1039
- });
1040
- }
1041
- }
1042
- }
1043
- /**
1044
- * Return file format by its MIME type
1045
- * @param mimeType - feature attributes
1046
- */
1047
- _getFormatByMimeType(mimeType) {
1048
- switch (mimeType) {
1049
- case 'image/jpeg':
1050
- return 'jpg';
1051
- case 'image/png':
1052
- return 'png';
1053
- case 'image/ktx2':
1054
- return 'ktx2';
1055
- default:
1056
- return 'jpg';
1057
- }
1058
- }
1059
- /**
1060
- * Find or create material in materialDefinitions array
1061
- * @param material - end-to-end index of the node
1062
- * @return material id
1063
- */
1064
- _findOrCreateMaterial(material) {
1065
- const hash = md5(JSON.stringify(material));
1066
- if (this.materialMap.has(hash)) {
1067
- return this.materialMap.get(hash) || 0;
1068
- }
1069
- const newMaterialId = this.materialDefinitions.push(material) - 1;
1070
- this.materialMap.set(hash, newMaterialId);
1071
- return newMaterialId;
1072
- }
1073
- /**
1074
- * Get unique geometry configuration index
1075
- * In the end of conversion configurations will be transformed to geometryDefinitions array
1076
- * @param hasTexture
1077
- * @param hasUvRegions
1078
- * @returns
1079
- */
1080
- findOrCreateGeometryDefinition(hasTexture, hasUvRegions) {
1081
- const geometryConfig = { hasTexture, hasUvRegions };
1082
- const hash = md5(JSON.stringify(geometryConfig));
1083
- if (this.geometryMap.has(hash)) {
1084
- return this.geometryMap.get(hash) || 0;
1085
- }
1086
- const newGeometryId = this.geometryConfigs.push(geometryConfig) - 1;
1087
- this.geometryMap.set(hash, newGeometryId);
1088
- return newGeometryId;
1089
- }
1090
- /**
1091
- * Creates attribute storage info based on either extension schema or property table.
1092
- * @param tileContent - content of the source tile
1093
- * @param propertyTable - feature properties from EXT_FEATURE_METADATA, EXT_STRUCTURAL_METADATA
1094
- */
1095
- createAttributeStorageInfo(tileContent, propertyTable) {
1096
- /*
1097
- In case the tileset doesn't have either EXT_structural_metadata or EXT_feature_metadata
1098
- that can be a source of attribute information so metadataClass is not specified
1099
- we will collect attribute information for node attributes from the property table
1100
- taken from each tile.
1101
- */
1102
- let attributeTypesMap = null;
1103
- if (this.options.metadataClass) {
1104
- if (!this.attributeMetadataInfo.attributeStorageInfo.length && tileContent?.gltf) {
1105
- attributeTypesMap = getAttributeTypesMapFromSchema(tileContent.gltf, this.options.metadataClass);
1106
- }
1107
- }
1108
- else if (propertyTable) {
1109
- attributeTypesMap = getAttributeTypesMapFromPropertyTable(propertyTable);
1110
- }
1111
- if (attributeTypesMap) {
1112
- // Add new storage attributes, fields and create popupInfo
1113
- this.attributeMetadataInfo.addMetadataInfo(attributeTypesMap);
1114
- }
1115
- }
1116
- /**
1117
- * Print statistics in the end of conversion
1118
- * @param params - output files data
1119
- */
1120
- async _finishConversion(params) {
1121
- const { tilesCount, tilesWithAddRefineCount } = this.refinementCounter;
1122
- const addRefinementPercentage = tilesWithAddRefineCount
1123
- ? (tilesWithAddRefineCount / tilesCount) * 100
1124
- : 0;
1125
- const filesSize = await calculateDatasetSize({
1126
- outputPath: params.outputPath,
1127
- tilesetName: params.tilesetName,
1128
- slpk: true
1129
- });
1130
- const diff = process.hrtime(this.conversionStartTime);
1131
- const conversionTime = timeConverter(diff);
1132
- console.log('------------------------------------------------'); // eslint-disable-line no-undef, no-console
1133
- console.log(`Finishing conversion of ${_3D_TILES}`); // eslint-disable-line no-undef, no-console
1134
- console.log(`Total conversion time: ${conversionTime}`); // eslint-disable-line no-undef, no-console
1135
- console.log('Vertex count: ', this.vertexCounter); // eslint-disable-line no-undef, no-console
1136
- console.log('File(s) size: ', filesSize, ' bytes'); // eslint-disable-line no-undef, no-console
1137
- console.log('Percentage of tiles with "ADD" refinement type:', addRefinementPercentage, '%'); // eslint-disable-line no-undef, no-console
1138
- console.log('------------------------------------------------'); // eslint-disable-line no-undef, no-console
1139
- }
1140
- /**
1141
- * Fetch preload options for ION tileset
1142
- */
1143
- async _fetchPreloadOptions() {
1144
- if (!this.Loader.preload) {
1145
- return {};
1146
- }
1147
- const options = {
1148
- 'cesium-ion': { accessToken: this.options.token || ION_DEFAULT_TOKEN }
1149
- };
1150
- const preloadOptions = await this.Loader.preload(this.options.inputUrl ?? '', options);
1151
- this.refreshTokenTime = process.hrtime();
1152
- return { ...options, ...preloadOptions };
1153
- }
1154
- /**
1155
- * Update options of source tileset
1156
- */
1157
- async _updateTilesetOptions() {
1158
- const diff = process.hrtime(this.refreshTokenTime);
1159
- if (diff[0] < REFRESH_TOKEN_TIMEOUT) {
1160
- return;
1161
- }
1162
- this.refreshTokenTime = process.hrtime();
1163
- const preloadOptions = await this._fetchPreloadOptions();
1164
- if (preloadOptions.headers) {
1165
- this.loadOptions.fetch = {
1166
- ...this.loadOptions.fetch,
1167
- headers: preloadOptions.headers
1168
- };
1169
- console.log('Authorization Bearer token has been updated'); // eslint-disable-line no-undef, no-console
1170
- }
1171
- }
1172
- /** Do calculations of all tiles and tiles with "ADD" type of refinement.
1173
- * @param tile
1174
- */
1175
- _checkAddRefinementTypeForTile(tile) {
1176
- const ADD_TILE_REFINEMENT = TILE_REFINEMENT.ADD;
1177
- if (tile.refine === ADD_TILE_REFINEMENT) {
1178
- this.refinementCounter.tilesWithAddRefineCount += 1;
1179
- console.warn('This tile uses "ADD" type of refinement'); // eslint-disable-line
1180
- }
1181
- this.refinementCounter.tilesCount += 1;
1182
- }
1183
- /**
1184
- * Check if the tile's content format is supported by the converter
1185
- * @param sourceTile
1186
- * @returns
1187
- */
1188
- isContentSupported(sourceTile) {
1189
- return ['b3dm', 'glTF', 'scenegraph'].includes(sourceTile.type || '');
1190
- }
1191
- }