@loaders.gl/tile-converter 4.1.1 → 4.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/3d-tiles-converter/3d-tiles-converter.d.ts +11 -0
- package/dist/3d-tiles-converter/3d-tiles-converter.d.ts.map +1 -1
- package/dist/3d-tiles-converter/3d-tiles-converter.js +77 -27
- package/dist/3d-tiles-converter/3d-tiles-converter.js.map +1 -1
- package/dist/3d-tiles-converter/helpers/load-i3s.d.ts +22 -1
- package/dist/3d-tiles-converter/helpers/load-i3s.d.ts.map +1 -1
- package/dist/3d-tiles-converter/helpers/load-i3s.js +49 -4
- package/dist/3d-tiles-converter/helpers/load-i3s.js.map +1 -1
- package/dist/converter-cli.js +2 -1
- package/dist/converter-cli.js.map +1 -1
- package/dist/converter.min.cjs +137 -130
- package/dist/deps-installer/deps-installer.js +1 -1
- package/dist/deps-installer/deps-installer.js.map +1 -1
- package/dist/i3s-converter/helpers/attribute-metadata-info.d.ts +10 -0
- package/dist/i3s-converter/helpers/attribute-metadata-info.d.ts.map +1 -1
- package/dist/i3s-converter/helpers/attribute-metadata-info.js +5 -0
- package/dist/i3s-converter/helpers/attribute-metadata-info.js.map +1 -1
- package/dist/i3s-converter/helpers/load-3d-tiles.d.ts.map +1 -1
- package/dist/i3s-converter/helpers/load-3d-tiles.js +22 -2
- package/dist/i3s-converter/helpers/load-3d-tiles.js.map +1 -1
- package/dist/i3s-converter/helpers/node-index-document.d.ts +2 -1
- package/dist/i3s-converter/helpers/node-index-document.d.ts.map +1 -1
- package/dist/i3s-converter/helpers/node-index-document.js +6 -8
- package/dist/i3s-converter/helpers/node-index-document.js.map +1 -1
- package/dist/i3s-converter/i3s-converter.d.ts +18 -0
- package/dist/i3s-converter/i3s-converter.d.ts.map +1 -1
- package/dist/i3s-converter/i3s-converter.js +121 -24
- package/dist/i3s-converter/i3s-converter.js.map +1 -1
- package/dist/i3s-server/bin/i3s-server.min.cjs +86 -86
- package/dist/index.cjs +792 -101
- package/dist/lib/json-schemas/conversion-dump-json-schema.d.ts +463 -0
- package/dist/lib/json-schemas/conversion-dump-json-schema.d.ts.map +1 -0
- package/dist/lib/json-schemas/conversion-dump-json-schema.js +463 -0
- package/dist/lib/json-schemas/conversion-dump-json-schema.js.map +1 -0
- package/dist/lib/utils/conversion-dump.d.ts +65 -8
- package/dist/lib/utils/conversion-dump.d.ts.map +1 -1
- package/dist/lib/utils/conversion-dump.js +98 -18
- package/dist/lib/utils/conversion-dump.js.map +1 -1
- package/dist/lib/utils/file-utils.d.ts +6 -0
- package/dist/lib/utils/file-utils.d.ts.map +1 -1
- package/dist/lib/utils/file-utils.js +7 -0
- package/dist/lib/utils/file-utils.js.map +1 -1
- package/dist/pgm-loader.js +1 -1
- package/dist/pgm-loader.js.map +1 -1
- package/package.json +15 -14
- package/src/3d-tiles-converter/3d-tiles-converter.ts +104 -31
- package/src/3d-tiles-converter/helpers/load-i3s.ts +86 -7
- package/src/converter-cli.ts +2 -1
- package/src/i3s-converter/helpers/attribute-metadata-info.ts +16 -0
- package/src/i3s-converter/helpers/load-3d-tiles.ts +52 -2
- package/src/i3s-converter/helpers/node-index-document.ts +18 -8
- package/src/i3s-converter/i3s-converter.ts +198 -41
- package/src/lib/json-schemas/conversion-dump-json-schema.ts +285 -0
- package/src/lib/utils/conversion-dump.ts +200 -26
- package/src/lib/utils/file-utils.ts +13 -0
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
NodeReference,
|
|
5
5
|
I3STilesetHeader
|
|
6
6
|
} from '@loaders.gl/i3s';
|
|
7
|
-
import type {Tiles3DTileJSON} from '@loaders.gl/3d-tiles';
|
|
7
|
+
import type {Tile3DBoundingVolume, Tiles3DTileJSON} from '@loaders.gl/3d-tiles';
|
|
8
8
|
|
|
9
9
|
import {join} from 'path';
|
|
10
10
|
import process from 'process';
|
|
@@ -24,8 +24,10 @@ import {WorkerFarm} from '@loaders.gl/worker-utils';
|
|
|
24
24
|
import {BROWSER_ERROR_MESSAGE} from '../constants';
|
|
25
25
|
import B3dmConverter, {I3SAttributesData} from './helpers/b3dm-converter';
|
|
26
26
|
import {I3STileHeader} from '@loaders.gl/i3s/src/types';
|
|
27
|
-
import {loadI3SContent} from './helpers/load-i3s';
|
|
27
|
+
import {loadFromArchive, loadI3SContent, openSLPK} from './helpers/load-i3s';
|
|
28
28
|
import {I3SLoaderOptions} from '@loaders.gl/i3s/src/i3s-loader';
|
|
29
|
+
import {ZipFileSystem} from '../../../zip/src';
|
|
30
|
+
import {ConversionDump, ConversionDumpOptions} from '../lib/utils/conversion-dump';
|
|
29
31
|
|
|
30
32
|
const I3S = 'I3S';
|
|
31
33
|
|
|
@@ -41,6 +43,7 @@ export default class Tiles3DConverter {
|
|
|
41
43
|
sourceTileset: I3STilesetHeader | null;
|
|
42
44
|
attributeStorageInfo?: AttributeStorageInfo[] | null;
|
|
43
45
|
workerSource: {[key: string]: string} = {};
|
|
46
|
+
slpkFilesystem: ZipFileSystem | null = null;
|
|
44
47
|
loaderOptions: I3SLoaderOptions = {
|
|
45
48
|
_nodeWorkers: true,
|
|
46
49
|
reuseWorkers: true,
|
|
@@ -52,6 +55,7 @@ export default class Tiles3DConverter {
|
|
|
52
55
|
workerUrl: './modules/i3s/dist/i3s-content-worker-node.js'
|
|
53
56
|
}
|
|
54
57
|
};
|
|
58
|
+
conversionDump: ConversionDump;
|
|
55
59
|
|
|
56
60
|
constructor() {
|
|
57
61
|
this.options = {};
|
|
@@ -62,6 +66,7 @@ export default class Tiles3DConverter {
|
|
|
62
66
|
this.sourceTileset = null;
|
|
63
67
|
this.attributeStorageInfo = null;
|
|
64
68
|
this.workerSource = {};
|
|
69
|
+
this.conversionDump = new ConversionDump();
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
/**
|
|
@@ -79,20 +84,32 @@ export default class Tiles3DConverter {
|
|
|
79
84
|
tilesetName: string;
|
|
80
85
|
maxDepth?: number;
|
|
81
86
|
egmFilePath: string;
|
|
87
|
+
inquirer?: Promise<unknown>;
|
|
82
88
|
}): Promise<any> {
|
|
83
89
|
if (isBrowser) {
|
|
84
90
|
console.log(BROWSER_ERROR_MESSAGE);
|
|
85
91
|
return BROWSER_ERROR_MESSAGE;
|
|
86
92
|
}
|
|
87
|
-
const {inputUrl, outputPath, tilesetName, maxDepth, egmFilePath} = options;
|
|
93
|
+
const {inputUrl, outputPath, tilesetName, maxDepth, egmFilePath, inquirer} = options;
|
|
88
94
|
this.conversionStartTime = process.hrtime();
|
|
89
|
-
this.options = {maxDepth};
|
|
95
|
+
this.options = {maxDepth, inquirer};
|
|
90
96
|
|
|
91
97
|
console.log('Loading egm file...'); // eslint-disable-line
|
|
92
98
|
this.geoidHeightModel = await load(egmFilePath, PGMLoader);
|
|
93
99
|
console.log('Loading egm file completed!'); // eslint-disable-line
|
|
94
100
|
|
|
95
|
-
this.
|
|
101
|
+
this.slpkFilesystem = await openSLPK(inputUrl);
|
|
102
|
+
|
|
103
|
+
this.sourceTileset = await loadFromArchive(
|
|
104
|
+
inputUrl,
|
|
105
|
+
I3SLoader,
|
|
106
|
+
{
|
|
107
|
+
...this.loaderOptions,
|
|
108
|
+
// @ts-expect-error `isTileset` can be boolean of 'auto' but TS expects a string
|
|
109
|
+
i3s: {...this.loaderOptions.i3s, isTileset: true}
|
|
110
|
+
},
|
|
111
|
+
this.slpkFilesystem
|
|
112
|
+
);
|
|
96
113
|
|
|
97
114
|
if (!this.sourceTileset) {
|
|
98
115
|
return;
|
|
@@ -105,11 +122,28 @@ export default class Tiles3DConverter {
|
|
|
105
122
|
|
|
106
123
|
this.tilesetPath = join(`${outputPath}`, `${tilesetName}`);
|
|
107
124
|
this.attributeStorageInfo = this.sourceTileset.attributeStorageInfo;
|
|
125
|
+
|
|
126
|
+
await this.conversionDump.createDump(options as ConversionDumpOptions);
|
|
127
|
+
if (this.conversionDump.restored && this.options.inquirer) {
|
|
128
|
+
const result = await this.options.inquirer.prompt([
|
|
129
|
+
{
|
|
130
|
+
name: 'resumeConversion',
|
|
131
|
+
type: 'confirm',
|
|
132
|
+
message:
|
|
133
|
+
'Dump file of the previous conversion exists, do you want to resume that conversion?'
|
|
134
|
+
}
|
|
135
|
+
]);
|
|
136
|
+
if (!result.resumeConversion) {
|
|
137
|
+
this.conversionDump.reset();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
108
140
|
// Removing the tilesetPath needed to exclude erroneous files after conversion
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
141
|
+
if (!this.conversionDump.restored) {
|
|
142
|
+
try {
|
|
143
|
+
await removeDir(this.tilesetPath);
|
|
144
|
+
} catch (e) {
|
|
145
|
+
// do nothing
|
|
146
|
+
}
|
|
113
147
|
}
|
|
114
148
|
|
|
115
149
|
const rootTile: Tiles3DTileJSON = {
|
|
@@ -125,8 +159,13 @@ export default class Tiles3DConverter {
|
|
|
125
159
|
|
|
126
160
|
const tileset = transform({root: rootTile}, tilesetTemplate());
|
|
127
161
|
await writeFile(this.tilesetPath, JSON.stringify(tileset), 'tileset.json');
|
|
162
|
+
await this.conversionDump.deleteDumpFile();
|
|
128
163
|
|
|
129
|
-
this._finishConversion({slpk: false, outputPath, tilesetName});
|
|
164
|
+
await this._finishConversion({slpk: false, outputPath, tilesetName});
|
|
165
|
+
|
|
166
|
+
if (this.slpkFilesystem) {
|
|
167
|
+
this.slpkFilesystem.destroy();
|
|
168
|
+
}
|
|
130
169
|
|
|
131
170
|
// Clean up worker pools
|
|
132
171
|
const workerFarm = WorkerFarm.getWorkerFarm({});
|
|
@@ -148,7 +187,22 @@ export default class Tiles3DConverter {
|
|
|
148
187
|
): Promise<void> {
|
|
149
188
|
const sourceChild = await this._loadChildNode(parentSourceNode, childNodeInfo);
|
|
150
189
|
if (sourceChild.contentUrl) {
|
|
151
|
-
|
|
190
|
+
if (
|
|
191
|
+
this.conversionDump.restored &&
|
|
192
|
+
this.conversionDump.isFileConversionComplete(`${sourceChild.id}.b3dm`) &&
|
|
193
|
+
(sourceChild.obb || sourceChild.mbs)
|
|
194
|
+
) {
|
|
195
|
+
const {child} = this._createChildAndBoundingVolume(sourceChild);
|
|
196
|
+
parentNode.children.push(child);
|
|
197
|
+
await this._addChildren(sourceChild, child, level + 1);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const content = await loadI3SContent(
|
|
201
|
+
this.sourceTileset,
|
|
202
|
+
sourceChild,
|
|
203
|
+
this.loaderOptions,
|
|
204
|
+
this.slpkFilesystem
|
|
205
|
+
);
|
|
152
206
|
|
|
153
207
|
if (!content) {
|
|
154
208
|
await this._addChildren(sourceChild, parentNode, level + 1);
|
|
@@ -162,33 +216,24 @@ export default class Tiles3DConverter {
|
|
|
162
216
|
featureAttributes = await this._loadChildAttributes(sourceChild, this.attributeStorageInfo);
|
|
163
217
|
}
|
|
164
218
|
|
|
165
|
-
|
|
166
|
-
sourceChild.obb = createObbFromMbs(sourceChild.mbs);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const boundingVolume = {
|
|
170
|
-
box: i3sObbTo3dTilesObb(sourceChild.obb, this.geoidHeightModel)
|
|
171
|
-
};
|
|
172
|
-
const child: Tiles3DTileJSON = {
|
|
173
|
-
boundingVolume,
|
|
174
|
-
geometricError: convertScreenThresholdToGeometricError(sourceChild),
|
|
175
|
-
children: []
|
|
176
|
-
};
|
|
219
|
+
const {child, boundingVolume} = this._createChildAndBoundingVolume(sourceChild);
|
|
177
220
|
|
|
178
221
|
const i3sAttributesData: I3SAttributesData = {
|
|
179
222
|
tileContent: content,
|
|
180
|
-
box: boundingVolume.box,
|
|
223
|
+
box: boundingVolume.box || [],
|
|
181
224
|
textureFormat: sourceChild.textureFormat
|
|
182
225
|
};
|
|
183
226
|
|
|
184
227
|
const b3dmConverter = new B3dmConverter();
|
|
185
228
|
const b3dm = await b3dmConverter.convert(i3sAttributesData, featureAttributes);
|
|
186
229
|
|
|
187
|
-
|
|
188
|
-
uri: `${sourceChild.id}.b3dm`,
|
|
189
|
-
boundingVolume
|
|
190
|
-
};
|
|
230
|
+
await this.conversionDump.addNode(`${sourceChild.id}.b3dm`, sourceChild.id);
|
|
191
231
|
await writeFile(this.tilesetPath, new Uint8Array(b3dm), `${sourceChild.id}.b3dm`);
|
|
232
|
+
await this.conversionDump.updateConvertedNodesDumpFile(
|
|
233
|
+
`${sourceChild.id}.b3dm`,
|
|
234
|
+
sourceChild.id,
|
|
235
|
+
true
|
|
236
|
+
);
|
|
192
237
|
parentNode.children.push(child);
|
|
193
238
|
|
|
194
239
|
await this._addChildren(sourceChild, child, level + 1);
|
|
@@ -235,20 +280,48 @@ export default class Tiles3DConverter {
|
|
|
235
280
|
} else {
|
|
236
281
|
const nodeUrl = this._relativeUrlToFullUrl(parentNode.url, childNodeInfo.href!);
|
|
237
282
|
// load metadata
|
|
238
|
-
const options = {
|
|
283
|
+
const options: I3SLoaderOptions = {
|
|
239
284
|
i3s: {
|
|
240
285
|
...this.loaderOptions,
|
|
286
|
+
// @ts-expect-error
|
|
241
287
|
isTileHeader: true,
|
|
242
288
|
loadContent: false
|
|
243
289
|
}
|
|
244
290
|
};
|
|
245
291
|
|
|
246
292
|
console.log(`Node conversion: ${nodeUrl}`); // eslint-disable-line no-console,no-undef
|
|
247
|
-
header = await
|
|
293
|
+
header = await loadFromArchive(nodeUrl, I3SLoader, options, this.slpkFilesystem);
|
|
248
294
|
}
|
|
249
295
|
return header;
|
|
250
296
|
}
|
|
251
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Create child and child's boundingVolume for the converted node
|
|
300
|
+
* @param sourceChild
|
|
301
|
+
* @returns child and child's boundingVolume
|
|
302
|
+
*/
|
|
303
|
+
private _createChildAndBoundingVolume(sourceChild: I3STileHeader): {
|
|
304
|
+
boundingVolume: Tile3DBoundingVolume;
|
|
305
|
+
child: Tiles3DTileJSON;
|
|
306
|
+
} {
|
|
307
|
+
if (!sourceChild.obb) {
|
|
308
|
+
sourceChild.obb = createObbFromMbs(sourceChild.mbs);
|
|
309
|
+
}
|
|
310
|
+
const boundingVolume: Tile3DBoundingVolume = {
|
|
311
|
+
box: i3sObbTo3dTilesObb(sourceChild.obb, this.geoidHeightModel)
|
|
312
|
+
};
|
|
313
|
+
const child: Tiles3DTileJSON = {
|
|
314
|
+
boundingVolume,
|
|
315
|
+
geometricError: convertScreenThresholdToGeometricError(sourceChild),
|
|
316
|
+
children: [],
|
|
317
|
+
content: {
|
|
318
|
+
uri: `${sourceChild.id}.b3dm`,
|
|
319
|
+
boundingVolume
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
return {boundingVolume, child};
|
|
323
|
+
}
|
|
324
|
+
|
|
252
325
|
/**
|
|
253
326
|
* Make an url of a resource from its relative url having the base url
|
|
254
327
|
* @param baseUrl the base url. A resulting url will be related from this url
|
|
@@ -292,7 +365,7 @@ export default class Tiles3DConverter {
|
|
|
292
365
|
attributeType: this._getAttributeType(attribute)
|
|
293
366
|
};
|
|
294
367
|
|
|
295
|
-
promises.push(
|
|
368
|
+
promises.push(loadFromArchive(inputUrl, I3SAttributeLoader, options, this.slpkFilesystem));
|
|
296
369
|
}
|
|
297
370
|
const attributesList = await Promise.all(promises);
|
|
298
371
|
this._replaceNestedArrays(attributesList);
|
|
@@ -1,36 +1,45 @@
|
|
|
1
|
-
import {load} from '@loaders.gl/core';
|
|
1
|
+
import {LoaderWithParser, load} from '@loaders.gl/core';
|
|
2
2
|
import {
|
|
3
3
|
I3STileContent,
|
|
4
4
|
I3STileHeader,
|
|
5
5
|
I3STilesetHeader,
|
|
6
6
|
I3SLoader,
|
|
7
|
-
I3SLoaderOptions
|
|
7
|
+
I3SLoaderOptions,
|
|
8
|
+
parseSLPKArchive
|
|
8
9
|
} from '@loaders.gl/i3s';
|
|
10
|
+
import {FileHandleFile} from '@loaders.gl/loader-utils';
|
|
11
|
+
import {ZipFileSystem} from '@loaders.gl/zip';
|
|
12
|
+
|
|
13
|
+
export type SLPKUrlParts = {slpkFileName: string; internalFileName: string};
|
|
9
14
|
|
|
10
15
|
/**
|
|
11
16
|
* Load I3S node content
|
|
12
17
|
* @param sourceTileset - source layer JSON
|
|
13
18
|
* @param sourceTile - source I3S node metadata
|
|
14
19
|
* @param tilesetLoadOptions - load options for Tiles3DLoader
|
|
20
|
+
* @param slpkFilesystem - loaded instance of ZipFileSystem for local convertion from SLPK file
|
|
15
21
|
* @returns - 3DTiles tile content or null
|
|
16
22
|
*/
|
|
17
23
|
export const loadI3SContent = async (
|
|
18
24
|
sourceTileset: I3STilesetHeader | null,
|
|
19
25
|
sourceTile: I3STileHeader,
|
|
20
|
-
tilesetLoadOptions: I3SLoaderOptions
|
|
26
|
+
tilesetLoadOptions: I3SLoaderOptions,
|
|
27
|
+
slpkFilesystem: ZipFileSystem | null
|
|
21
28
|
): Promise<I3STileContent | null> => {
|
|
22
29
|
if (!sourceTileset || !sourceTile.contentUrl) {
|
|
23
30
|
return null;
|
|
24
31
|
}
|
|
25
32
|
|
|
26
|
-
const loadOptions = {
|
|
33
|
+
const loadOptions: I3SLoaderOptions = {
|
|
27
34
|
...tilesetLoadOptions,
|
|
28
35
|
i3s: {
|
|
29
36
|
...tilesetLoadOptions.i3s,
|
|
37
|
+
// @ts-expect-error
|
|
30
38
|
isTileset: false,
|
|
39
|
+
// @ts-expect-error
|
|
31
40
|
isTileHeader: false,
|
|
32
41
|
_tileOptions: {
|
|
33
|
-
attributeUrls: sourceTile.attributeUrls,
|
|
42
|
+
attributeUrls: sourceTile.attributeUrls || [],
|
|
34
43
|
textureUrl: sourceTile.textureUrl,
|
|
35
44
|
textureFormat: sourceTile.textureFormat,
|
|
36
45
|
textureLoaderOptions: sourceTile.textureLoaderOptions,
|
|
@@ -40,13 +49,83 @@ export const loadI3SContent = async (
|
|
|
40
49
|
},
|
|
41
50
|
_tilesetOptions: {
|
|
42
51
|
store: sourceTileset.store,
|
|
52
|
+
// @ts-expect-error
|
|
43
53
|
attributeStorageInfo: sourceTileset.attributeStorageInfo,
|
|
54
|
+
// @ts-expect-error
|
|
44
55
|
fields: sourceTileset.fields
|
|
45
56
|
}
|
|
46
57
|
}
|
|
47
58
|
};
|
|
48
|
-
const tileContent = await
|
|
59
|
+
const tileContent = await loadFromArchive(
|
|
60
|
+
sourceTile.contentUrl,
|
|
61
|
+
I3SLoader,
|
|
62
|
+
loadOptions,
|
|
63
|
+
slpkFilesystem
|
|
64
|
+
);
|
|
49
65
|
|
|
50
|
-
// @ts-expect-error
|
|
51
66
|
return tileContent;
|
|
52
67
|
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Load local SLPK file to ZipFileSystem instance
|
|
71
|
+
* @param url - path to SLPK file
|
|
72
|
+
* @returns instance of ZipFileSystem or null if url is not an SLPK file
|
|
73
|
+
*/
|
|
74
|
+
export async function openSLPK(url: string): Promise<ZipFileSystem | null> {
|
|
75
|
+
const slpkUrlParts = url.split('.slpk');
|
|
76
|
+
const {slpkFileName} = getSlpkUrlParts(url) || {};
|
|
77
|
+
if (slpkFileName) {
|
|
78
|
+
const slpkFileName = `${slpkUrlParts[0]}.slpk`;
|
|
79
|
+
const fileProvider = new FileHandleFile(slpkFileName);
|
|
80
|
+
const archive = await parseSLPKArchive(fileProvider, undefined, slpkFileName);
|
|
81
|
+
const fileSystem = new ZipFileSystem(archive);
|
|
82
|
+
return fileSystem;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Load a resource with load options and .3tz format support
|
|
89
|
+
* @param url - resource URL
|
|
90
|
+
* @param loader - loader to parse data (Tiles3DLoader / CesiumIonLoader)
|
|
91
|
+
* @param loadOptions - i3s loader options
|
|
92
|
+
* @returns i3s resource
|
|
93
|
+
*/
|
|
94
|
+
export async function loadFromArchive(
|
|
95
|
+
url: string,
|
|
96
|
+
loader: LoaderWithParser,
|
|
97
|
+
loadOptions: I3SLoaderOptions,
|
|
98
|
+
fileSystem: ZipFileSystem | null
|
|
99
|
+
) {
|
|
100
|
+
const slpkUrlParts = getSlpkUrlParts(url);
|
|
101
|
+
if (fileSystem !== null && slpkUrlParts !== null) {
|
|
102
|
+
const {internalFileName} = slpkUrlParts;
|
|
103
|
+
const content = await load(internalFileName, loader, {
|
|
104
|
+
...loadOptions,
|
|
105
|
+
fetch: fileSystem.fetch.bind(fileSystem)
|
|
106
|
+
});
|
|
107
|
+
return content;
|
|
108
|
+
}
|
|
109
|
+
return await load(url, loader, loadOptions);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Extract slpk file path and internal from the url
|
|
114
|
+
* For example, for `./path/to/file.slpk/nodes/0` it returns
|
|
115
|
+
* {"slpkFileName": "./path/to/file.slpk", "internalFileName": "/nodes/0" }
|
|
116
|
+
* @param url full internal file path
|
|
117
|
+
* @returns object with internal slpk file parts
|
|
118
|
+
*/
|
|
119
|
+
function getSlpkUrlParts(url: string): SLPKUrlParts | null {
|
|
120
|
+
const slpkUrlParts = url.split('.slpk');
|
|
121
|
+
let result: SLPKUrlParts | null;
|
|
122
|
+
// Not '.slpk'. The file will be loaded with global fetch function
|
|
123
|
+
if (slpkUrlParts.length === 1) {
|
|
124
|
+
result = null;
|
|
125
|
+
} else if (slpkUrlParts.length === 2) {
|
|
126
|
+
result = {slpkFileName: `${slpkUrlParts[0]}.slpk`, internalFileName: slpkUrlParts[1].slice(1)};
|
|
127
|
+
} else {
|
|
128
|
+
throw new Error('Unexpected URL format');
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
package/src/converter-cli.ts
CHANGED
|
@@ -210,7 +210,8 @@ async function convert(options: ValidatedTileConversionOptions) {
|
|
|
210
210
|
outputPath: options.output,
|
|
211
211
|
tilesetName: options.name,
|
|
212
212
|
maxDepth: options.maxDepth,
|
|
213
|
-
egmFilePath: options.egm
|
|
213
|
+
egmFilePath: options.egm,
|
|
214
|
+
inquirer
|
|
214
215
|
});
|
|
215
216
|
break;
|
|
216
217
|
case TILESET_TYPE._3DTILES:
|
|
@@ -9,6 +9,12 @@ import type {
|
|
|
9
9
|
|
|
10
10
|
import {AttributeType} from '../types';
|
|
11
11
|
|
|
12
|
+
export type AttributeMetadataInfoObject = {
|
|
13
|
+
attributeStorageInfo: AttributeStorageInfo[];
|
|
14
|
+
fields: Field[];
|
|
15
|
+
popupInfo: PopupInfo | undefined;
|
|
16
|
+
};
|
|
17
|
+
|
|
12
18
|
export class AttributeMetadataInfo {
|
|
13
19
|
private _attributeStorageInfo: AttributeStorageInfo[];
|
|
14
20
|
private _fields: Field[];
|
|
@@ -95,6 +101,16 @@ export class AttributeMetadataInfo {
|
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Set AttributeMetadataInfo from object
|
|
106
|
+
* @param object - object with AttributeMetadataInfo props
|
|
107
|
+
*/
|
|
108
|
+
fromObject(object: AttributeMetadataInfoObject) {
|
|
109
|
+
this._attributeStorageInfo = object.attributeStorageInfo;
|
|
110
|
+
this._fields = object.fields;
|
|
111
|
+
this._popupInfo = object.popupInfo;
|
|
112
|
+
}
|
|
113
|
+
|
|
98
114
|
/**
|
|
99
115
|
* Generates storage attribute for map segmentation.
|
|
100
116
|
* @param attributeIndex - order index of attribute (f_0, f_1 ...).
|
|
@@ -4,8 +4,17 @@ import type {
|
|
|
4
4
|
Tiles3DTileJSONPostprocessed,
|
|
5
5
|
Tiles3DTilesetJSONPostprocessed
|
|
6
6
|
} from '@loaders.gl/3d-tiles';
|
|
7
|
-
import {
|
|
7
|
+
import {Tiles3DArchive} from '@loaders.gl/3d-tiles';
|
|
8
8
|
import {LoaderWithParser, load} from '@loaders.gl/core';
|
|
9
|
+
import {FileHandleFile, FileProvider} from '@loaders.gl/loader-utils';
|
|
10
|
+
import {
|
|
11
|
+
CD_HEADER_SIGNATURE,
|
|
12
|
+
ZipFileSystem,
|
|
13
|
+
parseHashTable,
|
|
14
|
+
parseZipCDFileHeader,
|
|
15
|
+
parseZipLocalFileHeader,
|
|
16
|
+
searchFromTheEnd
|
|
17
|
+
} from '@loaders.gl/zip';
|
|
9
18
|
|
|
10
19
|
/**
|
|
11
20
|
* Load nested 3DTiles tileset. If the sourceTile is not nested tileset - do nothing
|
|
@@ -104,7 +113,10 @@ export async function loadFromArchive(
|
|
|
104
113
|
}
|
|
105
114
|
if (filename) {
|
|
106
115
|
const tz3Path = `${tz3UrlParts[0]}.3tz`;
|
|
107
|
-
const
|
|
116
|
+
const fileProvider = new FileHandleFile(tz3Path);
|
|
117
|
+
const hashTable = await loadHashTable(fileProvider);
|
|
118
|
+
const archive = new Tiles3DArchive(fileProvider, hashTable, tz3Path);
|
|
119
|
+
const fileSystem = new ZipFileSystem(archive);
|
|
108
120
|
const content = await load(filename, loader, {
|
|
109
121
|
...loadOptions,
|
|
110
122
|
fetch: fileSystem.fetch.bind(fileSystem)
|
|
@@ -123,3 +135,41 @@ export async function loadFromArchive(
|
|
|
123
135
|
export function isNestedTileset(tile: Tiles3DTileJSONPostprocessed) {
|
|
124
136
|
return tile?.type === 'json' || tile?.type === '3tz';
|
|
125
137
|
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Load hash file from 3TZ
|
|
141
|
+
* @param fileProvider - binary reader of 3TZ
|
|
142
|
+
* @returns hash table of the 3TZ file content or undefined if the hash file is not presented inside
|
|
143
|
+
*/
|
|
144
|
+
async function loadHashTable(
|
|
145
|
+
fileProvider: FileProvider
|
|
146
|
+
): Promise<undefined | Record<string, bigint>> {
|
|
147
|
+
let hashTable: undefined | Record<string, bigint>;
|
|
148
|
+
|
|
149
|
+
const hashCDOffset = await searchFromTheEnd(fileProvider, CD_HEADER_SIGNATURE);
|
|
150
|
+
|
|
151
|
+
const cdFileHeader = await parseZipCDFileHeader(hashCDOffset, fileProvider);
|
|
152
|
+
|
|
153
|
+
// '@3dtilesIndex1@' is index file that must be the last in the archive. It allows
|
|
154
|
+
// to improve load and read performance when the archive contains a very large number
|
|
155
|
+
// of files.
|
|
156
|
+
if (cdFileHeader?.fileName === '@3dtilesIndex1@') {
|
|
157
|
+
const localFileHeader = await parseZipLocalFileHeader(
|
|
158
|
+
cdFileHeader.localHeaderOffset,
|
|
159
|
+
fileProvider
|
|
160
|
+
);
|
|
161
|
+
if (!localFileHeader) {
|
|
162
|
+
throw new Error('corrupted 3tz');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const fileDataOffset = localFileHeader.fileDataOffset;
|
|
166
|
+
const hashFile = await fileProvider.slice(
|
|
167
|
+
fileDataOffset,
|
|
168
|
+
fileDataOffset + localFileHeader.compressedSize
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
hashTable = parseHashTable(hashFile);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return hashTable;
|
|
175
|
+
}
|
|
@@ -12,6 +12,7 @@ import {openJson, writeFile, writeFileForSlpk} from '../../lib/utils/file-utils'
|
|
|
12
12
|
import I3SConverter from '../i3s-converter';
|
|
13
13
|
import {NODE as nodeTemplate} from '../json-templates/node';
|
|
14
14
|
import {I3SConvertedResources} from '../types';
|
|
15
|
+
import {DumpMetadata} from '../../lib/utils/conversion-dump';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Wrapper for https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DNodeIndexDocument.cmn.md data
|
|
@@ -287,9 +288,8 @@ export class NodeIndexDocument {
|
|
|
287
288
|
boundingVolumes: BoundingVolumes,
|
|
288
289
|
lodSelection: LodSelection[],
|
|
289
290
|
nodeInPage: NodeInPage,
|
|
290
|
-
resources: I3SConvertedResources
|
|
291
|
+
resources: I3SConvertedResources | DumpMetadata
|
|
291
292
|
): Promise<Node3DIndexDocument> {
|
|
292
|
-
const {texture, attributes} = resources;
|
|
293
293
|
const nodeId = nodeInPage.index!;
|
|
294
294
|
const parentNodeData = await parentNode.load();
|
|
295
295
|
const nodeData = {
|
|
@@ -313,19 +313,29 @@ export class NodeIndexDocument {
|
|
|
313
313
|
node.geometryData = [{href: './geometries/0'}];
|
|
314
314
|
node.sharedResource = {href: './shared'};
|
|
315
315
|
|
|
316
|
-
if (
|
|
316
|
+
if (
|
|
317
|
+
('texture' in resources && resources.texture) ||
|
|
318
|
+
('texelCountHint' in resources && resources.texelCountHint)
|
|
319
|
+
) {
|
|
317
320
|
node.textureData = [{href: './textures/0'}, {href: './textures/1'}];
|
|
318
321
|
}
|
|
319
322
|
|
|
320
323
|
if (
|
|
321
|
-
attributes &&
|
|
322
|
-
|
|
323
|
-
|
|
324
|
+
('attributes' in resources &&
|
|
325
|
+
resources.attributes &&
|
|
326
|
+
resources.attributes.length &&
|
|
327
|
+
parentNode.converter.layers0?.attributeStorageInfo?.length) ||
|
|
328
|
+
('attributesCount' in resources &&
|
|
329
|
+
resources.attributesCount &&
|
|
330
|
+
parentNode.converter.layers0?.attributeStorageInfo?.length)
|
|
324
331
|
) {
|
|
332
|
+
const attributesLength =
|
|
333
|
+
('attributes' in resources ? resources.attributes?.length : resources.attributesCount) ||
|
|
334
|
+
0;
|
|
325
335
|
node.attributeData = [];
|
|
326
336
|
const minimumLength =
|
|
327
|
-
|
|
328
|
-
?
|
|
337
|
+
attributesLength < parentNode.converter.layers0.attributeStorageInfo.length
|
|
338
|
+
? attributesLength
|
|
329
339
|
: parentNode.converter.layers0.attributeStorageInfo.length;
|
|
330
340
|
for (let index = 0; index < minimumLength; index++) {
|
|
331
341
|
const folderName = parentNode.converter.layers0.attributeStorageInfo[index].key;
|