@loaders.gl/tile-converter 3.1.8 → 3.2.0-alpha.1

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 (112) hide show
  1. package/dist/3d-tiles-converter/3d-tiles-converter.d.ts +78 -0
  2. package/dist/3d-tiles-converter/3d-tiles-converter.d.ts.map +1 -0
  3. package/dist/3d-tiles-converter/3d-tiles-converter.js +242 -0
  4. package/dist/3d-tiles-converter/helpers/b3dm-converter.d.ts +83 -0
  5. package/dist/3d-tiles-converter/helpers/b3dm-converter.d.ts.map +1 -0
  6. package/dist/3d-tiles-converter/helpers/b3dm-converter.js +278 -0
  7. package/dist/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.d.ts +13 -0
  8. package/dist/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.d.ts.map +1 -0
  9. package/dist/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.js +23 -0
  10. package/dist/3d-tiles-converter/helpers/texture-atlas.d.ts +9 -0
  11. package/dist/3d-tiles-converter/helpers/texture-atlas.d.ts.map +1 -0
  12. package/dist/3d-tiles-converter/helpers/texture-atlas.js +52 -0
  13. package/dist/3d-tiles-converter/json-templates/tileset.d.ts +15 -0
  14. package/dist/3d-tiles-converter/json-templates/tileset.d.ts.map +1 -0
  15. package/dist/3d-tiles-converter/json-templates/tileset.js +43 -0
  16. package/dist/bundle.d.ts +2 -0
  17. package/dist/bundle.d.ts.map +1 -0
  18. package/dist/bundle.js +5 -0
  19. package/dist/converter.min.js +19 -19
  20. package/dist/deps-installer/deps-installer.d.ts +4 -0
  21. package/dist/deps-installer/deps-installer.d.ts.map +1 -0
  22. package/dist/deps-installer/deps-installer.js +21 -0
  23. package/dist/dist.min.js +304 -494
  24. package/dist/es5/3d-tiles-converter/3d-tiles-converter.js +2 -1
  25. package/dist/es5/3d-tiles-converter/3d-tiles-converter.js.map +1 -1
  26. package/dist/es5/3d-tiles-converter/helpers/b3dm-converter.js +14 -21
  27. package/dist/es5/3d-tiles-converter/helpers/b3dm-converter.js.map +1 -1
  28. package/dist/es5/i3s-converter/i3s-converter.js +35 -25
  29. package/dist/es5/i3s-converter/i3s-converter.js.map +1 -1
  30. package/dist/es5/pgm-loader.js +1 -1
  31. package/dist/es5/pgm-loader.js.map +1 -1
  32. package/dist/esm/3d-tiles-converter/3d-tiles-converter.js +2 -1
  33. package/dist/esm/3d-tiles-converter/3d-tiles-converter.js.map +1 -1
  34. package/dist/esm/3d-tiles-converter/helpers/b3dm-converter.js +7 -5
  35. package/dist/esm/3d-tiles-converter/helpers/b3dm-converter.js.map +1 -1
  36. package/dist/esm/i3s-converter/i3s-converter.js +10 -0
  37. package/dist/esm/i3s-converter/i3s-converter.js.map +1 -1
  38. package/dist/esm/pgm-loader.js +1 -1
  39. package/dist/esm/pgm-loader.js.map +1 -1
  40. package/dist/i3s-converter/helpers/coordinate-converter.d.ts +41 -0
  41. package/dist/i3s-converter/helpers/coordinate-converter.d.ts.map +1 -0
  42. package/dist/i3s-converter/helpers/coordinate-converter.js +118 -0
  43. package/dist/i3s-converter/helpers/create-scene-server-path.d.ts +9 -0
  44. package/dist/i3s-converter/helpers/create-scene-server-path.d.ts.map +1 -0
  45. package/dist/i3s-converter/helpers/create-scene-server-path.js +28 -0
  46. package/dist/i3s-converter/helpers/geometry-attributes.d.ts +8 -0
  47. package/dist/i3s-converter/helpers/geometry-attributes.d.ts.map +1 -0
  48. package/dist/i3s-converter/helpers/geometry-attributes.js +176 -0
  49. package/dist/i3s-converter/helpers/geometry-converter.d.ts +12 -0
  50. package/dist/i3s-converter/helpers/geometry-converter.d.ts.map +1 -0
  51. package/dist/i3s-converter/helpers/geometry-converter.js +791 -0
  52. package/dist/i3s-converter/helpers/node-debug.d.ts +8 -0
  53. package/dist/i3s-converter/helpers/node-debug.d.ts.map +1 -0
  54. package/dist/i3s-converter/helpers/node-debug.js +114 -0
  55. package/dist/i3s-converter/helpers/node-pages.d.ts +116 -0
  56. package/dist/i3s-converter/helpers/node-pages.d.ts.map +1 -0
  57. package/dist/i3s-converter/helpers/node-pages.js +203 -0
  58. package/dist/i3s-converter/i3s-converter.d.ts +321 -0
  59. package/dist/i3s-converter/i3s-converter.d.ts.map +1 -0
  60. package/dist/i3s-converter/i3s-converter.js +994 -0
  61. package/dist/i3s-converter/json-templates/layers.d.ts +95 -0
  62. package/dist/i3s-converter/json-templates/layers.d.ts.map +1 -0
  63. package/dist/i3s-converter/json-templates/layers.js +199 -0
  64. package/dist/i3s-converter/json-templates/metadata.d.ts +22 -0
  65. package/dist/i3s-converter/json-templates/metadata.d.ts.map +1 -0
  66. package/dist/i3s-converter/json-templates/metadata.js +25 -0
  67. package/dist/i3s-converter/json-templates/node.d.ts +61 -0
  68. package/dist/i3s-converter/json-templates/node.d.ts.map +1 -0
  69. package/dist/i3s-converter/json-templates/node.js +89 -0
  70. package/dist/i3s-converter/json-templates/scene-server.d.ts +28 -0
  71. package/dist/i3s-converter/json-templates/scene-server.d.ts.map +1 -0
  72. package/dist/i3s-converter/json-templates/scene-server.js +31 -0
  73. package/dist/i3s-converter/json-templates/shared-resources.d.ts +14 -0
  74. package/dist/i3s-converter/json-templates/shared-resources.d.ts.map +1 -0
  75. package/dist/i3s-converter/json-templates/shared-resources.js +129 -0
  76. package/dist/i3s-converter/json-templates/store.d.ts +95 -0
  77. package/dist/i3s-converter/json-templates/store.d.ts.map +1 -0
  78. package/dist/i3s-converter/json-templates/store.js +103 -0
  79. package/dist/i3s-converter/types.d.ts +39 -0
  80. package/dist/i3s-converter/types.d.ts.map +1 -0
  81. package/dist/i3s-converter/types.js +2 -0
  82. package/dist/i3s-server/app.d.ts +3 -0
  83. package/dist/i3s-server/app.d.ts.map +1 -0
  84. package/dist/i3s-server/app.js +14 -0
  85. package/dist/i3s-server/controllers/index-controller.d.ts +2 -0
  86. package/dist/i3s-server/controllers/index-controller.d.ts.map +1 -0
  87. package/dist/i3s-server/controllers/index-controller.js +23 -0
  88. package/dist/i3s-server/routes/index.d.ts +3 -0
  89. package/dist/i3s-server/routes/index.d.ts.map +1 -0
  90. package/dist/i3s-server/routes/index.js +16 -0
  91. package/dist/index.d.ts +5 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +14 -0
  94. package/dist/lib/utils/compress-util.d.ts +6 -0
  95. package/dist/lib/utils/compress-util.d.ts.map +1 -0
  96. package/dist/lib/utils/compress-util.js +190 -0
  97. package/dist/lib/utils/file-utils.d.ts +6 -0
  98. package/dist/lib/utils/file-utils.d.ts.map +1 -0
  99. package/dist/lib/utils/file-utils.js +42 -0
  100. package/dist/lib/utils/lod-conversion-utils.d.ts +20 -0
  101. package/dist/lib/utils/lod-conversion-utils.d.ts.map +1 -0
  102. package/dist/lib/utils/lod-conversion-utils.js +57 -0
  103. package/dist/lib/utils/statistic-utills.d.ts +3 -0
  104. package/dist/lib/utils/statistic-utills.d.ts.map +1 -0
  105. package/dist/lib/utils/statistic-utills.js +64 -0
  106. package/dist/pgm-loader.d.ts +6 -0
  107. package/dist/pgm-loader.d.ts.map +1 -0
  108. package/dist/pgm-loader.js +23 -0
  109. package/package.json +16 -17
  110. package/src/3d-tiles-converter/3d-tiles-converter.ts +4 -1
  111. package/src/3d-tiles-converter/helpers/b3dm-converter.ts +6 -5
  112. package/src/i3s-converter/i3s-converter.ts +12 -0
