@loaders.gl/tile-converter 4.5.3 → 5.0.0-alpha.10
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/LICENSE +14 -3
- package/README.md +464 -0
- package/dist/converter.min.cjs +199 -260
- package/dist/i3s-server/bin/i3s-server.min.cjs +106 -341
- package/dist/index.cjs +38482 -0
- package/dist/index.js +38453 -0
- package/dist/v5/adapters.cjs +1992 -0
- package/dist/v5/adapters.d.ts +22 -0
- package/dist/v5/adapters.js +1972 -0
- package/dist/v5/browser-sink.d.ts +37 -0
- package/dist/v5/browser-source.d.ts +38 -0
- package/dist/v5/browser-tileset-source.d.ts +70 -0
- package/dist/v5/browser.cjs +64 -0
- package/dist/v5/browser.d.ts +34 -0
- package/dist/v5/browser.js +57 -0
- package/dist/v5/conversion-api.d.ts +122 -0
- package/dist/v5/conversion-archive.d.ts +32 -0
- package/dist/v5/core.cjs +863 -0
- package/dist/v5/core.d.ts +16 -0
- package/dist/v5/core.js +845 -0
- package/dist/v5/feature-arrow.d.ts +36 -0
- package/dist/v5/i3s-mesh-conversion.d.ts +26 -0
- package/dist/v5/index.cjs +63 -0
- package/dist/v5/index.d.ts +32 -0
- package/dist/v5/index.js +59 -0
- package/dist/v5/mesh-conversion.d.ts +62 -0
- package/dist/v5/mesh-draco.d.ts +8 -0
- package/dist/v5/mesh-source-features.d.ts +21 -0
- package/dist/v5/mesh-source-texture.d.ts +4 -0
- package/dist/v5/mesh-source.d.ts +34 -0
- package/dist/v5/mesh-tileset-archive.d.ts +18 -0
- package/dist/v5/mesh-tileset-sink.d.ts +51 -0
- package/dist/v5/mesh.d.ts +81 -0
- package/dist/v5/point-cloud-conversion.d.ts +31 -0
- package/dist/v5/point-cloud-source-encoder.d.ts +49 -0
- package/dist/v5/point-cloud-source.d.ts +25 -0
- package/dist/v5/point-cloud.d.ts +24 -0
- package/dist/v5/resource-manifest.d.ts +58 -0
- package/dist/v5/spatial-conversion.d.ts +59 -0
- package/dist/v5/tileset-source.d.ts +21 -0
- package/package.json +53 -27
- package/src/common/README.md +5 -0
- package/src/index.ts +1 -2
- package/src/{3d-tiles-converter → v4/3d-tiles-converter}/3d-tiles-converter.ts +14 -3
- package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/3d-tiles-content-converter.ts +2 -2
- package/src/v4/3d-tiles-converter/helpers/validate-i3s-spatial-reference.ts +124 -0
- package/src/{3d-tiles-converter → v4/3d-tiles-converter}/json-templates/tileset.ts +4 -4
- package/src/{converter-cli.ts → v4/converter-cli.ts} +4 -4
- package/src/{deps-installer → v4/deps-installer}/deps-installer.ts +10 -7
- package/src/{i3s-converter → v4/i3s-converter}/helpers/attribute-metadata-info.ts +1 -1
- package/src/{i3s-converter → v4/i3s-converter}/helpers/batch-ids-extensions.ts +1 -1
- package/src/v4/i3s-converter/helpers/encode-string-attribute.ts +23 -0
- package/src/{i3s-converter → v4/i3s-converter}/helpers/geometry-attributes.ts +1 -1
- package/src/{i3s-converter → v4/i3s-converter}/helpers/geometry-converter.ts +34 -59
- package/src/{i3s-converter → v4/i3s-converter}/helpers/gltf-attributes.ts +7 -6
- package/src/{i3s-converter → v4/i3s-converter}/helpers/load-3d-tiles.ts +5 -9
- package/src/{i3s-converter → v4/i3s-converter}/helpers/node-debug.ts +5 -6
- package/src/{i3s-converter → v4/i3s-converter}/i3s-converter.ts +16 -19
- package/src/{i3s-converter → v4/i3s-converter}/json-templates/geometry-definitions.ts +4 -4
- package/src/{i3s-converter → v4/i3s-converter}/json-templates/layers.ts +6 -6
- package/src/{i3s-converter → v4/i3s-converter}/json-templates/node.ts +1 -1
- package/src/{i3s-converter → v4/i3s-converter}/json-templates/scene-server.ts +1 -1
- package/src/{i3s-converter → v4/i3s-converter}/json-templates/shared-resources.ts +1 -1
- package/src/{i3s-converter → v4/i3s-converter}/json-templates/store.ts +1 -1
- package/src/{i3s-server → v4/i3s-server}/README.md +2 -2
- package/src/{i3s-server → v4/i3s-server}/controllers/slpk-controller.ts +1 -1
- package/src/{i3s-server → v4/i3s-server}/routes/slpk-router.ts +2 -1
- package/src/v4/index.ts +2 -0
- package/src/{lib → v4/lib}/utils/compress-util.ts +8 -7
- package/src/{lib → v4/lib}/utils/conversion-dump.ts +4 -4
- package/src/{lib → v4/lib}/utils/file-utils.ts +2 -2
- package/src/{lib → v4/lib}/utils/lod-conversion-utils.ts +2 -4
- package/src/v5/adapters.ts +56 -0
- package/src/v5/browser-sink.ts +130 -0
- package/src/v5/browser-source.ts +155 -0
- package/src/v5/browser-tileset-source.ts +328 -0
- package/src/v5/browser.ts +105 -0
- package/src/v5/conversion-api.ts +287 -0
- package/src/v5/conversion-archive.ts +85 -0
- package/src/v5/core.ts +61 -0
- package/src/v5/feature-arrow.ts +429 -0
- package/src/v5/i3s-mesh-conversion.ts +152 -0
- package/src/v5/index.ts +99 -0
- package/src/v5/mesh-conversion.ts +200 -0
- package/src/v5/mesh-draco.ts +138 -0
- package/src/v5/mesh-source-features.ts +232 -0
- package/src/v5/mesh-source-texture.ts +122 -0
- package/src/v5/mesh-source.ts +360 -0
- package/src/v5/mesh-tileset-archive.ts +50 -0
- package/src/v5/mesh-tileset-sink.ts +324 -0
- package/src/v5/mesh.ts +517 -0
- package/src/v5/point-cloud-conversion.ts +80 -0
- package/src/v5/point-cloud-source-encoder.ts +105 -0
- package/src/v5/point-cloud-source.ts +84 -0
- package/src/v5/point-cloud.ts +271 -0
- package/src/v5/resource-manifest.ts +213 -0
- package/src/v5/spatial-conversion.ts +305 -0
- package/src/v5/tileset-source.ts +54 -0
- /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/i3s-obb-to-3d-tiles-obb.ts +0 -0
- /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/load-i3s.ts +0 -0
- /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/texture-atlas.ts +0 -0
- /package/src/{constants.ts → v4/constants.ts} +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/coordinate-converter.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/create-scene-server-path.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/feature-attributes.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/node-index-document.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/node-pages.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/preprocess-3d-tiles.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/progress.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/tileset-traversal.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/json-templates/metadata.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/types.ts +0 -0
- /package/src/{i3s-server → v4/i3s-server}/app.ts +0 -0
- /package/src/{i3s-server → v4/i3s-server}/bin/www.ts +0 -0
- /package/src/{i3s-server → v4/i3s-server}/certs/cert.pem +0 -0
- /package/src/{i3s-server → v4/i3s-server}/certs/key.pem +0 -0
- /package/src/{i3s-server → v4/i3s-server}/utils/create-scene-server.ts +0 -0
- /package/src/{i3s-server → v4/i3s-server}/utils/server-utils.ts +0 -0
- /package/src/{lib → v4/lib}/json-schemas/conversion-dump-json-schema.ts +0 -0
- /package/src/{lib → v4/lib}/utils/cli-utils.ts +0 -0
- /package/src/{lib → v4/lib}/utils/geometry-utils.ts +0 -0
- /package/src/{lib → v4/lib}/utils/queue.ts +0 -0
- /package/src/{lib → v4/lib}/utils/statistic-utils.ts +0 -0
- /package/src/{lib → v4/lib}/utils/write-queue.ts +0 -0
- /package/src/{pgm-loader.ts → v4/pgm-loader.ts} +0 -0
package/dist/v5/core.cjs
ADDED
|
@@ -0,0 +1,863 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/v5/core.ts
|
|
21
|
+
var core_exports = {};
|
|
22
|
+
__export(core_exports, {
|
|
23
|
+
TileConversionError: () => TileConversionError,
|
|
24
|
+
convertTileset: () => convertTileset,
|
|
25
|
+
createBoundedMemoryTileConversionSink: () => createBoundedMemoryTileConversionSink,
|
|
26
|
+
createBrowserTileConversionSource: () => createBrowserTileConversionSource,
|
|
27
|
+
createBrowserTilesetConversionSource: () => createBrowserTilesetConversionSource,
|
|
28
|
+
createI3SConversionSpatialContext: () => createI3SConversionSpatialContext,
|
|
29
|
+
createManifestBackedTileConversionSink: () => createManifestBackedTileConversionSink,
|
|
30
|
+
createTiles3DConversionSpatialContext: () => createTiles3DConversionSpatialContext,
|
|
31
|
+
createTilesetConversionSource: () => createTilesetConversionSource,
|
|
32
|
+
inspectTileset: () => inspectTileset,
|
|
33
|
+
traversePointCloudSource: () => traversePointCloudSource,
|
|
34
|
+
validateTileset: () => validateTileset
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(core_exports);
|
|
37
|
+
|
|
38
|
+
// src/v5/conversion-api.ts
|
|
39
|
+
var TileConversionError = class extends Error {
|
|
40
|
+
/** Stable machine-readable failure code. */
|
|
41
|
+
code;
|
|
42
|
+
/** Diagnostics associated with this failure. */
|
|
43
|
+
diagnostics;
|
|
44
|
+
/** Creates a typed conversion failure. */
|
|
45
|
+
constructor(code, message, diagnostics = []) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = "TileConversionError";
|
|
48
|
+
this.code = code;
|
|
49
|
+
this.diagnostics = diagnostics;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
async function inspectTileset(source, signal) {
|
|
53
|
+
throwIfAborted(signal);
|
|
54
|
+
const inspection = await source.inspect(signal);
|
|
55
|
+
throwIfAborted(signal);
|
|
56
|
+
return inspection;
|
|
57
|
+
}
|
|
58
|
+
async function convertTileset(options) {
|
|
59
|
+
const {
|
|
60
|
+
source,
|
|
61
|
+
codec,
|
|
62
|
+
sink,
|
|
63
|
+
measureInputBytes,
|
|
64
|
+
measureOutputBytes,
|
|
65
|
+
maxInputResourceBytes = Number.POSITIVE_INFINITY,
|
|
66
|
+
maxOutputResourceBytes = Number.POSITIVE_INFINITY,
|
|
67
|
+
signal,
|
|
68
|
+
onProgress
|
|
69
|
+
} = options;
|
|
70
|
+
let inputResources = 0;
|
|
71
|
+
let outputResources = 0;
|
|
72
|
+
let inputBytes = 0;
|
|
73
|
+
let outputBytes = 0;
|
|
74
|
+
let largestOutputResourceBytes = 0;
|
|
75
|
+
const diagnostics = [];
|
|
76
|
+
const reportProgress = (phase) => {
|
|
77
|
+
onProgress?.({ phase, inputResources, outputResources, inputBytes, outputBytes });
|
|
78
|
+
};
|
|
79
|
+
try {
|
|
80
|
+
if (maxInputResourceBytes !== Number.POSITIVE_INFINITY && (!Number.isSafeInteger(maxInputResourceBytes) || maxInputResourceBytes < 0)) {
|
|
81
|
+
throw new TileConversionError(
|
|
82
|
+
"INVALID_INPUT_RESOURCE_LIMIT",
|
|
83
|
+
"maxInputResourceBytes must be a nonnegative safe integer or Infinity"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
reportProgress("inspect");
|
|
87
|
+
const inspection = await inspectTileset(source, signal);
|
|
88
|
+
for await (const inputResource of source.read(inspection, signal)) {
|
|
89
|
+
throwIfAborted(signal);
|
|
90
|
+
const inputResourceBytes = measureInputBytes(inputResource);
|
|
91
|
+
if (!Number.isSafeInteger(inputResourceBytes) || inputResourceBytes < 0) {
|
|
92
|
+
throw new TileConversionError(
|
|
93
|
+
"INVALID_INPUT_RESOURCE_SIZE",
|
|
94
|
+
"measureInputBytes must return a nonnegative safe integer"
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (inputResourceBytes > maxInputResourceBytes) {
|
|
98
|
+
throw new TileConversionError(
|
|
99
|
+
"INPUT_RESOURCE_TOO_LARGE",
|
|
100
|
+
`Input resource is ${inputResourceBytes} bytes, exceeding the configured limit of ${maxInputResourceBytes} bytes`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
inputResources++;
|
|
104
|
+
inputBytes += inputResourceBytes;
|
|
105
|
+
reportProgress("convert");
|
|
106
|
+
const convertedResources = await codec.convert(inputResource, inspection, signal);
|
|
107
|
+
for await (const outputResource of convertedResources) {
|
|
108
|
+
throwIfAborted(signal);
|
|
109
|
+
reportProgress("validate");
|
|
110
|
+
const resourceDiagnostics = await codec.validateOutput?.(outputResource, signal) || [];
|
|
111
|
+
diagnostics.push(...resourceDiagnostics);
|
|
112
|
+
const outputResourceBytes = measureOutputBytes(outputResource);
|
|
113
|
+
largestOutputResourceBytes = Math.max(largestOutputResourceBytes, outputResourceBytes);
|
|
114
|
+
if (outputResourceBytes > maxOutputResourceBytes) {
|
|
115
|
+
throw new TileConversionError(
|
|
116
|
+
"OUTPUT_RESOURCE_TOO_LARGE",
|
|
117
|
+
`Output resource is ${outputResourceBytes} bytes, exceeding the configured limit of ${maxOutputResourceBytes} bytes`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (resourceDiagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
121
|
+
throw new TileConversionError(
|
|
122
|
+
"OUTPUT_VALIDATION_FAILED",
|
|
123
|
+
"Output resource validation failed",
|
|
124
|
+
resourceDiagnostics
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
throwIfAborted(signal);
|
|
128
|
+
await sink.write(outputResource, signal);
|
|
129
|
+
throwIfAborted(signal);
|
|
130
|
+
outputResources++;
|
|
131
|
+
outputBytes += outputResourceBytes;
|
|
132
|
+
reportProgress("convert");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const report = {
|
|
136
|
+
state: "completed",
|
|
137
|
+
inputResources,
|
|
138
|
+
outputResources,
|
|
139
|
+
inputBytes,
|
|
140
|
+
outputBytes,
|
|
141
|
+
largestOutputResourceBytes,
|
|
142
|
+
diagnostics
|
|
143
|
+
};
|
|
144
|
+
throwIfAborted(signal);
|
|
145
|
+
reportProgress("finalize");
|
|
146
|
+
await sink.finalize(report);
|
|
147
|
+
return report;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
try {
|
|
150
|
+
await sink.abort(error);
|
|
151
|
+
} catch (abortError) {
|
|
152
|
+
throw new AggregateError(
|
|
153
|
+
[error, abortError],
|
|
154
|
+
"Conversion failed and the destination could not abort"
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function validateTileset(options) {
|
|
161
|
+
throwIfAborted(options.signal);
|
|
162
|
+
const diagnostics = await options.validate(options.tileset, options.signal);
|
|
163
|
+
throwIfAborted(options.signal);
|
|
164
|
+
return {
|
|
165
|
+
valid: !diagnostics.some((diagnostic) => diagnostic.severity === "error"),
|
|
166
|
+
diagnostics
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function throwIfAborted(signal) {
|
|
170
|
+
if (signal?.aborted) {
|
|
171
|
+
throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/v5/browser-source.ts
|
|
176
|
+
function createBrowserTileConversionSource(options) {
|
|
177
|
+
if (!options.resourceId || !Number.isSafeInteger(options.maxInputBytes) || options.maxInputBytes < 0) {
|
|
178
|
+
throw new TileConversionError(
|
|
179
|
+
"INVALID_BROWSER_SOURCE_OPTIONS",
|
|
180
|
+
"Browser sources require a resource ID and a non-negative safe input byte limit"
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
async inspect(signal) {
|
|
185
|
+
throwIfAborted2(signal);
|
|
186
|
+
const contentType = options.contentType ?? getBlobContentType(options.input);
|
|
187
|
+
return {
|
|
188
|
+
resourceId: options.resourceId,
|
|
189
|
+
...contentType ? { contentType } : {}
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
async *read(inspection, signal) {
|
|
193
|
+
throwIfAborted2(signal);
|
|
194
|
+
const input = options.input;
|
|
195
|
+
let data;
|
|
196
|
+
let contentType = inspection.contentType;
|
|
197
|
+
if (typeof input === "string") {
|
|
198
|
+
const response = await fetch(input, { signal });
|
|
199
|
+
if (!response.ok) {
|
|
200
|
+
throw new TileConversionError(
|
|
201
|
+
"BROWSER_SOURCE_FETCH_FAILED",
|
|
202
|
+
`Could not fetch browser input (${response.status} ${response.statusText})`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
contentType = contentType ?? response.headers.get("content-type") ?? void 0;
|
|
206
|
+
data = await readResponseBytes(response, options.maxInputBytes, signal);
|
|
207
|
+
} else {
|
|
208
|
+
if (input.size > options.maxInputBytes) {
|
|
209
|
+
throw createInputLimitError(options.maxInputBytes);
|
|
210
|
+
}
|
|
211
|
+
data = new Uint8Array(await input.arrayBuffer());
|
|
212
|
+
}
|
|
213
|
+
throwIfAborted2(signal);
|
|
214
|
+
yield {
|
|
215
|
+
resourceId: inspection.resourceId,
|
|
216
|
+
data,
|
|
217
|
+
...contentType ? { contentType } : {}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
async function readResponseBytes(response, maxInputBytes, signal) {
|
|
223
|
+
const reader = response.body?.getReader();
|
|
224
|
+
if (!reader) {
|
|
225
|
+
throw new TileConversionError(
|
|
226
|
+
"BROWSER_SOURCE_STREAM_UNAVAILABLE",
|
|
227
|
+
"Browser URL inputs must provide a readable response stream"
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
const data = new Uint8Array(maxInputBytes);
|
|
231
|
+
let totalBytes = 0;
|
|
232
|
+
try {
|
|
233
|
+
while (true) {
|
|
234
|
+
throwIfAborted2(signal);
|
|
235
|
+
const { done, value } = await reader.read();
|
|
236
|
+
if (done) break;
|
|
237
|
+
if (value.byteLength > maxInputBytes - totalBytes) {
|
|
238
|
+
await reader.cancel();
|
|
239
|
+
throw createInputLimitError(maxInputBytes);
|
|
240
|
+
}
|
|
241
|
+
data.set(value, totalBytes);
|
|
242
|
+
totalBytes += value.byteLength;
|
|
243
|
+
}
|
|
244
|
+
} finally {
|
|
245
|
+
reader.releaseLock();
|
|
246
|
+
}
|
|
247
|
+
return data.subarray(0, totalBytes);
|
|
248
|
+
}
|
|
249
|
+
function getBlobContentType(input) {
|
|
250
|
+
return typeof input === "string" ? void 0 : input.type || void 0;
|
|
251
|
+
}
|
|
252
|
+
function createInputLimitError(maxInputBytes) {
|
|
253
|
+
return new TileConversionError(
|
|
254
|
+
"INPUT_RESOURCE_TOO_LARGE",
|
|
255
|
+
`Browser input exceeds the configured ${maxInputBytes} byte memory limit`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
function throwIfAborted2(signal) {
|
|
259
|
+
if (signal?.aborted) {
|
|
260
|
+
throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/v5/point-cloud-source.ts
|
|
265
|
+
async function* traversePointCloudSource(source, options = {}) {
|
|
266
|
+
const { maxDepth = Number.POSITIVE_INFINITY, signal } = options;
|
|
267
|
+
if (maxDepth !== Number.POSITIVE_INFINITY && (!Number.isSafeInteger(maxDepth) || maxDepth < 0)) {
|
|
268
|
+
throw new RangeError("Point-cloud source maxDepth must be a non-negative integer");
|
|
269
|
+
}
|
|
270
|
+
throwIfAborted3(signal);
|
|
271
|
+
if (!source.isReady) {
|
|
272
|
+
await source.initialize();
|
|
273
|
+
}
|
|
274
|
+
throwIfAborted3(signal);
|
|
275
|
+
const root = await source.getRootTile();
|
|
276
|
+
const pendingTiles = [root];
|
|
277
|
+
const visitedTileIds = /* @__PURE__ */ new Set();
|
|
278
|
+
while (pendingTiles.length > 0) {
|
|
279
|
+
throwIfAborted3(signal);
|
|
280
|
+
const header = pendingTiles.pop();
|
|
281
|
+
if (visitedTileIds.has(header.id)) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
visitedTileIds.add(header.id);
|
|
285
|
+
const content = await source.loadTileContent(header);
|
|
286
|
+
throwIfAborted3(signal);
|
|
287
|
+
yield { header, content };
|
|
288
|
+
throwIfAborted3(signal);
|
|
289
|
+
if (header.level >= maxDepth) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const children = await source.getChildren(header);
|
|
293
|
+
throwIfAborted3(signal);
|
|
294
|
+
for (let index = children.length - 1; index >= 0; index--) {
|
|
295
|
+
pendingTiles.push(children[index]);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function throwIfAborted3(signal) {
|
|
300
|
+
if (signal?.aborted) {
|
|
301
|
+
throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/v5/spatial-conversion.ts
|
|
306
|
+
var import_tiles = require("@loaders.gl/tiles");
|
|
307
|
+
var import_core = require("@math.gl/core");
|
|
308
|
+
function createTiles3DConversionSpatialContext(discovered, options = {}) {
|
|
309
|
+
const spatialReference = createConversionSpatialReference(discovered, options);
|
|
310
|
+
if (spatialReference.status === "unresolved") {
|
|
311
|
+
throw createSpatialConversionError(spatialReference);
|
|
312
|
+
}
|
|
313
|
+
const transformer = spatialReference.status === "native" ? void 0 : createSpatialTransformer(
|
|
314
|
+
spatialReference,
|
|
315
|
+
() => new import_tiles.Tiles3DSpatialTransformer(spatialReference, options)
|
|
316
|
+
);
|
|
317
|
+
const outputSpatialReference = transformer ? (0, import_tiles.markTilesetSpatialReferenceTransformed)(spatialReference) : spatialReference;
|
|
318
|
+
return {
|
|
319
|
+
spatialReference: outputSpatialReference,
|
|
320
|
+
transformPositions: (positions) => transformer ? transformer.transformPositions(positions) : Float64Array.from(positions),
|
|
321
|
+
transformNormals: (normals, positions) => transformer ? transformer.transformNormals(normals, positions) : Float32Array.from(normals),
|
|
322
|
+
transformBoundingVolume: (volume, sourceTransform) => transformer ? transformer.transformBoundingVolume(volume, sourceTransform) : cloneBoundingVolume(volume)
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function createI3SConversionSpatialContext(discovered, options = {}) {
|
|
326
|
+
const spatialReference = createConversionSpatialReference(discovered, options);
|
|
327
|
+
if (spatialReference.status === "unresolved") {
|
|
328
|
+
throw createSpatialConversionError(spatialReference);
|
|
329
|
+
}
|
|
330
|
+
const transformer = spatialReference.status === "native" ? void 0 : createSpatialTransformer(
|
|
331
|
+
spatialReference,
|
|
332
|
+
() => new import_tiles.I3SSpatialTransformer(spatialReference, options)
|
|
333
|
+
);
|
|
334
|
+
const outputSpatialReference = transformer ? transformer.spatialReference : spatialReference;
|
|
335
|
+
return {
|
|
336
|
+
spatialReference: outputSpatialReference,
|
|
337
|
+
transformPositions: (positions, sourceOrigin) => transformer ? transformer.transformPositions(positions, sourceOrigin) : createNativeI3SPositions(positions, sourceOrigin, spatialReference),
|
|
338
|
+
transformPositionsAsync: (positions, sourceOrigin) => transformer ? transformer.transformPositionsAsync(positions, sourceOrigin) : Promise.resolve(createNativeI3SPositions(positions, sourceOrigin, spatialReference)),
|
|
339
|
+
transformNormals: (normals, positions, normalReferenceFrame) => transformer ? transformer.transformNormals(normals, positions, normalReferenceFrame) : Float32Array.from(normals),
|
|
340
|
+
transformBoundingVolume: (bounds) => transformer ? transformer.transformBoundingVolume(bounds) : cloneI3SBounds(bounds),
|
|
341
|
+
transformBoundingVolumeAsync: (bounds) => transformer ? transformer.transformBoundingVolumeAsync(bounds) : Promise.resolve(cloneI3SBounds(bounds))
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function createSpatialConversionError(spatialReference, cause) {
|
|
345
|
+
const diagnostics = spatialReference.warnings.map((message2) => ({
|
|
346
|
+
code: "SPATIAL_REFERENCE_WARNING",
|
|
347
|
+
message: message2,
|
|
348
|
+
severity: "warning"
|
|
349
|
+
}));
|
|
350
|
+
const causeMessage = cause instanceof Error ? `: ${cause.message}` : "";
|
|
351
|
+
const message = causeMessage ? `Spatial conversion could not be initialized${causeMessage}` : "The requested spatial conversion cannot be resolved from the available metadata";
|
|
352
|
+
diagnostics.push({
|
|
353
|
+
code: "SPATIAL_REFERENCE_UNRESOLVED",
|
|
354
|
+
message,
|
|
355
|
+
severity: "error"
|
|
356
|
+
});
|
|
357
|
+
return new TileConversionError("SPATIAL_REFERENCE_UNRESOLVED", message, diagnostics);
|
|
358
|
+
}
|
|
359
|
+
function createConversionSpatialReference(discovered, options) {
|
|
360
|
+
if (!("status" in discovered)) {
|
|
361
|
+
return (0, import_tiles.createTilesetSpatialReference)(discovered, options);
|
|
362
|
+
}
|
|
363
|
+
if (discovered.status === "transformed") {
|
|
364
|
+
throw createSpatialConversionError(
|
|
365
|
+
discovered,
|
|
366
|
+
new Error("coordinates are already transformed")
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
const sourceMetadata = {
|
|
370
|
+
sourceCrs: discovered.sourceCrs,
|
|
371
|
+
sourceCrsState: discovered.crs.state,
|
|
372
|
+
sourceCrsRepresentation: discovered.crs.state === "explicit" || discovered.crs.state === "default" ? discovered.crs.representation : void 0,
|
|
373
|
+
sourceCrsAlternatives: discovered.crs.state === "explicit" || discovered.crs.state === "default" ? discovered.crs.alternatives : void 0,
|
|
374
|
+
verticalCrs: discovered.verticalCrs,
|
|
375
|
+
units: discovered.units,
|
|
376
|
+
verticalUnitScale: discovered.verticalUnitScale,
|
|
377
|
+
coordinateEpoch: discovered.coordinateEpoch,
|
|
378
|
+
heightReference: discovered.heightReference,
|
|
379
|
+
elevationMode: discovered.elevationMode,
|
|
380
|
+
elevationOffset: discovered.elevationOffset,
|
|
381
|
+
elevationUnit: discovered.elevationUnit,
|
|
382
|
+
elevationUnitScale: discovered.elevationUnitScale,
|
|
383
|
+
coordinateFrame: discovered.coordinateFrame,
|
|
384
|
+
axisOrder: discovered.axisOrder,
|
|
385
|
+
provenance: discovered.provenance,
|
|
386
|
+
warnings: discovered.warnings
|
|
387
|
+
};
|
|
388
|
+
const retainedOptions = {
|
|
389
|
+
...options,
|
|
390
|
+
targetCrs: options.targetCrs ?? discovered.targetCrs,
|
|
391
|
+
targetHeightReference: options.targetHeightReference ?? discovered.targetHeightReference,
|
|
392
|
+
outputCoordinates: options.outputCoordinates ?? discovered.outputCoordinates
|
|
393
|
+
};
|
|
394
|
+
return (0, import_tiles.createTilesetSpatialReference)(sourceMetadata, retainedOptions);
|
|
395
|
+
}
|
|
396
|
+
function createSpatialTransformer(spatialReference, createTransformer) {
|
|
397
|
+
try {
|
|
398
|
+
return createTransformer();
|
|
399
|
+
} catch (error) {
|
|
400
|
+
throw createSpatialConversionError(spatialReference, error);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function cloneBoundingVolume(volume) {
|
|
404
|
+
return {
|
|
405
|
+
...volume,
|
|
406
|
+
box: volume.box ? [...volume.box] : void 0,
|
|
407
|
+
sphere: volume.sphere ? [...volume.sphere] : void 0,
|
|
408
|
+
region: volume.region ? [...volume.region] : void 0
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function cloneI3SBounds(bounds) {
|
|
412
|
+
return {
|
|
413
|
+
...bounds,
|
|
414
|
+
mbs: bounds.mbs ? Array.from(bounds.mbs) : void 0,
|
|
415
|
+
obb: bounds.obb ? {
|
|
416
|
+
center: Array.from(bounds.obb.center),
|
|
417
|
+
halfSize: Array.from(bounds.obb.halfSize),
|
|
418
|
+
quaternion: Array.from(bounds.obb.quaternion)
|
|
419
|
+
} : void 0
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function createNativeI3SPositions(sourcePositions, sourceOrigin, spatialReference) {
|
|
423
|
+
if (sourcePositions.length % 3 !== 0 || sourceOrigin.length < 3) {
|
|
424
|
+
throw new Error("I3S positions and origin must contain complete xyz coordinates");
|
|
425
|
+
}
|
|
426
|
+
const origin = [
|
|
427
|
+
Number(sourceOrigin[0]),
|
|
428
|
+
Number(sourceOrigin[1]),
|
|
429
|
+
Number(sourceOrigin[2])
|
|
430
|
+
];
|
|
431
|
+
const sourcePositions64 = Float64Array.from(sourcePositions);
|
|
432
|
+
const positions = new Float32Array(sourcePositions.length);
|
|
433
|
+
for (let index = 0; index < sourcePositions.length; index += 3) {
|
|
434
|
+
positions[index] = Number(sourcePositions[index]) - origin[0];
|
|
435
|
+
positions[index + 1] = Number(sourcePositions[index + 1]) - origin[1];
|
|
436
|
+
positions[index + 2] = Number(sourcePositions[index + 2]) - origin[2];
|
|
437
|
+
}
|
|
438
|
+
const isGeographic = spatialReference.coordinateFrame === "geographic";
|
|
439
|
+
const modelMatrix = isGeographic ? new import_core.Matrix4() : new import_core.Matrix4().translate(origin);
|
|
440
|
+
return {
|
|
441
|
+
positions,
|
|
442
|
+
sourcePositions: sourcePositions64,
|
|
443
|
+
origin,
|
|
444
|
+
cartographicOrigin: isGeographic ? origin : [0, 0, 0],
|
|
445
|
+
coordinateSystem: isGeographic ? "lnglat-offsets" : "cartesian",
|
|
446
|
+
modelMatrix
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/v5/tileset-source.ts
|
|
451
|
+
var import_tiles2 = require("@loaders.gl/tiles");
|
|
452
|
+
function createTilesetConversionSource(tileset, options = {}) {
|
|
453
|
+
return {
|
|
454
|
+
async inspect(signal) {
|
|
455
|
+
signal?.throwIfAborted();
|
|
456
|
+
await tileset.tilesetInitializationPromise;
|
|
457
|
+
signal?.throwIfAborted();
|
|
458
|
+
if (!tileset.root) {
|
|
459
|
+
throw new TileConversionError(
|
|
460
|
+
"TILESET_ROOT_REQUIRED",
|
|
461
|
+
"Tileset initialization completed without a root tile"
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
return tileset.source.getMetadata();
|
|
465
|
+
},
|
|
466
|
+
read: (_inspection, signal) => (0, import_tiles2.traverseTilesetContents)(tileset, { unloadContent: options.unloadContent, signal })
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// src/v5/resource-manifest.ts
|
|
471
|
+
var TILE_RESOURCE_MANIFEST_VERSION = 1;
|
|
472
|
+
async function createManifestBackedTileConversionSink(options) {
|
|
473
|
+
const priorManifest = await options.manifestStore.load();
|
|
474
|
+
const resources = /* @__PURE__ */ new Map();
|
|
475
|
+
if (priorManifest) {
|
|
476
|
+
validateManifest(priorManifest);
|
|
477
|
+
for (const entry of priorManifest.resources) {
|
|
478
|
+
resources.set(entry.resourceId, entry);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
let writeChain = Promise.resolve();
|
|
482
|
+
let writeFailed = false;
|
|
483
|
+
let writeFailure;
|
|
484
|
+
let closed = false;
|
|
485
|
+
const persistManifest = async (complete, report) => {
|
|
486
|
+
await options.manifestStore.save({
|
|
487
|
+
version: TILE_RESOURCE_MANIFEST_VERSION,
|
|
488
|
+
complete,
|
|
489
|
+
resources: [...resources.values()].sort(
|
|
490
|
+
(left, right) => left.resourceId < right.resourceId ? -1 : left.resourceId > right.resourceId ? 1 : 0
|
|
491
|
+
),
|
|
492
|
+
...report ? { report } : {}
|
|
493
|
+
});
|
|
494
|
+
};
|
|
495
|
+
const writeResource = async (resource, signal) => {
|
|
496
|
+
if (closed) {
|
|
497
|
+
throw new TileConversionError("RESOURCE_SINK_CLOSED", "Cannot write after sink finalization");
|
|
498
|
+
}
|
|
499
|
+
if (writeFailed) {
|
|
500
|
+
throw writeFailure;
|
|
501
|
+
}
|
|
502
|
+
const entry = createManifestEntry(options, resource);
|
|
503
|
+
const priorEntry = resources.get(entry.resourceId);
|
|
504
|
+
if (priorEntry) {
|
|
505
|
+
if (priorEntry.byteLength !== entry.byteLength || priorEntry.fingerprint !== entry.fingerprint) {
|
|
506
|
+
throw new TileConversionError(
|
|
507
|
+
"RESOURCE_RESUME_MISMATCH",
|
|
508
|
+
`Resource "${entry.resourceId}" changed since the saved manifest`
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
await options.sink.write(resource, signal);
|
|
514
|
+
resources.set(entry.resourceId, entry);
|
|
515
|
+
await persistManifest(false);
|
|
516
|
+
};
|
|
517
|
+
return {
|
|
518
|
+
write(resource, signal) {
|
|
519
|
+
const operation = writeChain.then(() => writeResource(resource, signal));
|
|
520
|
+
writeChain = operation.catch((error) => {
|
|
521
|
+
writeFailed = true;
|
|
522
|
+
writeFailure = error;
|
|
523
|
+
});
|
|
524
|
+
return operation;
|
|
525
|
+
},
|
|
526
|
+
async finalize(report) {
|
|
527
|
+
await writeChain;
|
|
528
|
+
if (writeFailed) {
|
|
529
|
+
throw writeFailure;
|
|
530
|
+
}
|
|
531
|
+
if (closed) {
|
|
532
|
+
throw new TileConversionError("RESOURCE_SINK_CLOSED", "Sink has already been finalized");
|
|
533
|
+
}
|
|
534
|
+
await options.sink.finalize(report);
|
|
535
|
+
await persistManifest(true, report);
|
|
536
|
+
closed = true;
|
|
537
|
+
},
|
|
538
|
+
async abort(reason) {
|
|
539
|
+
await writeChain;
|
|
540
|
+
if (closed) return;
|
|
541
|
+
closed = true;
|
|
542
|
+
resources.clear();
|
|
543
|
+
await persistManifest(false);
|
|
544
|
+
await options.sink.abort(reason);
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function createManifestEntry(options, resource) {
|
|
549
|
+
const resourceId = options.getResourceId(resource);
|
|
550
|
+
const byteLength = options.measureResourceBytes(resource);
|
|
551
|
+
const fingerprint = options.fingerprintResource(resource);
|
|
552
|
+
if (typeof resourceId !== "string" || resourceId.length === 0 || typeof fingerprint !== "string" || fingerprint.length === 0 || !Number.isSafeInteger(byteLength) || byteLength < 0) {
|
|
553
|
+
throw new TileConversionError(
|
|
554
|
+
"INVALID_RESOURCE_MANIFEST_ENTRY",
|
|
555
|
+
"Resource manifest entries require an ID, a fingerprint, and a non-negative safe byte length"
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
return { resourceId, byteLength, fingerprint };
|
|
559
|
+
}
|
|
560
|
+
function validateManifest(manifest) {
|
|
561
|
+
if (typeof manifest !== "object" || manifest === null || manifest.version !== TILE_RESOURCE_MANIFEST_VERSION || typeof manifest.complete !== "boolean" || !Array.isArray(manifest.resources)) {
|
|
562
|
+
throw new TileConversionError(
|
|
563
|
+
"UNSUPPORTED_RESOURCE_MANIFEST",
|
|
564
|
+
`Unsupported or malformed resource manifest version: ${String(manifest.version)}`
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
const resourceIds = /* @__PURE__ */ new Set();
|
|
568
|
+
for (const entry of manifest.resources) {
|
|
569
|
+
if (typeof entry?.resourceId !== "string" || entry.resourceId.length === 0 || typeof entry?.fingerprint !== "string" || entry.fingerprint.length === 0 || !Number.isSafeInteger(entry.byteLength) || entry.byteLength < 0 || resourceIds.has(entry.resourceId)) {
|
|
570
|
+
throw new TileConversionError(
|
|
571
|
+
"INVALID_RESOURCE_MANIFEST",
|
|
572
|
+
"Resource manifest contains an invalid or duplicate entry"
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
resourceIds.add(entry.resourceId);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/v5/browser-sink.ts
|
|
580
|
+
var BLOB_SIZE_GETTER = Object.getOwnPropertyDescriptor(Blob.prototype, "size")?.get;
|
|
581
|
+
function createBoundedMemoryTileConversionSink(options) {
|
|
582
|
+
const { maxTotalBytes } = options;
|
|
583
|
+
if (!Number.isSafeInteger(maxTotalBytes) || maxTotalBytes < 0) {
|
|
584
|
+
throw new TileConversionError(
|
|
585
|
+
"INVALID_OUTPUT_MEMORY_LIMIT",
|
|
586
|
+
"The browser output memory limit must be a non-negative safe integer"
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
const files = /* @__PURE__ */ new Map();
|
|
590
|
+
let totalBytes = 0;
|
|
591
|
+
let closed = false;
|
|
592
|
+
return {
|
|
593
|
+
getFiles() {
|
|
594
|
+
return [...files.values()].sort(
|
|
595
|
+
(left, right) => left.resourceId < right.resourceId ? -1 : left.resourceId > right.resourceId ? 1 : 0
|
|
596
|
+
);
|
|
597
|
+
},
|
|
598
|
+
async write(resource, signal) {
|
|
599
|
+
if (signal?.aborted) {
|
|
600
|
+
throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
601
|
+
}
|
|
602
|
+
if (closed) {
|
|
603
|
+
throw new TileConversionError(
|
|
604
|
+
"RESOURCE_SINK_CLOSED",
|
|
605
|
+
"Cannot write after sink finalization"
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
if (!resource.resourceId || files.has(resource.resourceId)) {
|
|
609
|
+
throw new TileConversionError(
|
|
610
|
+
"INVALID_BROWSER_RESOURCE_ID",
|
|
611
|
+
"Browser output resource IDs must be non-empty and unique"
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
const byteLength = resource.parts.reduce((total, part) => total + measureBlobPart(part), 0);
|
|
615
|
+
if (!Number.isSafeInteger(byteLength) || totalBytes + byteLength > maxTotalBytes) {
|
|
616
|
+
throw new TileConversionError(
|
|
617
|
+
"OUTPUT_MEMORY_LIMIT_EXCEEDED",
|
|
618
|
+
`Browser output exceeds the configured ${maxTotalBytes} byte memory limit`
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
const blob = new Blob([...resource.parts], { type: resource.contentType });
|
|
622
|
+
files.set(resource.resourceId, { resourceId: resource.resourceId, blob });
|
|
623
|
+
totalBytes += blob.size;
|
|
624
|
+
},
|
|
625
|
+
async finalize(_report) {
|
|
626
|
+
if (closed) {
|
|
627
|
+
throw new TileConversionError("RESOURCE_SINK_CLOSED", "Sink has already been finalized");
|
|
628
|
+
}
|
|
629
|
+
closed = true;
|
|
630
|
+
},
|
|
631
|
+
async abort() {
|
|
632
|
+
files.clear();
|
|
633
|
+
totalBytes = 0;
|
|
634
|
+
closed = true;
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function measureBlobPart(part) {
|
|
639
|
+
if (typeof part === "string") {
|
|
640
|
+
return new TextEncoder().encode(part).byteLength;
|
|
641
|
+
}
|
|
642
|
+
if (BLOB_SIZE_GETTER) {
|
|
643
|
+
try {
|
|
644
|
+
return BLOB_SIZE_GETTER.call(part);
|
|
645
|
+
} catch {
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (part instanceof ArrayBuffer || ArrayBuffer.isView(part)) {
|
|
649
|
+
return part.byteLength;
|
|
650
|
+
}
|
|
651
|
+
throw new TileConversionError("UNSUPPORTED_BROWSER_RESOURCE", "Unsupported browser Blob part");
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// src/v5/browser-tileset-source.ts
|
|
655
|
+
function createBrowserTilesetConversionSource(options) {
|
|
656
|
+
if (!Number.isSafeInteger(options.maxInputBytes) || options.maxInputBytes < 1 || !Number.isSafeInteger(options.maxInputResources) || options.maxInputResources < 1) {
|
|
657
|
+
throw new TileConversionError(
|
|
658
|
+
"INVALID_BROWSER_SOURCE_OPTIONS",
|
|
659
|
+
"Browser tileset sources require positive byte and resource limits"
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
const fetcher = options.fetcher ?? fetch;
|
|
663
|
+
return {
|
|
664
|
+
async inspect(signal) {
|
|
665
|
+
throwIfAborted4(signal);
|
|
666
|
+
const rootUrl = new URL(options.input).href;
|
|
667
|
+
const response = await fetcher(rootUrl, { signal });
|
|
668
|
+
if (!response.ok) {
|
|
669
|
+
throw new TileConversionError(
|
|
670
|
+
"BROWSER_SOURCE_FETCH_FAILED",
|
|
671
|
+
`Could not fetch browser tileset (${response.status} ${response.statusText})`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
const data = await readResponseBytes2(response, options.maxInputBytes, signal);
|
|
675
|
+
let tileset;
|
|
676
|
+
try {
|
|
677
|
+
tileset = JSON.parse(new TextDecoder().decode(data));
|
|
678
|
+
} catch {
|
|
679
|
+
throw new TileConversionError("INVALID_BROWSER_TILESET", "Tileset input is not valid JSON");
|
|
680
|
+
}
|
|
681
|
+
if (!isRecord(tileset) || !["1.0", "1.1"].includes(tileset.asset?.version || "") || !isRecord(tileset.root)) {
|
|
682
|
+
throw new TileConversionError(
|
|
683
|
+
"UNSUPPORTED_BROWSER_TILESET",
|
|
684
|
+
"Browser tileset traversal supports explicit 3D Tiles 1.0 and 1.1 documents"
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
const resources = collectResources(tileset.root, options.maxInputResources);
|
|
688
|
+
return { rootUrl, tileset, resources, tilesetBytes: data.byteLength };
|
|
689
|
+
},
|
|
690
|
+
async *read(inspection, signal) {
|
|
691
|
+
let inputBytes = inspection.tilesetBytes;
|
|
692
|
+
for (const descriptor of inspection.resources) {
|
|
693
|
+
throwIfAborted4(signal);
|
|
694
|
+
const resourceUrl = resolveResourceUrl(descriptor.uri, inspection.rootUrl);
|
|
695
|
+
const response = await fetcher(resourceUrl, { signal });
|
|
696
|
+
if (!response.ok) {
|
|
697
|
+
throw new TileConversionError(
|
|
698
|
+
"BROWSER_SOURCE_FETCH_FAILED",
|
|
699
|
+
`Could not fetch browser tile content (${response.status} ${response.statusText})`,
|
|
700
|
+
[
|
|
701
|
+
{
|
|
702
|
+
code: "BROWSER_SOURCE_FETCH_FAILED",
|
|
703
|
+
message: resourceUrl,
|
|
704
|
+
severity: "error",
|
|
705
|
+
resourceId: descriptor.resourceId
|
|
706
|
+
}
|
|
707
|
+
]
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
const remainingBytes = options.maxInputBytes - inputBytes;
|
|
711
|
+
const data = await readResponseBytes2(response, remainingBytes, signal);
|
|
712
|
+
inputBytes += data.byteLength;
|
|
713
|
+
const contentType = response.headers.get("content-type") ?? void 0;
|
|
714
|
+
if (isJsonContent(data, contentType)) {
|
|
715
|
+
throw new TileConversionError(
|
|
716
|
+
"UNSUPPORTED_BROWSER_TILESET_CONTENT",
|
|
717
|
+
`Nested tilesets and JSON tile content are not supported: ${descriptor.uri}`
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
yield { ...descriptor, data, ...contentType ? { contentType } : {} };
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
function collectResources(root, maxInputResources) {
|
|
726
|
+
const resources = [];
|
|
727
|
+
const stack = [
|
|
728
|
+
{ tile: root, path: [], transform: identityMatrix() }
|
|
729
|
+
];
|
|
730
|
+
while (stack.length > 0) {
|
|
731
|
+
const { tile, path, transform: parentTransform } = stack.pop();
|
|
732
|
+
if (!isRecord(tile)) {
|
|
733
|
+
throw new TileConversionError("INVALID_BROWSER_TILESET", "Tile entries must be objects");
|
|
734
|
+
}
|
|
735
|
+
if (tile.implicitTiling !== void 0 || tile.extensions?.["3DTILES_implicit_tiling"] !== void 0) {
|
|
736
|
+
throw new TileConversionError(
|
|
737
|
+
"UNSUPPORTED_BROWSER_TILESET",
|
|
738
|
+
"Browser tileset traversal does not yet support implicit tiling"
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
if (tile.contents !== void 0 && !Array.isArray(tile.contents)) {
|
|
742
|
+
throw new TileConversionError("INVALID_BROWSER_TILESET", "Tile contents must be an array");
|
|
743
|
+
}
|
|
744
|
+
if (tile.children !== void 0 && !Array.isArray(tile.children)) {
|
|
745
|
+
throw new TileConversionError("INVALID_BROWSER_TILESET", "Tile children must be an array");
|
|
746
|
+
}
|
|
747
|
+
const transform = multiplyMatrices(parentTransform, normalizeMatrix(tile.transform));
|
|
748
|
+
const contents = tile.contents ?? (tile.content ? [tile.content] : []);
|
|
749
|
+
for (let contentIndex = 0; contentIndex < contents.length; contentIndex++) {
|
|
750
|
+
const content = contents[contentIndex];
|
|
751
|
+
if (!isRecord(content)) {
|
|
752
|
+
throw new TileConversionError(
|
|
753
|
+
"INVALID_BROWSER_TILESET",
|
|
754
|
+
"Tile content entries must be objects"
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
const uri = content.uri ?? content.url;
|
|
758
|
+
if (typeof uri !== "string" || !uri) {
|
|
759
|
+
throw new TileConversionError(
|
|
760
|
+
"INVALID_BROWSER_TILESET",
|
|
761
|
+
`Tile content at ${path.join("/")} has no URI`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
if (resources.length >= maxInputResources) {
|
|
765
|
+
throw new TileConversionError(
|
|
766
|
+
"INPUT_RESOURCE_COUNT_EXCEEDED",
|
|
767
|
+
`Browser tileset exceeds the configured ${maxInputResources} resource limit`
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
resources.push({
|
|
771
|
+
resourceId: `tile-${path.length ? path.join("-") : "root"}-content-${contentIndex}`,
|
|
772
|
+
uri,
|
|
773
|
+
tilePath: path,
|
|
774
|
+
transform
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
const children = tile.children ?? [];
|
|
778
|
+
for (let childIndex = children.length - 1; childIndex >= 0; childIndex--) {
|
|
779
|
+
stack.push({
|
|
780
|
+
tile: children[childIndex],
|
|
781
|
+
path: [...path, childIndex],
|
|
782
|
+
transform
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return resources;
|
|
787
|
+
}
|
|
788
|
+
async function readResponseBytes2(response, maximumBytes, signal) {
|
|
789
|
+
const reader = response.body?.getReader();
|
|
790
|
+
if (!reader) {
|
|
791
|
+
throw new TileConversionError(
|
|
792
|
+
"BROWSER_SOURCE_STREAM_UNAVAILABLE",
|
|
793
|
+
"Browser inputs must provide a readable response stream"
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
const data = new Uint8Array(maximumBytes);
|
|
797
|
+
let byteLength = 0;
|
|
798
|
+
try {
|
|
799
|
+
while (true) {
|
|
800
|
+
throwIfAborted4(signal);
|
|
801
|
+
const { done, value } = await reader.read();
|
|
802
|
+
if (done) break;
|
|
803
|
+
if (value.byteLength > maximumBytes - byteLength) {
|
|
804
|
+
await reader.cancel();
|
|
805
|
+
throw new TileConversionError(
|
|
806
|
+
"INPUT_RESOURCE_TOO_LARGE",
|
|
807
|
+
`Browser tileset input exceeds the configured ${maximumBytes} byte remaining limit`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
data.set(value, byteLength);
|
|
811
|
+
byteLength += value.byteLength;
|
|
812
|
+
}
|
|
813
|
+
} finally {
|
|
814
|
+
reader.releaseLock();
|
|
815
|
+
}
|
|
816
|
+
return data.subarray(0, byteLength);
|
|
817
|
+
}
|
|
818
|
+
function isJsonContent(data, contentType) {
|
|
819
|
+
if (contentType?.includes("json")) return true;
|
|
820
|
+
let byteOffset = data[0] === 239 && data[1] === 187 && data[2] === 191 ? 3 : 0;
|
|
821
|
+
while ([9, 10, 13, 32].includes(data[byteOffset])) byteOffset++;
|
|
822
|
+
return data[byteOffset] === 123;
|
|
823
|
+
}
|
|
824
|
+
function resolveResourceUrl(uri, rootUrl) {
|
|
825
|
+
const root = new URL(rootUrl);
|
|
826
|
+
const resource = new URL(uri, root);
|
|
827
|
+
if (resource.origin === root.origin) {
|
|
828
|
+
for (const [name, value] of root.searchParams) {
|
|
829
|
+
if (!resource.searchParams.has(name)) resource.searchParams.append(name, value);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return resource.href;
|
|
833
|
+
}
|
|
834
|
+
function isRecord(value) {
|
|
835
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
836
|
+
}
|
|
837
|
+
function identityMatrix() {
|
|
838
|
+
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
839
|
+
}
|
|
840
|
+
function normalizeMatrix(matrix) {
|
|
841
|
+
if (matrix === void 0) return identityMatrix();
|
|
842
|
+
if (!Array.isArray(matrix) || matrix.length !== 16 || matrix.some((value) => !Number.isFinite(value))) {
|
|
843
|
+
throw new TileConversionError(
|
|
844
|
+
"INVALID_BROWSER_TILESET",
|
|
845
|
+
"Tile transform must contain 16 finite numbers"
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
return matrix;
|
|
849
|
+
}
|
|
850
|
+
function multiplyMatrices(left, right) {
|
|
851
|
+
const result = new Array(16);
|
|
852
|
+
for (let column = 0; column < 4; column++) {
|
|
853
|
+
for (let row = 0; row < 4; row++) {
|
|
854
|
+
result[column * 4 + row] = left[row] * right[column * 4] + left[4 + row] * right[column * 4 + 1] + left[8 + row] * right[column * 4 + 2] + left[12 + row] * right[column * 4 + 3];
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return result;
|
|
858
|
+
}
|
|
859
|
+
function throwIfAborted4(signal) {
|
|
860
|
+
if (signal?.aborted) {
|
|
861
|
+
throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
862
|
+
}
|
|
863
|
+
}
|