@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
|
@@ -141,7 +141,7 @@ export class ConversionDump {
|
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
143
|
} catch (error) {
|
|
144
|
-
//
|
|
144
|
+
// biome-ignore format: preserve intentional fixture layout
|
|
145
145
|
console.log('Can\'t open dump file', error); // eslint-disable-line no-console
|
|
146
146
|
}
|
|
147
147
|
}
|
|
@@ -196,7 +196,7 @@ export class ConversionDump {
|
|
|
196
196
|
)
|
|
197
197
|
);
|
|
198
198
|
} catch (error) {
|
|
199
|
-
//
|
|
199
|
+
// biome-ignore format: preserve intentional fixture layout
|
|
200
200
|
console.log('Can\'t update dump file', error); // eslint-disable-line no-console
|
|
201
201
|
}
|
|
202
202
|
}
|
|
@@ -289,7 +289,7 @@ export class ConversionDump {
|
|
|
289
289
|
value: boolean
|
|
290
290
|
) {
|
|
291
291
|
const nodeDump = this.tilesConverted[filename]?.nodes.find(
|
|
292
|
-
|
|
292
|
+
element => element.nodeId === nodeId
|
|
293
293
|
);
|
|
294
294
|
if (nodeDump) {
|
|
295
295
|
if (!nodeDump.progress) {
|
|
@@ -361,7 +361,7 @@ export class ConversionDump {
|
|
|
361
361
|
done: boolean
|
|
362
362
|
): Promise<void> {
|
|
363
363
|
const nodeDump = this.tilesConverted[filename]?.nodes.find(
|
|
364
|
-
|
|
364
|
+
element => element.nodeId === nodeId
|
|
365
365
|
);
|
|
366
366
|
if (nodeDump) {
|
|
367
367
|
nodeDump.done = done;
|
|
@@ -81,7 +81,7 @@ export async function openJson(path: string, fileName: string): Promise<{[key: s
|
|
|
81
81
|
const intervalId = setInterval(() => {
|
|
82
82
|
const pathFile = join(path, fileName);
|
|
83
83
|
load(pathFile, JSONLoader)
|
|
84
|
-
.then(
|
|
84
|
+
.then(result => {
|
|
85
85
|
clearInterval(intervalId);
|
|
86
86
|
resolve(result);
|
|
87
87
|
})
|
|
@@ -147,7 +147,7 @@ export async function renameFile(oldPath: string, newPath: string): Promise<void
|
|
|
147
147
|
try {
|
|
148
148
|
await fs.rename(oldPath, newPath);
|
|
149
149
|
} catch (err) {
|
|
150
|
-
//
|
|
150
|
+
// biome-ignore format: preserve intentional fixture layout
|
|
151
151
|
console.log('Can\'t rename file', err); // eslint-disable-line no-console
|
|
152
152
|
}
|
|
153
153
|
}
|
|
@@ -65,13 +65,11 @@ export function convertGeometricErrorToScreenThreshold(
|
|
|
65
65
|
* @returns lod metric in 3d-tiles format
|
|
66
66
|
*/
|
|
67
67
|
export function convertScreenThresholdToGeometricError(node: I3STileHeader): number {
|
|
68
|
-
const metricData = node.lodSelection?.find(
|
|
69
|
-
(metric) => metric.metricType === 'maxScreenThreshold'
|
|
70
|
-
);
|
|
68
|
+
const metricData = node.lodSelection?.find(metric => metric.metricType === 'maxScreenThreshold');
|
|
71
69
|
let maxError = metricData?.maxError;
|
|
72
70
|
if (!maxError) {
|
|
73
71
|
const sqMetricData = node.lodSelection?.find(
|
|
74
|
-
|
|
72
|
+
metric => metric.metricType === 'maxScreenThresholdSQ'
|
|
75
73
|
);
|
|
76
74
|
if (sqMetricData) {
|
|
77
75
|
maxError = Math.sqrt(sqMetricData.maxError / (Math.PI * 0.25));
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
// Format-specific codecs, feature mapping and archive packaging remain in the application.
|
|
6
|
+
export {convertFeatureAttributesToArrowBatches} from './feature-arrow.js';
|
|
7
|
+
export type {FeatureArrowBatchOptions, TileFeatureAttributes} from './feature-arrow.js';
|
|
8
|
+
export {encodePointCloudTile} from './point-cloud.js';
|
|
9
|
+
export type {EncodePointCloudTileOptions} from './point-cloud.js';
|
|
10
|
+
export {encodePointCloudSource, encodePointCloudSourceTile} from './point-cloud-source-encoder.js';
|
|
11
|
+
export type {
|
|
12
|
+
EncodedPointCloudSourceTile,
|
|
13
|
+
EncodePointCloudSourceOptions
|
|
14
|
+
} from './point-cloud-source-encoder.js';
|
|
15
|
+
export {convertPointCloudSource} from './point-cloud-conversion.js';
|
|
16
|
+
export type {ConvertPointCloudSourceOptions} from './point-cloud-conversion.js';
|
|
17
|
+
export {encodeMeshTile} from './mesh.js';
|
|
18
|
+
export type {
|
|
19
|
+
MeshTileMaterial,
|
|
20
|
+
MeshTileOptions,
|
|
21
|
+
MeshTileTexture,
|
|
22
|
+
MeshTileSampler,
|
|
23
|
+
MeshTileTextureTransform
|
|
24
|
+
} from './mesh.js';
|
|
25
|
+
export {createMeshConversionCodec} from './mesh-conversion.js';
|
|
26
|
+
export type {
|
|
27
|
+
MeshConversionInput,
|
|
28
|
+
EncodedMeshConversionResource,
|
|
29
|
+
MeshConversionCodecOptions
|
|
30
|
+
} from './mesh-conversion.js';
|
|
31
|
+
export {createSingleMeshTilesetSink, createMeshTilesetSink} from './mesh-tileset-sink.js';
|
|
32
|
+
export type {
|
|
33
|
+
SingleMeshTilesetSink,
|
|
34
|
+
SingleMeshTilesetSinkOptions,
|
|
35
|
+
MeshTilesetSink,
|
|
36
|
+
MeshTilesetSinkOptions
|
|
37
|
+
} from './mesh-tileset-sink.js';
|
|
38
|
+
export {
|
|
39
|
+
createTileConversionArchive,
|
|
40
|
+
encodeTileConversionArchiveInBatches
|
|
41
|
+
} from './conversion-archive.js';
|
|
42
|
+
export type {TileConversionArchiveOptions} from './conversion-archive.js';
|
|
43
|
+
export {createSingleMeshTilesetArchive} from './mesh-tileset-archive.js';
|
|
44
|
+
export type {SingleMeshTilesetArchiveOptions} from './mesh-tileset-archive.js';
|
|
45
|
+
export {createMeshTilesetConversionSource} from './mesh-source.js';
|
|
46
|
+
export type {
|
|
47
|
+
MeshSourceResource,
|
|
48
|
+
MeshTilesetSourceOptions,
|
|
49
|
+
MeshSourceFeatureOptions
|
|
50
|
+
} from './mesh-source.js';
|
|
51
|
+
export {createI3SMeshConversionCodec, createSingleMeshI3SSink} from './i3s-mesh-conversion.js';
|
|
52
|
+
export type {
|
|
53
|
+
I3SMeshConversionResource,
|
|
54
|
+
I3SMeshConversionCodecOptions,
|
|
55
|
+
SingleMeshI3SSink
|
|
56
|
+
} from './i3s-mesh-conversion.js';
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {TileConversionError} from './conversion-api.js';
|
|
6
|
+
import type {TileConversionReport, TileConversionSink} from './conversion-api.js';
|
|
7
|
+
|
|
8
|
+
const BLOB_SIZE_GETTER = Object.getOwnPropertyDescriptor(Blob.prototype, 'size')?.get;
|
|
9
|
+
|
|
10
|
+
/** One named browser output resource. */
|
|
11
|
+
export interface BrowserTileConversionResource {
|
|
12
|
+
/** Stable relative output path supplied by the target profile. */
|
|
13
|
+
readonly resourceId: string;
|
|
14
|
+
/** Parts combined into a Blob after the total output budget is checked. */
|
|
15
|
+
readonly parts: readonly BlobPart[];
|
|
16
|
+
/** MIME type recorded on the resulting Blob. */
|
|
17
|
+
readonly contentType?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A named output resource retained by the bounded browser sink. */
|
|
21
|
+
export interface BrowserTileConversionFile {
|
|
22
|
+
/** Stable relative output path supplied by the target profile. */
|
|
23
|
+
readonly resourceId: string;
|
|
24
|
+
/** Browser-native data for download or application-provided packaging. */
|
|
25
|
+
readonly blob: Blob;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Memory limit for a browser conversion sink. */
|
|
29
|
+
export interface BoundedMemoryTileConversionSinkOptions {
|
|
30
|
+
/** Maximum combined Blob size retained by this sink, captured at creation, in bytes. */
|
|
31
|
+
readonly maxTotalBytes: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Browser sink with read access to its bounded output resources. */
|
|
35
|
+
export interface BoundedMemoryTileConversionSink
|
|
36
|
+
extends TileConversionSink<BrowserTileConversionResource> {
|
|
37
|
+
/** Returns the retained files in stable resource-ID order. */
|
|
38
|
+
getFiles(): readonly BrowserTileConversionFile[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a browser-only sink that retains output as Blobs under a total byte limit.
|
|
43
|
+
*
|
|
44
|
+
* The sink does not download or archive files. Applications can use `getFiles()` after
|
|
45
|
+
* `convertTileset` completes and package or download the returned Blobs as needed.
|
|
46
|
+
*
|
|
47
|
+
* @param options - Maximum in-memory output size.
|
|
48
|
+
* @returns A sink that stores browser-native Blobs and clears them on abort.
|
|
49
|
+
*/
|
|
50
|
+
export function createBoundedMemoryTileConversionSink(
|
|
51
|
+
options: BoundedMemoryTileConversionSinkOptions
|
|
52
|
+
): BoundedMemoryTileConversionSink {
|
|
53
|
+
const {maxTotalBytes} = options;
|
|
54
|
+
if (!Number.isSafeInteger(maxTotalBytes) || maxTotalBytes < 0) {
|
|
55
|
+
throw new TileConversionError(
|
|
56
|
+
'INVALID_OUTPUT_MEMORY_LIMIT',
|
|
57
|
+
'The browser output memory limit must be a non-negative safe integer'
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const files = new Map<string, BrowserTileConversionFile>();
|
|
62
|
+
let totalBytes = 0;
|
|
63
|
+
let closed = false;
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
getFiles() {
|
|
67
|
+
return [...files.values()].sort((left, right) =>
|
|
68
|
+
left.resourceId < right.resourceId ? -1 : left.resourceId > right.resourceId ? 1 : 0
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
async write(resource, signal) {
|
|
72
|
+
if (signal?.aborted) {
|
|
73
|
+
throw signal.reason ?? new DOMException('The operation was aborted', 'AbortError');
|
|
74
|
+
}
|
|
75
|
+
if (closed) {
|
|
76
|
+
throw new TileConversionError(
|
|
77
|
+
'RESOURCE_SINK_CLOSED',
|
|
78
|
+
'Cannot write after sink finalization'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (!resource.resourceId || files.has(resource.resourceId)) {
|
|
82
|
+
throw new TileConversionError(
|
|
83
|
+
'INVALID_BROWSER_RESOURCE_ID',
|
|
84
|
+
'Browser output resource IDs must be non-empty and unique'
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const byteLength = resource.parts.reduce((total, part) => total + measureBlobPart(part), 0);
|
|
89
|
+
if (!Number.isSafeInteger(byteLength) || totalBytes + byteLength > maxTotalBytes) {
|
|
90
|
+
throw new TileConversionError(
|
|
91
|
+
'OUTPUT_MEMORY_LIMIT_EXCEEDED',
|
|
92
|
+
`Browser output exceeds the configured ${maxTotalBytes} byte memory limit`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const blob = new Blob([...resource.parts], {type: resource.contentType});
|
|
97
|
+
files.set(resource.resourceId, {resourceId: resource.resourceId, blob});
|
|
98
|
+
totalBytes += blob.size;
|
|
99
|
+
},
|
|
100
|
+
async finalize(_report: TileConversionReport) {
|
|
101
|
+
if (closed) {
|
|
102
|
+
throw new TileConversionError('RESOURCE_SINK_CLOSED', 'Sink has already been finalized');
|
|
103
|
+
}
|
|
104
|
+
closed = true;
|
|
105
|
+
},
|
|
106
|
+
async abort() {
|
|
107
|
+
files.clear();
|
|
108
|
+
totalBytes = 0;
|
|
109
|
+
closed = true;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Measures supported Blob parts without copying them into a Blob first. */
|
|
115
|
+
function measureBlobPart(part: BlobPart): number {
|
|
116
|
+
if (typeof part === 'string') {
|
|
117
|
+
return new TextEncoder().encode(part).byteLength;
|
|
118
|
+
}
|
|
119
|
+
if (BLOB_SIZE_GETTER) {
|
|
120
|
+
try {
|
|
121
|
+
return BLOB_SIZE_GETTER.call(part) as number;
|
|
122
|
+
} catch {
|
|
123
|
+
// Keep checking the remaining supported Blob part types.
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (part instanceof ArrayBuffer || ArrayBuffer.isView(part)) {
|
|
127
|
+
return part.byteLength;
|
|
128
|
+
}
|
|
129
|
+
throw new TileConversionError('UNSUPPORTED_BROWSER_RESOURCE', 'Unsupported browser Blob part');
|
|
130
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {TileConversionError} from './conversion-api.js';
|
|
6
|
+
import type {TileConversionSource} from './conversion-api.js';
|
|
7
|
+
|
|
8
|
+
/** Metadata available before a browser resource is read. */
|
|
9
|
+
export interface BrowserTileConversionInspection {
|
|
10
|
+
/** Stable resource path assigned to the input tile. */
|
|
11
|
+
readonly resourceId: string;
|
|
12
|
+
/** Optional MIME type supplied by the caller or source Blob. */
|
|
13
|
+
readonly contentType?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Raw bytes read from one browser tile resource. */
|
|
17
|
+
export interface BrowserTileConversionInputResource {
|
|
18
|
+
/** Stable resource path assigned to the input tile. */
|
|
19
|
+
readonly resourceId: string;
|
|
20
|
+
/** Complete resource bytes for a profile-specific codec. */
|
|
21
|
+
readonly data: Uint8Array;
|
|
22
|
+
/** Optional MIME type supplied by the caller, Blob, or URL response. */
|
|
23
|
+
readonly contentType?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Browser input and size limit for a single-resource conversion. */
|
|
27
|
+
export interface BrowserTileConversionSourceOptions {
|
|
28
|
+
/** Input URL or already available browser Blob. */
|
|
29
|
+
readonly input: string | Blob;
|
|
30
|
+
/** Stable path to associate with the input resource. */
|
|
31
|
+
readonly resourceId: string;
|
|
32
|
+
/** Maximum number of input bytes held in memory. */
|
|
33
|
+
readonly maxInputBytes: number;
|
|
34
|
+
/** Optional MIME type override. */
|
|
35
|
+
readonly contentType?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a browser source for one mesh resource provided as a URL or Blob.
|
|
40
|
+
*
|
|
41
|
+
* URL responses are read incrementally and canceled if they exceed `maxInputBytes`.
|
|
42
|
+
* Tileset JSON traversal and format decoding remain profile-specific source and codec work.
|
|
43
|
+
*
|
|
44
|
+
* @param options - Input, stable resource ID, and maximum input size.
|
|
45
|
+
* @returns A source that yields one complete resource to a v5 codec.
|
|
46
|
+
*/
|
|
47
|
+
export function createBrowserTileConversionSource(
|
|
48
|
+
options: BrowserTileConversionSourceOptions
|
|
49
|
+
): TileConversionSource<BrowserTileConversionInspection, BrowserTileConversionInputResource> {
|
|
50
|
+
if (
|
|
51
|
+
!options.resourceId ||
|
|
52
|
+
!Number.isSafeInteger(options.maxInputBytes) ||
|
|
53
|
+
options.maxInputBytes < 0
|
|
54
|
+
) {
|
|
55
|
+
throw new TileConversionError(
|
|
56
|
+
'INVALID_BROWSER_SOURCE_OPTIONS',
|
|
57
|
+
'Browser sources require a resource ID and a non-negative safe input byte limit'
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
async inspect(signal) {
|
|
63
|
+
throwIfAborted(signal);
|
|
64
|
+
const contentType = options.contentType ?? getBlobContentType(options.input);
|
|
65
|
+
return {
|
|
66
|
+
resourceId: options.resourceId,
|
|
67
|
+
...(contentType ? {contentType} : {})
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
async *read(inspection, signal) {
|
|
71
|
+
throwIfAborted(signal);
|
|
72
|
+
const input = options.input;
|
|
73
|
+
let data: Uint8Array;
|
|
74
|
+
let contentType = inspection.contentType;
|
|
75
|
+
|
|
76
|
+
if (typeof input === 'string') {
|
|
77
|
+
const response = await fetch(input, {signal});
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
throw new TileConversionError(
|
|
80
|
+
'BROWSER_SOURCE_FETCH_FAILED',
|
|
81
|
+
`Could not fetch browser input (${response.status} ${response.statusText})`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
contentType = contentType ?? response.headers.get('content-type') ?? undefined;
|
|
85
|
+
data = await readResponseBytes(response, options.maxInputBytes, signal);
|
|
86
|
+
} else {
|
|
87
|
+
if (input.size > options.maxInputBytes) {
|
|
88
|
+
throw createInputLimitError(options.maxInputBytes);
|
|
89
|
+
}
|
|
90
|
+
data = new Uint8Array(await input.arrayBuffer());
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
throwIfAborted(signal);
|
|
94
|
+
yield {
|
|
95
|
+
resourceId: inspection.resourceId,
|
|
96
|
+
data,
|
|
97
|
+
...(contentType ? {contentType} : {})
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Reads a response stream while enforcing the input byte limit. */
|
|
104
|
+
async function readResponseBytes(
|
|
105
|
+
response: Response,
|
|
106
|
+
maxInputBytes: number,
|
|
107
|
+
signal?: AbortSignal
|
|
108
|
+
): Promise<Uint8Array> {
|
|
109
|
+
const reader = response.body?.getReader();
|
|
110
|
+
if (!reader) {
|
|
111
|
+
throw new TileConversionError(
|
|
112
|
+
'BROWSER_SOURCE_STREAM_UNAVAILABLE',
|
|
113
|
+
'Browser URL inputs must provide a readable response stream'
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const data = new Uint8Array(maxInputBytes);
|
|
118
|
+
let totalBytes = 0;
|
|
119
|
+
try {
|
|
120
|
+
while (true) {
|
|
121
|
+
throwIfAborted(signal);
|
|
122
|
+
const {done, value} = await reader.read();
|
|
123
|
+
if (done) break;
|
|
124
|
+
if (value.byteLength > maxInputBytes - totalBytes) {
|
|
125
|
+
await reader.cancel();
|
|
126
|
+
throw createInputLimitError(maxInputBytes);
|
|
127
|
+
}
|
|
128
|
+
data.set(value, totalBytes);
|
|
129
|
+
totalBytes += value.byteLength;
|
|
130
|
+
}
|
|
131
|
+
} finally {
|
|
132
|
+
reader.releaseLock();
|
|
133
|
+
}
|
|
134
|
+
return data.subarray(0, totalBytes);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Returns a Blob's MIME type without requiring a same-realm Blob constructor. */
|
|
138
|
+
function getBlobContentType(input: string | Blob): string | undefined {
|
|
139
|
+
return typeof input === 'string' ? undefined : input.type || undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Creates the typed error used when browser input exceeds its configured limit. */
|
|
143
|
+
function createInputLimitError(maxInputBytes: number): TileConversionError {
|
|
144
|
+
return new TileConversionError(
|
|
145
|
+
'INPUT_RESOURCE_TOO_LARGE',
|
|
146
|
+
`Browser input exceeds the configured ${maxInputBytes} byte memory limit`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Throws the signal reason when a browser source operation has been canceled. */
|
|
151
|
+
function throwIfAborted(signal?: AbortSignal): void {
|
|
152
|
+
if (signal?.aborted) {
|
|
153
|
+
throw signal.reason ?? new DOMException('The operation was aborted', 'AbortError');
|
|
154
|
+
}
|
|
155
|
+
}
|