@@ -0,0 +1,994 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const core_1 = require("@loaders.gl/core");
7
+ const tiles_1 = require("@loaders.gl/tiles");
8
+ const _3d_tiles_1 = require("@loaders.gl/3d-tiles");
9
+ const path_1 = require("path");
10
+ const uuid_1 = require("uuid");
11
+ const process_1 = __importDefault(require("process"));
12
+ const json_map_transform_1 = __importDefault(require("json-map-transform"));
13
+ const md5_1 = __importDefault(require("md5"));
14
+ const node_pages_1 = __importDefault(require("./helpers/node-pages"));
15
+ const file_utils_1 = require("../lib/utils/file-utils");
16
+ const compress_util_1 = require("../lib/utils/compress-util");
17
+ const statistic_utills_1 = require("../lib/utils/statistic-utills");
18
+ const geometry_converter_1 = __importDefault(require("./helpers/geometry-converter"));
19
+ const coordinate_converter_1 = require("./helpers/coordinate-converter");
20
+ const create_scene_server_path_1 = require("./helpers/create-scene-server-path");
21
+ const lod_conversion_utils_1 = require("../lib/utils/lod-conversion-utils");
22
+ const pgm_loader_1 = require("../pgm-loader");
23
+ const layers_1 = require("./json-templates/layers");
24
+ const node_1 = require("./json-templates/node");
25
+ const shared_resources_1 = require("./json-templates/shared-resources");
26
+ const node_debug_1 = require("./helpers/node-debug");
27
+ const textures_1 = require("@loaders.gl/textures");
28
+ const images_1 = require("@loaders.gl/images");
29
+ const ION_DEFAULT_TOKEN = process_1.default.env.IonToken || // eslint-disable-line
30
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJlYWMxMzcyYy0zZjJkLTQwODctODNlNi01MDRkZmMzMjIxOWIiLCJpZCI6OTYyMCwic2NvcGVzIjpbImFzbCIsImFzciIsImdjIl0sImlhdCI6MTU2Mjg2NjI3M30.1FNiClUyk00YH_nWfSGpiQAjR5V2OvREDq1PJ5QMjWQ'; // eslint-disable-line
31
+ const HARDCODED_NODES_PER_PAGE = 64;
32
+ const _3D_TILES = '3DTILES';
33
+ const _3D_OBJECT_LAYER_TYPE = '3DObject';
34
+ const STRING_TYPE = 'string';
35
+ const SHORT_INT_TYPE = 'Int32';
36
+ const DOUBLE_TYPE = 'double';
37
+ const OBJECT_ID_TYPE = 'OBJECTID';
38
+ const REFRESH_TOKEN_TIMEOUT = 1800; // 30 minutes in seconds
39
+ const CESIUM_DATASET_PREFIX = 'https://';
40
+ // const FS_FILE_TOO_LARGE = 'ERR_FS_FILE_TOO_LARGE';
41
+ /**
42
+ * Converter from 3d-tiles tileset to i3s layer
43
+ */
44
+ class I3SConverter {
45
+ constructor() {
46
+ this.boundingVolumeWarnings = [];
47
+ this.conversionStartTime = [0, 0];
48
+ this.refreshTokenTime = [0, 0];
49
+ this.sourceTileset = null;
50
+ this.geoidHeightModel = null;
51
+ this.Loader = _3d_tiles_1.Tiles3DLoader;
52
+ this.nodePages = new node_pages_1.default(file_utils_1.writeFile, HARDCODED_NODES_PER_PAGE);
53
+ this.fileMap = {};
54
+ this.options = {};
55
+ this.layers0Path = '';
56
+ this.materialMap = new Map();
57
+ this.materialDefinitions = [];
58
+ this.vertexCounter = 0;
59
+ this.layers0 = null;
60
+ this.featuresHashArray = [];
61
+ this.refinementCounter = {
62
+ tilesCount: 0,
63
+ tilesWithAddRefineCount: 0
64
+ };
65
+ this.validate = false;
66
+ this.generateTextures = false;
67
+ this.generateBoundingVolumes = false;
68
+ this.layersHasTexture = false;
69
+ }
70
+ /**
71
+ * Convert a 3d tileset
72
+ * @param options
73
+ * @param options.inputUrl the url to read the tileset from
74
+ * @param options.outputPath the output filename
75
+ * @param options.tilesetName the output name of the tileset
76
+ * @param options.maxDepth The max tree depth of conversion
77
+ * @param options.slpk Generate slpk (Scene Layer Packages) output file
78
+ * @param options.sevenZipExe Location of 7z.exe archiver to create slpk on Windows
79
+ * @param options.egmFilePath location of *.pgm file to convert heights from ellipsoidal to gravity-related format
80
+ * @param options.token Token for Cesium ION tilesets authentication
81
+ * @param options.draco Generate I3S 1.7 draco compressed geometries
82
+ * @param options.validate -enable validation
83
+ */
84
+ async convert(options) {
85
+ this.conversionStartTime = process_1.default.hrtime();
86
+ const { tilesetName, slpk, egmFilePath, inputUrl, validate, outputPath, draco, sevenZipExe, maxDepth, token, generateTextures, generateBoundingVolumes } = options;
87
+ this.options = { maxDepth, slpk, sevenZipExe, egmFilePath, draco, token, inputUrl };
88
+ this.validate = Boolean(validate);
89
+ this.Loader = inputUrl.indexOf(CESIUM_DATASET_PREFIX) !== -1 ? _3d_tiles_1.CesiumIonLoader : _3d_tiles_1.Tiles3DLoader;
90
+ this.generateTextures = Boolean(generateTextures);
91
+ this.generateBoundingVolumes = Boolean(generateBoundingVolumes);
92
+ console.log('Loading egm file...'); // eslint-disable-line
93
+ this.geoidHeightModel = await (0, core_1.load)(egmFilePath, pgm_loader_1.PGMLoader);
94
+ console.log('Loading egm file completed!'); // eslint-disable-line
95
+ if (slpk) {
96
+ this.nodePages.useWriteFunction(file_utils_1.writeFileForSlpk);
97
+ }
98
+ const preloadOptions = await this._fetchPreloadOptions();
99
+ const tilesetOptions = { loadOptions: { basis: { format: 'rgba32' } } };
100
+ if (preloadOptions.headers) {
101
+ tilesetOptions.loadOptions.fetch = { headers: preloadOptions.headers };
102
+ }
103
+ Object.assign(tilesetOptions, preloadOptions);
104
+ const sourceTilesetJson = await (0, core_1.load)(inputUrl, this.Loader, tilesetOptions.loadOptions);
105
+ // console.log(tilesetJson); // eslint-disable-line
106
+ this.sourceTileset = new tiles_1.Tileset3D(sourceTilesetJson, tilesetOptions);
107
+ await this._createAndSaveTileset(outputPath, tilesetName);
108
+ await this._finishConversion({ slpk: Boolean(slpk), outputPath, tilesetName });
109
+ return sourceTilesetJson;
110
+ }
111
+ /**
112
+ * Convert and save the layer and embedded tiles
113
+ * @param outputPath - path to save output data
114
+ * @param tilesetName - new tileset path
115
+ */
116
+ async _createAndSaveTileset(outputPath, tilesetName) {
117
+ const tilesetPath = (0, path_1.join)(`${outputPath}`, `${tilesetName}`);
118
+ // Removing the tilesetPath needed to exclude erroneous files after conversion
119
+ try {
120
+ await (0, file_utils_1.removeDir)(tilesetPath);
121
+ }
122
+ catch (e) {
123
+ // do nothing
124
+ }
125
+ this.layers0Path = (0, path_1.join)(tilesetPath, 'SceneServer', 'layers', '0');
126
+ this._formLayers0(tilesetName);
127
+ this.materialDefinitions = [];
128
+ this.materialMap = new Map();
129
+ const sourceRootTile = this.sourceTileset.root;
130
+ const boundingVolumes = (0, coordinate_converter_1.createBoundingVolumes)(sourceRootTile, this.geoidHeightModel);
131
+ const parentId = this.nodePages.push({
132
+ index: 0,
133
+ lodThreshold: 0,
134
+ obb: boundingVolumes.obb,
135
+ children: []
136
+ });
137
+ const isCreateSlpk = this.options.slpk;
138
+ const root0 = this._formRootNodeIndexDocument(boundingVolumes);
139
+ await this._convertNodesTree(root0, sourceRootTile, parentId, boundingVolumes);
140
+ this.layers0.materialDefinitions = this.materialDefinitions;
141
+ if (this.layersHasTexture === false) {
142
+ this.layers0.store.defaultGeometrySchema.ordering =
143
+ this.layers0.store.defaultGeometrySchema.ordering.filter((attribute) => attribute !== 'uv0');
144
+ }
145
+ await this._writeLayers0();
146
+ (0, create_scene_server_path_1.createSceneServerPath)(tilesetName, this.layers0, tilesetPath);
147
+ await this._writeNodeIndexDocument(root0, 'root', (0, path_1.join)(this.layers0Path, 'nodes', 'root'));
148
+ await this.nodePages.save(this.layers0Path, this.fileMap, isCreateSlpk);
149
+ await this._createSlpk(tilesetPath);
150
+ }
151
+ /**
152
+ * Form object of 3DSceneLayer https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DSceneLayer.cmn.md
153
+ * @param tilesetName - Name of layer
154
+ */
155
+ _formLayers0(tilesetName) {
156
+ const extent = (0, coordinate_converter_1.convertCommonToI3SExtentCoordinate)(this.sourceTileset);
157
+ const layers0data = {
158
+ version: `{${(0, uuid_1.v4)().toUpperCase()}}`,
159
+ id: 0,
160
+ name: tilesetName,
161
+ href: './layers/0',
162
+ store: {
163
+ id: `{${(0, uuid_1.v4)().toUpperCase()}}`,
164
+ extent
165
+ },
166
+ nodePages: {
167
+ nodesPerPage: HARDCODED_NODES_PER_PAGE
168
+ },
169
+ compressGeometry: this.options.draco
170
+ };
171
+ this.layers0 = (0, json_map_transform_1.default)(layers0data, (0, layers_1.LAYERS)());
172
+ }
173
+ /**
174
+ * Convert and save the layer and embedded tiles
175
+ * @param boundingVolumes - mbs and obb data about node's bounding volume
176
+ * @return 3DNodeIndexDocument data https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DNodeIndexDocument.cmn.md
177
+ */
178
+ _formRootNodeIndexDocument(boundingVolumes) {
179
+ const root0data = {
180
+ version: `{${(0, uuid_1.v4)().toUpperCase()}}`,
181
+ id: 'root',
182
+ level: 0,
183
+ lodSelection: [
184
+ {
185
+ metricType: 'maxScreenThresholdSQ',
186
+ maxError: 0
187
+ },
188
+ {
189
+ metricType: 'maxScreenThreshold',
190
+ maxError: 0
191
+ }
192
+ ],
193
+ ...boundingVolumes,
194
+ children: []
195
+ };
196
+ return (0, json_map_transform_1.default)(root0data, (0, node_1.NODE)());
197
+ }
198
+ /**
199
+ * Form object of 3DSceneLayer https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DSceneLayer.cmn.md
200
+ * @param root0 - 3DNodeIndexDocument of root node https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DNodeIndexDocument.cmn.md
201
+ * @param sourceRootTile - Source (3DTile) tile data
202
+ * @param parentId - node id in node pages
203
+ * @param boundingVolumes - mbs and obb data about node's bounding volume
204
+ */
205
+ async _convertNodesTree(root0, sourceRootTile, parentId, boundingVolumes) {
206
+ await this.sourceTileset._loadTile(sourceRootTile);
207
+ if (this.isContentSupported(sourceRootTile)) {
208
+ root0.children = root0.children || [];
209
+ root0.children.push({
210
+ id: '1',
211
+ href: './1',
212
+ ...boundingVolumes
213
+ });
214
+ const [child] = await this._createNode(root0, sourceRootTile, parentId, 0);
215
+ const childPath = (0, path_1.join)(this.layers0Path, 'nodes', child.path);
216
+ if (this.options.slpk) {
217
+ this.fileMap['nodes/1/3dNodeIndexDocument.json.gz'] = await (0, file_utils_1.writeFileForSlpk)(childPath, JSON.stringify(child), '3dNodeIndexDocument.json');
218
+ }
219
+ else {
220
+ await (0, file_utils_1.writeFile)(childPath, JSON.stringify(child));
221
+ }
222
+ }
223
+ else {
224
+ await this._addChildrenWithNeighborsAndWriteFile({
225
+ parentNode: root0,
226
+ sourceTiles: sourceRootTile.children,
227
+ parentId,
228
+ level: 1
229
+ });
230
+ }
231
+ await sourceRootTile.unloadContent();
232
+ }
233
+ /**
234
+ * Write 3DSceneLayer https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DSceneLayer.cmn.md in file
235
+ */
236
+ async _writeLayers0() {
237
+ if (this.options.slpk) {
238
+ this.fileMap['3dSceneLayer.json.gz'] = await (0, file_utils_1.writeFileForSlpk)(this.layers0Path, JSON.stringify(this.layers0), '3dSceneLayer.json');
239
+ }
240
+ else {
241
+ await (0, file_utils_1.writeFile)(this.layers0Path, JSON.stringify(this.layers0));
242
+ }
243
+ }
244
+ /**
245
+ * Write 3DNodeIndexDocument https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DNodeIndexDocument.cmn.md in file
246
+ */
247
+ async _writeNodeIndexDocument(root0, nodePath, rootPath) {
248
+ if (this.options.slpk) {
249
+ this.fileMap[`nodes/${nodePath}/3dNodeIndexDocument.json.gz`] = await (0, file_utils_1.writeFileForSlpk)(rootPath, JSON.stringify(root0), '3dNodeIndexDocument.json');
250
+ }
251
+ else {
252
+ await (0, file_utils_1.writeFile)(rootPath, JSON.stringify(root0));
253
+ }
254
+ }
255
+ /**
256
+ * Pack files into *.slpk archive
257
+ * @param tilesetPath - Path to save file
258
+ */
259
+ async _createSlpk(tilesetPath) {
260
+ if (this.options.slpk) {
261
+ const slpkTilesetPath = (0, path_1.join)(tilesetPath, 'SceneServer', 'layers', '0');
262
+ const slpkFileName = `${tilesetPath}.slpk`;
263
+ await (0, compress_util_1.compressWithChildProcess)(slpkTilesetPath, slpkFileName, 0, '.',
264
+ // @ts-expect-error
265
+ this.options.sevenZipExe);
266
+ // TODO: `addFileToZip` corrupts archive so it can't be validated with windows i3s_converter.exe
267
+ // const fileHash128Path = `${tilesetPath}/@specialIndexFileHASH128@`;
268
+ // try {
269
+ // await generateHash128FromZip(slpkFileName, fileHash128Path);
270
+ // await addFileToZip(
271
+ // tilesetPath,
272
+ // '@specialIndexFileHASH128@',
273
+ // slpkFileName,
274
+ // this.options.sevenZipExe
275
+ // );
276
+ // } catch (error) {
277
+ // if (error.code === FS_FILE_TOO_LARGE) {
278
+ // console.warn(`${slpkFileName} file is too big to generate a hash`); // eslint-disable-line
279
+ // } else {
280
+ // console.error(error); // eslint-disable-line
281
+ // }
282
+ // }
283
+ // All converted files are contained in slpk now they can be deleted
284
+ try {
285
+ await (0, file_utils_1.removeDir)(tilesetPath);
286
+ }
287
+ catch (e) {
288
+ // do nothing
289
+ }
290
+ }
291
+ }
292
+ /**
293
+ * Add child nodes recursively and write them to files
294
+ * @param data - arguments
295
+ * @param data.sourceTiles - array of source child nodes
296
+ * @param data.parentNode - 3DNodeIndexDocument of parent node for processing child nodes
297
+ * @param data.parentId - id of parent node in node pages
298
+ * @param data.level - level of node (distanse to root node in the tree)
299
+ */
300
+ async _addChildrenWithNeighborsAndWriteFile(data) {
301
+ const childNodes = [];
302
+ await this._addChildren({ ...data, childNodes });
303
+ await this._addNeighborsAndWriteFile(data.parentNode, childNodes);
304
+ }
305
+ /**
306
+ * Add child nodes recursively and write them to files
307
+ * @param data - arguments
308
+ * @param data.childNodes - array of target child nodes
309
+ * @param data.sourceTiles - array of source child nodes
310
+ * @param data.parentNode - 3DNodeIndexDocument of parent node for processing child nodes
311
+ * @param data.parentId - id of parent node in node pages
312
+ * @param data.level - level of node (distanse to root node in the tree)
313
+ */
314
+ async _addChildren(data) {
315
+ const { childNodes, sourceTiles, parentNode, parentId, level } = data;
316
+ if (this.options.maxDepth && level > this.options.maxDepth) {
317
+ return;
318
+ }
319
+ for (const sourceTile of sourceTiles) {
320
+ if (sourceTile.type === 'json') {
321
+ await this.sourceTileset._loadTile(sourceTile);
322
+ await this._addChildren({
323
+ parentNode,
324
+ sourceTiles: sourceTile.children,
325
+ childNodes,
326
+ parentId,
327
+ level: level + 1
328
+ });
329
+ await sourceTile.unloadContent();
330
+ }
331
+ else {
332
+ const children = await this._createNode(parentNode, sourceTile, parentId, level);
333
+ parentNode.children = parentNode.children || [];
334
+ for (const child of children) {
335
+ parentNode.children.push({
336
+ id: child.id,
337
+ href: `../${child.path}`,
338
+ obb: child.obb,
339
+ mbs: child.mbs
340
+ });
341
+ childNodes.push(child);
342
+ }
343
+ }
344
+ if (sourceTile.id) {
345
+ console.log(sourceTile.id); // eslint-disable-line
346
+ }
347
+ }
348
+ }
349
+ /**
350
+ * Add neightbors to 3DNodeIndexDocument and write it in a file
351
+ * @param parentNode - arguments
352
+ * @param childNodes - array of target child nodes
353
+ */
354
+ async _addNeighborsAndWriteFile(parentNode, childNodes) {
355
+ for (const node of childNodes) {
356
+ const childPath = (0, path_1.join)(this.layers0Path, 'nodes', node.path);
357
+ const nodePath = node.path;
358
+ delete node.path;
359
+ // Don't do large amount of "neightbors" to avoid big memory consumption
360
+ if (Number(parentNode?.children?.length) < 1000) {
361
+ for (const neighbor of parentNode.children || []) {
362
+ // eslint-disable-next-line max-depth
363
+ if (node.id === neighbor.id) {
364
+ continue; // eslint-disable-line
365
+ }
366
+ if (node.neighbors) {
367
+ node.neighbors.push({ ...neighbor });
368
+ }
369
+ }
370
+ }
371
+ else {
372
+ // eslint-disable-next-line no-console, no-undef
373
+ console.warn(`Node ${node.id}: neighbors attribute is omited because of large number of neigbors`);
374
+ delete node.neighbors;
375
+ }
376
+ await this._writeNodeIndexDocument(node, nodePath, childPath);
377
+ node.neighbors = [];
378
+ }
379
+ }
380
+ /**
381
+ * Convert tile to one or more I3S nodes
382
+ * @param parentTile - parent 3DNodeIndexDocument
383
+ * @param sourceTile - source tile (3DTile)
384
+ * @param parentId - id of parent node in node pages
385
+ * @param level - level of node (distanse to root node in the tree)
386
+ */
387
+ async _createNode(parentTile, sourceTile, parentId, level) {
388
+ if (this.validate) {
389
+ this._checkAddRefinementTypeForTile(sourceTile);
390
+ }
391
+ await this._updateTilesetOptions();
392
+ await this.sourceTileset._loadTile(sourceTile);
393
+ let boundingVolumes = (0, coordinate_converter_1.createBoundingVolumes)(sourceTile, this.geoidHeightModel);
394
+ const batchTable = sourceTile?.content?.batchTableJson;
395
+ if (batchTable) {
396
+ this._convertAttributeStorageInfo(sourceTile.content);
397
+ }
398
+ const resourcesData = await this._convertResources(sourceTile);
399
+ const nodes = [];
400
+ const nodesInPage = [];
401
+ const emptyResources = {
402
+ geometry: null,
403
+ compressedGeometry: null,
404
+ texture: null,
405
+ sharedResources: null,
406
+ meshMaterial: null,
407
+ vertexCount: null,
408
+ attributes: null,
409
+ featureCount: null,
410
+ boundingVolumes: null
411
+ };
412
+ for (const resources of resourcesData || [emptyResources]) {
413
+ this.layersHasTexture = this.layersHasTexture || Boolean(resources.texture);
414
+ if (this.generateBoundingVolumes && resources.boundingVolumes) {
415
+ boundingVolumes = resources.boundingVolumes;
416
+ }
417
+ const lodSelection = (0, lod_conversion_utils_1.convertGeometricErrorToScreenThreshold)(sourceTile, boundingVolumes);
418
+ const maxScreenThresholdSQ = lodSelection.find((val) => val.metricType === 'maxScreenThresholdSQ') || { maxError: 0 };
419
+ const nodeInPage = this._createNodeInNodePages(maxScreenThresholdSQ, boundingVolumes, sourceTile, parentId, resources);
420
+ const node = this._createNodeIndexDocument(parentTile, boundingVolumes, lodSelection, nodeInPage, resources);
421
+ if (nodeInPage.mesh) {
422
+ await this._writeResources(resources, node.path);
423
+ }
424
+ if (this.validate) {
425
+ this.boundingVolumeWarnings = (0, node_debug_1.validateNodeBoundingVolumes)(node);
426
+ if (this.boundingVolumeWarnings && this.boundingVolumeWarnings.length) {
427
+ console.warn('Bounding Volume Warnings: ', ...this.boundingVolumeWarnings); //eslint-disable-line
428
+ }
429
+ }
430
+ nodes.push(node);
431
+ nodesInPage.push(nodeInPage);
432
+ }
433
+ sourceTile.unloadContent();
434
+ await this._addChildrenWithNeighborsAndWriteFile({
435
+ parentNode: nodes[0],
436
+ sourceTiles: sourceTile.children,
437
+ parentId: nodesInPage[0].index,
438
+ level: level + 1
439
+ });
440
+ return nodes;
441
+ }
442
+ /**
443
+ * Convert attributesStorageInfo https://github.com/Esri/i3s-spec/blob/master/docs/1.7/attributeStorageInfo.cmn.md
444
+ * from B3DM batch table
445
+ * @param sourceTileContent - tile content of 3DTile
446
+ * @return {void}
447
+ */
448
+ _convertAttributeStorageInfo(sourceTileContent) {
449
+ // In legacy b3dm files sometimes sourceTileContent is null.
450
+ const batchTable = sourceTileContent && sourceTileContent.batchTableJson;
451
+ if (batchTable && !this.layers0?.attributeStorageInfo?.length) {
452
+ this._convertBatchTableInfoToNodeAttributes(batchTable);
453
+ }
454
+ }
455
+ /**
456
+ * Convert tile to one or more I3S nodes
457
+ * @param sourceTile - source tile (3DTile)
458
+ * result.geometry - ArrayBuffer with geometry attributes
459
+ * result.compressedGeometry - ArrayBuffer with compressed (draco) geometry
460
+ * result.texture - texture image
461
+ * result.sharedResources - shared resource data object
462
+ * result.meshMaterial - PBR-like material object
463
+ * result.vertexCount - number of vertices in geometry
464
+ * result.attributes - feature attributes
465
+ * result.featureCount - number of features
466
+ */
467
+ async _convertResources(sourceTile) {
468
+ if (!this.isContentSupported(sourceTile)) {
469
+ return null;
470
+ }
471
+ const resourcesData = await (0, geometry_converter_1.default)(sourceTile.content, Number(this.nodePages.nodesCounter), this.featuresHashArray, this.layers0?.attributeStorageInfo, this.options.draco, this.generateBoundingVolumes, this.geoidHeightModel);
472
+ return resourcesData;
473
+ }
474
+ /**
475
+ * Create a new node object (https://github.com/Esri/i3s-spec/blob/master/docs/1.7/node.cmn.md)
476
+ * in node pages (https://github.com/Esri/i3s-spec/blob/master/docs/1.7/nodePage.cmn.md)
477
+ * @param maxScreenThresholdSQ - Level of Details (LOD) metric
478
+ * @param boundingVolumes - Bounding volumes
479
+ * @param sourceTile - source tile (3DTile)
480
+ * @param parentId - id of parent node in node pages
481
+ * @param resources - the node resources data
482
+ * @param resources.meshMaterial - PBR-like material object
483
+ * @param resources.texture - texture image
484
+ * @param resources.vertexCount - number of vertices in geometry
485
+ * @param resources.featureCount - number of features
486
+ * @return the node object in node pages
487
+ */
488
+ _createNodeInNodePages(maxScreenThresholdSQ, boundingVolumes, sourceTile, parentId, resources) {
489
+ const { meshMaterial, texture, vertexCount, featureCount, geometry } = resources;
490
+ const nodeInPage = {
491
+ index: 0,
492
+ lodThreshold: maxScreenThresholdSQ.maxError,
493
+ obb: boundingVolumes.obb,
494
+ children: []
495
+ };
496
+ if (geometry && this.isContentSupported(sourceTile)) {
497
+ nodeInPage.mesh = {
498
+ geometry: {
499
+ definition: texture ? 0 : 1,
500
+ resource: 0
501
+ },
502
+ attribute: {
503
+ resource: 0
504
+ },
505
+ material: {
506
+ definition: 0
507
+ }
508
+ };
509
+ }
510
+ const nodeId = this.nodePages.push(nodeInPage, parentId);
511
+ if (meshMaterial) {
512
+ this.nodePages.updateMaterialByNodeId(nodeId, this._findOrCreateMaterial(meshMaterial));
513
+ }
514
+ if (texture) {
515
+ const texelCountHint = texture.image.height * texture.image.width;
516
+ this.nodePages.updateTexelCountHintByNodeId(nodeId, texelCountHint);
517
+ }
518
+ if (vertexCount) {
519
+ this.vertexCounter += vertexCount;
520
+ this.nodePages.updateVertexCountByNodeId(nodeId, vertexCount);
521
+ }
522
+ this.nodePages.updateNodeAttributeByNodeId(nodeId);
523
+ if (featureCount) {
524
+ this.nodePages.updateFeatureCountByNodeId(nodeId, featureCount);
525
+ }
526
+ return nodeInPage;
527
+ }
528
+ /**
529
+ * Create a new node page object in node pages
530
+ * @param parentNode - 3DNodeIndexDocument https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DNodeIndexDocument.cmn.md object of the parent node
531
+ * @param boundingVolumes - Bounding volumes
532
+ * @param lodSelection - Level of Details (LOD) metrics
533
+ * @param nodeInPage - corresponding node object in a node page
534
+ * @param resources - the node resources data
535
+ * @param resources.texture - texture image
536
+ * @param resources.attributes - feature attributes
537
+ * @return 3DNodeIndexDocument https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DNodeIndexDocument.cmn.md object
538
+ */
539
+ _createNodeIndexDocument(parentNode, boundingVolumes, lodSelection, nodeInPage, resources) {
540
+ const { texture, attributes } = resources;
541
+ const nodeId = nodeInPage.index;
542
+ const nodeData = {
543
+ version: parentNode.version,
544
+ id: nodeId.toString(),
545
+ path: nodeId.toString(),
546
+ level: parentNode.level + 1,
547
+ ...boundingVolumes,
548
+ lodSelection,
549
+ parentNode: {
550
+ id: parentNode.id,
551
+ href: `../${parentNode.id}`,
552
+ mbs: parentNode.mbs,
553
+ obb: parentNode.obb
554
+ },
555
+ children: [],
556
+ neighbors: []
557
+ };
558
+ const node = (0, json_map_transform_1.default)(nodeData, (0, node_1.NODE)());
559
+ if (nodeInPage.mesh) {
560
+ node.geometryData = [{ href: './geometries/0' }];
561
+ node.sharedResource = { href: './shared' };
562
+ if (texture) {
563
+ node.textureData = [{ href: './textures/0' }, { href: './textures/1' }];
564
+ }
565
+ if (attributes && attributes.length && this.layers0?.attributeStorageInfo?.length) {
566
+ node.attributeData = [];
567
+ for (let index = 0; index < attributes.length; index++) {
568
+ const folderName = this.layers0.attributeStorageInfo[index].key;
569
+ node.attributeData.push({ href: `./attributes/${folderName}/0` });
570
+ }
571
+ }
572
+ }
573
+ return node;
574
+ }
575
+ /**
576
+ * Write node resources in files
577
+ * @param resources - source tile (3DTile)
578
+ * @param resources.geometry - Uint8Array with geometry attributes
579
+ * @param resources.compressedGeometry - Uint8Array with compressed (draco) geometry
580
+ * @param resources.texture - texture image
581
+ * @param resources.sharedResources - shared resource data object
582
+ * @param resources.attributes - feature attributes
583
+ * @return {Promise<void>}
584
+ */
585
+ async _writeResources(resources, nodePath) {
586
+ const { geometry: geometryBuffer, compressedGeometry, texture, sharedResources, attributes } = resources;
587
+ const childPath = (0, path_1.join)(this.layers0Path, 'nodes', nodePath);
588
+ const slpkChildPath = (0, path_1.join)('nodes', nodePath);
589
+ await this._writeGeometries(geometryBuffer, compressedGeometry, childPath, slpkChildPath);
590
+ await this._writeShared(sharedResources, childPath, slpkChildPath, nodePath);
591
+ await this._writeTexture(texture, childPath, slpkChildPath);
592
+ await this._writeAttributes(attributes, childPath, slpkChildPath);
593
+ }
594
+ /**
595
+ * Write non-compressed and compressed geometries in files
596
+ * @param geometryBuffer - Uint8Array with geometry attributes
597
+ * @param compressedGeometry - Uint8Array with compressed (draco) geometry
598
+ * @param childPath - a child path to write resources
599
+ * @param slpkChildPath - resource path inside *slpk file
600
+ */
601
+ async _writeGeometries(geometryBuffer, compressedGeometry, childPath, slpkChildPath) {
602
+ if (this.options.slpk) {
603
+ const slpkGeometryPath = (0, path_1.join)(childPath, 'geometries');
604
+ this.fileMap[`${slpkChildPath}/geometries/0.bin.gz`] = await (0, file_utils_1.writeFileForSlpk)(slpkGeometryPath, geometryBuffer, '0.bin');
605
+ }
606
+ else {
607
+ const geometryPath = (0, path_1.join)(childPath, 'geometries/0/');
608
+ await (0, file_utils_1.writeFile)(geometryPath, geometryBuffer, 'index.bin');
609
+ }
610
+ if (this.options.draco) {
611
+ if (this.options.slpk) {
612
+ const slpkCompressedGeometryPath = (0, path_1.join)(childPath, 'geometries');
613
+ this.fileMap[`${slpkChildPath}/geometries/1.bin.gz`] = await (0, file_utils_1.writeFileForSlpk)(slpkCompressedGeometryPath, compressedGeometry, '1.bin');
614
+ }
615
+ else {
616
+ const compressedGeometryPath = (0, path_1.join)(childPath, 'geometries/1/');
617
+ await (0, file_utils_1.writeFile)(compressedGeometryPath, compressedGeometry, 'index.bin');
618
+ }
619
+ }
620
+ }
621
+ /**
622
+ * Write shared resources in a file
623
+ * @param sharedResources - shared resource data object
624
+ * @param childPath - a child path to write resources
625
+ * @param slpkChildPath - resource path inside *slpk file
626
+ * @param nodePath - a node path
627
+ */
628
+ async _writeShared(sharedResources, childPath, slpkChildPath, nodePath) {
629
+ sharedResources.nodePath = nodePath;
630
+ const sharedData = (0, json_map_transform_1.default)(sharedResources, (0, shared_resources_1.SHARED_RESOURCES)());
631
+ const sharedDataStr = JSON.stringify(sharedData);
632
+ if (this.options.slpk) {
633
+ const slpkSharedPath = (0, path_1.join)(childPath, 'shared');
634
+ this.fileMap[`${slpkChildPath}/shared/sharedResource.json.gz`] = await (0, file_utils_1.writeFileForSlpk)(slpkSharedPath, sharedDataStr, 'sharedResource.json');
635
+ }
636
+ else {
637
+ const sharedPath = (0, path_1.join)(childPath, 'shared/');
638
+ await (0, file_utils_1.writeFile)(sharedPath, sharedDataStr);
639
+ }
640
+ }
641
+ /**
642
+ * Generates textures based on texture mime type and fill in textureSetDefinitions data.
643
+ * @param texture - the texture image
644
+ * @param childPath - a child path to write resources
645
+ * @param slpkChildPath - the resource path inside *slpk file
646
+ */
647
+ async _writeTexture(texture, childPath, slpkChildPath) {
648
+ if (texture) {
649
+ const format = this._getFormatByMimeType(texture?.mimeType);
650
+ const formats = [];
651
+ const textureData = texture.bufferView.data;
652
+ switch (format) {
653
+ case 'jpg':
654
+ case 'png': {
655
+ formats.push({ name: '0', format });
656
+ await this.writeTextureFile(textureData, '0', format, childPath, slpkChildPath);
657
+ if (this.generateTextures) {
658
+ formats.push({ name: '1', format: 'ktx2' });
659
+ const ktx2TextureData = new Uint8Array(await (0, core_1.encode)(texture.image, textures_1.KTX2BasisUniversalTextureWriter));
660
+ await this.writeTextureFile(ktx2TextureData, '1', 'ktx2', childPath, slpkChildPath);
661
+ }
662
+ break;
663
+ }
664
+ case 'ktx2': {
665
+ formats.push({ name: '1', format });
666
+ await this.writeTextureFile(textureData, '1', format, childPath, slpkChildPath);
667
+ if (this.generateTextures) {
668
+ formats.push({ name: '0', format: 'jpg' });
669
+ const decodedFromKTX2TextureData = new Uint8Array(await (0, core_1.encode)(texture.image.data[0], images_1.ImageWriter));
670
+ await this.writeTextureFile(decodedFromKTX2TextureData, '0', 'jpg', childPath, slpkChildPath);
671
+ }
672
+ }
673
+ }
674
+ if (!this.layers0.textureSetDefinitions.length) {
675
+ this.layers0.textureSetDefinitions.push({ formats });
676
+ }
677
+ }
678
+ }
679
+ /**
680
+ * Write the texture image in a file
681
+ * @param textureData
682
+ * @param name
683
+ * @param format
684
+ * @param childPath
685
+ * @param slpkChildPath
686
+ */
687
+ async writeTextureFile(textureData, name, format, childPath, slpkChildPath) {
688
+ if (this.options.slpk) {
689
+ const slpkTexturePath = (0, path_1.join)(childPath, 'textures');
690
+ const compress = false;
691
+ this.fileMap[`${slpkChildPath}/textures/${name}.${format}`] = await (0, file_utils_1.writeFileForSlpk)(slpkTexturePath, textureData, `${name}.${format}`, compress);
692
+ }
693
+ else {
694
+ const texturePath = (0, path_1.join)(childPath, `textures/${name}/`);
695
+ await (0, file_utils_1.writeFile)(texturePath, textureData, `index.${format}`);
696
+ }
697
+ }
698
+ /**
699
+ * Write feature attributes in files
700
+ * @param attributes - feature attributes
701
+ * @param childPath - a child path to write resources
702
+ * @param slpkChildPath - the resource path inside *slpk file
703
+ */
704
+ async _writeAttributes(attributes, childPath, slpkChildPath) {
705
+ if (attributes.length && this.layers0?.attributeStorageInfo?.length) {
706
+ for (let index = 0; index < attributes.length; index++) {
707
+ const folderName = this.layers0.attributeStorageInfo[index].key;
708
+ const fileBuffer = new Uint8Array(attributes[index]);
709
+ if (this.options.slpk) {
710
+ const slpkAttributesPath = (0, path_1.join)(childPath, 'attributes', folderName);
711
+ this.fileMap[`${slpkChildPath}/attributes/${folderName}.bin.gz`] = await (0, file_utils_1.writeFileForSlpk)(slpkAttributesPath, fileBuffer, '0.bin');
712
+ }
713
+ else {
714
+ const attributesPath = (0, path_1.join)(childPath, `attributes/${folderName}/0`);
715
+ await (0, file_utils_1.writeFile)(attributesPath, fileBuffer, 'index.bin');
716
+ }
717
+ }
718
+ }
719
+ }
720
+ /**
721
+ * Return file format by its MIME type
722
+ * @param mimeType - feature attributes
723
+ */
724
+ _getFormatByMimeType(mimeType) {
725
+ switch (mimeType) {
726
+ case 'image/jpeg':
727
+ return 'jpg';
728
+ case 'image/png':
729
+ return 'png';
730
+ case 'image/ktx2':
731
+ return 'ktx2';
732
+ default:
733
+ return 'jpg';
734
+ }
735
+ }
736
+ /**
737
+ * Find or create material in materialDefinitions array
738
+ * @param material - end-to-end index of the node
739
+ * @return material id
740
+ */
741
+ _findOrCreateMaterial(material) {
742
+ const hash = (0, md5_1.default)(JSON.stringify(material));
743
+ if (this.materialMap.has(hash)) {
744
+ return this.materialMap.get(hash);
745
+ }
746
+ const newMaterialId = this.materialDefinitions.push(material) - 1;
747
+ this.materialMap.set(hash, newMaterialId);
748
+ return newMaterialId;
749
+ }
750
+ /**
751
+ * Generate storage attribute for map segmentation.
752
+ * @param attributeIndex - order index of attribute (f_0, f_1 ...).
753
+ * @param key - attribute key from batch table.\
754
+ * @param attributeType - attribute type.
755
+ * @return Updated storageAttribute.
756
+ */
757
+ _createdStorageAttribute(attributeIndex, key, attributeType) {
758
+ const storageAttribute = {
759
+ key: `f_${attributeIndex}`,
760
+ name: key,
761
+ ordering: ['attributeValues'],
762
+ header: [{ property: 'count', valueType: 'UInt32' }],
763
+ attributeValues: { valueType: 'Int32', valuesPerElement: 1 }
764
+ };
765
+ switch (attributeType) {
766
+ case OBJECT_ID_TYPE:
767
+ this._setupIdAttribute(storageAttribute);
768
+ break;
769
+ case STRING_TYPE:
770
+ this._setupStringAttribute(storageAttribute);
771
+ break;
772
+ case DOUBLE_TYPE:
773
+ this._setupDoubleAttribute(storageAttribute);
774
+ break;
775
+ case SHORT_INT_TYPE:
776
+ break;
777
+ default:
778
+ this._setupStringAttribute(storageAttribute);
779
+ }
780
+ return storageAttribute;
781
+ }
782
+ /**
783
+ * Get the attribute type for attributeStorageInfo https://github.com/Esri/i3s-spec/blob/master/docs/1.7/attributeStorageInfo.cmn.md
784
+ * @param key - attribute's key
785
+ * @param attribute - attribute's type in batchTable
786
+ */
787
+ getAttributeType(key, attribute) {
788
+ if (key === OBJECT_ID_TYPE) {
789
+ return OBJECT_ID_TYPE;
790
+ }
791
+ if (typeof attribute === STRING_TYPE) {
792
+ return STRING_TYPE;
793
+ }
794
+ else if (typeof attribute === 'number') {
795
+ return Number.isInteger(attribute) ? SHORT_INT_TYPE : DOUBLE_TYPE;
796
+ }
797
+ return STRING_TYPE;
798
+ }
799
+ /**
800
+ * Setup storage attribute as string.
801
+ * @param storageAttribute - attribute for map segmentation.
802
+ */
803
+ _setupStringAttribute(storageAttribute) {
804
+ storageAttribute.ordering.unshift('attributeByteCounts');
805
+ storageAttribute.header.push({ property: 'attributeValuesByteCount', valueType: 'UInt32' });
806
+ storageAttribute.attributeValues = {
807
+ valueType: 'String',
808
+ encoding: 'UTF-8',
809
+ valuesPerElement: 1
810
+ };
811
+ storageAttribute.attributeByteCounts = {
812
+ valueType: 'UInt32',
813
+ valuesPerElement: 1
814
+ };
815
+ }
816
+ /**
817
+ * Setup Id attribute for map segmentation.
818
+ * @param storageAttribute - attribute for map segmentation .
819
+ */
820
+ _setupIdAttribute(storageAttribute) {
821
+ storageAttribute.attributeValues = {
822
+ valueType: 'Oid32',
823
+ valuesPerElement: 1
824
+ };
825
+ }
826
+ /**
827
+ * Setup double attribute for map segmentation.
828
+ * @param storageAttribute - attribute for map segmentation .
829
+ */
830
+ _setupDoubleAttribute(storageAttribute) {
831
+ storageAttribute.attributeValues = {
832
+ valueType: 'Float64',
833
+ valuesPerElement: 1
834
+ };
835
+ }
836
+ /**
837
+ * Setup field attribute for map segmentation.
838
+ * @param key - attribute for map segmentation.
839
+ * @param fieldAttributeType - esri attribute type ('esriFieldTypeString' or 'esriFieldTypeOID').
840
+ */
841
+ _createFieldAttribute(key, fieldAttributeType) {
842
+ return {
843
+ name: key,
844
+ type: fieldAttributeType,
845
+ alias: key
846
+ };
847
+ }
848
+ /**
849
+ * Do conversion of 3DTiles batch table to I3s node attributes.
850
+ * @param batchTable - Table with layer meta data.
851
+ */
852
+ _convertBatchTableInfoToNodeAttributes(batchTable) {
853
+ let attributeIndex = 0;
854
+ const batchTableWithObjectId = {
855
+ OBJECTID: [0],
856
+ ...batchTable
857
+ };
858
+ for (const key in batchTableWithObjectId) {
859
+ const firstAttribute = batchTableWithObjectId[key][0];
860
+ const attributeType = this.getAttributeType(key, firstAttribute);
861
+ const storageAttribute = this._createdStorageAttribute(attributeIndex, key, attributeType);
862
+ const fieldAttributeType = this._getFieldAttributeType(attributeType);
863
+ const fieldAttribute = this._createFieldAttribute(key, fieldAttributeType);
864
+ const popupInfo = this._createPopupInfo(batchTableWithObjectId);
865
+ this.layers0.attributeStorageInfo.push(storageAttribute);
866
+ this.layers0.fields.push(fieldAttribute);
867
+ this.layers0.popupInfo = popupInfo;
868
+ this.layers0.layerType = _3D_OBJECT_LAYER_TYPE;
869
+ attributeIndex += 1;
870
+ }
871
+ }
872
+ /**
873
+ * Find and return attribute type based on key form Batch table.
874
+ * @param attributeType
875
+ */
876
+ _getFieldAttributeType(attributeType) {
877
+ switch (attributeType) {
878
+ case OBJECT_ID_TYPE:
879
+ return 'esriFieldTypeOID';
880
+ case STRING_TYPE:
881
+ return 'esriFieldTypeString';
882
+ case SHORT_INT_TYPE:
883
+ return 'esriFieldTypeInteger';
884
+ case DOUBLE_TYPE:
885
+ return 'esriFieldTypeDouble';
886
+ default:
887
+ return 'esriFieldTypeString';
888
+ }
889
+ }
890
+ /**
891
+ * Generate popup info to show metadata on the map.
892
+ * @param batchTable - Batch table data with OBJECTID.
893
+ * @return data for correct rendering of popup.
894
+ */
895
+ _createPopupInfo(batchTable) {
896
+ const title = '{OBJECTID}';
897
+ const mediaInfos = [];
898
+ const fieldInfos = [];
899
+ const popupElements = [];
900
+ const expressionInfos = [];
901
+ for (const key in batchTable) {
902
+ fieldInfos.push({
903
+ fieldName: key,
904
+ visible: true,
905
+ isEditable: false,
906
+ label: key
907
+ });
908
+ }
909
+ popupElements.push({
910
+ fieldInfos,
911
+ type: 'fields'
912
+ });
913
+ return {
914
+ title,
915
+ mediaInfos,
916
+ popupElements,
917
+ fieldInfos,
918
+ expressionInfos
919
+ };
920
+ }
921
+ /**
922
+ * Print statistics in the end of conversion
923
+ * @param params - output files data
924
+ */
925
+ async _finishConversion(params) {
926
+ const { tilesCount, tilesWithAddRefineCount } = this.refinementCounter;
927
+ const addRefinementPercentage = tilesWithAddRefineCount
928
+ ? (tilesWithAddRefineCount / tilesCount) * 100
929
+ : 0;
930
+ const filesSize = await (0, statistic_utills_1.calculateFilesSize)(params);
931
+ const diff = process_1.default.hrtime(this.conversionStartTime);
932
+ const conversionTime = (0, statistic_utills_1.timeConverter)(diff);
933
+ console.log(`------------------------------------------------`); // eslint-disable-line no-undef, no-console
934
+ console.log(`Finishing conversion of ${_3D_TILES}`); // eslint-disable-line no-undef, no-console
935
+ console.log(`Total conversion time: ${conversionTime}`); // eslint-disable-line no-undef, no-console
936
+ console.log(`Vertex count: `, this.vertexCounter); // eslint-disable-line no-undef, no-console
937
+ console.log(`File(s) size: `, filesSize, ' bytes'); // eslint-disable-line no-undef, no-console
938
+ console.log(`Percentage of tiles with "ADD" refinement type:`, addRefinementPercentage, '%'); // eslint-disable-line no-undef, no-console
939
+ console.log(`------------------------------------------------`); // eslint-disable-line no-undef, no-console
940
+ }
941
+ /**
942
+ * Fetch preload options for ION tileset
943
+ */
944
+ async _fetchPreloadOptions() {
945
+ if (!this.Loader.preload) {
946
+ return {};
947
+ }
948
+ const options = {
949
+ 'cesium-ion': { accessToken: this.options.token || ION_DEFAULT_TOKEN }
950
+ };
951
+ const preloadOptions = await this.Loader.preload(this.options.inputUrl, options);
952
+ this.refreshTokenTime = process_1.default.hrtime();
953
+ return { ...options, ...preloadOptions };
954
+ }
955
+ /**
956
+ * Update options of source tileset
957
+ */
958
+ async _updateTilesetOptions() {
959
+ const diff = process_1.default.hrtime(this.refreshTokenTime);
960
+ if (diff[0] < REFRESH_TOKEN_TIMEOUT) {
961
+ return;
962
+ }
963
+ this.refreshTokenTime = process_1.default.hrtime();
964
+ const preloadOptions = await this._fetchPreloadOptions();
965
+ this.sourceTileset.options = { ...this.sourceTileset.options, ...preloadOptions };
966
+ if (preloadOptions.headers) {
967
+ this.sourceTileset.loadOptions.fetch = {
968
+ ...this.sourceTileset.loadOptions.fetch,
969
+ headers: preloadOptions.headers
970
+ };
971
+ console.log('Authorization Bearer token has been updated'); // eslint-disable-line no-undef, no-console
972
+ }
973
+ }
974
+ /** Do calculations of all tiles and tiles with "ADD" type of refinement.
975
+ * @param tile
976
+ */
977
+ _checkAddRefinementTypeForTile(tile) {
978
+ const ADD_TILE_REFINEMENT = 1;
979
+ if (tile.refine === ADD_TILE_REFINEMENT) {
980
+ this.refinementCounter.tilesWithAddRefineCount += 1;
981
+ console.warn('This tile uses "ADD" type of refinement'); // eslint-disable-line
982
+ }
983
+ this.refinementCounter.tilesCount += 1;
984
+ }
985
+ /**
986
+ * Check if the tile's content format is supported by the converter
987
+ * @param sourceRootTile
988
+ * @returns
989
+ */
990
+ isContentSupported(sourceRootTile) {
991
+ return ['b3dm', 'glTF'].includes(sourceRootTile?.content?.type);
992
+ }
993
+ }
994
+ exports.default = I3SConverter;