@loaders.gl/tile-converter 5.0.0-alpha.11 → 5.0.0-alpha.14
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 +1 -1
- package/README.md +180 -4
- package/bin/converter-v5.js +2 -0
- package/dist/converter.min.cjs +172 -182
- package/dist/i3s-server/bin/i3s-server.min.cjs +113 -158
- package/dist/index.cjs +18832 -19228
- package/dist/index.js +19566 -19968
- package/dist/v5/adapters.cjs +2002 -230
- package/dist/v5/adapters.d.ts +20 -0
- package/dist/v5/adapters.js +2005 -214
- package/dist/v5/archive-source.d.ts +36 -0
- package/dist/v5/browser-tileset-source.d.ts +55 -11
- package/dist/v5/browser.cjs +18 -0
- package/dist/v5/browser.d.ts +15 -1
- package/dist/v5/browser.js +26 -0
- package/dist/v5/conversion-api.d.ts +34 -0
- package/dist/v5/converter-cli.d.ts +1 -0
- package/dist/v5/converter-cli.js +1230 -0
- package/dist/v5/core.cjs +547 -77
- package/dist/v5/core.d.ts +3 -1
- package/dist/v5/core.js +540 -70
- package/dist/v5/feature-arrow.d.ts +1 -1
- package/dist/v5/i3s-mesh-conversion.d.ts +5 -1
- package/dist/v5/i3s-mesh-source-features.d.ts +2 -2
- package/dist/v5/i3s-mesh-source-material.d.ts +1 -1
- package/dist/v5/i3s-mesh-source.d.ts +2 -2
- package/dist/v5/i3s-selection-source.d.ts +70 -0
- package/dist/v5/index.cjs +15 -0
- package/dist/v5/index.d.ts +13 -1
- package/dist/v5/index.js +22 -0
- package/dist/v5/mesh-conversion.d.ts +21 -3
- package/dist/v5/mesh-draco.d.ts +2 -2
- package/dist/v5/mesh-features.d.ts +2 -2
- package/dist/v5/mesh-hierarchy.d.ts +47 -0
- package/dist/v5/mesh-source-features.d.ts +6 -5
- package/dist/v5/mesh-source-schema.d.ts +12 -0
- package/dist/v5/mesh-source-texture.d.ts +3 -3
- package/dist/v5/mesh-source.d.ts +10 -7
- package/dist/v5/mesh-tileset-sink.d.ts +5 -0
- package/dist/v5/mesh.d.ts +1 -1
- package/dist/v5/node-cli.d.ts +9 -0
- package/dist/v5/node-conversion.d.ts +34 -0
- package/dist/v5/node-feature-schema.d.ts +3 -0
- package/dist/v5/node-i3s-source.d.ts +41 -0
- package/dist/v5/node-source.d.ts +47 -0
- package/dist/v5/node.cjs +913 -0
- package/dist/v5/node.d.ts +6 -0
- package/dist/v5/node.js +890 -0
- package/dist/v5/point-cloud-attributes.d.ts +19 -0
- package/dist/v5/point-cloud-conversion.d.ts +1 -1
- package/dist/v5/point-cloud-copc.d.ts +44 -0
- package/dist/v5/point-cloud-file.d.ts +83 -0
- package/dist/v5/point-cloud-potree.d.ts +47 -0
- package/dist/v5/point-cloud-source-encoder.d.ts +1 -1
- package/dist/v5/point-cloud-source.d.ts +1 -1
- package/dist/v5/point-cloud-tileset.d.ts +44 -0
- package/dist/v5/point-cloud.d.ts +1 -1
- package/dist/v5/resource-manifest.d.ts +10 -1
- package/dist/v5/selected-mesh-source.d.ts +53 -0
- package/dist/v5/spatial-conversion.d.ts +1 -1
- package/dist/v5/tileset-source.d.ts +1 -1
- package/package.json +25 -32
- package/src/index.ts +4 -1
- package/src/v4/3d-tiles-converter/3d-tiles-converter.ts +11 -12
- package/src/v4/3d-tiles-converter/helpers/3d-tiles-content-converter.ts +7 -3
- package/src/v4/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.ts +2 -2
- package/src/v4/3d-tiles-converter/helpers/load-i3s.ts +4 -6
- package/src/v4/3d-tiles-converter/helpers/texture-atlas.ts +1 -1
- package/src/v4/3d-tiles-converter/helpers/validate-i3s-spatial-reference.ts +2 -2
- package/src/v4/3d-tiles-converter/json-templates/tileset.ts +58 -39
- package/src/v4/converter-cli.ts +10 -4
- package/src/v4/deps-installer/deps-installer.ts +3 -3
- package/src/v4/i3s-converter/helpers/attribute-metadata-info.ts +1 -1
- package/src/v4/i3s-converter/helpers/batch-ids-extensions.ts +9 -10
- package/src/v4/i3s-converter/helpers/conversion-gltf.ts +108 -0
- package/src/v4/i3s-converter/helpers/coordinate-converter.ts +6 -5
- package/src/v4/i3s-converter/helpers/create-scene-server-path.ts +5 -6
- package/src/v4/i3s-converter/helpers/feature-attributes.ts +4 -4
- package/src/v4/i3s-converter/helpers/geometry-attributes.ts +1 -1
- package/src/v4/i3s-converter/helpers/geometry-converter.ts +69 -77
- package/src/v4/i3s-converter/helpers/gltf-attributes.ts +58 -74
- package/src/v4/i3s-converter/helpers/load-3d-tiles.ts +6 -54
- package/src/v4/i3s-converter/helpers/node-debug.ts +7 -3
- package/src/v4/i3s-converter/helpers/node-index-document.ts +11 -9
- package/src/v4/i3s-converter/helpers/node-pages.ts +3 -4
- package/src/v4/i3s-converter/helpers/preprocess-3d-tiles.ts +2 -2
- package/src/v4/i3s-converter/helpers/tileset-traversal.ts +1 -1
- package/src/v4/i3s-converter/i3s-converter.ts +29 -31
- package/src/v4/i3s-converter/json-templates/geometry-definitions.ts +71 -66
- package/src/v4/i3s-converter/json-templates/layers.ts +97 -132
- package/src/v4/i3s-converter/json-templates/metadata.ts +31 -21
- package/src/v4/i3s-converter/json-templates/node.ts +60 -74
- package/src/v4/i3s-converter/json-templates/scene-server.ts +36 -27
- package/src/v4/i3s-converter/json-templates/shared-resources.ts +62 -123
- package/src/v4/i3s-converter/json-templates/store.ts +39 -57
- package/src/v4/i3s-converter/types.ts +4 -4
- package/src/v4/i3s-server/controllers/slpk-controller.ts +3 -3
- package/src/v4/i3s-server/utils/create-scene-server.ts +3 -3
- package/src/v4/lib/utils/builder-input.ts +8 -0
- package/src/v4/lib/utils/conversion-dump.ts +5 -1
- package/src/v4/lib/utils/file-utils.ts +1 -1
- package/src/v4/lib/utils/hash-string.ts +21 -0
- package/src/v4/lib/utils/lod-conversion-utils.ts +3 -3
- package/src/v4/pgm-loader.ts +2 -2
- package/src/v5/adapters.ts +53 -0
- package/src/v5/archive-source.ts +235 -0
- package/src/v5/browser-tileset-source.ts +418 -48
- package/src/v5/browser.ts +46 -0
- package/src/v5/conversion-api.ts +184 -17
- package/src/v5/conversion-archive.ts +2 -2
- package/src/v5/converter-cli.ts +28 -0
- package/src/v5/core.ts +7 -0
- package/src/v5/feature-arrow.ts +2 -2
- package/src/v5/i3s-mesh-conversion.ts +41 -3
- package/src/v5/i3s-mesh-sink.ts +1 -1
- package/src/v5/i3s-mesh-source-features.ts +3 -3
- package/src/v5/i3s-mesh-source-material.ts +18 -11
- package/src/v5/i3s-mesh-source.ts +3 -2
- package/src/v5/i3s-selection-source.ts +298 -0
- package/src/v5/index.ts +41 -0
- package/src/v5/mesh-conversion.ts +24 -4
- package/src/v5/mesh-draco.ts +2 -2
- package/src/v5/mesh-features.ts +3 -3
- package/src/v5/mesh-hierarchy.ts +648 -0
- package/src/v5/mesh-source-features.ts +165 -51
- package/src/v5/mesh-source-schema.ts +140 -0
- package/src/v5/mesh-source-texture.ts +49 -19
- package/src/v5/mesh-source.ts +80 -49
- package/src/v5/mesh-tileset-archive.ts +1 -1
- package/src/v5/mesh-tileset-sink.ts +2 -2
- package/src/v5/mesh.ts +1 -1
- package/src/v5/node-cli.ts +201 -0
- package/src/v5/node-conversion.ts +362 -0
- package/src/v5/node-feature-schema.ts +155 -0
- package/src/v5/node-i3s-source.ts +181 -0
- package/src/v5/node-source.ts +328 -0
- package/src/v5/node.ts +16 -0
- package/src/v5/point-cloud-attributes.ts +113 -0
- package/src/v5/point-cloud-conversion.ts +1 -1
- package/src/v5/point-cloud-copc.ts +289 -0
- package/src/v5/point-cloud-file.ts +306 -0
- package/src/v5/point-cloud-potree.ts +134 -0
- package/src/v5/point-cloud-source-encoder.ts +1 -1
- package/src/v5/point-cloud-source.ts +1 -1
- package/src/v5/point-cloud-spatial.ts +2 -2
- package/src/v5/point-cloud-tileset.ts +137 -0
- package/src/v5/point-cloud.ts +3 -3
- package/src/v5/resource-fetcher.ts +4 -1
- package/src/v5/resource-manifest.ts +40 -5
- package/src/v5/selected-mesh-source.ts +384 -0
- package/src/v5/spatial-conversion.ts +90 -19
- package/src/v5/tileset-source.ts +2 -2
package/dist/v5/adapters.cjs
CHANGED
|
@@ -20,32 +20,49 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/v5/adapters.ts
|
|
21
21
|
var adapters_exports = {};
|
|
22
22
|
__export(adapters_exports, {
|
|
23
|
+
SELECTED_MESH_LIMITS: () => SELECTED_MESH_LIMITS,
|
|
23
24
|
convertFeatureAttributesToArrowBatches: () => convertFeatureAttributesToArrowBatches,
|
|
24
25
|
convertPointCloudSource: () => convertPointCloudSource,
|
|
26
|
+
convertPointCloudToPotree: () => convertPointCloudToPotree,
|
|
27
|
+
convertPointCloudToTileset: () => convertPointCloudToTileset,
|
|
28
|
+
createCOPCConversionCodec: () => createCOPCConversionCodec,
|
|
25
29
|
createI3SMeshConversionCodec: () => createI3SMeshConversionCodec,
|
|
26
30
|
createI3SMeshSink: () => createI3SMeshSink,
|
|
27
31
|
createI3SMeshTilesetConversionSource: () => createI3SMeshTilesetConversionSource,
|
|
28
32
|
createMeshConversionCodec: () => createMeshConversionCodec,
|
|
33
|
+
createMeshHierarchyTilesetSink: () => createMeshHierarchyTilesetSink,
|
|
34
|
+
createMeshSourceFeatureOptions: () => createMeshSourceFeatureOptions,
|
|
29
35
|
createMeshTilesetConversionSource: () => createMeshTilesetConversionSource,
|
|
30
36
|
createMeshTilesetSink: () => createMeshTilesetSink,
|
|
31
37
|
createPointCloudTilesetSink: () => createPointCloudTilesetSink,
|
|
38
|
+
createSelectedI3SMeshConversionSource: () => createSelectedI3SMeshConversionSource,
|
|
39
|
+
createSelectedMeshConversionSource: () => createSelectedMeshConversionSource,
|
|
32
40
|
createSingleMeshI3SSink: () => createSingleMeshI3SSink,
|
|
33
41
|
createSingleMeshTilesetArchive: () => createSingleMeshTilesetArchive,
|
|
34
42
|
createSingleMeshTilesetSink: () => createSingleMeshTilesetSink,
|
|
35
43
|
createTileConversionArchive: () => createTileConversionArchive,
|
|
44
|
+
createTileConversionArchiveTransport: () => createTileConversionArchiveTransport,
|
|
36
45
|
createTileConversionResourceFetcher: () => createTileConversionResourceFetcher,
|
|
37
46
|
encodeMeshTile: () => encodeMeshTile,
|
|
47
|
+
encodePointCloudCOPC: () => encodePointCloudCOPC,
|
|
38
48
|
encodePointCloudSource: () => encodePointCloudSource,
|
|
39
49
|
encodePointCloudSourceTile: () => encodePointCloudSourceTile,
|
|
40
50
|
encodePointCloudTile: () => encodePointCloudTile,
|
|
41
51
|
encodePointCloudTileWithMetadata: () => encodePointCloudTileWithMetadata,
|
|
52
|
+
encodePotreeArchive: () => encodePotreeArchive,
|
|
42
53
|
encodeTileConversionArchiveInBatches: () => encodeTileConversionArchiveInBatches,
|
|
54
|
+
extractMeshVertices: () => extractMeshVertices,
|
|
55
|
+
inspectI3SMeshConversionSource: () => inspectI3SMeshConversionSource,
|
|
56
|
+
mapPointCloudAttributes: () => mapPointCloudAttributes,
|
|
57
|
+
measureMeshBytes: () => measureMeshBytes,
|
|
58
|
+
openPointCloudFile: () => openPointCloudFile,
|
|
59
|
+
openTileConversionArchive: () => openTileConversionArchive,
|
|
43
60
|
transformPointCloudSourceTile: () => transformPointCloudSourceTile
|
|
44
61
|
});
|
|
45
62
|
module.exports = __toCommonJS(adapters_exports);
|
|
46
63
|
|
|
47
64
|
// src/v5/feature-arrow.ts
|
|
48
|
-
var import_schema_utils = require("@loaders.gl/schema-utils");
|
|
65
|
+
var import_schema_utils = require("@loaders.gl/core/schema-utils");
|
|
49
66
|
var import_core = require("@loaders.gl/tile-converter/v5/core");
|
|
50
67
|
var DEFAULT_FEATURE_ID_FIELD = "feature_id";
|
|
51
68
|
var FEATURE_CLASS_METADATA_KEY = "loaders.gl:feature-class";
|
|
@@ -367,8 +384,8 @@ function isBinaryType(dataType) {
|
|
|
367
384
|
}
|
|
368
385
|
|
|
369
386
|
// src/v5/point-cloud.ts
|
|
370
|
-
var import_d_tiles = require("@loaders.gl/3d-tiles");
|
|
371
|
-
var import_schema_utils2 = require("@loaders.gl/schema-utils");
|
|
387
|
+
var import_d_tiles = require("@loaders.gl/tiles-3d/3d-tiles");
|
|
388
|
+
var import_schema_utils2 = require("@loaders.gl/core/schema-utils");
|
|
372
389
|
var import_core2 = require("@loaders.gl/tile-converter/v5/core");
|
|
373
390
|
function encodePointCloudTile(pointBatch, options = {}) {
|
|
374
391
|
return encodePointCloudTileWithMetadata(pointBatch, options).pnts;
|
|
@@ -631,8 +648,8 @@ function validateBatchTable(batchTableJson, batchIds) {
|
|
|
631
648
|
|
|
632
649
|
// src/v5/point-cloud-spatial.ts
|
|
633
650
|
var import_core3 = require("@math.gl/core");
|
|
634
|
-
var
|
|
635
|
-
var import_schema_utils3 = require("@loaders.gl/schema-utils");
|
|
651
|
+
var import_tiles_3d = require("@loaders.gl/tiles-3d");
|
|
652
|
+
var import_schema_utils3 = require("@loaders.gl/core/schema-utils");
|
|
636
653
|
var import_core4 = require("@loaders.gl/tile-converter/v5/core");
|
|
637
654
|
async function transformPointCloudSourceTile(sourceTile, options) {
|
|
638
655
|
options.signal?.throwIfAborted();
|
|
@@ -641,7 +658,7 @@ async function transformPointCloudSourceTile(sourceTile, options) {
|
|
|
641
658
|
const reference = options.spatialContext.spatialReference;
|
|
642
659
|
const contentReference = content.spatialReference;
|
|
643
660
|
const retainTransformedEcef = contentReference?.status === "transformed" && (contentReference.targetCrs || contentReference.sourceCrs) === "EPSG:4978" && reference.status === "native" && reference.sourceCrs === "EPSG:4978" && reference.verticalUnitScale === 1 && (reference.units?.every((unit) => unit === "meter") ?? true);
|
|
644
|
-
const sourceReference = retainTransformedEcef ? (0,
|
|
661
|
+
const sourceReference = retainTransformedEcef ? (0, import_tiles_3d.createTilesetSpatialReference)({
|
|
645
662
|
sourceCrs: "EPSG:4978",
|
|
646
663
|
coordinateFrame: "geocentric",
|
|
647
664
|
axisOrder: "xyz",
|
|
@@ -655,7 +672,7 @@ async function transformPointCloudSourceTile(sourceTile, options) {
|
|
|
655
672
|
"Point content and context must agree on the input CRS, units, height reference and epoch"
|
|
656
673
|
);
|
|
657
674
|
}
|
|
658
|
-
const sourceFrame = reference.coordinateFrame === "unknown" && reference.sourceCrs ? (0,
|
|
675
|
+
const sourceFrame = reference.coordinateFrame === "unknown" && reference.sourceCrs ? (0, import_tiles_3d.getSpatialCoordinateFrame)(reference.sourceCrs) : reference.coordinateFrame;
|
|
659
676
|
const geographic = sourceFrame === "geographic";
|
|
660
677
|
if (!["xy", "xyz", "unknown"].includes(reference.axisOrder) || (geographic ? !["lnglat", "lnglat-offsets"].includes(content.coordinateSystem) : content.coordinateSystem !== "cartesian" || !["projected", "geocentric", "cartesian", "local"].includes(sourceFrame))) {
|
|
661
678
|
throw new import_core4.TileConversionError(
|
|
@@ -766,7 +783,7 @@ async function transformPointCloudSourceTile(sourceTile, options) {
|
|
|
766
783
|
maximum[axis] = Math.max(maximum[axis], value);
|
|
767
784
|
}
|
|
768
785
|
const center = minimum.map((value, axis) => value / 2 + maximum[axis] / 2);
|
|
769
|
-
const targetFrame = reference.status === "native" ? sourceFrame : (0,
|
|
786
|
+
const targetFrame = reference.status === "native" ? sourceFrame : (0, import_tiles_3d.getSpatialCoordinateFrame)(reference.targetCrs || reference.sourceCrs);
|
|
770
787
|
const targetIsGeographic = targetFrame === "geographic";
|
|
771
788
|
const attributes = {
|
|
772
789
|
...mesh.attributes,
|
|
@@ -890,7 +907,7 @@ var import_gltf = require("@loaders.gl/gltf");
|
|
|
890
907
|
var import_images = require("@loaders.gl/images");
|
|
891
908
|
|
|
892
909
|
// src/v5/mesh-features.ts
|
|
893
|
-
var import_schema_utils4 = require("@loaders.gl/schema-utils");
|
|
910
|
+
var import_schema_utils4 = require("@loaders.gl/core/schema-utils");
|
|
894
911
|
var import_core8 = require("@loaders.gl/tile-converter/v5/core");
|
|
895
912
|
function prepareMeshFeatureGeometry(mesh, features) {
|
|
896
913
|
if (!features) return mesh;
|
|
@@ -1444,7 +1461,7 @@ function validateMeshTextureTransform(transform) {
|
|
|
1444
1461
|
}
|
|
1445
1462
|
|
|
1446
1463
|
// src/v5/mesh-conversion.ts
|
|
1447
|
-
var
|
|
1464
|
+
var import_tiles_3d2 = require("@loaders.gl/tiles-3d");
|
|
1448
1465
|
var import_core11 = require("@loaders.gl/tile-converter/v5/core");
|
|
1449
1466
|
|
|
1450
1467
|
// src/v5/mesh-draco.ts
|
|
@@ -1587,7 +1604,7 @@ function createMeshConversionCodec(options) {
|
|
|
1587
1604
|
}
|
|
1588
1605
|
const spatialReference = spatialContext.spatialReference;
|
|
1589
1606
|
const outputCrs = spatialReference.targetCrs || spatialReference.sourceCrs;
|
|
1590
|
-
const outputFrame = spatialReference.status === "native" ? spatialReference.coordinateFrame : outputCrs ? (0,
|
|
1607
|
+
const outputFrame = spatialReference.status === "native" ? spatialReference.coordinateFrame : outputCrs ? (0, import_tiles_3d2.getSpatialCoordinateFrame)(outputCrs) : "unknown";
|
|
1591
1608
|
if (!["native", "transformed"].includes(spatialReference.status) || !["projected", "geocentric", "local", "cartesian"].includes(outputFrame)) {
|
|
1592
1609
|
throw new import_core11.TileConversionError(
|
|
1593
1610
|
"MESH_OUTPUT_FRAME_UNSUPPORTED",
|
|
@@ -1676,6 +1693,8 @@ function createMeshConversionCodec(options) {
|
|
|
1676
1693
|
}
|
|
1677
1694
|
yield {
|
|
1678
1695
|
id: resource.id,
|
|
1696
|
+
contentResourceId: resource.contentResourceId,
|
|
1697
|
+
contentPrimitive: resource.contentPrimitive,
|
|
1679
1698
|
glb: encoded.glb,
|
|
1680
1699
|
origin,
|
|
1681
1700
|
boundingBox: [minimum, maximum],
|
|
@@ -1929,8 +1948,8 @@ function createMeshTilesetSink(options) {
|
|
|
1929
1948
|
}
|
|
1930
1949
|
|
|
1931
1950
|
// src/v5/conversion-archive.ts
|
|
1932
|
-
var import_d_tiles_archive_writer = require("@loaders.gl/3d-tiles/3d-tiles-archive-writer");
|
|
1933
|
-
var import_i3s_slpk_writer = require("@loaders.gl/i3s/i3s-slpk-writer");
|
|
1951
|
+
var import_d_tiles_archive_writer = require("@loaders.gl/tiles-3d/3d-tiles/3d-tiles-archive-writer");
|
|
1952
|
+
var import_i3s_slpk_writer = require("@loaders.gl/tiles-3d/i3s/i3s-slpk-writer");
|
|
1934
1953
|
var import_core14 = require("@loaders.gl/tile-converter/v5/core");
|
|
1935
1954
|
async function createTileConversionArchive(files, options) {
|
|
1936
1955
|
const { resources, format, maxArchiveBytes, signal } = prepareArchiveInput(files, options);
|
|
@@ -1965,7 +1984,7 @@ function prepareArchiveInput(files, options) {
|
|
|
1965
1984
|
}
|
|
1966
1985
|
|
|
1967
1986
|
// src/v5/mesh-tileset-archive.ts
|
|
1968
|
-
var import_d_tiles_archive_writer2 = require("@loaders.gl/3d-tiles/3d-tiles-archive-writer");
|
|
1987
|
+
var import_d_tiles_archive_writer2 = require("@loaders.gl/tiles-3d/3d-tiles/3d-tiles-archive-writer");
|
|
1969
1988
|
var import_core15 = require("@loaders.gl/tile-converter/v5/core");
|
|
1970
1989
|
async function createSingleMeshTilesetArchive(files, options) {
|
|
1971
1990
|
const { maxArchiveBytes, signal } = options;
|
|
@@ -1987,17 +2006,19 @@ async function createSingleMeshTilesetArchive(files, options) {
|
|
|
1987
2006
|
}
|
|
1988
2007
|
|
|
1989
2008
|
// src/v5/mesh-source.ts
|
|
1990
|
-
var
|
|
1991
|
-
var
|
|
2009
|
+
var import_core19 = require("@math.gl/core");
|
|
2010
|
+
var import_gltf4 = require("@loaders.gl/gltf");
|
|
1992
2011
|
var import_core20 = require("@loaders.gl/tile-converter/v5/core");
|
|
2012
|
+
var import_core21 = require("@loaders.gl/tile-converter/v5/core");
|
|
1993
2013
|
|
|
1994
2014
|
// src/v5/mesh-source-texture.ts
|
|
1995
2015
|
var import_images2 = require("@loaders.gl/images");
|
|
2016
|
+
var import_gltf2 = require("@loaders.gl/gltf");
|
|
1996
2017
|
var import_core16 = require("@loaders.gl/tile-converter/v5/core");
|
|
1997
|
-
function mapMeshSourceTexture(textureInfo) {
|
|
1998
|
-
const texture = textureInfo?.
|
|
1999
|
-
const image = texture?.source;
|
|
2000
|
-
const sampler = texture?.sampler;
|
|
2018
|
+
function mapMeshSourceTexture(gltf, textureInfo) {
|
|
2019
|
+
const texture = textureInfo ? gltf.json.textures?.[textureInfo.index] : void 0;
|
|
2020
|
+
const image = texture?.source === void 0 ? void 0 : gltf.json.images?.[texture.source];
|
|
2021
|
+
const sampler = texture?.sampler === void 0 ? void 0 : gltf.json.samplers?.[texture.sampler];
|
|
2001
2022
|
const extensions = textureInfo?.extensions || {};
|
|
2002
2023
|
const hasTransform = Object.hasOwn(extensions, "KHR_texture_transform");
|
|
2003
2024
|
const transform = extensions.KHR_texture_transform;
|
|
@@ -2007,7 +2028,7 @@ function mapMeshSourceTexture(textureInfo) {
|
|
|
2007
2028
|
(name) => !["id", "index", "texture", "texCoord", "extensions", "extras"].includes(name)
|
|
2008
2029
|
) || !texture || Object.keys(texture).some(
|
|
2009
2030
|
(name) => !["id", "source", "sampler", "name", "extensions", "extras"].includes(name)
|
|
2010
|
-
) || Object.keys(texture.extensions || {}).length || !image || image.uri !== void 0 && image.bufferView !== void 0 || Object.keys(image.extensions || {}).length || image.uri === void 0 && (
|
|
2031
|
+
) || Object.keys(texture.extensions || {}).length || !image || image.uri !== void 0 && image.bufferView !== void 0 || Object.keys(image.extensions || {}).length || image.uri === void 0 && (image.bufferView === void 0 || !gltf.json.bufferViews?.[image.bufferView] || Object.keys(gltf.json.bufferViews?.[image.bufferView]?.extensions || {}).length || !["image/png", "image/jpeg"].includes(image.mimeType || "")) || sampler && (Object.keys(sampler.extensions || {}).length || Object.keys(sampler).some(
|
|
2011
2032
|
(name) => ![
|
|
2012
2033
|
"id",
|
|
2013
2034
|
"parameters",
|
|
@@ -2032,7 +2053,7 @@ function mapMeshSourceTexture(textureInfo) {
|
|
|
2032
2053
|
minFilter: sampler.minFilter
|
|
2033
2054
|
} : void 0;
|
|
2034
2055
|
return {
|
|
2035
|
-
...mapMeshSourceImage(image),
|
|
2056
|
+
...mapMeshSourceImage(gltf, image),
|
|
2036
2057
|
...hasTransform ? {
|
|
2037
2058
|
transform: {
|
|
2038
2059
|
offset: transform.offset,
|
|
@@ -2043,9 +2064,12 @@ function mapMeshSourceTexture(textureInfo) {
|
|
|
2043
2064
|
...selectedSampler && Object.values(selectedSampler).some((value) => value !== void 0) ? { sampler: selectedSampler } : {}
|
|
2044
2065
|
};
|
|
2045
2066
|
}
|
|
2046
|
-
function mapMeshSourceImage(image) {
|
|
2067
|
+
function mapMeshSourceImage(gltf, image) {
|
|
2047
2068
|
if (image.uri === void 0) {
|
|
2048
|
-
return {
|
|
2069
|
+
return {
|
|
2070
|
+
data: new import_gltf2.GLTFScenegraph(gltf).getTypedArrayForBufferView(image.bufferView),
|
|
2071
|
+
mimeType: image.mimeType
|
|
2072
|
+
};
|
|
2049
2073
|
}
|
|
2050
2074
|
const match = typeof image.uri === "string" ? /^data:(image\/(?:png|jpeg));base64,([A-Za-z0-9+/]+={0,2})$/.exec(image.uri) : null;
|
|
2051
2075
|
if (!match || image.mimeType !== void 0 && image.mimeType !== match[1]) {
|
|
@@ -2065,10 +2089,11 @@ function mapMeshSourceImage(image) {
|
|
|
2065
2089
|
mimeType: match[1]
|
|
2066
2090
|
};
|
|
2067
2091
|
}
|
|
2068
|
-
async function resolveMeshSourceTexture(textureInfo, readExternalResource, contentUri, signal) {
|
|
2069
|
-
const
|
|
2092
|
+
async function resolveMeshSourceTexture(gltf, textureInfo, readExternalResource, contentUri, signal) {
|
|
2093
|
+
const texture = textureInfo ? gltf.json.textures?.[textureInfo.index] : void 0;
|
|
2094
|
+
const image = texture?.source === void 0 ? void 0 : gltf.json.images?.[texture.source];
|
|
2070
2095
|
if (!image || image.uri === void 0 || image.uri.startsWith("data:"))
|
|
2071
|
-
return mapMeshSourceTexture(textureInfo);
|
|
2096
|
+
return mapMeshSourceTexture(gltf, textureInfo);
|
|
2072
2097
|
if (!readExternalResource || image.bufferView !== void 0)
|
|
2073
2098
|
throw new import_core16.TileConversionError(
|
|
2074
2099
|
"MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
@@ -2087,168 +2112,336 @@ async function resolveMeshSourceTexture(textureInfo, readExternalResource, conte
|
|
|
2087
2112
|
"MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
2088
2113
|
"External images require matching PNG/JPEG headers"
|
|
2089
2114
|
);
|
|
2115
|
+
const resolvedImage = {
|
|
2116
|
+
...image,
|
|
2117
|
+
uri: void 0,
|
|
2118
|
+
mimeType: metadata.mimeType,
|
|
2119
|
+
bufferView: (gltf.json.bufferViews || []).length
|
|
2120
|
+
};
|
|
2090
2121
|
const resolved = {
|
|
2091
|
-
...
|
|
2092
|
-
|
|
2093
|
-
...
|
|
2094
|
-
|
|
2122
|
+
...gltf,
|
|
2123
|
+
buffers: [
|
|
2124
|
+
...gltf.buffers,
|
|
2125
|
+
{
|
|
2126
|
+
arrayBuffer: data.buffer,
|
|
2127
|
+
byteOffset: data.byteOffset,
|
|
2128
|
+
byteLength: data.byteLength
|
|
2129
|
+
}
|
|
2130
|
+
],
|
|
2131
|
+
json: {
|
|
2132
|
+
...gltf.json,
|
|
2133
|
+
images: gltf.json.images.map(
|
|
2134
|
+
(entry, index) => index === texture.source ? resolvedImage : entry
|
|
2135
|
+
),
|
|
2136
|
+
bufferViews: [
|
|
2137
|
+
...gltf.json.bufferViews || [],
|
|
2138
|
+
{ buffer: gltf.buffers.length, byteLength: data.byteLength }
|
|
2139
|
+
]
|
|
2095
2140
|
}
|
|
2096
2141
|
};
|
|
2097
|
-
return mapMeshSourceTexture(resolved);
|
|
2142
|
+
return mapMeshSourceTexture(resolved, textureInfo);
|
|
2098
2143
|
}
|
|
2099
2144
|
|
|
2100
2145
|
// src/v5/mesh-source-features.ts
|
|
2146
|
+
var import_gltf3 = require("@loaders.gl/gltf");
|
|
2147
|
+
|
|
2148
|
+
// src/v5/mesh-source-schema.ts
|
|
2101
2149
|
var import_core17 = require("@loaders.gl/tile-converter/v5/core");
|
|
2150
|
+
var SCALAR_TYPES = {
|
|
2151
|
+
INT8: "int8",
|
|
2152
|
+
UINT8: "uint8",
|
|
2153
|
+
INT16: "int16",
|
|
2154
|
+
UINT16: "uint16",
|
|
2155
|
+
INT32: "int32",
|
|
2156
|
+
UINT32: "uint32",
|
|
2157
|
+
INT64: "int64",
|
|
2158
|
+
UINT64: "uint64",
|
|
2159
|
+
FLOAT32: "float32",
|
|
2160
|
+
FLOAT64: "float64"
|
|
2161
|
+
};
|
|
2162
|
+
function createMeshSourceFeatureOptions(gltf, options) {
|
|
2163
|
+
const metadata = gltf.extensions?.EXT_structural_metadata;
|
|
2164
|
+
const table = metadata?.propertyTables?.[0];
|
|
2165
|
+
const properties = metadata?.schema?.classes?.[options.metadataClass]?.properties;
|
|
2166
|
+
if (!options.metadataClass || metadata?.propertyTables?.length !== 1 || table?.class !== options.metadataClass || !properties || !Number.isSafeInteger(table.count) || table.count < 1 || table.count > 4294967295 || metadata.propertyTextures?.length || metadata.propertyAttributes?.length) {
|
|
2167
|
+
throw new import_core17.TileConversionError(
|
|
2168
|
+
"MESH_FEATURE_SCHEMA_REQUIRED",
|
|
2169
|
+
"Schema mapping requires one inline class, one matching property table and a positive uint32 row count"
|
|
2170
|
+
);
|
|
2171
|
+
}
|
|
2172
|
+
if (Object.keys(table.properties || {}).some((name) => !Object.hasOwn(properties, name))) {
|
|
2173
|
+
throw new import_core17.TileConversionError(
|
|
2174
|
+
"MESH_FEATURE_PROPERTY_UNMAPPED",
|
|
2175
|
+
"Property table columns must be declared in the selected class"
|
|
2176
|
+
);
|
|
2177
|
+
}
|
|
2178
|
+
const fields = Object.entries(properties).map(([name, property]) => {
|
|
2179
|
+
const declaration = property;
|
|
2180
|
+
const column = table.properties?.[name];
|
|
2181
|
+
if (declaration.array || !["SCALAR", "STRING"].includes(declaration.type) || declaration.normalized || declaration.offset !== void 0 || declaration.scale !== void 0 || column?.offset !== void 0 || column?.scale !== void 0 || declaration.required && (declaration.noData !== void 0 || declaration.default !== void 0)) {
|
|
2182
|
+
throw new import_core17.TileConversionError(
|
|
2183
|
+
"MESH_FEATURE_PROPERTY_UNSUPPORTED",
|
|
2184
|
+
`Property ${name} requires an explicit separately qualified mapping`
|
|
2185
|
+
);
|
|
2186
|
+
}
|
|
2187
|
+
const type = declaration.type === "STRING" ? "utf8" : Object.hasOwn(SCALAR_TYPES, declaration.componentType || "") ? SCALAR_TYPES[declaration.componentType] : void 0;
|
|
2188
|
+
if (!type) {
|
|
2189
|
+
throw new import_core17.TileConversionError(
|
|
2190
|
+
"MESH_FEATURE_PROPERTY_UNSUPPORTED",
|
|
2191
|
+
`Property ${name} has no supported declared scalar width`
|
|
2192
|
+
);
|
|
2193
|
+
}
|
|
2194
|
+
return {
|
|
2195
|
+
name,
|
|
2196
|
+
type,
|
|
2197
|
+
nullable: declaration.required !== true,
|
|
2198
|
+
metadata: { "loaders.gl:structural-property": JSON.stringify(declaration) }
|
|
2199
|
+
};
|
|
2200
|
+
});
|
|
2201
|
+
const identifierName = options.featureIdField || "feature_id";
|
|
2202
|
+
const sourceIdentifier = options.sourceFeatureIdProperty;
|
|
2203
|
+
const sourceField = sourceIdentifier ? fields.find((field) => field.name === sourceIdentifier) : void 0;
|
|
2204
|
+
if (sourceIdentifier && !sourceField || fields.some((field) => field.name === identifierName && field !== sourceField) || sourceField && !["utf8", "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64"].includes(
|
|
2205
|
+
String(sourceField.type)
|
|
2206
|
+
)) {
|
|
2207
|
+
throw new import_core17.TileConversionError(
|
|
2208
|
+
"MESH_FEATURE_ID_PROPERTY_REQUIRED",
|
|
2209
|
+
"Select a declared string/integer identifier and a distinct output identifier name"
|
|
2210
|
+
);
|
|
2211
|
+
}
|
|
2212
|
+
return {
|
|
2213
|
+
...options,
|
|
2214
|
+
schema: {
|
|
2215
|
+
fields: [
|
|
2216
|
+
{
|
|
2217
|
+
...sourceField,
|
|
2218
|
+
name: identifierName,
|
|
2219
|
+
type: sourceField?.type || "uint32",
|
|
2220
|
+
nullable: false
|
|
2221
|
+
},
|
|
2222
|
+
...fields.filter((field) => field !== sourceField)
|
|
2223
|
+
],
|
|
2224
|
+
metadata: { "loaders.gl:source-schema": JSON.stringify(metadata.schema) }
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
// src/v5/mesh-source-features.ts
|
|
2230
|
+
var import_core18 = require("@loaders.gl/tile-converter/v5/core");
|
|
2102
2231
|
function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
2103
2232
|
const declarations = primitive.extensions?.EXT_mesh_features?.featureIds;
|
|
2104
|
-
const metadata = gltf.extensions?.EXT_structural_metadata;
|
|
2233
|
+
const metadata = gltf.json.extensions?.EXT_structural_metadata;
|
|
2105
2234
|
const legacy = primitive.attributes._BATCHID;
|
|
2106
|
-
if (!declarations &&
|
|
2235
|
+
if (!declarations && legacy === void 0 && !metadata && !Object.keys(payload.batchTableJson || {}).length && !payload.header?.batchLength) {
|
|
2107
2236
|
if (Object.keys(primitive.attributes).some((name) => /^_FEATURE_ID_\d+$/.test(name)))
|
|
2108
|
-
throw new
|
|
2237
|
+
throw new import_core18.TileConversionError(
|
|
2109
2238
|
"MESH_FEATURE_MAPPING_REQUIRED",
|
|
2110
2239
|
"Feature attributes require an EXT_mesh_features declaration"
|
|
2111
2240
|
);
|
|
2112
2241
|
return void 0;
|
|
2113
2242
|
}
|
|
2114
2243
|
if (!options)
|
|
2115
|
-
throw new
|
|
2244
|
+
throw new import_core18.TileConversionError(
|
|
2116
2245
|
"MESH_FEATURE_SCHEMA_REQUIRED",
|
|
2117
|
-
"Feature extraction requires
|
|
2246
|
+
"Feature extraction requires a class selection and a declared Arrow or inline source schema"
|
|
2118
2247
|
);
|
|
2119
2248
|
if (!options.metadataClass)
|
|
2120
|
-
throw new
|
|
2249
|
+
throw new import_core18.TileConversionError(
|
|
2121
2250
|
"MESH_FEATURE_CLASS_REQUIRED",
|
|
2122
2251
|
"Feature metadata class must be explicit"
|
|
2123
2252
|
);
|
|
2253
|
+
const mapping = "schema" in options ? options : createMeshSourceFeatureOptions(gltf.json, options);
|
|
2254
|
+
const propertyMappings = /* @__PURE__ */ new Map();
|
|
2124
2255
|
let count;
|
|
2125
|
-
let
|
|
2256
|
+
let attributeReference = legacy;
|
|
2126
2257
|
let properties;
|
|
2127
2258
|
if (declarations) {
|
|
2128
|
-
if (legacy || declarations.length !== 1 || metadata?.propertyTables?.length !== 1 || metadata.propertyTextures?.length || metadata.propertyAttributes?.length)
|
|
2129
|
-
throw new
|
|
2259
|
+
if (legacy !== void 0 || declarations.length !== 1 || metadata?.propertyTables?.length !== 1 || metadata.propertyTextures?.length || metadata.propertyAttributes?.length)
|
|
2260
|
+
throw new import_core18.TileConversionError(
|
|
2130
2261
|
"MESH_FEATURE_MAPPING_UNSUPPORTED",
|
|
2131
2262
|
"Only one attribute-backed feature set and one property table are supported"
|
|
2132
2263
|
);
|
|
2133
2264
|
const declaration = declarations[0];
|
|
2134
|
-
if (!Number.isInteger(declaration.attribute) || declaration.attribute < 0 || declaration.propertyTable !== 0 || declaration.texture || declaration.implicit || declaration.nullFeatureId !== void 0)
|
|
2135
|
-
throw new
|
|
2265
|
+
if (typeof declaration.attribute !== "number" || !Number.isInteger(declaration.attribute) || declaration.attribute < 0 || declaration.propertyTable !== 0 || declaration.texture || declaration.implicit || declaration.nullFeatureId !== void 0)
|
|
2266
|
+
throw new import_core18.TileConversionError(
|
|
2136
2267
|
"MESH_FEATURE_MAPPING_UNSUPPORTED",
|
|
2137
2268
|
"Features require an attribute and property table 0 without null/texture/implicit IDs"
|
|
2138
2269
|
);
|
|
2139
|
-
|
|
2270
|
+
attributeReference = primitive.attributes[`_FEATURE_ID_${declaration.attribute}`];
|
|
2140
2271
|
const table = metadata.propertyTables[0];
|
|
2141
|
-
if (table.class !== options.metadataClass ||
|
|
2142
|
-
throw new
|
|
2272
|
+
if (table.class !== options.metadataClass || !Number.isSafeInteger(declaration.featureCount) || declaration.featureCount <= 0 || declaration.featureCount > table.count)
|
|
2273
|
+
throw new import_core18.TileConversionError(
|
|
2143
2274
|
"MESH_FEATURE_CLASS_MISMATCH",
|
|
2144
|
-
"Feature class and declared
|
|
2275
|
+
"Feature class must agree and declared feature count must fit the property table"
|
|
2145
2276
|
);
|
|
2146
2277
|
count = table.count;
|
|
2147
|
-
|
|
2278
|
+
if (!Number.isSafeInteger(count) || count <= 0 || count > 4294967295) {
|
|
2279
|
+
throw new import_core18.TileConversionError(
|
|
2280
|
+
"MESH_FEATURE_IDS_INVALID",
|
|
2281
|
+
"Feature row count must fit uint32 indices"
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
properties = /* @__PURE__ */ Object.create(null);
|
|
2148
2285
|
const classProperties = metadata.schema?.classes?.[table.class]?.properties;
|
|
2149
2286
|
if (!classProperties)
|
|
2150
|
-
throw new
|
|
2287
|
+
throw new import_core18.TileConversionError(
|
|
2151
2288
|
"MESH_FEATURE_SCHEMA_REQUIRED",
|
|
2152
2289
|
"A decoded inline structural metadata class is required"
|
|
2153
2290
|
);
|
|
2154
2291
|
if (Object.keys(table.properties || {}).some((name) => !Object.hasOwn(classProperties, name)))
|
|
2155
|
-
throw new
|
|
2292
|
+
throw new import_core18.TileConversionError(
|
|
2156
2293
|
"MESH_FEATURE_PROPERTY_UNMAPPED",
|
|
2157
2294
|
"Property table columns must be declared in the metadata class"
|
|
2158
2295
|
);
|
|
2159
2296
|
for (const [name, property] of Object.entries(classProperties)) {
|
|
2160
2297
|
const column = table.properties?.[name];
|
|
2161
2298
|
if (!["SCALAR", "STRING"].includes(property.type) || property.array || property.noData !== void 0 && (property.normalized || property.offset !== void 0 || property.scale !== void 0 || column?.offset !== void 0 || column?.scale !== void 0) || ["INT64", "UINT64"].includes(property.componentType || "") && (property.normalized || property.offset !== void 0 || property.scale !== void 0 || column?.offset !== void 0 || column?.scale !== void 0))
|
|
2162
|
-
throw new
|
|
2299
|
+
throw new import_core18.TileConversionError(
|
|
2163
2300
|
"MESH_FEATURE_PROPERTY_UNSUPPORTED",
|
|
2164
2301
|
`Property ${name} requires a separately qualified semantic mapping`
|
|
2165
2302
|
);
|
|
2166
2303
|
const data = column?.data;
|
|
2167
|
-
if (data === void 0 && property.
|
|
2168
|
-
throw new
|
|
2304
|
+
if (column !== void 0 && (data === void 0 || data === null) || data === void 0 && property.required === true)
|
|
2305
|
+
throw new import_core18.TileConversionError(
|
|
2169
2306
|
"MESH_FEATURE_PROPERTY_UNAVAILABLE",
|
|
2170
2307
|
`Property ${name} has not been decoded`
|
|
2171
2308
|
);
|
|
2172
|
-
const
|
|
2309
|
+
const integer64 = ["INT64", "UINT64"].includes(property.componentType || "");
|
|
2310
|
+
if (integer64 && property.default !== void 0 && (typeof property.default !== "number" || !Number.isSafeInteger(property.default))) {
|
|
2311
|
+
throw new import_core18.TileConversionError(
|
|
2312
|
+
"MESH_FEATURE_PROPERTY_UNSUPPORTED",
|
|
2313
|
+
`Property ${name} requires an exact 64-bit default`
|
|
2314
|
+
);
|
|
2315
|
+
}
|
|
2316
|
+
const defaultValue = integer64 && property.default !== void 0 ? BigInt(property.default) : property.default ?? null;
|
|
2317
|
+
const decoded = data;
|
|
2173
2318
|
if (property.noData === void 0 || data === void 0) {
|
|
2174
|
-
properties[name] = decoded;
|
|
2319
|
+
if (decoded !== void 0) properties[name] = decoded;
|
|
2320
|
+
else propertyMappings.set(name, { defaultValue });
|
|
2175
2321
|
continue;
|
|
2176
2322
|
}
|
|
2177
2323
|
const sentinel = property.noData;
|
|
2178
|
-
const integer64 = ["INT64", "UINT64"].includes(property.componentType || "");
|
|
2179
2324
|
if ((property.type === "STRING" ? typeof sentinel !== "string" : typeof sentinel !== "number" || !Number.isFinite(sentinel) || integer64 && !Number.isSafeInteger(sentinel)) || !decoded || !Array.isArray(decoded) && !ArrayBuffer.isView(decoded) || decoded.length !== count)
|
|
2180
|
-
throw new
|
|
2325
|
+
throw new import_core18.TileConversionError(
|
|
2181
2326
|
"MESH_FEATURE_PROPERTY_UNSUPPORTED",
|
|
2182
2327
|
`Property ${name} requires a valid decoded noData mapping`
|
|
2183
2328
|
);
|
|
2184
2329
|
const comparable = integer64 ? BigInt(sentinel) : sentinel;
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
);
|
|
2330
|
+
const values = decoded;
|
|
2331
|
+
properties[name] = values;
|
|
2332
|
+
propertyMappings.set(name, { noData: comparable, defaultValue });
|
|
2189
2333
|
}
|
|
2190
2334
|
} else {
|
|
2191
2335
|
if (metadata)
|
|
2192
|
-
throw new
|
|
2336
|
+
throw new import_core18.TileConversionError(
|
|
2193
2337
|
"MESH_FEATURE_MAPPING_UNSUPPORTED",
|
|
2194
2338
|
"Structural metadata requires an attribute-backed feature declaration"
|
|
2195
2339
|
);
|
|
2196
2340
|
count = payload.header?.batchLength ?? Number(payload.featureTableJson?.BATCH_LENGTH);
|
|
2197
2341
|
properties = payload.batchTableJson || {};
|
|
2198
2342
|
}
|
|
2343
|
+
const accessor = attributeReference === void 0 ? void 0 : (0, import_gltf3.getGLTFAccessor)(gltf, attributeReference);
|
|
2344
|
+
const attribute = accessor ? {
|
|
2345
|
+
...accessor,
|
|
2346
|
+
components: accessor.type === "SCALAR" ? 1 : Number(accessor.type.slice(3)),
|
|
2347
|
+
value: (0, import_gltf3.getGLTFAccessorValue)(gltf, accessor)
|
|
2348
|
+
} : void 0;
|
|
2199
2349
|
const vertexCount = mesh.attributes.POSITION.value.length / 3;
|
|
2200
|
-
if (!Number.isSafeInteger(count) || count <= 0 || !attribute || attribute.components !== 1 || attribute.normalized || attribute.value.length !== vertexCount
|
|
2201
|
-
|
|
2202
|
-
))
|
|
2203
|
-
throw new import_core17.TileConversionError(
|
|
2350
|
+
if (!Number.isSafeInteger(count) || count <= 0 || !attribute || attribute.components !== 1 || attribute.normalized || attribute.value.length !== vertexCount)
|
|
2351
|
+
throw new import_core18.TileConversionError(
|
|
2204
2352
|
"MESH_FEATURE_IDS_INVALID",
|
|
2205
2353
|
"Feature IDs must be one valid property-table row index per vertex"
|
|
2206
2354
|
);
|
|
2355
|
+
const vertexRows = declarations ? /* @__PURE__ */ new Set() : void 0;
|
|
2356
|
+
for (const value of attribute.value) {
|
|
2357
|
+
if (!Number.isSafeInteger(value) || value < 0 || value >= count)
|
|
2358
|
+
throw new import_core18.TileConversionError(
|
|
2359
|
+
"MESH_FEATURE_IDS_INVALID",
|
|
2360
|
+
"Feature IDs must reference valid property-table rows"
|
|
2361
|
+
);
|
|
2362
|
+
vertexRows?.add(Number(value));
|
|
2363
|
+
}
|
|
2364
|
+
if (declarations && vertexRows.size !== declarations[0].featureCount)
|
|
2365
|
+
throw new import_core18.TileConversionError(
|
|
2366
|
+
"MESH_FEATURE_IDS_INVALID",
|
|
2367
|
+
"Declared feature count must match distinct vertex feature IDs"
|
|
2368
|
+
);
|
|
2207
2369
|
const selectedAttribute = declarations ? `_FEATURE_ID_${declarations[0].attribute}` : "_BATCHID";
|
|
2208
2370
|
if (Object.keys(primitive.attributes).some(
|
|
2209
2371
|
(name) => (name === "_BATCHID" || /^_FEATURE_ID_\d+$/.test(name)) && name !== selectedAttribute
|
|
2210
2372
|
))
|
|
2211
|
-
throw new
|
|
2373
|
+
throw new import_core18.TileConversionError(
|
|
2212
2374
|
"MESH_FEATURE_MAPPING_UNSUPPORTED",
|
|
2213
2375
|
"Additional feature ID sets require separate mappings"
|
|
2214
2376
|
);
|
|
2215
2377
|
for (const [name, values] of Object.entries(properties)) {
|
|
2216
2378
|
if (!values || !Array.isArray(values) && !ArrayBuffer.isView(values) || values.length !== count)
|
|
2217
|
-
throw new
|
|
2379
|
+
throw new import_core18.TileConversionError(
|
|
2218
2380
|
"MESH_FEATURE_PROPERTY_UNAVAILABLE",
|
|
2219
|
-
`Property ${name} must be a decoded column matching the
|
|
2381
|
+
`Property ${name} must be a decoded column matching the feature row count`
|
|
2220
2382
|
);
|
|
2221
2383
|
}
|
|
2222
2384
|
const sourceIdentifier = options.sourceFeatureIdProperty;
|
|
2223
|
-
if (sourceIdentifier && !properties[sourceIdentifier])
|
|
2224
|
-
throw new
|
|
2385
|
+
if (sourceIdentifier && !properties[sourceIdentifier] && !propertyMappings.has(sourceIdentifier))
|
|
2386
|
+
throw new import_core18.TileConversionError(
|
|
2225
2387
|
"MESH_FEATURE_ID_PROPERTY_REQUIRED",
|
|
2226
2388
|
"Stable source identifier property is missing"
|
|
2227
2389
|
);
|
|
2228
|
-
const batches = convertFeatureAttributesToArrowBatches(
|
|
2229
|
-
Array.from({ length: count }, (_, index) => ({
|
|
2230
|
-
featureId: sourceIdentifier ? properties[sourceIdentifier][index] : index,
|
|
2231
|
-
metadataClass: options.metadataClass,
|
|
2232
|
-
properties: Object.fromEntries(
|
|
2233
|
-
Object.entries(properties).filter(([name]) => name !== sourceIdentifier).map(([name, values]) => [name, values[index]])
|
|
2234
|
-
)
|
|
2235
|
-
})),
|
|
2236
|
-
options
|
|
2237
|
-
);
|
|
2238
2390
|
const indices = mesh.indices?.value;
|
|
2239
|
-
const
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2391
|
+
const cornerCount = indices?.length ?? vertexCount;
|
|
2392
|
+
if (cornerCount % 3)
|
|
2393
|
+
throw new import_core18.TileConversionError(
|
|
2394
|
+
"MESH_FEATURE_IDS_INVALID",
|
|
2395
|
+
"Triangle geometry requires complete triples"
|
|
2243
2396
|
);
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2397
|
+
const triangleFeatureIndices = new Uint32Array(cornerCount / 3);
|
|
2398
|
+
const referencedRows = /* @__PURE__ */ new Set();
|
|
2399
|
+
const readFeatureRow = (corner) => {
|
|
2400
|
+
if (!Number.isSafeInteger(corner) || corner < 0 || corner >= vertexCount)
|
|
2401
|
+
throw new import_core18.TileConversionError(
|
|
2402
|
+
"MESH_FEATURE_IDS_INVALID",
|
|
2403
|
+
"Triangle indices must reference valid mesh vertices"
|
|
2404
|
+
);
|
|
2405
|
+
return Number(attribute.value[corner]);
|
|
2406
|
+
};
|
|
2407
|
+
for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
|
|
2408
|
+
const firstCorner = indices?.[triangle * 3] ?? triangle * 3;
|
|
2409
|
+
const secondCorner = indices?.[triangle * 3 + 1] ?? triangle * 3 + 1;
|
|
2410
|
+
const thirdCorner = indices?.[triangle * 3 + 2] ?? triangle * 3 + 2;
|
|
2411
|
+
const firstRow = readFeatureRow(firstCorner);
|
|
2412
|
+
const secondRow = readFeatureRow(secondCorner);
|
|
2413
|
+
const thirdRow = readFeatureRow(thirdCorner);
|
|
2414
|
+
if ((firstRow !== secondRow || firstRow !== thirdRow) && firstCorner !== secondCorner && firstCorner !== thirdCorner && secondCorner !== thirdCorner)
|
|
2415
|
+
throw new import_core18.TileConversionError(
|
|
2247
2416
|
"MESH_FEATURE_TRIANGLE_MIXED",
|
|
2248
2417
|
"All vertices of a nondegenerate triangle must reference the same feature row"
|
|
2249
2418
|
);
|
|
2250
|
-
triangleFeatureIndices[triangle] =
|
|
2419
|
+
triangleFeatureIndices[triangle] = firstRow;
|
|
2420
|
+
referencedRows.add(firstRow);
|
|
2251
2421
|
}
|
|
2422
|
+
const selectedRows = [...referencedRows].sort((left, right) => left - right);
|
|
2423
|
+
const denseRows = new Map(selectedRows.map((row, index) => [row, index]));
|
|
2424
|
+
for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
|
|
2425
|
+
triangleFeatureIndices[triangle] = denseRows.get(triangleFeatureIndices[triangle]);
|
|
2426
|
+
}
|
|
2427
|
+
const readProperty = (name, row) => {
|
|
2428
|
+
const values = properties[name];
|
|
2429
|
+
const propertyMapping = propertyMappings.get(name);
|
|
2430
|
+
if (!values) return propertyMapping?.defaultValue;
|
|
2431
|
+
const value = values[row];
|
|
2432
|
+
return propertyMapping && value === propertyMapping.noData ? propertyMapping.defaultValue : value;
|
|
2433
|
+
};
|
|
2434
|
+
const propertyNames = [
|
|
2435
|
+
.../* @__PURE__ */ new Set([...Object.keys(properties), ...propertyMappings.keys()])
|
|
2436
|
+
].filter((name) => name !== sourceIdentifier);
|
|
2437
|
+
const batches = convertFeatureAttributesToArrowBatches(
|
|
2438
|
+
selectedRows.map((row) => ({
|
|
2439
|
+
featureId: sourceIdentifier ? readProperty(sourceIdentifier, row) : row,
|
|
2440
|
+
metadataClass: options.metadataClass,
|
|
2441
|
+
properties: Object.fromEntries(propertyNames.map((name) => [name, readProperty(name, row)]))
|
|
2442
|
+
})),
|
|
2443
|
+
mapping
|
|
2444
|
+
);
|
|
2252
2445
|
return {
|
|
2253
2446
|
batches,
|
|
2254
2447
|
triangleFeatureIndices,
|
|
@@ -2259,20 +2452,20 @@ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
|
2259
2452
|
|
|
2260
2453
|
// src/v5/mesh-source.ts
|
|
2261
2454
|
function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
2262
|
-
const source = (0,
|
|
2455
|
+
const source = (0, import_core21.createTilesetConversionSource)(tileset, options);
|
|
2263
2456
|
return {
|
|
2264
2457
|
/** Validates the source frame and supported dataset metadata before traversal. */
|
|
2265
2458
|
async inspect(signal) {
|
|
2266
2459
|
const metadata = await source.inspect(signal);
|
|
2267
2460
|
const reference = metadata.spatialReference;
|
|
2268
2461
|
if (metadata.type !== "TILES3D" || !reference || reference.status !== "native" || reference.sourceCrs !== "EPSG:4978" || reference.heightReference !== "ellipsoidal" || reference.axisOrder !== "xyz" || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter")) {
|
|
2269
|
-
throw new
|
|
2462
|
+
throw new import_core20.TileConversionError(
|
|
2270
2463
|
"MESH_SOURCE_FRAME_UNSUPPORTED",
|
|
2271
2464
|
"Mesh extraction requires native EPSG:4978 xyz coordinates in meters and ellipsoidal heights"
|
|
2272
2465
|
);
|
|
2273
2466
|
}
|
|
2274
2467
|
if (metadata.tileset.metadata || metadata.tileset.groups?.length) {
|
|
2275
|
-
throw new
|
|
2468
|
+
throw new import_core20.TileConversionError(
|
|
2276
2469
|
"MESH_SOURCE_METADATA_UNSUPPORTED",
|
|
2277
2470
|
"Tileset/group metadata requires a separate mapping"
|
|
2278
2471
|
);
|
|
@@ -2284,64 +2477,70 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
2284
2477
|
for await (const item of source.read(inspection, signal)) {
|
|
2285
2478
|
const placementReference = item.tile.header._spatialReference || tileset.spatialReference;
|
|
2286
2479
|
if (placementReference && (placementReference.status !== "native" || placementReference.sourceCrs !== "EPSG:4978"))
|
|
2287
|
-
throw new
|
|
2480
|
+
throw new import_core20.TileConversionError(
|
|
2288
2481
|
"MESH_SOURCE_FRAME_UNSUPPORTED",
|
|
2289
2482
|
"Nested mesh placements require native ECEF coordinates"
|
|
2290
2483
|
);
|
|
2291
2484
|
if (item.tile.header.metadata || item.tile.header.implicitMetadata)
|
|
2292
|
-
throw new
|
|
2485
|
+
throw new import_core20.TileConversionError(
|
|
2293
2486
|
"MESH_SOURCE_METADATA_UNSUPPORTED",
|
|
2294
2487
|
"Tile metadata requires a separate mapping"
|
|
2295
2488
|
);
|
|
2296
2489
|
for (const content of item.contents) {
|
|
2297
2490
|
signal?.throwIfAborted();
|
|
2298
2491
|
if (content.metadata || content.group !== void 0)
|
|
2299
|
-
throw new
|
|
2492
|
+
throw new import_core20.TileConversionError(
|
|
2300
2493
|
"MESH_SOURCE_METADATA_UNSUPPORTED",
|
|
2301
2494
|
"Content metadata requires a separate mapping"
|
|
2302
2495
|
);
|
|
2303
2496
|
const payload = content.payload;
|
|
2304
2497
|
if (!payload?.gltf || !["glTF", "gltf", "b3dm"].includes(payload.type || content.type || "")) {
|
|
2305
|
-
throw new
|
|
2498
|
+
throw new import_core20.TileConversionError(
|
|
2306
2499
|
"MESH_SOURCE_CONTENT_UNSUPPORTED",
|
|
2307
2500
|
"Mesh extraction requires decoded GLB/glTF or B3DM content"
|
|
2308
2501
|
);
|
|
2309
2502
|
}
|
|
2310
|
-
const
|
|
2503
|
+
const gltfWithBuffers = payload.gltf;
|
|
2504
|
+
const gltf = gltfWithBuffers.json;
|
|
2311
2505
|
if (gltf.animations?.length || gltf.skins?.length || Object.keys(gltf.extensions || {}).some(
|
|
2312
2506
|
(name) => !["EXT_structural_metadata", "CESIUM_RTC"].includes(name)
|
|
2313
2507
|
)) {
|
|
2314
|
-
throw new
|
|
2508
|
+
throw new import_core20.TileConversionError(
|
|
2315
2509
|
"MESH_SOURCE_SCENE_UNSUPPORTED",
|
|
2316
2510
|
"Animated/skinned scenes and unmapped scene extensions are unsupported"
|
|
2317
2511
|
);
|
|
2318
2512
|
}
|
|
2319
2513
|
const axis = payload.gltfUpAxis || "Y";
|
|
2320
2514
|
if (!["X", "Y", "Z"].includes(axis))
|
|
2321
|
-
throw new
|
|
2322
|
-
const placement = new
|
|
2515
|
+
throw new import_core20.TileConversionError("MESH_SOURCE_AXIS_UNSUPPORTED", "Unknown glTF up axis");
|
|
2516
|
+
const placement = new import_core19.Matrix4(item.tile.computedTransform);
|
|
2323
2517
|
const rtc = payload.rtcCenter || gltf.extensions?.CESIUM_RTC?.center;
|
|
2324
2518
|
if (rtc) {
|
|
2325
2519
|
if (rtc.length !== 3 || rtc.some((value) => !Number.isFinite(value)))
|
|
2326
|
-
throw new
|
|
2520
|
+
throw new import_core20.TileConversionError(
|
|
2327
2521
|
"MESH_SOURCE_TRANSFORM_INVALID",
|
|
2328
2522
|
"RTC center must contain finite xyz coordinates"
|
|
2329
2523
|
);
|
|
2330
2524
|
placement.translate(rtc);
|
|
2331
2525
|
}
|
|
2332
2526
|
if (axis === "Y")
|
|
2333
|
-
placement.multiplyRight(new
|
|
2527
|
+
placement.multiplyRight(new import_core19.Matrix4([1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1]));
|
|
2334
2528
|
if (axis === "X")
|
|
2335
|
-
placement.multiplyRight(new
|
|
2336
|
-
const roots = gltf.scene?.nodes || (gltf.scenes?.length === 1 ? gltf.scenes[0].nodes : void 0);
|
|
2529
|
+
placement.multiplyRight(new import_core19.Matrix4([0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1]));
|
|
2530
|
+
const roots = (gltf.scene !== void 0 ? gltf.scenes?.[gltf.scene]?.nodes : void 0) || (gltf.scenes?.length === 1 ? gltf.scenes[0].nodes : void 0);
|
|
2337
2531
|
if (!roots?.length)
|
|
2338
|
-
throw new
|
|
2532
|
+
throw new import_core20.TileConversionError(
|
|
2339
2533
|
"MESH_SOURCE_SCENE_UNSUPPORTED",
|
|
2340
2534
|
"A default scene or exactly one scene is required"
|
|
2341
2535
|
);
|
|
2342
2536
|
let primitiveIndex = 0;
|
|
2343
|
-
|
|
2537
|
+
const primitives = traverseMeshNodes(gltfWithBuffers, roots, placement);
|
|
2538
|
+
let current = primitives.next();
|
|
2539
|
+
while (!current.done) {
|
|
2540
|
+
const selected = current.value;
|
|
2541
|
+
const next = primitives.next();
|
|
2344
2542
|
const resource = await extractPrimitive(
|
|
2543
|
+
gltfWithBuffers,
|
|
2345
2544
|
selected.primitive,
|
|
2346
2545
|
selected.transform,
|
|
2347
2546
|
`${item.tile.id}/${content.index}/${primitiveIndex++}`,
|
|
@@ -2350,16 +2549,21 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
2350
2549
|
signal
|
|
2351
2550
|
);
|
|
2352
2551
|
const features = extractMeshFeatures(
|
|
2353
|
-
|
|
2552
|
+
gltfWithBuffers,
|
|
2354
2553
|
selected.primitive,
|
|
2355
2554
|
payload,
|
|
2356
2555
|
resource.mesh,
|
|
2357
2556
|
options.features
|
|
2358
2557
|
);
|
|
2359
|
-
yield {
|
|
2558
|
+
yield {
|
|
2559
|
+
...resource,
|
|
2560
|
+
features,
|
|
2561
|
+
contentPrimitive: { index: primitiveIndex - 1, last: Boolean(next.done) }
|
|
2562
|
+
};
|
|
2563
|
+
current = next;
|
|
2360
2564
|
}
|
|
2361
2565
|
if (!primitiveIndex)
|
|
2362
|
-
throw new
|
|
2566
|
+
throw new import_core20.TileConversionError(
|
|
2363
2567
|
"MESH_SOURCE_CONTENT_EMPTY",
|
|
2364
2568
|
"Declared content contains no supported mesh primitive"
|
|
2365
2569
|
);
|
|
@@ -2368,58 +2572,66 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
2368
2572
|
}
|
|
2369
2573
|
};
|
|
2370
2574
|
}
|
|
2371
|
-
function* traverseMeshNodes(nodes, parent, ancestors = /* @__PURE__ */ new Set()) {
|
|
2372
|
-
for (const
|
|
2373
|
-
|
|
2374
|
-
|
|
2575
|
+
function* traverseMeshNodes(gltf, nodes, parent, ancestors = /* @__PURE__ */ new Set()) {
|
|
2576
|
+
for (const nodeIndex of nodes) {
|
|
2577
|
+
const node = gltf.json.nodes[nodeIndex];
|
|
2578
|
+
if (!node || ancestors.has(nodeIndex) || node.skin !== void 0 || node.weights || Object.keys(node.extensions || {}).length)
|
|
2579
|
+
throw new import_core20.TileConversionError(
|
|
2375
2580
|
"MESH_SOURCE_NODE_UNSUPPORTED",
|
|
2376
2581
|
"Cyclic, skinned, morphed, or extended nodes are unsupported"
|
|
2377
2582
|
);
|
|
2378
|
-
const transform = new
|
|
2379
|
-
node.matrix ? new
|
|
2583
|
+
const transform = new import_core19.Matrix4(parent).multiplyRight(
|
|
2584
|
+
node.matrix ? new import_core19.Matrix4(node.matrix) : new import_core19.Matrix4().translate(node.translation || [0, 0, 0]).multiplyRight(new import_core19.Matrix4().fromQuaternion(node.rotation || [0, 0, 0, 1])).scale(node.scale || [1, 1, 1])
|
|
2380
2585
|
);
|
|
2381
|
-
if (node.mesh) {
|
|
2382
|
-
|
|
2383
|
-
|
|
2586
|
+
if (node.mesh !== void 0) {
|
|
2587
|
+
const mesh = gltf.json.meshes[node.mesh];
|
|
2588
|
+
if (mesh.weights || Object.keys(mesh.extensions || {}).length)
|
|
2589
|
+
throw new import_core20.TileConversionError(
|
|
2384
2590
|
"MESH_SOURCE_NODE_UNSUPPORTED",
|
|
2385
2591
|
"Morph weights and mesh extensions are unsupported"
|
|
2386
2592
|
);
|
|
2387
|
-
for (const primitive of
|
|
2593
|
+
for (const primitive of mesh.primitives) yield { primitive, transform };
|
|
2388
2594
|
}
|
|
2389
|
-
yield* traverseMeshNodes(
|
|
2595
|
+
yield* traverseMeshNodes(
|
|
2596
|
+
gltf,
|
|
2597
|
+
node.children || [],
|
|
2598
|
+
transform,
|
|
2599
|
+
/* @__PURE__ */ new Set([...ancestors, nodeIndex])
|
|
2600
|
+
);
|
|
2390
2601
|
}
|
|
2391
2602
|
}
|
|
2392
|
-
async function extractPrimitive(primitive, transform, id, readExternalResource, contentUri, signal) {
|
|
2603
|
+
async function extractPrimitive(gltf, primitive, transform, id, readExternalResource, contentUri, signal) {
|
|
2393
2604
|
if (primitive.targets?.length || Object.keys(primitive.extensions || {}).some((name) => name !== "EXT_mesh_features"))
|
|
2394
|
-
throw new
|
|
2605
|
+
throw new import_core20.TileConversionError(
|
|
2395
2606
|
"MESH_SOURCE_PRIMITIVE_UNSUPPORTED",
|
|
2396
2607
|
"Morph targets and unmapped primitive extensions are unsupported"
|
|
2397
2608
|
);
|
|
2398
2609
|
if (Array.from(transform).some((value) => !Number.isFinite(value)) || transform[3] !== 0 || transform[7] !== 0 || transform[11] !== 0 || transform[15] !== 1 || transform.determinant() <= 0)
|
|
2399
|
-
throw new
|
|
2610
|
+
throw new import_core20.TileConversionError(
|
|
2400
2611
|
"MESH_SOURCE_TRANSFORM_INVALID",
|
|
2401
2612
|
"Mesh placement requires a finite invertible affine transform with positive determinant"
|
|
2402
2613
|
);
|
|
2403
2614
|
const attributes = {};
|
|
2404
|
-
for (const [name,
|
|
2615
|
+
for (const [name, reference] of Object.entries(primitive.attributes)) {
|
|
2616
|
+
const accessor = (0, import_gltf4.getGLTFAccessor)(gltf, reference);
|
|
2405
2617
|
if (name === "_BATCHID" || /^_FEATURE_ID_\d+$/.test(name)) continue;
|
|
2406
2618
|
if (!["POSITION", "NORMAL", "COLOR_0", "TEXCOORD_0"].includes(name))
|
|
2407
|
-
throw new
|
|
2619
|
+
throw new import_core20.TileConversionError(
|
|
2408
2620
|
"MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
|
|
2409
2621
|
`Unsupported mesh attribute ${name}`
|
|
2410
2622
|
);
|
|
2411
2623
|
attributes[name] = {
|
|
2412
|
-
value:
|
|
2413
|
-
size: accessor.
|
|
2624
|
+
value: (0, import_gltf4.getGLTFAccessorValue)(gltf, reference),
|
|
2625
|
+
size: accessor.type === "SCALAR" ? 1 : Number(accessor.type.slice(3)),
|
|
2414
2626
|
...accessor.normalized ? { normalized: true } : {}
|
|
2415
2627
|
};
|
|
2416
2628
|
}
|
|
2417
2629
|
const mesh = validateMeshGeometry(
|
|
2418
2630
|
{
|
|
2419
2631
|
topology: "triangle-list",
|
|
2420
|
-
mode: primitive.mode
|
|
2632
|
+
mode: [5, 6].includes(primitive.mode ?? 4) ? 4 : primitive.mode ?? 4,
|
|
2421
2633
|
attributes,
|
|
2422
|
-
indices: createTriangleIndices(primitive, (attributes.POSITION?.value.length ?? 0) / 3)
|
|
2634
|
+
indices: createTriangleIndices(gltf, primitive, (attributes.POSITION?.value.length ?? 0) / 3)
|
|
2423
2635
|
},
|
|
2424
2636
|
true
|
|
2425
2637
|
);
|
|
@@ -2440,7 +2652,7 @@ async function extractPrimitive(primitive, transform, id, readExternalResource,
|
|
|
2440
2652
|
mesh.attributes.POSITION = { value: positions, size: 3 };
|
|
2441
2653
|
if (mesh.attributes.NORMAL) {
|
|
2442
2654
|
const values = mesh.attributes.NORMAL.value;
|
|
2443
|
-
const normalMatrix = new
|
|
2655
|
+
const normalMatrix = new import_core19.Matrix4(transform).invert().transpose();
|
|
2444
2656
|
const normals = new Float32Array(values.length);
|
|
2445
2657
|
for (let index = 0; index < values.length; index += 3) {
|
|
2446
2658
|
const normal = normalMatrix.transformAsVector(Array.from(values.subarray(index, index + 3)));
|
|
@@ -2457,14 +2669,15 @@ async function extractPrimitive(primitive, transform, id, readExternalResource,
|
|
|
2457
2669
|
mesh,
|
|
2458
2670
|
origin: minimum.map((value, axis) => value / 2 + maximum[axis] / 2),
|
|
2459
2671
|
material: await mapMeshSourceMaterial(
|
|
2460
|
-
primitive.material,
|
|
2672
|
+
primitive.material === void 0 ? void 0 : gltf.json.materials?.[primitive.material],
|
|
2461
2673
|
readExternalResource,
|
|
2462
2674
|
contentUri,
|
|
2463
|
-
signal
|
|
2675
|
+
signal,
|
|
2676
|
+
gltf
|
|
2464
2677
|
)
|
|
2465
2678
|
};
|
|
2466
2679
|
}
|
|
2467
|
-
async function mapMeshSourceMaterial(material, readExternalResource, contentUri, signal) {
|
|
2680
|
+
async function mapMeshSourceMaterial(material, readExternalResource, contentUri, signal, gltf) {
|
|
2468
2681
|
if (!material) return void 0;
|
|
2469
2682
|
const pbr = material.pbrMetallicRoughness;
|
|
2470
2683
|
if (Object.entries(material).some(
|
|
@@ -2481,7 +2694,7 @@ async function mapMeshSourceMaterial(material, readExternalResource, contentUri,
|
|
|
2481
2694
|
) || Object.entries(pbr || {}).some(
|
|
2482
2695
|
([name, value]) => value !== void 0 && !["baseColorFactor", "baseColorTexture", "metallicFactor", "roughnessFactor"].includes(name)
|
|
2483
2696
|
) || material.emissiveFactor?.some((value) => value !== 0))
|
|
2484
|
-
throw new
|
|
2697
|
+
throw new import_core20.TileConversionError(
|
|
2485
2698
|
"MESH_SOURCE_MATERIAL_UNSUPPORTED",
|
|
2486
2699
|
"Only metallic-roughness factors and one embedded base-color image are supported"
|
|
2487
2700
|
);
|
|
@@ -2489,6 +2702,7 @@ async function mapMeshSourceMaterial(material, readExternalResource, contentUri,
|
|
|
2489
2702
|
baseColorFactor: pbr?.baseColorFactor,
|
|
2490
2703
|
..."baseColorTexture" in (pbr || {}) ? {
|
|
2491
2704
|
baseColorTexture: await resolveMeshSourceTexture(
|
|
2705
|
+
gltf,
|
|
2492
2706
|
pbr.baseColorTexture,
|
|
2493
2707
|
readExternalResource,
|
|
2494
2708
|
contentUri,
|
|
@@ -2502,17 +2716,18 @@ async function mapMeshSourceMaterial(material, readExternalResource, contentUri,
|
|
|
2502
2716
|
doubleSided: material.doubleSided
|
|
2503
2717
|
};
|
|
2504
2718
|
}
|
|
2505
|
-
function createTriangleIndices(primitive, vertexCount) {
|
|
2506
|
-
const
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2719
|
+
function createTriangleIndices(gltf, primitive, vertexCount) {
|
|
2720
|
+
const accessor = primitive.indices === void 0 ? void 0 : (0, import_gltf4.getGLTFAccessor)(gltf, primitive.indices);
|
|
2721
|
+
const attribute = accessor ? {
|
|
2722
|
+
value: (0, import_gltf4.getGLTFAccessorValue)(gltf, accessor),
|
|
2723
|
+
size: 1,
|
|
2724
|
+
normalized: accessor.normalized
|
|
2510
2725
|
} : void 0;
|
|
2511
|
-
if (primitive.mode !== 5) return attribute;
|
|
2726
|
+
if (primitive.mode !== 5 && primitive.mode !== 6) return attribute;
|
|
2512
2727
|
const sourceIndices = validateMeshIndices(attribute, vertexCount, 5);
|
|
2513
2728
|
const indexCount = sourceIndices?.length ?? vertexCount;
|
|
2514
2729
|
if (!Number.isSafeInteger(indexCount) || indexCount < 3) {
|
|
2515
|
-
throw new
|
|
2730
|
+
throw new import_core20.TileConversionError(
|
|
2516
2731
|
"MESH_TRIANGLE_COUNT_INVALID",
|
|
2517
2732
|
"Triangle strips require at least three vertices"
|
|
2518
2733
|
);
|
|
@@ -2520,9 +2735,10 @@ function createTriangleIndices(primitive, vertexCount) {
|
|
|
2520
2735
|
const IndexArray = vertexCount < 65536 ? Uint16Array : Uint32Array;
|
|
2521
2736
|
const indices = new IndexArray((indexCount - 2) * 3);
|
|
2522
2737
|
for (let triangle = 0; triangle < indexCount - 2; triangle++) {
|
|
2523
|
-
const second = triangle + 1 + triangle % 2;
|
|
2524
|
-
const third = triangle + 2 - triangle % 2;
|
|
2525
|
-
|
|
2738
|
+
const second = primitive.mode === 6 ? triangle + 1 : triangle + 1 + triangle % 2;
|
|
2739
|
+
const third = primitive.mode === 6 ? triangle + 2 : triangle + 2 - triangle % 2;
|
|
2740
|
+
const first = primitive.mode === 6 ? 0 : triangle;
|
|
2741
|
+
indices[triangle * 3] = sourceIndices?.[first] ?? first;
|
|
2526
2742
|
indices[triangle * 3 + 1] = sourceIndices?.[second] ?? second;
|
|
2527
2743
|
indices[triangle * 3 + 2] = sourceIndices?.[third] ?? third;
|
|
2528
2744
|
}
|
|
@@ -2530,14 +2746,20 @@ function createTriangleIndices(primitive, vertexCount) {
|
|
|
2530
2746
|
}
|
|
2531
2747
|
|
|
2532
2748
|
// src/v5/i3s-mesh-conversion.ts
|
|
2533
|
-
var import_i3s = require("@loaders.gl/i3s");
|
|
2534
|
-
var import_core21 = require("@loaders.gl/tile-converter/v5/core");
|
|
2749
|
+
var import_i3s = require("@loaders.gl/tiles-3d/i3s");
|
|
2535
2750
|
var import_core22 = require("@loaders.gl/tile-converter/v5/core");
|
|
2751
|
+
var import_core23 = require("@loaders.gl/tile-converter/v5/core");
|
|
2536
2752
|
function createI3SMeshConversionCodec(options) {
|
|
2753
|
+
if (options.integratedMeshPolicy !== void 0 && options.integratedMeshPolicy !== "as-3d-object") {
|
|
2754
|
+
throw new import_core22.TileConversionError(
|
|
2755
|
+
"I3S_LAYER_TYPE_POLICY_INVALID",
|
|
2756
|
+
"Unknown IntegratedMesh conversion policy"
|
|
2757
|
+
);
|
|
2758
|
+
}
|
|
2537
2759
|
const reference = options.spatialContext.spatialReference;
|
|
2538
2760
|
const height = reference.targetHeightReference === "native" ? reference.heightReference : reference.targetHeightReference;
|
|
2539
2761
|
if (!["native", "transformed"].includes(reference.status) || (reference.targetCrs || reference.sourceCrs) !== "EPSG:4978" || height !== "ellipsoidal" || reference.status === "native" && (!["xyz", "unknown"].includes(reference.axisOrder) || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter")))
|
|
2540
|
-
throw new
|
|
2762
|
+
throw new import_core22.TileConversionError(
|
|
2541
2763
|
"I3S_MESH_FRAME_UNSUPPORTED",
|
|
2542
2764
|
"I3S mesh authoring requires resolved ECEF coordinates and ellipsoidal heights"
|
|
2543
2765
|
);
|
|
@@ -2546,6 +2768,12 @@ function createI3SMeshConversionCodec(options) {
|
|
|
2546
2768
|
/** Places a source mesh in ECEF and authors one layer without modifying source arrays. */
|
|
2547
2769
|
async *convert(resource, _inspection, signal) {
|
|
2548
2770
|
signal?.throwIfAborted();
|
|
2771
|
+
if (resource.sourceLayerType === "IntegratedMesh" && options.integratedMeshPolicy !== "as-3d-object") {
|
|
2772
|
+
throw new import_core22.TileConversionError(
|
|
2773
|
+
"I3S_LAYER_TYPE_CHANGE_REQUIRED",
|
|
2774
|
+
"IntegratedMesh to 3DObject conversion requires integratedMeshPolicy=as-3d-object"
|
|
2775
|
+
);
|
|
2776
|
+
}
|
|
2549
2777
|
const mesh = validateMeshGeometry(resource.mesh, true);
|
|
2550
2778
|
const positions = mesh.attributes.POSITION.value;
|
|
2551
2779
|
const context = options.spatialContext;
|
|
@@ -2562,6 +2790,7 @@ function createI3SMeshConversionCodec(options) {
|
|
|
2562
2790
|
if (mesh.attributes.NORMAL) mesh.attributes.NORMAL = { value: transformed.normals, size: 3 };
|
|
2563
2791
|
let layer;
|
|
2564
2792
|
try {
|
|
2793
|
+
await (0, import_i3s.preloadI3SMeshCompression)();
|
|
2565
2794
|
const writerOptions = {
|
|
2566
2795
|
...options,
|
|
2567
2796
|
material: resource.material,
|
|
@@ -2570,18 +2799,26 @@ function createI3SMeshConversionCodec(options) {
|
|
|
2570
2799
|
};
|
|
2571
2800
|
layer = options.draco === false ? (0, import_i3s.encodeI3SMeshLayer)(mesh, writerOptions) : await (0, import_i3s.encodeI3SMeshLayerWithDraco)(mesh, writerOptions, options.dracoLibraryOptions);
|
|
2572
2801
|
} catch (error) {
|
|
2573
|
-
throw new
|
|
2802
|
+
throw new import_core22.TileConversionError(
|
|
2574
2803
|
"I3S_MESH_PROFILE_UNSUPPORTED",
|
|
2575
2804
|
error instanceof Error ? error.message : String(error)
|
|
2576
2805
|
);
|
|
2577
2806
|
}
|
|
2578
2807
|
signal?.throwIfAborted();
|
|
2579
2808
|
objectIdOffset += resource.features?.batches.reduce((total, batch) => total + batch.data.numRows, 0) ?? 1;
|
|
2580
|
-
yield { id: resource.id, ...layer };
|
|
2809
|
+
yield { id: resource.id, ...layer, sourceLayerType: resource.sourceLayerType };
|
|
2581
2810
|
},
|
|
2582
2811
|
/** Reports measured precision and explicitly authorized integer representation changes. */
|
|
2583
2812
|
async validateOutput(resource) {
|
|
2584
2813
|
return [
|
|
2814
|
+
...resource.sourceLayerType === "IntegratedMesh" ? [
|
|
2815
|
+
{
|
|
2816
|
+
code: "I3S_LAYER_TYPE_CHANGED",
|
|
2817
|
+
message: "Explicitly authored IntegratedMesh geometry as I3S 1.7 3DObject; integrated surface layer semantics and source LOD are not preserved",
|
|
2818
|
+
severity: "warning",
|
|
2819
|
+
resourceId: resource.id
|
|
2820
|
+
}
|
|
2821
|
+
] : [],
|
|
2585
2822
|
...resource.maximumPositionError > 0 ? [
|
|
2586
2823
|
{
|
|
2587
2824
|
code: "I3S_MESH_POSITION_ROUNDING",
|
|
@@ -2601,7 +2838,7 @@ function createI3SMeshConversionCodec(options) {
|
|
|
2601
2838
|
};
|
|
2602
2839
|
}
|
|
2603
2840
|
function createSingleMeshI3SSink(options) {
|
|
2604
|
-
const memory = (0,
|
|
2841
|
+
const memory = (0, import_core23.createBoundedMemoryTileConversionSink)(options);
|
|
2605
2842
|
let state = "empty";
|
|
2606
2843
|
let completed = false;
|
|
2607
2844
|
return {
|
|
@@ -2611,7 +2848,7 @@ function createSingleMeshI3SSink(options) {
|
|
|
2611
2848
|
async write(resource, signal) {
|
|
2612
2849
|
signal?.throwIfAborted();
|
|
2613
2850
|
if (state !== "empty")
|
|
2614
|
-
throw new
|
|
2851
|
+
throw new import_core22.TileConversionError(
|
|
2615
2852
|
"SINGLE_I3S_MESH_UNAVAILABLE",
|
|
2616
2853
|
"The initial I3S sink accepts exactly one mesh layer"
|
|
2617
2854
|
);
|
|
@@ -2630,7 +2867,7 @@ function createSingleMeshI3SSink(options) {
|
|
|
2630
2867
|
/** Commits a complete single-layer result. */
|
|
2631
2868
|
async finalize(report) {
|
|
2632
2869
|
if (state !== "written")
|
|
2633
|
-
throw new
|
|
2870
|
+
throw new import_core22.TileConversionError(
|
|
2634
2871
|
"SINGLE_I3S_MESH_INCOMPLETE",
|
|
2635
2872
|
"Finalization requires exactly one successfully written layer"
|
|
2636
2873
|
);
|
|
@@ -2650,16 +2887,16 @@ function createSingleMeshI3SSink(options) {
|
|
|
2650
2887
|
// src/v5/i3s-mesh-sink.ts
|
|
2651
2888
|
var import_geospatial = require("@math.gl/geospatial");
|
|
2652
2889
|
var import_compression = require("@loaders.gl/compression");
|
|
2653
|
-
var
|
|
2890
|
+
var import_core24 = require("@loaders.gl/tile-converter/v5/core");
|
|
2654
2891
|
function createI3SMeshSink(options) {
|
|
2655
2892
|
const { maxMeshes, maxResourceBytes } = options;
|
|
2656
2893
|
if (!Number.isInteger(maxMeshes) || maxMeshes < 1 || maxMeshes > 64 || !Number.isSafeInteger(maxResourceBytes) || maxResourceBytes < 1)
|
|
2657
|
-
throw new
|
|
2894
|
+
throw new import_core24.TileConversionError(
|
|
2658
2895
|
"I3S_COLLECTION_OPTIONS_INVALID",
|
|
2659
2896
|
"I3S collections require 1\u201364 meshes and a positive resource byte limit"
|
|
2660
2897
|
);
|
|
2661
|
-
const memory = (0,
|
|
2662
|
-
const compressor = new import_compression.GZipCompressor({ useNative: false
|
|
2898
|
+
const memory = (0, import_core24.createBoundedMemoryTileConversionSink)(options);
|
|
2899
|
+
const compressor = new import_compression.GZipCompressor({ useNative: false });
|
|
2663
2900
|
const decompressor = new import_compression.GZipDecompressor({ useNative: false });
|
|
2664
2901
|
const leaves = [];
|
|
2665
2902
|
const identifiers = /* @__PURE__ */ new Set();
|
|
@@ -2671,7 +2908,7 @@ function createI3SMeshSink(options) {
|
|
|
2671
2908
|
async function readBytes(bytes) {
|
|
2672
2909
|
const output = await decompressor.decompress(bytes);
|
|
2673
2910
|
if (output.byteLength > maxResourceBytes)
|
|
2674
|
-
throw new
|
|
2911
|
+
throw new import_core24.TileConversionError(
|
|
2675
2912
|
"I3S_COLLECTION_RESOURCE_TOO_LARGE",
|
|
2676
2913
|
"I3S metadata exceeds maxResourceBytes"
|
|
2677
2914
|
);
|
|
@@ -2683,7 +2920,7 @@ function createI3SMeshSink(options) {
|
|
|
2683
2920
|
async function writeJson(resourceId, value) {
|
|
2684
2921
|
const bytes = new TextEncoder().encode(JSON.stringify(value));
|
|
2685
2922
|
if (bytes.byteLength > maxResourceBytes)
|
|
2686
|
-
throw new
|
|
2923
|
+
throw new import_core24.TileConversionError(
|
|
2687
2924
|
"I3S_COLLECTION_RESOURCE_TOO_LARGE",
|
|
2688
2925
|
"I3S index exceeds maxResourceBytes"
|
|
2689
2926
|
);
|
|
@@ -2700,7 +2937,7 @@ function createI3SMeshSink(options) {
|
|
|
2700
2937
|
async write(resource, signal) {
|
|
2701
2938
|
signal?.throwIfAborted();
|
|
2702
2939
|
if (state !== "open" || identifiers.has(resource.id) || leaves.length >= maxMeshes)
|
|
2703
|
-
throw new
|
|
2940
|
+
throw new import_core24.TileConversionError(
|
|
2704
2941
|
"I3S_COLLECTION_UNAVAILABLE",
|
|
2705
2942
|
"I3S collection is busy, closed, full, or repeats a placement"
|
|
2706
2943
|
);
|
|
@@ -2713,7 +2950,7 @@ function createI3SMeshSink(options) {
|
|
|
2713
2950
|
authored.store.defaultGeometrySchema
|
|
2714
2951
|
]);
|
|
2715
2952
|
if (schemaKey !== void 0 && key !== schemaKey)
|
|
2716
|
-
throw new
|
|
2953
|
+
throw new import_core24.TileConversionError(
|
|
2717
2954
|
"I3S_COLLECTION_SCHEMA_MISMATCH",
|
|
2718
2955
|
"Mesh nodes must share feature fields and the legacy geometry layout"
|
|
2719
2956
|
);
|
|
@@ -2725,7 +2962,7 @@ function createI3SMeshSink(options) {
|
|
|
2725
2962
|
if (objectIds.byteLength !== 4 + count * 4 || !count || objectIdRanges.some(([start, end]) => first < end && first + count > start) || Array.from({ length: count }, (_, index2) => objectIds.getUint32(4 + index2 * 4, true)).some(
|
|
2726
2963
|
(value, index2) => value !== first + index2
|
|
2727
2964
|
))
|
|
2728
|
-
throw new
|
|
2965
|
+
throw new import_core24.TileConversionError(
|
|
2729
2966
|
"I3S_COLLECTION_IDS_INVALID",
|
|
2730
2967
|
"Generated object IDs must be contiguous and disjoint across nodes"
|
|
2731
2968
|
);
|
|
@@ -2736,7 +2973,7 @@ function createI3SMeshSink(options) {
|
|
|
2736
2973
|
"nodes/1/3dNodeIndexDocument.json.gz"
|
|
2737
2974
|
);
|
|
2738
2975
|
if (node.obb.quaternion.join(",") !== "0,0,0,1" || [...node.obb.center, ...node.obb.halfSize].some((value) => !Number.isFinite(value)) || node.obb.halfSize.some((value) => value < 0))
|
|
2739
|
-
throw new
|
|
2976
|
+
throw new import_core24.TileConversionError(
|
|
2740
2977
|
"I3S_COLLECTION_BOUNDS_INVALID",
|
|
2741
2978
|
"Expected finite axis-aligned bounds from the I3S mesh writer"
|
|
2742
2979
|
);
|
|
@@ -2787,7 +3024,7 @@ function createI3SMeshSink(options) {
|
|
|
2787
3024
|
/** Rebuilds both indices and layer bounds only after every leaf has completed. */
|
|
2788
3025
|
async finalize(report) {
|
|
2789
3026
|
if (state !== "open" || !layer || !leaves.length)
|
|
2790
|
-
throw new
|
|
3027
|
+
throw new import_core24.TileConversionError(
|
|
2791
3028
|
"I3S_COLLECTION_INCOMPLETE",
|
|
2792
3029
|
"Finalization requires successfully written mesh nodes"
|
|
2793
3030
|
);
|
|
@@ -2812,7 +3049,7 @@ function createI3SMeshSink(options) {
|
|
|
2812
3049
|
quaternion: [0, 0, 0, 1]
|
|
2813
3050
|
};
|
|
2814
3051
|
if ([...obb.center, ...obb.halfSize].some((value) => !Number.isFinite(value)))
|
|
2815
|
-
throw new
|
|
3052
|
+
throw new import_core24.TileConversionError(
|
|
2816
3053
|
"I3S_COLLECTION_BOUNDS_INVALID",
|
|
2817
3054
|
"Collection bounds cannot be represented"
|
|
2818
3055
|
);
|
|
@@ -2872,7 +3109,7 @@ function createI3SMeshSink(options) {
|
|
|
2872
3109
|
}
|
|
2873
3110
|
|
|
2874
3111
|
// src/v5/conversion-api.ts
|
|
2875
|
-
var
|
|
3112
|
+
var TileConversionError18 = class extends Error {
|
|
2876
3113
|
/** Stable machine-readable failure code. */
|
|
2877
3114
|
code;
|
|
2878
3115
|
/** Diagnostics associated with this failure. */
|
|
@@ -2890,7 +3127,7 @@ var TileConversionError17 = class extends Error {
|
|
|
2890
3127
|
function createTileConversionResourceFetcher(options) {
|
|
2891
3128
|
let consumed = options.initialInputBytes ?? 0;
|
|
2892
3129
|
if (!Number.isSafeInteger(options.maxInputBytes) || options.maxInputBytes < 1 || !Number.isSafeInteger(consumed) || consumed < 0 || consumed > options.maxInputBytes)
|
|
2893
|
-
throw new
|
|
3130
|
+
throw new TileConversionError18(
|
|
2894
3131
|
"INVALID_INPUT_MEMORY_LIMIT",
|
|
2895
3132
|
"Resource fetch requires a positive aggregate byte budget and valid initial bytes"
|
|
2896
3133
|
);
|
|
@@ -2899,7 +3136,7 @@ function createTileConversionResourceFetcher(options) {
|
|
|
2899
3136
|
options.signal?.throwIfAborted();
|
|
2900
3137
|
const url = new URL(input instanceof Request ? input.url : String(input));
|
|
2901
3138
|
if (!["http:", "https:", "blob:", "data:"].includes(url.protocol))
|
|
2902
|
-
throw new
|
|
3139
|
+
throw new TileConversionError18(
|
|
2903
3140
|
"EXTERNAL_RESOURCE_UNSUPPORTED",
|
|
2904
3141
|
"Conversion transport requires HTTP(S), Blob or embedded data resources"
|
|
2905
3142
|
);
|
|
@@ -2908,14 +3145,14 @@ function createTileConversionResourceFetcher(options) {
|
|
|
2908
3145
|
const response = await fetcher(input, { ...requestOptions, signal });
|
|
2909
3146
|
if (!response.ok) {
|
|
2910
3147
|
await response.body?.cancel();
|
|
2911
|
-
throw new
|
|
3148
|
+
throw new TileConversionError18(
|
|
2912
3149
|
"BROWSER_SOURCE_FETCH_FAILED",
|
|
2913
3150
|
`Could not fetch conversion resource (${response.status})`
|
|
2914
3151
|
);
|
|
2915
3152
|
}
|
|
2916
3153
|
const reader = response.body?.getReader();
|
|
2917
3154
|
if (!reader)
|
|
2918
|
-
throw new
|
|
3155
|
+
throw new TileConversionError18(
|
|
2919
3156
|
"BROWSER_SOURCE_STREAM_UNAVAILABLE",
|
|
2920
3157
|
"Conversion resources require readable response streams"
|
|
2921
3158
|
);
|
|
@@ -2933,7 +3170,7 @@ function createTileConversionResourceFetcher(options) {
|
|
|
2933
3170
|
signal?.throwIfAborted();
|
|
2934
3171
|
if (done) break;
|
|
2935
3172
|
if (value.byteLength > options.maxInputBytes - consumed)
|
|
2936
|
-
throw new
|
|
3173
|
+
throw new TileConversionError18(
|
|
2937
3174
|
"INPUT_RESOURCE_TOO_LARGE",
|
|
2938
3175
|
"Selected contents and external dependencies exceed the aggregate input byte budget"
|
|
2939
3176
|
);
|
|
@@ -2954,25 +3191,27 @@ function createTileConversionResourceFetcher(options) {
|
|
|
2954
3191
|
bytes.set(part, offset);
|
|
2955
3192
|
offset += part.byteLength;
|
|
2956
3193
|
}
|
|
2957
|
-
|
|
3194
|
+
const boundedResponse = new Response(bytes, {
|
|
2958
3195
|
status: response.status,
|
|
2959
3196
|
statusText: response.statusText,
|
|
2960
3197
|
headers: response.headers
|
|
2961
3198
|
});
|
|
3199
|
+
Object.defineProperty(boundedResponse, "url", { value: response.url });
|
|
3200
|
+
return boundedResponse;
|
|
2962
3201
|
};
|
|
2963
3202
|
}
|
|
2964
3203
|
|
|
2965
3204
|
// src/v5/point-cloud-tileset-sink.ts
|
|
2966
|
-
var
|
|
3205
|
+
var import_core25 = require("@loaders.gl/tile-converter/v5/core");
|
|
2967
3206
|
function createPointCloudTilesetSink(options) {
|
|
2968
3207
|
const { maxTiles, geometricError } = options;
|
|
2969
3208
|
if (!Number.isSafeInteger(maxTiles) || maxTiles < 1 || !Number.isFinite(geometricError) || geometricError < 0) {
|
|
2970
|
-
throw new
|
|
3209
|
+
throw new import_core25.TileConversionError(
|
|
2971
3210
|
"POINT_CLOUD_TILESET_OPTIONS_INVALID",
|
|
2972
3211
|
"Positive maxTiles and finite nonnegative geometricError are required"
|
|
2973
3212
|
);
|
|
2974
3213
|
}
|
|
2975
|
-
const memory = (0,
|
|
3214
|
+
const memory = (0, import_core25.createBoundedMemoryTileConversionSink)(options);
|
|
2976
3215
|
const children = [];
|
|
2977
3216
|
const identifiers = /* @__PURE__ */ new Set();
|
|
2978
3217
|
const minimum = [Infinity, Infinity, Infinity];
|
|
@@ -2986,12 +3225,12 @@ function createPointCloudTilesetSink(options) {
|
|
|
2986
3225
|
async write(resource, signal) {
|
|
2987
3226
|
signal?.throwIfAborted();
|
|
2988
3227
|
if (state !== "open")
|
|
2989
|
-
throw new
|
|
3228
|
+
throw new import_core25.TileConversionError(
|
|
2990
3229
|
"POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
|
|
2991
3230
|
"Point sink is busy or closed"
|
|
2992
3231
|
);
|
|
2993
3232
|
if (!resource.id || identifiers.has(resource.id) || children.length >= maxTiles) {
|
|
2994
|
-
throw new
|
|
3233
|
+
throw new import_core25.TileConversionError(
|
|
2995
3234
|
"POINT_CLOUD_TILESET_PLACEMENT_INVALID",
|
|
2996
3235
|
"Placements require unique nonempty identifiers within maxTiles"
|
|
2997
3236
|
);
|
|
@@ -3004,7 +3243,7 @@ function createPointCloudTilesetSink(options) {
|
|
|
3004
3243
|
signal
|
|
3005
3244
|
);
|
|
3006
3245
|
if (state !== "writing")
|
|
3007
|
-
throw new
|
|
3246
|
+
throw new import_core25.TileConversionError(
|
|
3008
3247
|
"POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
|
|
3009
3248
|
"Point sink was aborted during writing"
|
|
3010
3249
|
);
|
|
@@ -3023,7 +3262,7 @@ function createPointCloudTilesetSink(options) {
|
|
|
3023
3262
|
/** Serializes the enclosing root after all placements have completed. */
|
|
3024
3263
|
async finalize(report) {
|
|
3025
3264
|
if (state !== "open" || !children.length)
|
|
3026
|
-
throw new
|
|
3265
|
+
throw new import_core25.TileConversionError(
|
|
3027
3266
|
"POINT_CLOUD_TILESET_SINK_INCOMPLETE",
|
|
3028
3267
|
"Finalization requires successfully written point tiles"
|
|
3029
3268
|
);
|
|
@@ -3033,7 +3272,7 @@ function createPointCloudTilesetSink(options) {
|
|
|
3033
3272
|
Math.hypot(...minimum.map((value, axis) => maximum[axis] - value))
|
|
3034
3273
|
);
|
|
3035
3274
|
if (!Number.isFinite(rootError))
|
|
3036
|
-
throw new
|
|
3275
|
+
throw new import_core25.TileConversionError(
|
|
3037
3276
|
"POINT_CLOUD_TILESET_BOUNDS_INVALID",
|
|
3038
3277
|
"Enclosing bounds must have a finite diagonal"
|
|
3039
3278
|
);
|
|
@@ -3053,13 +3292,13 @@ function createPointCloudTilesetSink(options) {
|
|
|
3053
3292
|
contentType: "application/json"
|
|
3054
3293
|
});
|
|
3055
3294
|
if (state !== "writing")
|
|
3056
|
-
throw new
|
|
3295
|
+
throw new import_core25.TileConversionError(
|
|
3057
3296
|
"POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
|
|
3058
3297
|
"Point sink was aborted during finalization"
|
|
3059
3298
|
);
|
|
3060
3299
|
await memory.finalize(report);
|
|
3061
3300
|
if (state !== "writing")
|
|
3062
|
-
throw new
|
|
3301
|
+
throw new import_core25.TileConversionError(
|
|
3063
3302
|
"POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
|
|
3064
3303
|
"Point sink was aborted during finalization"
|
|
3065
3304
|
);
|
|
@@ -3080,13 +3319,13 @@ function getPointTileBounds(resource, geometricError) {
|
|
|
3080
3319
|
const reference = resource.spatialReference;
|
|
3081
3320
|
const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
3082
3321
|
if (!reference || !["native", "transformed"].includes(reference.status) || (reference.targetCrs || reference.sourceCrs) !== "EPSG:4978" || (reference.targetHeightReference === "native" ? reference.heightReference : reference.targetHeightReference) !== "ellipsoidal" || resource.coordinateSystem !== "cartesian" || resource.cartographicOrigin.length !== 3 || resource.cartographicOrigin.some((value) => value !== 0) || resource.modelMatrix && (resource.modelMatrix.length !== 16 || resource.modelMatrix.some((value, index) => value !== identity[index])) || reference.status === "native" && (reference.coordinateFrame !== "geocentric" || reference.axisOrder !== "xyz" || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter"))) {
|
|
3083
|
-
throw new
|
|
3322
|
+
throw new import_core25.TileConversionError(
|
|
3084
3323
|
"POINT_CLOUD_TILESET_FRAME_UNSUPPORTED",
|
|
3085
3324
|
"Point tiles require declared EPSG:4978 xyz meters, ellipsoidal heights and no additional source placement"
|
|
3086
3325
|
);
|
|
3087
3326
|
}
|
|
3088
3327
|
if (!Number.isFinite(resource.maximumPositionError) || resource.maximumPositionError < 0 || resource.maximumPositionError > geometricError) {
|
|
3089
|
-
throw new
|
|
3328
|
+
throw new import_core25.TileConversionError(
|
|
3090
3329
|
"POINT_CLOUD_GEOMETRIC_ERROR_INVALID",
|
|
3091
3330
|
"geometricError must include measured position reconstruction error"
|
|
3092
3331
|
);
|
|
@@ -3094,7 +3333,7 @@ function getPointTileBounds(resource, geometricError) {
|
|
|
3094
3333
|
const [minimum, maximum] = resource.localBoundingBox;
|
|
3095
3334
|
const center = resource.rtcCenter;
|
|
3096
3335
|
if (center.length !== 3 || minimum.length !== 3 || maximum.length !== 3 || [...center, ...minimum, ...maximum].some((value) => !Number.isFinite(value)) || minimum.some((value, axis) => value > maximum[axis])) {
|
|
3097
|
-
throw new
|
|
3336
|
+
throw new import_core25.TileConversionError(
|
|
3098
3337
|
"POINT_CLOUD_TILESET_BOUNDS_INVALID",
|
|
3099
3338
|
"RTC center and encoded xyz bounds must be finite and ordered"
|
|
3100
3339
|
);
|
|
@@ -3102,7 +3341,7 @@ function getPointTileBounds(resource, geometricError) {
|
|
|
3102
3341
|
const lower = minimum.map((value, axis) => value + center[axis]);
|
|
3103
3342
|
const upper = maximum.map((value, axis) => value + center[axis]);
|
|
3104
3343
|
if ([...lower, ...upper].some((value) => !Number.isFinite(value)))
|
|
3105
|
-
throw new
|
|
3344
|
+
throw new import_core25.TileConversionError(
|
|
3106
3345
|
"POINT_CLOUD_TILESET_BOUNDS_INVALID",
|
|
3107
3346
|
"Reconstructed ECEF bounds must be finite"
|
|
3108
3347
|
);
|
|
@@ -3115,12 +3354,13 @@ function createPointBoundsBox(minimum, maximum) {
|
|
|
3115
3354
|
}
|
|
3116
3355
|
|
|
3117
3356
|
// src/v5/i3s-mesh-source.ts
|
|
3118
|
-
var
|
|
3357
|
+
var import_core28 = require("@loaders.gl/tile-converter/v5/core");
|
|
3119
3358
|
|
|
3120
3359
|
// src/v5/i3s-mesh-source-material.ts
|
|
3121
3360
|
var import_images3 = require("@loaders.gl/images");
|
|
3122
|
-
var
|
|
3361
|
+
var import_core26 = require("@loaders.gl/tile-converter/v5/core");
|
|
3123
3362
|
async function mapI3SMeshSourceMaterial(content, header, readExternalResource, contentUri, signal) {
|
|
3363
|
+
const gltf = { json: { asset: { version: "2.0" } }, buffers: [] };
|
|
3124
3364
|
const material = Object.fromEntries(
|
|
3125
3365
|
Object.entries(content.material || {}).filter(([, value]) => value !== void 0)
|
|
3126
3366
|
);
|
|
@@ -3129,7 +3369,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3129
3369
|
) : {};
|
|
3130
3370
|
material.pbrMetallicRoughness = metallicRoughness;
|
|
3131
3371
|
if (material.cullFace !== void 0 && material.cullFace !== "back") {
|
|
3132
|
-
throw new
|
|
3372
|
+
throw new import_core26.TileConversionError(
|
|
3133
3373
|
"I3S_MESH_SOURCE_MATERIAL_UNSUPPORTED",
|
|
3134
3374
|
"Only back-face culling is supported"
|
|
3135
3375
|
);
|
|
@@ -3148,7 +3388,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3148
3388
|
}
|
|
3149
3389
|
const encodedTextures = content.textures || {};
|
|
3150
3390
|
if (!textureInfo && (resources.length || content.texture || Object.keys(encodedTextures).length)) {
|
|
3151
|
-
throw new
|
|
3391
|
+
throw new import_core26.TileConversionError(
|
|
3152
3392
|
"I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
3153
3393
|
"Texture resources require a base-color mapping"
|
|
3154
3394
|
);
|
|
@@ -3161,7 +3401,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3161
3401
|
) || ["wrapS", "wrapT"].some(
|
|
3162
3402
|
(name) => textureInfo[name] !== void 0 && (typeof textureInfo[name] !== "string" || !Object.hasOwn(wrappingModes, textureInfo[name]))
|
|
3163
3403
|
) || resources.length > 1 || resources.some((resource) => resource.textureSetDefinitionId !== definitionId) || Object.keys(encodedTextures).some((key) => key !== String(definitionId))) {
|
|
3164
|
-
throw new
|
|
3404
|
+
throw new import_core26.TileConversionError(
|
|
3165
3405
|
"I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
3166
3406
|
"Only one base-color texture set on TEXCOORD_0 with qualified wrapping is supported"
|
|
3167
3407
|
);
|
|
@@ -3169,7 +3409,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3169
3409
|
let encoded = encodedTextures[String(definitionId)] || content.texture || textureInfo.texture?.source?.image;
|
|
3170
3410
|
if (!encoded) {
|
|
3171
3411
|
if (!resources.length || !readExternalResource) {
|
|
3172
|
-
throw new
|
|
3412
|
+
throw new import_core26.TileConversionError(
|
|
3173
3413
|
"I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
3174
3414
|
"Encoded texture bytes or an explicit bounded resource reader are required"
|
|
3175
3415
|
);
|
|
@@ -3187,8 +3427,8 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3187
3427
|
}
|
|
3188
3428
|
}
|
|
3189
3429
|
const declaredFormat = resources[0]?.textureFormat;
|
|
3190
|
-
if (!image || !["image/png", "image/jpeg"].includes(image.mimeType) || declaredFormat && (declaredFormat === "png" ? image.mimeType !== "image/png" : declaredFormat === "jpg" ? image.mimeType !== "image/jpeg" : true)) {
|
|
3191
|
-
throw new
|
|
3430
|
+
if (!(data instanceof Uint8Array) || !image || !["image/png", "image/jpeg"].includes(image.mimeType) || declaredFormat && (declaredFormat === "png" ? image.mimeType !== "image/png" : declaredFormat === "jpg" ? image.mimeType !== "image/jpeg" : true)) {
|
|
3431
|
+
throw new import_core26.TileConversionError(
|
|
3192
3432
|
"I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
3193
3433
|
"I3S base-color bytes must have matching PNG/JPEG headers"
|
|
3194
3434
|
);
|
|
@@ -3196,22 +3436,27 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3196
3436
|
const sampler = Object.fromEntries(
|
|
3197
3437
|
["wrapS", "wrapT"].filter((name) => textureInfo[name] !== void 0).map((name) => [name, wrappingModes[textureInfo[name]]])
|
|
3198
3438
|
);
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
};
|
|
3439
|
+
gltf.buffers.push({
|
|
3440
|
+
arrayBuffer: data.buffer,
|
|
3441
|
+
byteOffset: data.byteOffset,
|
|
3442
|
+
byteLength: data.byteLength
|
|
3443
|
+
});
|
|
3444
|
+
gltf.json.buffers = [{ byteLength: data.byteLength }];
|
|
3445
|
+
gltf.json.bufferViews = [{ buffer: 0, byteLength: data.byteLength }];
|
|
3446
|
+
gltf.json.images = [{ mimeType: image.mimeType, bufferView: 0 }];
|
|
3447
|
+
gltf.json.samplers = [sampler];
|
|
3448
|
+
gltf.json.textures = [{ source: 0, sampler: 0 }];
|
|
3449
|
+
metallicRoughness.baseColorTexture = { texCoord: 0, index: 0 };
|
|
3206
3450
|
}
|
|
3207
3451
|
const mapped = await mapMeshSourceMaterial(
|
|
3208
3452
|
material,
|
|
3209
3453
|
void 0,
|
|
3210
3454
|
contentUri,
|
|
3211
|
-
signal
|
|
3455
|
+
signal,
|
|
3456
|
+
gltf
|
|
3212
3457
|
);
|
|
3213
3458
|
if (mapped?.baseColorTexture && !content.sourceAttributes?.uv0) {
|
|
3214
|
-
throw new
|
|
3459
|
+
throw new import_core26.TileConversionError(
|
|
3215
3460
|
"MESH_TEXCOORD_REQUIRED",
|
|
3216
3461
|
"Base-color textures require TEXCOORD_0"
|
|
3217
3462
|
);
|
|
@@ -3220,8 +3465,8 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3220
3465
|
}
|
|
3221
3466
|
|
|
3222
3467
|
// src/v5/i3s-mesh-source-features.ts
|
|
3223
|
-
var import_i3s2 = require("@loaders.gl/i3s");
|
|
3224
|
-
var
|
|
3468
|
+
var import_i3s2 = require("@loaders.gl/tiles-3d/i3s");
|
|
3469
|
+
var import_core27 = require("@loaders.gl/tile-converter/v5/core");
|
|
3225
3470
|
async function extractI3SMeshFeatures(content, item, metadata, options, readExternalResource, contentUri, signal) {
|
|
3226
3471
|
const maximumAttributeBytes = options.maxAttributeBytes;
|
|
3227
3472
|
const storage = metadata.tileset.attributeStorageInfo;
|
|
@@ -3229,7 +3474,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3229
3474
|
if (!options.metadataClass || !options.objectIdProperty || options.rawMetadataField || !Number.isSafeInteger(maximumAttributeBytes) || maximumAttributeBytes <= 0 || !storage?.length || attributeUrls?.length !== storage.length || !readExternalResource || new Set(storage.map((attribute2) => attribute2.name)).size !== storage.length || new Set(storage.map((attribute2) => attribute2.key)).size !== storage.length || !storage.some((attribute2) => attribute2.name === options.objectIdProperty) || (metadata.tileset.fields || []).some(
|
|
3230
3475
|
(field) => field.domain || field.type === "esriFieldTypeDate" || !storage.some((attribute2) => attribute2.name === field.name)
|
|
3231
3476
|
)) {
|
|
3232
|
-
throw new
|
|
3477
|
+
throw new import_core27.TileConversionError(
|
|
3233
3478
|
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3234
3479
|
"I3S attributes require an explicit scalar schema, object ID column, byte budget and resource reader; domains/dates/raw metadata need a custom mapper"
|
|
3235
3480
|
);
|
|
@@ -3252,7 +3497,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3252
3497
|
) || attribute2.ordering && JSON.stringify(
|
|
3253
3498
|
attribute2.ordering.map((value) => value === "ObjectIds" ? "objectIds" : value)
|
|
3254
3499
|
) !== JSON.stringify(expectedOrdering) || type === "String" && (values.encoding !== "UTF-8" || attribute2.attributeByteCounts?.valueType !== "UInt32" || attribute2.attributeByteCounts.valuesPerElement !== 1) || !attributeUrls[index]) {
|
|
3255
|
-
throw new
|
|
3500
|
+
throw new import_core27.TileConversionError(
|
|
3256
3501
|
"I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
|
|
3257
3502
|
`Attribute ${attribute2.name} requires a standard scalar/string resource layout`
|
|
3258
3503
|
);
|
|
@@ -3261,7 +3506,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3261
3506
|
signal?.throwIfAborted();
|
|
3262
3507
|
inputBytes += bytes instanceof Uint8Array ? bytes.byteLength : Infinity;
|
|
3263
3508
|
if (!(bytes instanceof Uint8Array) || inputBytes > maximumAttributeBytes) {
|
|
3264
|
-
throw new
|
|
3509
|
+
throw new import_core27.TileConversionError(
|
|
3265
3510
|
"INPUT_RESOURCE_TOO_LARGE",
|
|
3266
3511
|
"I3S node attributes exceed maxAttributeBytes"
|
|
3267
3512
|
);
|
|
@@ -3274,14 +3519,14 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3274
3519
|
i3s: { attributeValues: "exact" }
|
|
3275
3520
|
});
|
|
3276
3521
|
} catch (error) {
|
|
3277
|
-
throw new
|
|
3522
|
+
throw new import_core27.TileConversionError(
|
|
3278
3523
|
"I3S_MESH_SOURCE_ATTRIBUTE_INVALID",
|
|
3279
3524
|
`Invalid attribute ${attribute2.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
3280
3525
|
);
|
|
3281
3526
|
}
|
|
3282
3527
|
const column = parsed[attribute2.name];
|
|
3283
3528
|
if (!column || count !== void 0 && column.length !== count || !column.length) {
|
|
3284
|
-
throw new
|
|
3529
|
+
throw new import_core27.TileConversionError(
|
|
3285
3530
|
"I3S_MESH_SOURCE_ATTRIBUTE_COUNT_INVALID",
|
|
3286
3531
|
"Every attribute must have the same nonzero row count"
|
|
3287
3532
|
);
|
|
@@ -3289,7 +3534,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3289
3534
|
if (Array.from(column).some(
|
|
3290
3535
|
(value) => typeof value === "number" && !Number.isFinite(value)
|
|
3291
3536
|
)) {
|
|
3292
|
-
throw new
|
|
3537
|
+
throw new import_core27.TileConversionError(
|
|
3293
3538
|
"I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
|
|
3294
3539
|
"Numeric noData values require an explicit custom mapper"
|
|
3295
3540
|
);
|
|
@@ -3302,7 +3547,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3302
3547
|
for (let index = 0; index < count; index++) {
|
|
3303
3548
|
const identifier = identifiers[index];
|
|
3304
3549
|
if (!(typeof identifier === "bigint" || typeof identifier === "number" && Number.isSafeInteger(identifier)) || attributeRows.has(BigInt(identifier))) {
|
|
3305
|
-
throw new
|
|
3550
|
+
throw new import_core27.TileConversionError(
|
|
3306
3551
|
"I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
|
|
3307
3552
|
"Object IDs must be unique exact integers"
|
|
3308
3553
|
);
|
|
@@ -3313,7 +3558,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3313
3558
|
const geometryIdentifiers = content.featureIds;
|
|
3314
3559
|
const attribute = content.sourceAttributes?.id;
|
|
3315
3560
|
if (geometryIdentifiers.length !== vertexCount || attribute?.size !== 1 || attribute.normalized || attribute.value.length !== vertexCount || Array.from(geometryIdentifiers).some((value, index) => value !== attribute.value[index])) {
|
|
3316
|
-
throw new
|
|
3561
|
+
throw new import_core27.TileConversionError(
|
|
3317
3562
|
"I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
|
|
3318
3563
|
"Geometry requires one exact unnormalized object ID per vertex"
|
|
3319
3564
|
);
|
|
@@ -3324,7 +3569,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3324
3569
|
const covered = new Uint8Array(triangleFeatureIndices.length);
|
|
3325
3570
|
for (const range of content.drawRanges) {
|
|
3326
3571
|
if (!Number.isSafeInteger(range.firstPrimitive) || range.firstPrimitive < 0 || !Number.isSafeInteger(range.primitiveCount) || range.primitiveCount <= 0 || range.firstPrimitive + range.primitiveCount > covered.length) {
|
|
3327
|
-
throw new
|
|
3572
|
+
throw new import_core27.TileConversionError(
|
|
3328
3573
|
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3329
3574
|
"Feature draw ranges must cover valid triangles"
|
|
3330
3575
|
);
|
|
@@ -3332,7 +3577,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3332
3577
|
for (let triangle = range.firstPrimitive; triangle < range.firstPrimitive + range.primitiveCount; triangle++) {
|
|
3333
3578
|
const vertex = indices?.[triangle * 3] ?? triangle * 3;
|
|
3334
3579
|
if (covered[triangle] || geometryIdentifiers[vertex] !== range.featureId) {
|
|
3335
|
-
throw new
|
|
3580
|
+
throw new import_core27.TileConversionError(
|
|
3336
3581
|
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3337
3582
|
"Feature draw ranges must agree with decoded ownership without overlapping"
|
|
3338
3583
|
);
|
|
@@ -3341,7 +3586,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3341
3586
|
}
|
|
3342
3587
|
}
|
|
3343
3588
|
if (covered.some((value) => value === 0)) {
|
|
3344
|
-
throw new
|
|
3589
|
+
throw new import_core27.TileConversionError(
|
|
3345
3590
|
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3346
3591
|
"Feature draw ranges must cover every triangle"
|
|
3347
3592
|
);
|
|
@@ -3355,7 +3600,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3355
3600
|
const featureIds = corners.map((corner) => geometryIdentifiers[corner]);
|
|
3356
3601
|
const row = attributeRows.get(BigInt(featureIds[0]));
|
|
3357
3602
|
if (row === void 0 || featureIds.some((identifier) => identifier !== featureIds[0])) {
|
|
3358
|
-
throw new
|
|
3603
|
+
throw new import_core27.TileConversionError(
|
|
3359
3604
|
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3360
3605
|
"Each triangle must reference one declared attribute row"
|
|
3361
3606
|
);
|
|
@@ -3364,14 +3609,14 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3364
3609
|
usedRows.add(row);
|
|
3365
3610
|
}
|
|
3366
3611
|
if (usedRows.size !== count) {
|
|
3367
|
-
throw new
|
|
3612
|
+
throw new import_core27.TileConversionError(
|
|
3368
3613
|
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3369
3614
|
"Attribute rows without geometry require a separately qualified mapping"
|
|
3370
3615
|
);
|
|
3371
3616
|
}
|
|
3372
3617
|
const stableProperty = options.sourceFeatureIdProperty ?? options.objectIdProperty;
|
|
3373
3618
|
if (!Object.hasOwn(columns, stableProperty)) {
|
|
3374
|
-
throw new
|
|
3619
|
+
throw new import_core27.TileConversionError(
|
|
3375
3620
|
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3376
3621
|
"Stable identifier column is missing"
|
|
3377
3622
|
);
|
|
@@ -3392,7 +3637,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3392
3637
|
for (let index = 0; index < column.length; index++) {
|
|
3393
3638
|
const identifier = column.get(index);
|
|
3394
3639
|
if (stableIds.has(identifier)) {
|
|
3395
|
-
throw new
|
|
3640
|
+
throw new import_core27.TileConversionError(
|
|
3396
3641
|
"I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
|
|
3397
3642
|
"Stable IDs must be unique"
|
|
3398
3643
|
);
|
|
@@ -3410,25 +3655,25 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3410
3655
|
|
|
3411
3656
|
// src/v5/i3s-mesh-source.ts
|
|
3412
3657
|
function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
3413
|
-
const source = (0,
|
|
3658
|
+
const source = (0, import_core28.createTilesetConversionSource)(tileset, options);
|
|
3414
3659
|
return {
|
|
3415
3660
|
/** Validates the layer profile and explicit source decode policy before reading. */
|
|
3416
3661
|
async inspect(signal) {
|
|
3417
3662
|
if (options.features && options.getFeatures) {
|
|
3418
|
-
throw new
|
|
3663
|
+
throw new import_core28.TileConversionError(
|
|
3419
3664
|
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3420
3665
|
"Choose explicit resource features or a custom getFeatures mapper, not both"
|
|
3421
3666
|
);
|
|
3422
3667
|
}
|
|
3423
3668
|
const metadata = await source.inspect(signal);
|
|
3424
3669
|
if (metadata.type !== "I3S" || !["3DObject", "IntegratedMesh"].includes(metadata.tileset.layerType) || tileset.options.i3s?.geometryMode !== "source") {
|
|
3425
|
-
throw new
|
|
3670
|
+
throw new import_core28.TileConversionError(
|
|
3426
3671
|
"I3S_MESH_SOURCE_PROFILE_UNSUPPORTED",
|
|
3427
3672
|
"I3S mesh extraction requires a 3DObject/IntegratedMesh runtime with geometryMode=source"
|
|
3428
3673
|
);
|
|
3429
3674
|
}
|
|
3430
3675
|
if (!metadata.spatialReference?.sourceCrs) {
|
|
3431
|
-
throw new
|
|
3676
|
+
throw new import_core28.TileConversionError(
|
|
3432
3677
|
"I3S_MESH_SOURCE_REFERENCE_REQUIRED",
|
|
3433
3678
|
"I3S conversion requires an explicit source CRS"
|
|
3434
3679
|
);
|
|
@@ -3456,7 +3701,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3456
3701
|
].some(
|
|
3457
3702
|
(field) => JSON.stringify(content.spatialReference?.[field]) !== JSON.stringify(metadata.spatialReference?.[field])
|
|
3458
3703
|
)) {
|
|
3459
|
-
throw new
|
|
3704
|
+
throw new import_core28.TileConversionError(
|
|
3460
3705
|
"I3S_MESH_SOURCE_GEOMETRY_UNSUPPORTED",
|
|
3461
3706
|
"I3S extraction requires original source-frame Float64 triangle geometry"
|
|
3462
3707
|
);
|
|
@@ -3464,7 +3709,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3464
3709
|
if (!["earth-centered", "vertex-reference-frame"].includes(
|
|
3465
3710
|
content.normalReferenceFrame || ""
|
|
3466
3711
|
) || content.normalReferenceFrame !== (metadata.tileset.store?.normalReferenceFrame || "earth-centered")) {
|
|
3467
|
-
throw new
|
|
3712
|
+
throw new import_core28.TileConversionError(
|
|
3468
3713
|
"I3S_MESH_SOURCE_NORMAL_FRAME_UNSUPPORTED",
|
|
3469
3714
|
"Unknown I3S vector basis"
|
|
3470
3715
|
);
|
|
@@ -3472,20 +3717,20 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3472
3717
|
if (content.meshSegmentation || Object.entries(attributes).some(
|
|
3473
3718
|
([name, value]) => value && !["position", "normal", "uv0", "id", "faceRange"].includes(name)
|
|
3474
3719
|
)) {
|
|
3475
|
-
throw new
|
|
3720
|
+
throw new import_core28.TileConversionError(
|
|
3476
3721
|
"I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
|
|
3477
3722
|
"Only positions, normals, TEXCOORD_0 and explicitly mapped feature associations are supported"
|
|
3478
3723
|
);
|
|
3479
3724
|
}
|
|
3480
3725
|
const hasFeatures = content.featureIds.length > 0 || metadata.tileset.fields?.length || metadata.tileset.attributeStorageInfo?.length;
|
|
3481
3726
|
if (Array.from(content.featureIds).some((value) => !Number.isSafeInteger(value))) {
|
|
3482
|
-
throw new
|
|
3727
|
+
throw new import_core28.TileConversionError(
|
|
3483
3728
|
"I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
|
|
3484
3729
|
"Decoded numeric feature IDs must be exact safe integers"
|
|
3485
3730
|
);
|
|
3486
3731
|
}
|
|
3487
3732
|
if (hasFeatures && !options.getFeatures && !options.features) {
|
|
3488
|
-
throw new
|
|
3733
|
+
throw new import_core28.TileConversionError(
|
|
3489
3734
|
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3490
3735
|
"I3S feature data requires explicit resource features or a custom Arrow mapper"
|
|
3491
3736
|
);
|
|
@@ -3504,7 +3749,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3504
3749
|
true
|
|
3505
3750
|
);
|
|
3506
3751
|
if (mesh.attributes.POSITION.value.length / 3 !== content.vertexCount) {
|
|
3507
|
-
throw new
|
|
3752
|
+
throw new import_core28.TileConversionError(
|
|
3508
3753
|
"I3S_MESH_SOURCE_COUNT_INVALID",
|
|
3509
3754
|
"I3S vertexCount must match the decoded geometry"
|
|
3510
3755
|
);
|
|
@@ -3520,7 +3765,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3520
3765
|
) : void 0;
|
|
3521
3766
|
signal?.throwIfAborted();
|
|
3522
3767
|
if (hasFeatures && !features) {
|
|
3523
|
-
throw new
|
|
3768
|
+
throw new import_core28.TileConversionError(
|
|
3524
3769
|
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3525
3770
|
"The feature mapper must return all declared feature data"
|
|
3526
3771
|
);
|
|
@@ -3536,6 +3781,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3536
3781
|
yield {
|
|
3537
3782
|
id: `${item.tile.id}/${entry.index}`,
|
|
3538
3783
|
origin: [0, 0, 0],
|
|
3784
|
+
sourceLayerType: metadata.tileset.layerType,
|
|
3539
3785
|
mesh,
|
|
3540
3786
|
normalReferenceFrame: content.normalReferenceFrame === "vertex-reference-frame" ? "vertex-reference-frame" : "earth-centered",
|
|
3541
3787
|
features,
|
|
@@ -3546,3 +3792,1529 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3546
3792
|
}
|
|
3547
3793
|
};
|
|
3548
3794
|
}
|
|
3795
|
+
|
|
3796
|
+
// src/v5/point-cloud-attributes.ts
|
|
3797
|
+
var import_schema_utils5 = require("@loaders.gl/core/schema-utils");
|
|
3798
|
+
var import_core29 = require("@loaders.gl/tile-converter/v5/core");
|
|
3799
|
+
function mapPointCloudAttributes(input, mapping) {
|
|
3800
|
+
const mesh = getPointCloudMesh(input);
|
|
3801
|
+
const attributes = /* @__PURE__ */ Object.create(null);
|
|
3802
|
+
const selected = /* @__PURE__ */ new Set();
|
|
3803
|
+
for (const [target, source] of Object.entries(mapping.attributes)) {
|
|
3804
|
+
if (!target || ["__proto__", "constructor", "prototype"].includes(target) || !Object.hasOwn(mesh.attributes, source))
|
|
3805
|
+
throw new import_core29.TileConversionError(
|
|
3806
|
+
"POINT_CLOUD_MAPPING_INVALID",
|
|
3807
|
+
`Invalid point attribute mapping ${target}: ${source}`
|
|
3808
|
+
);
|
|
3809
|
+
if (Object.hasOwn(mapping.drop || {}, source))
|
|
3810
|
+
throw new import_core29.TileConversionError(
|
|
3811
|
+
"POINT_CLOUD_MAPPING_INVALID",
|
|
3812
|
+
`Point attribute ${source} is both selected and dropped`
|
|
3813
|
+
);
|
|
3814
|
+
attributes[target] = { ...mesh.attributes[source] };
|
|
3815
|
+
selected.add(source);
|
|
3816
|
+
}
|
|
3817
|
+
const droppedAttributes = /* @__PURE__ */ Object.create(null);
|
|
3818
|
+
for (const [source, reason] of Object.entries(mapping.drop || {}).sort(
|
|
3819
|
+
([left], [right]) => left < right ? -1 : left > right ? 1 : 0
|
|
3820
|
+
)) {
|
|
3821
|
+
if (!Object.hasOwn(mesh.attributes, source) || typeof reason !== "string" || !reason.trim())
|
|
3822
|
+
throw new import_core29.TileConversionError(
|
|
3823
|
+
"POINT_CLOUD_MAPPING_INVALID",
|
|
3824
|
+
`Invalid loss declaration for ${source}`
|
|
3825
|
+
);
|
|
3826
|
+
droppedAttributes[source] = reason;
|
|
3827
|
+
}
|
|
3828
|
+
const unmapped = Object.keys(mesh.attributes).filter(
|
|
3829
|
+
(name) => !selected.has(name) && !Object.hasOwn(droppedAttributes, name)
|
|
3830
|
+
);
|
|
3831
|
+
if (unmapped.length || !attributes.POSITION)
|
|
3832
|
+
throw new import_core29.TileConversionError(
|
|
3833
|
+
"POINT_CLOUD_MAPPING_REQUIRED",
|
|
3834
|
+
`Map POSITION and explicitly retain or drop every attribute: ${unmapped.join(", ")}`
|
|
3835
|
+
);
|
|
3836
|
+
return {
|
|
3837
|
+
mesh: { ...mesh, attributes, schema: { fields: [], metadata: { ...mesh.schema?.metadata } } },
|
|
3838
|
+
droppedAttributes
|
|
3839
|
+
};
|
|
3840
|
+
}
|
|
3841
|
+
function getPointCloudMesh(input, maxInputBytes = Infinity) {
|
|
3842
|
+
const buffers = /* @__PURE__ */ new Set();
|
|
3843
|
+
let bytes = 0;
|
|
3844
|
+
const countBuffer = (buffer) => {
|
|
3845
|
+
if (!buffers.has(buffer)) {
|
|
3846
|
+
buffers.add(buffer);
|
|
3847
|
+
bytes += buffer.byteLength;
|
|
3848
|
+
}
|
|
3849
|
+
if (bytes > maxInputBytes)
|
|
3850
|
+
throw new import_core29.TileConversionError("POINT_CLOUD_INPUT_BUDGET", "Point input byte budget exceeded");
|
|
3851
|
+
};
|
|
3852
|
+
if ("shape" in input) {
|
|
3853
|
+
const checkColumn = (column) => {
|
|
3854
|
+
if (column.nullCount)
|
|
3855
|
+
throw new import_core29.TileConversionError(
|
|
3856
|
+
"POINT_CLOUD_NULL_UNSUPPORTED",
|
|
3857
|
+
"Null point values require an explicit preprocessing step"
|
|
3858
|
+
);
|
|
3859
|
+
for (const buffer of Object.values(column.buffers)) if (buffer) countBuffer(buffer.buffer);
|
|
3860
|
+
for (const view of column.variadicBuffers) countBuffer(view.buffer);
|
|
3861
|
+
for (const child of column.children) checkColumn(child);
|
|
3862
|
+
for (const dictionary of column.dictionary?.data || []) checkColumn(dictionary);
|
|
3863
|
+
};
|
|
3864
|
+
for (const batch of input.data.batches) checkColumn(batch.data);
|
|
3865
|
+
} else
|
|
3866
|
+
for (const attribute of Object.values(input.attributes)) countBuffer(attribute.value.buffer);
|
|
3867
|
+
const mesh = "shape" in input ? (0, import_schema_utils5.convertTableToMesh)(input) : input;
|
|
3868
|
+
if (mesh.topology !== "point-list" || mesh.indices)
|
|
3869
|
+
throw new import_core29.TileConversionError(
|
|
3870
|
+
"POINT_CLOUD_INPUT_INVALID",
|
|
3871
|
+
"Decoded unindexed point-list input is required"
|
|
3872
|
+
);
|
|
3873
|
+
return mesh;
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
// src/v5/point-cloud-copc.ts
|
|
3877
|
+
var import_copc = require("@loaders.gl/tiles-3d/copc");
|
|
3878
|
+
var import_core30 = require("@loaders.gl/tile-converter/v5/core");
|
|
3879
|
+
var SCALAR_RANGES = {
|
|
3880
|
+
intensity: [0, 65535],
|
|
3881
|
+
classification: [0, 255],
|
|
3882
|
+
nir: [0, 65535],
|
|
3883
|
+
gpsTime: [-Infinity, Infinity],
|
|
3884
|
+
scanAngle: [-32768, 32767],
|
|
3885
|
+
userData: [0, 255],
|
|
3886
|
+
pointSourceId: [0, 65535],
|
|
3887
|
+
returnNumber: [1, 15],
|
|
3888
|
+
numberOfReturns: [1, 15],
|
|
3889
|
+
scannerChannel: [0, 3],
|
|
3890
|
+
scanDirectionFlag: [0, 1],
|
|
3891
|
+
edgeOfFlightLine: [0, 1],
|
|
3892
|
+
synthetic: [0, 1],
|
|
3893
|
+
keyPoint: [0, 1],
|
|
3894
|
+
withheld: [0, 1],
|
|
3895
|
+
overlap: [0, 1]
|
|
3896
|
+
};
|
|
3897
|
+
async function encodePointCloudCOPC(input, options) {
|
|
3898
|
+
const maxInputBytes = options.maxInputBytes ?? 64 * 1024 * 1024;
|
|
3899
|
+
const maxPoints = options.maxPoints ?? 1e6;
|
|
3900
|
+
const maxOutputBytes = options.maxOutputBytes ?? 128 * 1024 * 1024;
|
|
3901
|
+
if ([maxInputBytes, maxPoints, maxOutputBytes].some(
|
|
3902
|
+
(value) => !Number.isSafeInteger(value) || value < 1
|
|
3903
|
+
) || options.scale.length !== 3 || options.scale.some((value) => !Number.isFinite(value) || value <= 0) || options.offset.length !== 3 || options.offset.some((value) => !Number.isFinite(value)) || typeof options.wkt !== "string" || !options.wkt.trim() || !Number.isFinite(options.maxPositionError) || options.maxPositionError < 0)
|
|
3904
|
+
throw new import_core30.TileConversionError(
|
|
3905
|
+
"POINT_CLOUD_COPC_OPTIONS_INVALID",
|
|
3906
|
+
"COPC requires explicit finite scale, offset, CRS, precision and positive budgets"
|
|
3907
|
+
);
|
|
3908
|
+
options.signal?.throwIfAborted();
|
|
3909
|
+
const source = getPointCloudMesh(input, maxInputBytes);
|
|
3910
|
+
const { mesh, droppedAttributes } = mapPointCloudAttributes(source, options.mapping);
|
|
3911
|
+
const position = mesh.attributes.POSITION;
|
|
3912
|
+
const pointCount = mesh.header?.vertexCount ?? position.value.length / 3;
|
|
3913
|
+
if (!Number.isSafeInteger(pointCount) || pointCount < 1 || pointCount > maxPoints)
|
|
3914
|
+
throw new import_core30.TileConversionError(
|
|
3915
|
+
"POINT_CLOUD_POINT_BUDGET",
|
|
3916
|
+
"COPC requires a nonempty batch within the point limit"
|
|
3917
|
+
);
|
|
3918
|
+
if (!(position.value instanceof Float32Array || position.value instanceof Float64Array) || position.size !== 3 || position.normalized)
|
|
3919
|
+
throw new import_core30.TileConversionError(
|
|
3920
|
+
"POINT_CLOUD_POSITION_INVALID",
|
|
3921
|
+
"COPC requires decoded floating XYZ"
|
|
3922
|
+
);
|
|
3923
|
+
const extraNames = new Set(options.extraBytes?.map((field) => field.attribute) || []);
|
|
3924
|
+
const descriptorNames = new Set(
|
|
3925
|
+
options.extraBytes?.map((field) => field.name || field.attribute) || []
|
|
3926
|
+
);
|
|
3927
|
+
if (extraNames.size !== (options.extraBytes?.length || 0) || descriptorNames.size !== extraNames.size || options.extraBytes?.some((field) => {
|
|
3928
|
+
const name = field.name || field.attribute;
|
|
3929
|
+
const description = field.description || "";
|
|
3930
|
+
return !mesh.attributes[field.attribute] || !field.attribute || !name.length || name.length > 32 || description.length > 32 || /[^\x20-\x7e]/.test(name) || /[^\x20-\x7e]/.test(description);
|
|
3931
|
+
}))
|
|
3932
|
+
throw new import_core30.TileConversionError(
|
|
3933
|
+
"POINT_CLOUD_EXTRA_BYTES_INVALID",
|
|
3934
|
+
"Extra Bytes descriptors require unique mapped attributes and names/descriptions within 32 printable ASCII bytes"
|
|
3935
|
+
);
|
|
3936
|
+
for (const name of extraNames)
|
|
3937
|
+
if (name === "POSITION" || name === "COLOR_0" || Object.hasOwn(SCALAR_RANGES, name))
|
|
3938
|
+
throw new import_core30.TileConversionError(
|
|
3939
|
+
"POINT_CLOUD_EXTRA_BYTES_INVALID",
|
|
3940
|
+
"Map standard attributes directly instead of duplicating them as Extra Bytes"
|
|
3941
|
+
);
|
|
3942
|
+
for (const [name, attribute] of Object.entries(mesh.attributes)) {
|
|
3943
|
+
const color2 = name === "COLOR_0";
|
|
3944
|
+
const extra = extraNames.has(name);
|
|
3945
|
+
if (name !== "POSITION" && !color2 && !extra && !Object.hasOwn(SCALAR_RANGES, name))
|
|
3946
|
+
throw new import_core30.TileConversionError(
|
|
3947
|
+
"POINT_CLOUD_ATTRIBUTE_UNSUPPORTED",
|
|
3948
|
+
`COPC cannot preserve ${name}; supply an explicit supported mapping or drop reason`
|
|
3949
|
+
);
|
|
3950
|
+
if (attribute.transform || attribute.byteOffset || attribute.byteStride || attribute.componentType === "float16" || attribute.value.length !== pointCount * attribute.size || !color2 && attribute.normalized || name !== "POSITION" && !color2 && (extra ? ![1, 3].includes(attribute.size) : attribute.size !== 1))
|
|
3951
|
+
throw new import_core30.TileConversionError(
|
|
3952
|
+
"POINT_CLOUD_ATTRIBUTE_INVALID",
|
|
3953
|
+
`COPC requires packed decoded attributes: ${name}`
|
|
3954
|
+
);
|
|
3955
|
+
if (color2) {
|
|
3956
|
+
if (attribute.size !== 3 || !(attribute.value instanceof Uint8Array || attribute.value instanceof Uint16Array))
|
|
3957
|
+
throw new import_core30.TileConversionError(
|
|
3958
|
+
"POINT_CLOUD_COLOR_INVALID",
|
|
3959
|
+
"COPC requires explicitly mapped uint8/uint16 RGB"
|
|
3960
|
+
);
|
|
3961
|
+
} else if (extra) {
|
|
3962
|
+
for (const value of attribute.value)
|
|
3963
|
+
if (typeof value !== "bigint" && !Number.isFinite(value))
|
|
3964
|
+
throw new import_core30.TileConversionError(
|
|
3965
|
+
"POINT_CLOUD_ATTRIBUTE_RANGE",
|
|
3966
|
+
`Nonfinite Extra Bytes value: ${name}`
|
|
3967
|
+
);
|
|
3968
|
+
} else if (name !== "POSITION") {
|
|
3969
|
+
const [minimum2, maximum2] = SCALAR_RANGES[name];
|
|
3970
|
+
for (const value of attribute.value) {
|
|
3971
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < minimum2 || value > maximum2 || name !== "gpsTime" && !Number.isInteger(value))
|
|
3972
|
+
throw new import_core30.TileConversionError(
|
|
3973
|
+
"POINT_CLOUD_ATTRIBUTE_RANGE",
|
|
3974
|
+
`COPC value outside the qualified range: ${name}`
|
|
3975
|
+
);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
let maximumPositionError = 0;
|
|
3980
|
+
const minimum = [Infinity, Infinity, Infinity];
|
|
3981
|
+
const maximum = [-Infinity, -Infinity, -Infinity];
|
|
3982
|
+
const errors = [0, 0, 0];
|
|
3983
|
+
for (let row = 0; row < pointCount; row++) {
|
|
3984
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
3985
|
+
const value = position.value[row * 3 + axis];
|
|
3986
|
+
const encoded = Math.round((value - options.offset[axis]) / options.scale[axis]);
|
|
3987
|
+
if (!Number.isFinite(value) || !Number.isSafeInteger(encoded) || encoded < -2147483648 || encoded > 2147483647)
|
|
3988
|
+
throw new import_core30.TileConversionError(
|
|
3989
|
+
"POINT_CLOUD_POSITION_RANGE",
|
|
3990
|
+
"COPC position exceeds signed int32 quantization"
|
|
3991
|
+
);
|
|
3992
|
+
minimum[axis] = Math.min(minimum[axis], value);
|
|
3993
|
+
maximum[axis] = Math.max(maximum[axis], value);
|
|
3994
|
+
errors[axis] = encoded * options.scale[axis] + options.offset[axis] - value;
|
|
3995
|
+
}
|
|
3996
|
+
maximumPositionError = Math.max(maximumPositionError, Math.hypot(...errors));
|
|
3997
|
+
if (maximumPositionError > options.maxPositionError)
|
|
3998
|
+
throw new import_core30.TileConversionError(
|
|
3999
|
+
"POINT_CLOUD_PRECISION_EXCEEDED",
|
|
4000
|
+
"COPC position rounding exceeds the declared error gate"
|
|
4001
|
+
);
|
|
4002
|
+
if (row % 16384 === 0) {
|
|
4003
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
4004
|
+
options.signal?.throwIfAborted();
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
const width = Math.max(...maximum.map((value, axis) => value - minimum[axis]), ...options.scale);
|
|
4008
|
+
const radius = width / 2;
|
|
4009
|
+
if (!Number.isFinite(Math.hypot(radius, radius, radius)) || minimum.some((value, axis) => {
|
|
4010
|
+
const center = value + (maximum[axis] - value) / 2;
|
|
4011
|
+
return !Number.isFinite(value + maximum[axis]) || !Number.isFinite(center - radius) || !Number.isFinite(center + radius);
|
|
4012
|
+
}))
|
|
4013
|
+
throw new import_core30.TileConversionError("POINT_CLOUD_BOUNDS_INVALID", "COPC coordinate extent overflow");
|
|
4014
|
+
options.signal?.throwIfAborted();
|
|
4015
|
+
const color = mesh.attributes.COLOR_0;
|
|
4016
|
+
const copc = import_copc.COPCWriter.encodeSync(mesh, {
|
|
4017
|
+
copc: {
|
|
4018
|
+
...options.organization,
|
|
4019
|
+
scale: [...options.scale],
|
|
4020
|
+
offset: [...options.offset],
|
|
4021
|
+
wkt: options.wkt,
|
|
4022
|
+
pointDataRecordFormat: mesh.attributes.nir ? 8 : color ? 7 : 6,
|
|
4023
|
+
colorDepth: color?.value instanceof Uint16Array ? 16 : 8,
|
|
4024
|
+
extraBytes: options.extraBytes
|
|
4025
|
+
}
|
|
4026
|
+
});
|
|
4027
|
+
options.signal?.throwIfAborted();
|
|
4028
|
+
if (copc.byteLength > maxOutputBytes)
|
|
4029
|
+
throw new import_core30.TileConversionError(
|
|
4030
|
+
"POINT_CLOUD_OUTPUT_BUDGET",
|
|
4031
|
+
"Completed COPC output exceeds the byte budget"
|
|
4032
|
+
);
|
|
4033
|
+
return { copc, pointCount, maximumPositionError, droppedAttributes };
|
|
4034
|
+
}
|
|
4035
|
+
function createCOPCConversionCodec(options) {
|
|
4036
|
+
return {
|
|
4037
|
+
async *convert(input, _inspection, signal) {
|
|
4038
|
+
const combinedSignal = options.signal && signal ? AbortSignal.any([options.signal, signal]) : options.signal || signal;
|
|
4039
|
+
yield await encodePointCloudCOPC(input, { ...options, signal: combinedSignal });
|
|
4040
|
+
}
|
|
4041
|
+
};
|
|
4042
|
+
}
|
|
4043
|
+
|
|
4044
|
+
// src/v5/point-cloud-tileset.ts
|
|
4045
|
+
var import_point_cloud_tile_source = require("@loaders.gl/tiles-3d/point-cloud-tile-source");
|
|
4046
|
+
var import_core31 = require("@loaders.gl/tile-converter/v5/core");
|
|
4047
|
+
async function convertPointCloudToTileset(input, options) {
|
|
4048
|
+
const reference = options.spatialReference;
|
|
4049
|
+
if (!reference || reference.status !== "native" || reference.sourceCrs !== "EPSG:4978" || reference.targetCrs && reference.targetCrs !== "EPSG:4978" || reference.coordinateFrame !== "geocentric" || reference.axisOrder !== "xyz" || reference.heightReference !== "ellipsoidal" || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter")) {
|
|
4050
|
+
throw new import_core31.TileConversionError(
|
|
4051
|
+
"POINT_CLOUD_TILESET_FRAME_UNSUPPORTED",
|
|
4052
|
+
"Point authoring requires explicit native EPSG:4978 xyz meters and ellipsoidal heights"
|
|
4053
|
+
);
|
|
4054
|
+
}
|
|
4055
|
+
if (options.archive && (!Number.isSafeInteger(options.archive.maxArchiveBytes) || options.archive.maxArchiveBytes < 0)) {
|
|
4056
|
+
throw new import_core31.TileConversionError(
|
|
4057
|
+
"INVALID_ARCHIVE_BYTE_LIMIT",
|
|
4058
|
+
"Archive byte limit must be a non-negative safe integer"
|
|
4059
|
+
);
|
|
4060
|
+
}
|
|
4061
|
+
options.signal?.throwIfAborted();
|
|
4062
|
+
const sink = createPointCloudTilesetSink(options);
|
|
4063
|
+
let source;
|
|
4064
|
+
try {
|
|
4065
|
+
source = new import_point_cloud_tile_source.PointCloudTileSource(input, {
|
|
4066
|
+
pointCloudTiler: { ...options.tiling, signal: options.signal, spatialReference: reference }
|
|
4067
|
+
});
|
|
4068
|
+
const { pointCount } = await source.getMetadata();
|
|
4069
|
+
const report = await convertPointCloudSource(source, {
|
|
4070
|
+
sink,
|
|
4071
|
+
signal: options.signal,
|
|
4072
|
+
onProgress: options.onProgress,
|
|
4073
|
+
measureInputBytes: (tile) => {
|
|
4074
|
+
const table = tile.content?.data.data;
|
|
4075
|
+
return table ? table.schema.fields.reduce(
|
|
4076
|
+
(bytes, _field, index) => bytes + (table.getChildAt(index)?.byteLength ?? 0),
|
|
4077
|
+
0
|
|
4078
|
+
) : 0;
|
|
4079
|
+
},
|
|
4080
|
+
getTileEncodingOptions: (tile) => ({
|
|
4081
|
+
rtcCenter: [
|
|
4082
|
+
tile.header.boundingVolume.center[0],
|
|
4083
|
+
tile.header.boundingVolume.center[1],
|
|
4084
|
+
tile.header.boundingVolume.center[2]
|
|
4085
|
+
],
|
|
4086
|
+
maxPositionError: options.geometricError
|
|
4087
|
+
})
|
|
4088
|
+
});
|
|
4089
|
+
options.signal?.throwIfAborted();
|
|
4090
|
+
const files = sink.getFiles();
|
|
4091
|
+
const archive = options.archive ? await createTileConversionArchive(files, {
|
|
4092
|
+
format: "3tz",
|
|
4093
|
+
maxArchiveBytes: options.archive.maxArchiveBytes,
|
|
4094
|
+
signal: options.signal
|
|
4095
|
+
}) : void 0;
|
|
4096
|
+
options.signal?.throwIfAborted();
|
|
4097
|
+
return { pointCount, report, files, archive };
|
|
4098
|
+
} catch (error) {
|
|
4099
|
+
await sink.abort(error);
|
|
4100
|
+
throw error;
|
|
4101
|
+
} finally {
|
|
4102
|
+
source?.close();
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
// src/v5/point-cloud-file.ts
|
|
4107
|
+
var import_copc2 = require("@loaders.gl/tiles-3d/copc");
|
|
4108
|
+
var import_potree = require("@loaders.gl/tiles-3d/potree");
|
|
4109
|
+
var import_schema_utils6 = require("@loaders.gl/core/schema-utils");
|
|
4110
|
+
var import_zip = require("@loaders.gl/archives/zip");
|
|
4111
|
+
async function encodePotreeArchive(input, options) {
|
|
4112
|
+
validateLimit(options.maxArchiveBytes);
|
|
4113
|
+
const dataset = await (0, import_potree.encodePotreeDataset)(input, { potree: options.potree });
|
|
4114
|
+
return encodePotreeDatasetArchive(dataset, options.maxArchiveBytes, options.potree?.signal);
|
|
4115
|
+
}
|
|
4116
|
+
async function encodePotreeDatasetArchive(dataset, maxArchiveBytes, signal) {
|
|
4117
|
+
validateLimit(maxArchiveBytes);
|
|
4118
|
+
for (const path of dataset.files.keys()) validatePath(path);
|
|
4119
|
+
signal?.throwIfAborted();
|
|
4120
|
+
const archive = await (0, import_zip.encodeZip)(Object.fromEntries(dataset.files), {
|
|
4121
|
+
compression: "store",
|
|
4122
|
+
zip64: "never",
|
|
4123
|
+
maxEntries: 65535,
|
|
4124
|
+
maxArchiveBytes: BigInt(maxArchiveBytes),
|
|
4125
|
+
signal
|
|
4126
|
+
});
|
|
4127
|
+
signal?.throwIfAborted();
|
|
4128
|
+
return new Blob([archive], { type: "application/zip" });
|
|
4129
|
+
}
|
|
4130
|
+
async function openPointCloudFile(input, options) {
|
|
4131
|
+
validateLimit(options.maxFileBytes);
|
|
4132
|
+
if (input.size > options.maxFileBytes) throw new RangeError("Point file input budget exceeded");
|
|
4133
|
+
options.signal?.throwIfAborted();
|
|
4134
|
+
if (options.format === "copc") {
|
|
4135
|
+
const source2 = import_copc2.COPCSourceLoader.createDataSource(input, options.sourceOptions || {});
|
|
4136
|
+
try {
|
|
4137
|
+
await source2.metadata;
|
|
4138
|
+
options.signal?.throwIfAborted();
|
|
4139
|
+
return { source: source2, close: () => source2.close() };
|
|
4140
|
+
} catch (error) {
|
|
4141
|
+
await source2.close();
|
|
4142
|
+
throw error;
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
if (options.format !== "potree-zip") throw new TypeError("Unsupported point file format");
|
|
4146
|
+
const maxEntries = options.maxEntries ?? 1e5;
|
|
4147
|
+
const maxResourceBytes = options.maxResourceBytes ?? 64 * 1024 * 1024;
|
|
4148
|
+
validateLimit(maxEntries);
|
|
4149
|
+
validateLimit(maxResourceBytes);
|
|
4150
|
+
const controller = new AbortController();
|
|
4151
|
+
const signals = [controller.signal, options.signal, options.sourceOptions?.potree?.signal].filter(
|
|
4152
|
+
(value) => Boolean(value)
|
|
4153
|
+
);
|
|
4154
|
+
const signal = AbortSignal.any(signals);
|
|
4155
|
+
let archive;
|
|
4156
|
+
const entries = /* @__PURE__ */ new Map();
|
|
4157
|
+
let source;
|
|
4158
|
+
try {
|
|
4159
|
+
archive = await (0, import_zip.openZipArchive)(input, {
|
|
4160
|
+
maxArchiveBytes: BigInt(options.maxFileBytes),
|
|
4161
|
+
maxEntries,
|
|
4162
|
+
maxReadBytes: Math.max(65557, maxResourceBytes),
|
|
4163
|
+
maxCompressedBytes: BigInt(maxResourceBytes),
|
|
4164
|
+
maxDecodedBytes: BigInt(maxResourceBytes),
|
|
4165
|
+
signal
|
|
4166
|
+
});
|
|
4167
|
+
for await (const entry of archive.entries()) {
|
|
4168
|
+
if (entry.directory) continue;
|
|
4169
|
+
validatePath(entry.path);
|
|
4170
|
+
if (entry.compression !== "store")
|
|
4171
|
+
throw new Error("Potree bundles require unencrypted STORE entries");
|
|
4172
|
+
entries.set(entry.path, entry);
|
|
4173
|
+
}
|
|
4174
|
+
const candidates = ["metadata.json", "cloud.js"].filter((path) => entries.has(path));
|
|
4175
|
+
const metadataPath = options.metadataPath || (candidates.length === 1 ? candidates[0] : "");
|
|
4176
|
+
validatePath(metadataPath);
|
|
4177
|
+
if (!entries.has(metadataPath) || !/(^|\/)(metadata\.json|cloud\.js)$/.test(metadataPath))
|
|
4178
|
+
throw new Error("Potree ZIP requires one metadata entry or an explicit metadataPath");
|
|
4179
|
+
const prefix = "https://point-bundle.loaders.gl.invalid/";
|
|
4180
|
+
const fetchResource = async (resource, request = {}) => {
|
|
4181
|
+
signal.throwIfAborted();
|
|
4182
|
+
request.signal?.throwIfAborted();
|
|
4183
|
+
const url = new URL(resource);
|
|
4184
|
+
if (url.origin !== new URL(prefix).origin || url.search || url.hash)
|
|
4185
|
+
throw new Error("Resource outside the Potree bundle");
|
|
4186
|
+
const path = decodeURIComponent(url.pathname.slice(1));
|
|
4187
|
+
validatePath(path);
|
|
4188
|
+
const entry = entries.get(path);
|
|
4189
|
+
if (!entry) throw new Error(`Missing Potree bundle resource: ${path}`);
|
|
4190
|
+
const range = new Headers(request.headers).get("range");
|
|
4191
|
+
const match = range?.match(/^bytes=(\d+)-(\d+)$/);
|
|
4192
|
+
if (range && !match) throw new RangeError("Unsupported Potree bundle byte range");
|
|
4193
|
+
const begin = match ? Number(match[1]) : 0;
|
|
4194
|
+
const end = match ? Number(match[2]) + 1 : Number(entry.size);
|
|
4195
|
+
if (!Number.isSafeInteger(begin) || !Number.isSafeInteger(end) || begin < 0 || end < begin || BigInt(end) > entry.size || end - begin > maxResourceBytes)
|
|
4196
|
+
throw new RangeError("Potree bundle resource/range budget exceeded");
|
|
4197
|
+
const headers = new Headers({
|
|
4198
|
+
"content-length": String(end - begin),
|
|
4199
|
+
"accept-ranges": "bytes"
|
|
4200
|
+
});
|
|
4201
|
+
if (match) headers.set("content-range", `bytes ${begin}-${end - 1}/${entry.size}`);
|
|
4202
|
+
const bytes = match ? await archive.readRange(entry, {
|
|
4203
|
+
start: BigInt(begin),
|
|
4204
|
+
end: BigInt(end),
|
|
4205
|
+
signal: request.signal ?? void 0
|
|
4206
|
+
}) : await archive.read(entry, { signal: request.signal ?? void 0 });
|
|
4207
|
+
const response = new Response(bytes, {
|
|
4208
|
+
status: match ? 206 : 200,
|
|
4209
|
+
headers
|
|
4210
|
+
});
|
|
4211
|
+
Object.defineProperty(response, "url", { value: url.href });
|
|
4212
|
+
signal.throwIfAborted();
|
|
4213
|
+
return response;
|
|
4214
|
+
};
|
|
4215
|
+
source = import_potree.PotreeSourceLoader.createDataSource(`${prefix}${metadataPath}`, {
|
|
4216
|
+
...options.sourceOptions,
|
|
4217
|
+
core: { ...options.sourceOptions?.core, fetch: fetchResource },
|
|
4218
|
+
potree: { ...options.sourceOptions?.potree, signal }
|
|
4219
|
+
});
|
|
4220
|
+
await source.initialize();
|
|
4221
|
+
signal.throwIfAborted();
|
|
4222
|
+
return {
|
|
4223
|
+
source,
|
|
4224
|
+
async close() {
|
|
4225
|
+
controller.abort();
|
|
4226
|
+
source?.close();
|
|
4227
|
+
await archive?.close();
|
|
4228
|
+
}
|
|
4229
|
+
};
|
|
4230
|
+
} catch (error) {
|
|
4231
|
+
controller.abort();
|
|
4232
|
+
source?.close();
|
|
4233
|
+
await archive?.close();
|
|
4234
|
+
throw error;
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
function extractMeshVertices(mesh, options) {
|
|
4238
|
+
validateLimit(options.maxPoints);
|
|
4239
|
+
const position = mesh.attributes.POSITION;
|
|
4240
|
+
const pointCount = position?.value.length / 3;
|
|
4241
|
+
if (mesh.topology !== "triangle-list" || mesh.mode !== 4 || !position || position.size !== 3 || position.normalized || position.transform || position.byteStride || position.byteOffset || !(position.value instanceof Float32Array || position.value instanceof Float64Array) || !Number.isSafeInteger(pointCount) || pointCount > options.maxPoints || position.value.some((value) => !Number.isFinite(value)))
|
|
4242
|
+
throw new Error(
|
|
4243
|
+
"Mesh vertex harvesting requires packed finite triangle XYZ within the point budget"
|
|
4244
|
+
);
|
|
4245
|
+
const attributes = { POSITION: position };
|
|
4246
|
+
for (const name of options.attributes) {
|
|
4247
|
+
const attribute = mesh.attributes[name];
|
|
4248
|
+
if (!attribute || attribute.transform || attribute.byteStride || attribute.byteOffset || !Number.isSafeInteger(attribute.size) || attribute.size < 1 || attribute.value.length !== pointCount * attribute.size)
|
|
4249
|
+
throw new Error(`Invalid selected mesh vertex attribute: ${name}`);
|
|
4250
|
+
attributes[name] = attribute;
|
|
4251
|
+
}
|
|
4252
|
+
return { topology: "point-list", mode: 0, attributes, schema: (0, import_schema_utils6.deduceMeshSchema)(attributes) };
|
|
4253
|
+
}
|
|
4254
|
+
function validatePath(path) {
|
|
4255
|
+
if (!path || !/^[A-Za-z0-9_.\/-]+$/.test(path) || path.split("/").some((part) => !part || part === "." || part === ".."))
|
|
4256
|
+
throw new Error("Potree bundle paths must be canonical relative ASCII paths");
|
|
4257
|
+
}
|
|
4258
|
+
function validateLimit(value) {
|
|
4259
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
4260
|
+
throw new RangeError("Point file limits must be positive safe integers");
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
// src/v5/point-cloud-potree.ts
|
|
4264
|
+
var import_schema_utils7 = require("@loaders.gl/core/schema-utils");
|
|
4265
|
+
var import_potree2 = require("@loaders.gl/tiles-3d/potree");
|
|
4266
|
+
async function convertPointCloudToPotree(input, options) {
|
|
4267
|
+
if (typeof options.projection !== "string" || !options.projection.trim()) {
|
|
4268
|
+
throw new Error("Potree authoring requires an explicit native CRS declaration");
|
|
4269
|
+
}
|
|
4270
|
+
for (const limit of [
|
|
4271
|
+
options.maxGatheredBytes,
|
|
4272
|
+
options.maxOutputBytes,
|
|
4273
|
+
options.archive?.maxArchiveBytes
|
|
4274
|
+
]) {
|
|
4275
|
+
if (limit !== void 0 && (!Number.isSafeInteger(limit) || limit < 1)) {
|
|
4276
|
+
throw new RangeError("Potree authoring byte limits must be positive safe integers");
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
if (options.maxGatheredBytes === void 0 || options.maxOutputBytes === void 0) {
|
|
4280
|
+
throw new RangeError("Potree authoring requires gathered and encoded byte limits");
|
|
4281
|
+
}
|
|
4282
|
+
options.signal?.throwIfAborted();
|
|
4283
|
+
const tiler = new import_schema_utils7.PointCloudTiler(input, {
|
|
4284
|
+
...options.tiling,
|
|
4285
|
+
maxNodes: Math.min(options.tiling?.maxNodes ?? 1e5, options.potree?.maxNodes ?? 1e5),
|
|
4286
|
+
maxTileBytes: Math.min(
|
|
4287
|
+
options.tiling?.maxTileBytes ?? 64 * 1024 * 1024,
|
|
4288
|
+
options.maxGatheredBytes
|
|
4289
|
+
),
|
|
4290
|
+
signal: options.signal
|
|
4291
|
+
});
|
|
4292
|
+
const nodes = [];
|
|
4293
|
+
let gatheredBytes = 0;
|
|
4294
|
+
let pointByteLength = 0;
|
|
4295
|
+
let pointCount;
|
|
4296
|
+
try {
|
|
4297
|
+
const pending = [await tiler.getRootNode()];
|
|
4298
|
+
pointCount = tiler.pointCount;
|
|
4299
|
+
while (pending.length) {
|
|
4300
|
+
options.signal?.throwIfAborted();
|
|
4301
|
+
const node = pending.pop();
|
|
4302
|
+
if (node.pointCount * pointByteLength > options.maxGatheredBytes - gatheredBytes) {
|
|
4303
|
+
throw new RangeError("Potree gathered node byte budget exceeded");
|
|
4304
|
+
}
|
|
4305
|
+
const mesh = await tiler.getTileMesh(node.id);
|
|
4306
|
+
const nodeBytes = Array.from(
|
|
4307
|
+
new Set(Object.values(mesh.attributes).map((attribute) => attribute.value.buffer))
|
|
4308
|
+
).reduce((bytes, buffer) => bytes + buffer.byteLength, 0);
|
|
4309
|
+
if (nodeBytes > options.maxGatheredBytes - gatheredBytes) {
|
|
4310
|
+
throw new RangeError("Potree gathered node byte budget exceeded");
|
|
4311
|
+
}
|
|
4312
|
+
if (node.pointCount) pointByteLength = nodeBytes / node.pointCount;
|
|
4313
|
+
gatheredBytes += nodeBytes;
|
|
4314
|
+
nodes.push({ id: node.id, mesh });
|
|
4315
|
+
const children = await tiler.getChildNodes(node.id);
|
|
4316
|
+
for (let index = children.length - 1; index >= 0; index--) pending.push(children[index]);
|
|
4317
|
+
}
|
|
4318
|
+
} finally {
|
|
4319
|
+
tiler.close();
|
|
4320
|
+
}
|
|
4321
|
+
const dataset = await (0, import_potree2.encodePotreeDataset)(nodes, {
|
|
4322
|
+
potree: {
|
|
4323
|
+
...options.potree,
|
|
4324
|
+
projection: options.projection,
|
|
4325
|
+
maxOutputBytes: options.maxOutputBytes,
|
|
4326
|
+
signal: options.signal
|
|
4327
|
+
}
|
|
4328
|
+
});
|
|
4329
|
+
const archive = options.archive ? await encodePotreeDatasetArchive(dataset, options.archive.maxArchiveBytes, options.signal) : void 0;
|
|
4330
|
+
options.signal?.throwIfAborted();
|
|
4331
|
+
return { pointCount, nodeCount: nodes.length, dataset, archive };
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4334
|
+
// src/v5/mesh-hierarchy.ts
|
|
4335
|
+
var import_core32 = require("@math.gl/core");
|
|
4336
|
+
var import_tiles_3d3 = require("@loaders.gl/tiles-3d");
|
|
4337
|
+
var import_core33 = require("@loaders.gl/tile-converter/v5/core");
|
|
4338
|
+
function prepareMeshHierarchy(inspection, resourceIds) {
|
|
4339
|
+
const rejectHierarchy = (message) => {
|
|
4340
|
+
throw new import_core33.TileConversionError("MESH_HIERARCHY_UNSUPPORTED", message);
|
|
4341
|
+
};
|
|
4342
|
+
if (!inspection.documents?.length || inspection.documents.length > 32)
|
|
4343
|
+
rejectHierarchy(
|
|
4344
|
+
"Hierarchy preservation requires expanded metadata inspection of at most 32 explicit document placements"
|
|
4345
|
+
);
|
|
4346
|
+
if (!resourceIds.length || resourceIds.length > 64 || new Set(resourceIds).size !== resourceIds.length || resourceIds.length !== inspection.resources.length || inspection.resources.some((resource) => !resourceIds.includes(resource.resourceId)))
|
|
4347
|
+
rejectHierarchy(
|
|
4348
|
+
"Hierarchy preservation requires every content placement, without duplicates (at most 64)"
|
|
4349
|
+
);
|
|
4350
|
+
const entryTransform = new import_core32.Matrix4();
|
|
4351
|
+
if (inspection.documents[0].parentResourceId !== void 0 || !Array.isArray(inspection.documents[0].parentTransform) || inspection.documents[0].parentTransform.length !== 16 || inspection.documents[0].parentTransform.some((value, index) => value !== entryTransform[index]))
|
|
4352
|
+
rejectHierarchy("The entry document must have identity parent placement");
|
|
4353
|
+
const documents = inspection.documents.map((placement) => placement.tileset);
|
|
4354
|
+
const document = documents[0];
|
|
4355
|
+
const externalDocuments = /* @__PURE__ */ new Map();
|
|
4356
|
+
for (let index = 1; index < inspection.documents.length; index++) {
|
|
4357
|
+
const identifier = inspection.documents[index].parentResourceId;
|
|
4358
|
+
if (!identifier || externalDocuments.has(identifier))
|
|
4359
|
+
rejectHierarchy(
|
|
4360
|
+
"Every external document requires a distinct inspected parent content placement"
|
|
4361
|
+
);
|
|
4362
|
+
externalDocuments.set(identifier, index);
|
|
4363
|
+
}
|
|
4364
|
+
for (const document2 of documents) {
|
|
4365
|
+
if (document2.metadata || document2.groups || document2.schema || document2.schemaUri || Object.keys(document2.extensions ?? {}).some((name) => name !== "EXT_geospatial_crs") || (document2.extensionsRequired ?? []).some((name) => name !== "EXT_geospatial_crs"))
|
|
4366
|
+
rejectHierarchy(
|
|
4367
|
+
"Dataset metadata and unmapped extensions require a separate hierarchy profile"
|
|
4368
|
+
);
|
|
4369
|
+
if (!["1.0", "1.1"].includes(document2.asset?.version ?? ""))
|
|
4370
|
+
rejectHierarchy("Hierarchy preservation requires a 3D Tiles 1.0 or 1.1 document");
|
|
4371
|
+
if (document2.geometricError !== void 0 && (typeof document2.geometricError !== "number" || !Number.isFinite(document2.geometricError) || document2.geometricError < 0))
|
|
4372
|
+
rejectHierarchy("Dataset geometricError must be finite nonnegative meters");
|
|
4373
|
+
}
|
|
4374
|
+
const spatialReferences = documents.map(
|
|
4375
|
+
(document2) => (0, import_tiles_3d3.get3DTilesSpatialReference)(document2)
|
|
4376
|
+
);
|
|
4377
|
+
const spatialReference = (0, import_core33.createTiles3DConversionSpatialContext)(
|
|
4378
|
+
spatialReferences[0]
|
|
4379
|
+
).spatialReference;
|
|
4380
|
+
if (spatialReference.status !== "native" || spatialReference.sourceCrs !== "EPSG:4978" || spatialReference.heightReference !== "ellipsoidal" || spatialReference.axisOrder !== "xyz" || spatialReference.verticalUnitScale !== 1 || spatialReference.units?.some((unit) => unit !== "meter"))
|
|
4381
|
+
rejectHierarchy(
|
|
4382
|
+
"Hierarchy preservation requires native EPSG:4978 xyz meters and ellipsoidal heights"
|
|
4383
|
+
);
|
|
4384
|
+
for (const reference of spatialReferences.slice(1)) {
|
|
4385
|
+
const inherited = reference.crs.state === "absent" && reference.coordinateEpoch === void 0 && !reference.verticalCrs && !reference.warnings.length;
|
|
4386
|
+
const context = (0, import_core33.createTiles3DConversionSpatialContext)(inherited ? spatialReference : reference);
|
|
4387
|
+
if (context.spatialReference.status !== "native" || context.spatialReference.sourceCrs !== spatialReference.sourceCrs || context.spatialReference.heightReference !== spatialReference.heightReference || context.spatialReference.axisOrder !== spatialReference.axisOrder || context.spatialReference.verticalUnitScale !== 1 || context.spatialReference.units?.some((unit) => unit !== "meter") || context.spatialReference.coordinateEpoch !== spatialReference.coordinateEpoch)
|
|
4388
|
+
rejectHierarchy("External document CRS and coordinate epoch must match the entry frame");
|
|
4389
|
+
}
|
|
4390
|
+
const nodes = [];
|
|
4391
|
+
const visitedDocuments = /* @__PURE__ */ new Set([0]);
|
|
4392
|
+
const stack = [
|
|
4393
|
+
{
|
|
4394
|
+
tile: document.root,
|
|
4395
|
+
documentIndex: 0,
|
|
4396
|
+
prefix: "",
|
|
4397
|
+
path: [],
|
|
4398
|
+
parent: -1,
|
|
4399
|
+
transform: new import_core32.Matrix4(),
|
|
4400
|
+
refine: void 0
|
|
4401
|
+
}
|
|
4402
|
+
];
|
|
4403
|
+
while (stack.length) {
|
|
4404
|
+
const {
|
|
4405
|
+
tile,
|
|
4406
|
+
path,
|
|
4407
|
+
parent,
|
|
4408
|
+
documentIndex,
|
|
4409
|
+
prefix,
|
|
4410
|
+
transform: parentTransform,
|
|
4411
|
+
refine: inheritedRefine
|
|
4412
|
+
} = stack.pop();
|
|
4413
|
+
const document2 = documents[documentIndex];
|
|
4414
|
+
if (nodes.length >= 256) rejectHierarchy("Hierarchy preservation supports at most 256 tiles");
|
|
4415
|
+
if (!tile || typeof tile !== "object" || Array.isArray(tile) || tile.implicitTiling !== void 0 || tile.extensions !== void 0 || tile.metadata !== void 0 || tile.viewerRequestVolume !== void 0 || tile.content !== void 0 && tile.contents !== void 0 || tile.contents !== void 0 && (!Array.isArray(tile.contents) || !tile.contents.length || document2.asset?.version !== "1.1") || tile.children !== void 0 && !Array.isArray(tile.children))
|
|
4416
|
+
rejectHierarchy(
|
|
4417
|
+
"Hierarchy tiles require explicit children, exclusive content/contents declarations and no unmapped metadata, extensions or request volumes"
|
|
4418
|
+
);
|
|
4419
|
+
const refine = tile.refine ?? inheritedRefine;
|
|
4420
|
+
if (refine !== "ADD" && refine !== "REPLACE")
|
|
4421
|
+
rejectHierarchy("Root refinement must be explicit ADD or REPLACE; children may inherit");
|
|
4422
|
+
const matrix = tile.transform;
|
|
4423
|
+
if (matrix !== void 0 && (!Array.isArray(matrix) || matrix.length !== 16 || matrix.some((value) => typeof value !== "number" || !Number.isFinite(value)) || matrix[3] !== 0 || matrix[7] !== 0 || matrix[11] !== 0 || matrix[15] !== 1))
|
|
4424
|
+
rejectHierarchy("Hierarchy transforms must be finite affine matrices");
|
|
4425
|
+
const transform = new import_core32.Matrix4(parentTransform).multiplyRight(matrix ?? new import_core32.Matrix4());
|
|
4426
|
+
const columns = [0, 1, 2].map(
|
|
4427
|
+
(column) => [0, 1, 2].reduce((sum, row) => sum + Math.abs(transform[column * 4 + row]), 0)
|
|
4428
|
+
);
|
|
4429
|
+
const rows = [0, 1, 2].map(
|
|
4430
|
+
(row) => [0, 1, 2].reduce((sum, column) => sum + Math.abs(transform[column * 4 + row]), 0)
|
|
4431
|
+
);
|
|
4432
|
+
const scale = document2.asset?.version === "1.0" ? 1 : Math.sqrt(Math.max(...columns)) * Math.sqrt(Math.max(...rows));
|
|
4433
|
+
const geometricError2 = (tile.geometricError ?? NaN) * scale;
|
|
4434
|
+
if (typeof tile.geometricError !== "number" || (tile.geometricError ?? -1) < 0 || !Number.isFinite(geometricError2) || Array.from(transform).some((value) => !Number.isFinite(value)))
|
|
4435
|
+
rejectHierarchy("Every hierarchy tile must declare a finite nonnegative metric error");
|
|
4436
|
+
const contents = tile.contents ?? (tile.content ? [tile.content] : []);
|
|
4437
|
+
const identifiers = contents.map(
|
|
4438
|
+
(_, contentIndex) => `${prefix}tile-${path.length ? path.join("-") : "root"}-content-${contentIndex}`
|
|
4439
|
+
);
|
|
4440
|
+
const externalIndexes = identifiers.map((identifier) => externalDocuments.get(identifier));
|
|
4441
|
+
const externalIndex = externalIndexes.find((index2) => index2 !== void 0);
|
|
4442
|
+
for (const [contentIndex, content] of contents.entries()) {
|
|
4443
|
+
if (!content || typeof content !== "object" || Array.isArray(content) || content.metadata !== void 0 || content.group !== void 0 || content.extensions !== void 0)
|
|
4444
|
+
rejectHierarchy("Content metadata, groups and extensions require a separate mapping");
|
|
4445
|
+
if (externalIndexes[contentIndex] === void 0 && !inspection.resources.some(
|
|
4446
|
+
(resource) => resource.resourceId === identifiers[contentIndex] && resource.documentIndex === documentIndex && resource.uri === (content.uri ?? content.url) && resource.tilePath.length === path.length && resource.tilePath.every((value, index2) => value === path[index2])
|
|
4447
|
+
))
|
|
4448
|
+
rejectHierarchy("Hierarchy content must match the inspected placement");
|
|
4449
|
+
}
|
|
4450
|
+
const resourceId = identifiers[0];
|
|
4451
|
+
if (externalIndex !== void 0) {
|
|
4452
|
+
if (contents.length !== 1 || tile.contents !== void 0 || tile.children?.length || visitedDocuments.has(externalIndex))
|
|
4453
|
+
rejectHierarchy(
|
|
4454
|
+
"External references require a single content, no source children and a unique document placement"
|
|
4455
|
+
);
|
|
4456
|
+
visitedDocuments.add(externalIndex);
|
|
4457
|
+
const placement = inspection.documents[externalIndex];
|
|
4458
|
+
if (!Array.isArray(placement.parentTransform) || placement.parentTransform.length !== 16 || placement.parentTransform.some((value, index2) => value !== transform[index2]))
|
|
4459
|
+
rejectHierarchy("External document placement must match its referencing tile transform");
|
|
4460
|
+
const contentUri = tile.content?.uri ?? tile.content?.url;
|
|
4461
|
+
if (!contentUri || !new URL(contentUri, inspection.documents[documentIndex].url).pathname.toLowerCase().endsWith(".json"))
|
|
4462
|
+
rejectHierarchy("External documents require an inspected .json content reference");
|
|
4463
|
+
}
|
|
4464
|
+
const index = nodes.length;
|
|
4465
|
+
nodes.push({
|
|
4466
|
+
children: [],
|
|
4467
|
+
resourceIds: externalIndex === void 0 ? identifiers : [],
|
|
4468
|
+
geometricError: Math.max(
|
|
4469
|
+
geometricError2,
|
|
4470
|
+
externalIndex === void 0 ? 0 : documents[externalIndex].geometricError ?? 0
|
|
4471
|
+
),
|
|
4472
|
+
refine
|
|
4473
|
+
});
|
|
4474
|
+
if (parent !== -1) nodes[parent].children.push(index);
|
|
4475
|
+
if (externalIndex !== void 0) {
|
|
4476
|
+
stack.push({
|
|
4477
|
+
tile: documents[externalIndex].root,
|
|
4478
|
+
path: [],
|
|
4479
|
+
parent: index,
|
|
4480
|
+
documentIndex: externalIndex,
|
|
4481
|
+
prefix: resourceId + "/",
|
|
4482
|
+
transform,
|
|
4483
|
+
refine: void 0
|
|
4484
|
+
});
|
|
4485
|
+
}
|
|
4486
|
+
if (nodes.length + stack.length + (tile.children?.length ?? 0) > 256)
|
|
4487
|
+
rejectHierarchy("Hierarchy preservation supports at most 256 tiles");
|
|
4488
|
+
for (let child = (tile.children?.length ?? 0) - 1; child >= 0; child--)
|
|
4489
|
+
stack.push({
|
|
4490
|
+
tile: tile.children[child],
|
|
4491
|
+
documentIndex,
|
|
4492
|
+
prefix,
|
|
4493
|
+
path: [...path, child],
|
|
4494
|
+
parent: index,
|
|
4495
|
+
transform,
|
|
4496
|
+
refine
|
|
4497
|
+
});
|
|
4498
|
+
}
|
|
4499
|
+
if (visitedDocuments.size !== documents.length || nodes.reduce((count, node) => count + node.resourceIds.length, 0) !== resourceIds.length || nodes.some((node) => !node.resourceIds.length && !node.children.length))
|
|
4500
|
+
rejectHierarchy("Contentless leaves or unmatched content placements cannot be preserved");
|
|
4501
|
+
const geometricError = document.geometricError ?? nodes[0].geometricError;
|
|
4502
|
+
if (typeof geometricError !== "number" || !Number.isFinite(geometricError) || geometricError < 0)
|
|
4503
|
+
rejectHierarchy("Dataset geometricError must be finite nonnegative meters");
|
|
4504
|
+
return {
|
|
4505
|
+
nodes,
|
|
4506
|
+
geometricError: Math.max(geometricError, nodes[0].geometricError),
|
|
4507
|
+
coordinateEpoch: spatialReference.coordinateEpoch
|
|
4508
|
+
};
|
|
4509
|
+
}
|
|
4510
|
+
function createMeshHierarchyTilesetSink(options) {
|
|
4511
|
+
const hierarchy = prepareMeshHierarchy(options.inspection, options.resourceIds);
|
|
4512
|
+
const extensions = structuredClone(
|
|
4513
|
+
options.inspection.tileset.extensions
|
|
4514
|
+
);
|
|
4515
|
+
const extensionsRequired = structuredClone(
|
|
4516
|
+
options.inspection.tileset.extensionsRequired
|
|
4517
|
+
);
|
|
4518
|
+
const { maxPositionError } = options;
|
|
4519
|
+
if (!Number.isFinite(maxPositionError) || maxPositionError < 0 || hierarchy.nodes.some((node) => !Number.isFinite(node.geometricError + maxPositionError)) || !Number.isFinite(hierarchy.geometricError + maxPositionError))
|
|
4520
|
+
throw new import_core33.TileConversionError(
|
|
4521
|
+
"MESH_GEOMETRIC_ERROR_INVALID",
|
|
4522
|
+
"Hierarchy position allowance and resulting errors must be finite nonnegative meters"
|
|
4523
|
+
);
|
|
4524
|
+
const memory = (0, import_core33.createBoundedMemoryTileConversionSink)(options);
|
|
4525
|
+
const placements = /* @__PURE__ */ new Map();
|
|
4526
|
+
const completedContents = /* @__PURE__ */ new Set();
|
|
4527
|
+
const primitiveIdentifiers = /* @__PURE__ */ new Set();
|
|
4528
|
+
let state = "open";
|
|
4529
|
+
let completed = false;
|
|
4530
|
+
return {
|
|
4531
|
+
/** Exposes files only after complete finalization. */
|
|
4532
|
+
getFiles: () => completed ? memory.getFiles() : [],
|
|
4533
|
+
/** Retains ordered codec outputs and requires a terminal primitive for each content. */
|
|
4534
|
+
async write(resource, signal) {
|
|
4535
|
+
signal?.throwIfAborted();
|
|
4536
|
+
const identifier = resource.contentResourceId;
|
|
4537
|
+
const { index: primitiveIndex, last: lastPrimitive } = resource.contentPrimitive ?? {
|
|
4538
|
+
index: 0,
|
|
4539
|
+
last: true
|
|
4540
|
+
};
|
|
4541
|
+
if (state !== "open" || !identifier || completedContents.has(identifier) || primitiveIdentifiers.has(resource.id) || primitiveIdentifiers.size >= 64 || !Number.isSafeInteger(primitiveIndex) || primitiveIndex !== (placements.get(identifier)?.length ?? 0) || typeof lastPrimitive !== "boolean" || !hierarchy.nodes.some((node2) => node2.resourceIds.includes(identifier)))
|
|
4542
|
+
throw new import_core33.TileConversionError(
|
|
4543
|
+
"MESH_HIERARCHY_PLACEMENT_INVALID",
|
|
4544
|
+
"Hierarchy writes require ordered, distinct primitives for declared contents before close (at most 64)"
|
|
4545
|
+
);
|
|
4546
|
+
if (resource.spatialReference.coordinateEpoch !== hierarchy.coordinateEpoch)
|
|
4547
|
+
throw new import_core33.TileConversionError(
|
|
4548
|
+
"MESH_SOURCE_EPOCH_MISMATCH",
|
|
4549
|
+
"Hierarchy output coordinate epoch must match its source"
|
|
4550
|
+
);
|
|
4551
|
+
const tile = createMeshTileset(resource, maxPositionError).root;
|
|
4552
|
+
const nodeIndex = hierarchy.nodes.findIndex((node2) => node2.resourceIds.includes(identifier));
|
|
4553
|
+
const node = hierarchy.nodes[nodeIndex];
|
|
4554
|
+
const sibling = node.resourceIds.map((identifier2) => placements.get(identifier2)?.[0]).find(Boolean);
|
|
4555
|
+
if (sibling && sibling.origin.some((value, axis) => value !== resource.origin[axis]))
|
|
4556
|
+
throw new import_core33.TileConversionError(
|
|
4557
|
+
"MESH_HIERARCHY_PLACEMENT_INVALID",
|
|
4558
|
+
"Contents on one tile must share an encoded origin"
|
|
4559
|
+
);
|
|
4560
|
+
const suffix = node.resourceIds.length > 1 ? "-" + node.resourceIds.indexOf(identifier) : "";
|
|
4561
|
+
const primitiveSuffix = primitiveIndex > 0 || !lastPrimitive ? "-primitive-" + primitiveIndex : "";
|
|
4562
|
+
const uri = `meshes/${nodeIndex}${suffix}${primitiveSuffix}.glb`;
|
|
4563
|
+
state = "writing";
|
|
4564
|
+
await memory.write(
|
|
4565
|
+
{ resourceId: uri, parts: [resource.glb], contentType: "model/gltf-binary" },
|
|
4566
|
+
signal
|
|
4567
|
+
);
|
|
4568
|
+
const contentPlacements = placements.get(identifier) ?? [];
|
|
4569
|
+
contentPlacements.push({
|
|
4570
|
+
transform: [...tile.transform],
|
|
4571
|
+
origin: [...resource.origin],
|
|
4572
|
+
localBoundingBox: resource.localBoundingBox.map((bound) => [...bound]),
|
|
4573
|
+
uri
|
|
4574
|
+
});
|
|
4575
|
+
placements.set(identifier, contentPlacements);
|
|
4576
|
+
primitiveIdentifiers.add(resource.id);
|
|
4577
|
+
if (lastPrimitive) completedContents.add(identifier);
|
|
4578
|
+
state = "open";
|
|
4579
|
+
},
|
|
4580
|
+
/** Publishes original topology only after every source content has completed. */
|
|
4581
|
+
async finalize(report) {
|
|
4582
|
+
if (state !== "open" || completedContents.size !== hierarchy.nodes.reduce((count, node) => count + node.resourceIds.length, 0))
|
|
4583
|
+
throw new import_core33.TileConversionError(
|
|
4584
|
+
"MESH_HIERARCHY_INCOMPLETE",
|
|
4585
|
+
"Finalization requires every declared hierarchy content"
|
|
4586
|
+
);
|
|
4587
|
+
state = "writing";
|
|
4588
|
+
const tiles = new Array(hierarchy.nodes.length);
|
|
4589
|
+
const bounds = new Array(hierarchy.nodes.length);
|
|
4590
|
+
const transforms = hierarchy.nodes.map((node) => {
|
|
4591
|
+
const placement = placements.get(node.resourceIds[0])?.[0];
|
|
4592
|
+
return placement ? new import_core32.Matrix4(placement.transform) : new import_core32.Matrix4();
|
|
4593
|
+
});
|
|
4594
|
+
for (let index = hierarchy.nodes.length - 1; index >= 0; index--) {
|
|
4595
|
+
const node = hierarchy.nodes[index];
|
|
4596
|
+
const placement = placements.get(node.resourceIds[0])?.[0];
|
|
4597
|
+
const minimum = [Infinity, Infinity, Infinity];
|
|
4598
|
+
const maximum = [-Infinity, -Infinity, -Infinity];
|
|
4599
|
+
const contributors = node.children.map((child) => bounds[child]);
|
|
4600
|
+
const nodePlacements = node.resourceIds.flatMap((identifier) => placements.get(identifier));
|
|
4601
|
+
for (const placement2 of nodePlacements) {
|
|
4602
|
+
contributors.push(
|
|
4603
|
+
placement2.localBoundingBox.map(
|
|
4604
|
+
(bound, boundIndex) => bound.map((value, axis) => {
|
|
4605
|
+
const padding = 8 * Number.EPSILON * Math.max(
|
|
4606
|
+
1,
|
|
4607
|
+
Math.abs(placement2.origin[axis]),
|
|
4608
|
+
...placement2.localBoundingBox.map((limit) => Math.abs(limit[axis]))
|
|
4609
|
+
);
|
|
4610
|
+
return value + placement2.origin[axis] + (boundIndex ? padding : -padding);
|
|
4611
|
+
})
|
|
4612
|
+
)
|
|
4613
|
+
);
|
|
4614
|
+
}
|
|
4615
|
+
for (const [lower, upper] of contributors)
|
|
4616
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
4617
|
+
minimum[axis] = Math.min(minimum[axis], lower[axis]);
|
|
4618
|
+
maximum[axis] = Math.max(maximum[axis], upper[axis]);
|
|
4619
|
+
}
|
|
4620
|
+
if ([...minimum, ...maximum].some((value) => !Number.isFinite(value)))
|
|
4621
|
+
throw new import_core33.TileConversionError(
|
|
4622
|
+
"MESH_TILESET_BOUNDS_INVALID",
|
|
4623
|
+
"Hierarchy absolute bounds must remain finite"
|
|
4624
|
+
);
|
|
4625
|
+
bounds[index] = [minimum, maximum];
|
|
4626
|
+
const inverse = new import_core32.Matrix4(transforms[index]).invert();
|
|
4627
|
+
const center = Array.from(
|
|
4628
|
+
inverse.transformAsPoint(minimum.map((value, axis) => value / 2 + maximum[axis] / 2))
|
|
4629
|
+
);
|
|
4630
|
+
const halfSize = minimum.map((value, axis) => maximum[axis] / 2 - value / 2);
|
|
4631
|
+
const halfAxes = [0, 1, 2].flatMap(
|
|
4632
|
+
(axis) => Array.from(
|
|
4633
|
+
inverse.transformAsVector(
|
|
4634
|
+
halfSize.map((value, coordinate) => coordinate === axis ? value : 0)
|
|
4635
|
+
)
|
|
4636
|
+
)
|
|
4637
|
+
);
|
|
4638
|
+
if ([...center, ...halfAxes].some((value) => !Number.isFinite(value)))
|
|
4639
|
+
throw new import_core33.TileConversionError(
|
|
4640
|
+
"MESH_TILESET_BOUNDS_INVALID",
|
|
4641
|
+
"Hierarchy local bounds must remain finite"
|
|
4642
|
+
);
|
|
4643
|
+
tiles[index] = {
|
|
4644
|
+
transform: Array.from(transforms[index]),
|
|
4645
|
+
boundingVolume: { box: [...center, ...halfAxes] },
|
|
4646
|
+
geometricError: node.geometricError + maxPositionError,
|
|
4647
|
+
refine: node.refine,
|
|
4648
|
+
...nodePlacements.length > 1 ? { contents: nodePlacements.map((placement2) => ({ uri: placement2.uri })) } : placement ? { content: { uri: placement.uri } } : {},
|
|
4649
|
+
...node.children.length ? {
|
|
4650
|
+
children: node.children.map((child) => {
|
|
4651
|
+
tiles[child].transform = Array.from(
|
|
4652
|
+
new import_core32.Matrix4(inverse).multiplyRight(transforms[child])
|
|
4653
|
+
);
|
|
4654
|
+
if (tiles[child].transform.some((value) => !Number.isFinite(value)))
|
|
4655
|
+
throw new import_core33.TileConversionError(
|
|
4656
|
+
"MESH_TILESET_BOUNDS_INVALID",
|
|
4657
|
+
"Relative hierarchy transforms must remain finite"
|
|
4658
|
+
);
|
|
4659
|
+
return tiles[child];
|
|
4660
|
+
})
|
|
4661
|
+
} : {}
|
|
4662
|
+
};
|
|
4663
|
+
}
|
|
4664
|
+
const json = JSON.stringify({
|
|
4665
|
+
asset: { version: "1.1" },
|
|
4666
|
+
...extensions && Object.keys(extensions).length ? { extensions, extensionsUsed: Object.keys(extensions) } : {},
|
|
4667
|
+
...extensionsRequired?.length ? { extensionsRequired } : {},
|
|
4668
|
+
geometricError: hierarchy.geometricError + maxPositionError,
|
|
4669
|
+
root: tiles[0]
|
|
4670
|
+
});
|
|
4671
|
+
await memory.write({
|
|
4672
|
+
resourceId: "tileset.json",
|
|
4673
|
+
parts: [json],
|
|
4674
|
+
contentType: "application/json"
|
|
4675
|
+
});
|
|
4676
|
+
await memory.finalize(report);
|
|
4677
|
+
state = "closed";
|
|
4678
|
+
completed = true;
|
|
4679
|
+
},
|
|
4680
|
+
/** Discards retained resources and hides all files after failure. */
|
|
4681
|
+
async abort(error) {
|
|
4682
|
+
state = "closed";
|
|
4683
|
+
completed = false;
|
|
4684
|
+
placements.clear();
|
|
4685
|
+
completedContents.clear();
|
|
4686
|
+
primitiveIdentifiers.clear();
|
|
4687
|
+
await memory.abort(error);
|
|
4688
|
+
}
|
|
4689
|
+
};
|
|
4690
|
+
}
|
|
4691
|
+
|
|
4692
|
+
// src/v5/selected-mesh-source.ts
|
|
4693
|
+
var import_core34 = require("@loaders.gl/core");
|
|
4694
|
+
var import_d_tiles2 = require("@loaders.gl/tiles-3d/3d-tiles");
|
|
4695
|
+
var import_tiles_3d4 = require("@loaders.gl/tiles-3d");
|
|
4696
|
+
var import_core35 = require("@loaders.gl/tile-converter/v5/core");
|
|
4697
|
+
var import_adapters = require("@loaders.gl/tile-converter/v5/adapters");
|
|
4698
|
+
var SELECTED_MESH_LIMITS = {
|
|
4699
|
+
/** Root JSON plus selected content transport bytes; also the decoded geometry/image/feature byte gate. */
|
|
4700
|
+
maxInputBytes: 16 * 1024 * 1024,
|
|
4701
|
+
/** Maximum declared content placements inspected. */
|
|
4702
|
+
maxInputResources: 1e3,
|
|
4703
|
+
/** Maximum inspected root/external document placements. */
|
|
4704
|
+
maxTilesetDocuments: 32,
|
|
4705
|
+
/** Maximum explicitly selected content placements in one partial archive. */
|
|
4706
|
+
maxSelectedResources: 64,
|
|
4707
|
+
/** Maximum primitive placements authored across all selected contents. */
|
|
4708
|
+
maxMeshResources: 64,
|
|
4709
|
+
/** Maximum retained file bytes and final archive bytes. */
|
|
4710
|
+
maxOutputBytes: 32 * 1024 * 1024,
|
|
4711
|
+
/** Maximum measured reconstruction error in meters. */
|
|
4712
|
+
maxPositionError: 0.01
|
|
4713
|
+
};
|
|
4714
|
+
async function createSelectedMeshConversionSource(inspection, options) {
|
|
4715
|
+
const {
|
|
4716
|
+
resourceIds,
|
|
4717
|
+
signal,
|
|
4718
|
+
dracoLibraryOptions,
|
|
4719
|
+
features,
|
|
4720
|
+
preserveHierarchy,
|
|
4721
|
+
fetcher = fetch
|
|
4722
|
+
} = options;
|
|
4723
|
+
const maxInputBytes = options.maxInputBytes ?? SELECTED_MESH_LIMITS.maxInputBytes;
|
|
4724
|
+
if (!Number.isSafeInteger(maxInputBytes) || maxInputBytes < 1 || maxInputBytes > SELECTED_MESH_LIMITS.maxInputBytes)
|
|
4725
|
+
throw new import_core35.TileConversionError(
|
|
4726
|
+
"INVALID_SELECTED_MESH_OPTIONS",
|
|
4727
|
+
"Selected mesh input budget must be a positive safe integer at most 16 MiB"
|
|
4728
|
+
);
|
|
4729
|
+
if (!resourceIds.length || resourceIds.length > SELECTED_MESH_LIMITS.maxSelectedResources || new Set(resourceIds).size !== resourceIds.length || resourceIds.some(
|
|
4730
|
+
(identifier) => !inspection.resources.some((resource) => resource.resourceId === identifier)
|
|
4731
|
+
))
|
|
4732
|
+
throw new Error("Select between 1 and 64 distinct content placements.");
|
|
4733
|
+
if (preserveHierarchy !== void 0 && typeof preserveHierarchy !== "boolean")
|
|
4734
|
+
throw new import_core35.TileConversionError(
|
|
4735
|
+
"MESH_HIERARCHY_UNSUPPORTED",
|
|
4736
|
+
"preserveHierarchy must be a boolean"
|
|
4737
|
+
);
|
|
4738
|
+
if (preserveHierarchy) prepareMeshHierarchy(inspection, resourceIds);
|
|
4739
|
+
const descriptors = inspection.resources.filter(
|
|
4740
|
+
(resource) => resourceIds.includes(resource.resourceId)
|
|
4741
|
+
);
|
|
4742
|
+
const selections = descriptors.map(
|
|
4743
|
+
(descriptor) => (0, import_core35.selectBrowserTilesetContent)(inspection, descriptor)
|
|
4744
|
+
);
|
|
4745
|
+
const selectedContents = new Map(
|
|
4746
|
+
descriptors.map((descriptor, index) => [descriptor.resourceId, selections[index]])
|
|
4747
|
+
);
|
|
4748
|
+
if (!preserveHierarchy && resourceIds.length > 1 && selections.some((selection) => !selection.isLeaf))
|
|
4749
|
+
throw new Error(
|
|
4750
|
+
"Multi-selection requires leaf contents; source LOD hierarchy is not exported."
|
|
4751
|
+
);
|
|
4752
|
+
signal.throwIfAborted();
|
|
4753
|
+
const boundedFetcher = (0, import_adapters.createTileConversionResourceFetcher)({
|
|
4754
|
+
maxInputBytes,
|
|
4755
|
+
initialInputBytes: inspection.tilesetBytes,
|
|
4756
|
+
fetcher,
|
|
4757
|
+
signal
|
|
4758
|
+
});
|
|
4759
|
+
const qualification = createSelectedRuntime(
|
|
4760
|
+
inspection.tileset,
|
|
4761
|
+
new Uint8Array(),
|
|
4762
|
+
signal,
|
|
4763
|
+
boundedFetcher,
|
|
4764
|
+
inspection.rootUrl,
|
|
4765
|
+
dracoLibraryOptions
|
|
4766
|
+
);
|
|
4767
|
+
let spatialContext;
|
|
4768
|
+
try {
|
|
4769
|
+
await qualification.tilesetInitializationPromise;
|
|
4770
|
+
const metadata = await (0, import_adapters.createMeshTilesetConversionSource)(qualification).inspect(signal);
|
|
4771
|
+
spatialContext = (0, import_core35.createTiles3DConversionSpatialContext)(metadata.spatialReference);
|
|
4772
|
+
} finally {
|
|
4773
|
+
qualification.destroy();
|
|
4774
|
+
}
|
|
4775
|
+
const rawSource = options.source ?? (0, import_core35.createBrowserTilesetConversionSource)({
|
|
4776
|
+
input: inspection.rootUrl,
|
|
4777
|
+
...SELECTED_MESH_LIMITS,
|
|
4778
|
+
maxInputBytes,
|
|
4779
|
+
fetcher: boundedFetcher
|
|
4780
|
+
});
|
|
4781
|
+
const selectedInspection = { ...inspection, resources: descriptors };
|
|
4782
|
+
const source = {
|
|
4783
|
+
/** Reuses inspected declarations without fetching the root again. */
|
|
4784
|
+
inspect: async (inspectionSignal) => {
|
|
4785
|
+
signal.throwIfAborted();
|
|
4786
|
+
inspectionSignal?.throwIfAborted();
|
|
4787
|
+
return selectedInspection;
|
|
4788
|
+
},
|
|
4789
|
+
/** Decodes and releases each selected placement in declaration order. */
|
|
4790
|
+
async *read(_inspection, readSignal) {
|
|
4791
|
+
const signal2 = readSignal ? AbortSignal.any([options.signal, readSignal]) : options.signal;
|
|
4792
|
+
signal2.throwIfAborted();
|
|
4793
|
+
let decodedBytes = 0;
|
|
4794
|
+
let primitiveCount = 0;
|
|
4795
|
+
const tileOrigins = /* @__PURE__ */ new Map();
|
|
4796
|
+
for await (const raw of rawSource.read(selectedInspection, signal2)) {
|
|
4797
|
+
const selection = selectedContents.get(raw.resourceId);
|
|
4798
|
+
if (!selection)
|
|
4799
|
+
throw new import_core35.TileConversionError(
|
|
4800
|
+
"INVALID_BROWSER_SELECTION",
|
|
4801
|
+
"Source yielded an uninspected content placement"
|
|
4802
|
+
);
|
|
4803
|
+
const contentUrl = raw.contentUrl ?? selection.contentUrl;
|
|
4804
|
+
const runtime = createSelectedRuntime(
|
|
4805
|
+
selection.tileset,
|
|
4806
|
+
raw.data,
|
|
4807
|
+
signal2,
|
|
4808
|
+
boundedFetcher,
|
|
4809
|
+
contentUrl,
|
|
4810
|
+
dracoLibraryOptions,
|
|
4811
|
+
spatialContext.spatialReference
|
|
4812
|
+
);
|
|
4813
|
+
try {
|
|
4814
|
+
await runtime.tilesetInitializationPromise;
|
|
4815
|
+
const meshSource = (0, import_adapters.createMeshTilesetConversionSource)(runtime, {
|
|
4816
|
+
unloadContent: true,
|
|
4817
|
+
features,
|
|
4818
|
+
readExternalResource: async (uri, _contentUri, resourceSignal) => {
|
|
4819
|
+
const response = await boundedFetcher(new URL(uri, contentUrl), {
|
|
4820
|
+
signal: resourceSignal
|
|
4821
|
+
});
|
|
4822
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
4823
|
+
}
|
|
4824
|
+
});
|
|
4825
|
+
const metadata = await meshSource.inspect(signal2);
|
|
4826
|
+
const selectedContext = (0, import_core35.createTiles3DConversionSpatialContext)(metadata.spatialReference);
|
|
4827
|
+
if (selectedContext.spatialReference.coordinateEpoch !== spatialContext.spatialReference.coordinateEpoch)
|
|
4828
|
+
throw new import_core35.TileConversionError(
|
|
4829
|
+
"MESH_SOURCE_EPOCH_MISMATCH",
|
|
4830
|
+
"Selected content coordinate epoch must match the root; epoch transformations are unsupported"
|
|
4831
|
+
);
|
|
4832
|
+
let meshCount = 0;
|
|
4833
|
+
for await (const resource of meshSource.read(metadata, signal2)) {
|
|
4834
|
+
meshCount++;
|
|
4835
|
+
if (++primitiveCount > SELECTED_MESH_LIMITS.maxMeshResources)
|
|
4836
|
+
throw new Error("Selected contents exceed the mesh primitive placement limit.");
|
|
4837
|
+
decodedBytes += measureMeshBytes(resource);
|
|
4838
|
+
if (decodedBytes > maxInputBytes)
|
|
4839
|
+
throw new import_core35.TileConversionError(
|
|
4840
|
+
"INPUT_RESOURCE_TOO_LARGE",
|
|
4841
|
+
"Selected decoded geometry and encoded images exceed the aggregate input byte limit."
|
|
4842
|
+
);
|
|
4843
|
+
const tileIdentifier = raw.resourceId.replace(/content-\d+$/, "");
|
|
4844
|
+
if (preserveHierarchy && !tileOrigins.has(tileIdentifier))
|
|
4845
|
+
tileOrigins.set(tileIdentifier, [...resource.origin]);
|
|
4846
|
+
yield {
|
|
4847
|
+
...resource,
|
|
4848
|
+
origin: preserveHierarchy ? tileOrigins.get(tileIdentifier) : resource.origin,
|
|
4849
|
+
id: `${raw.resourceId}/${resource.id}`,
|
|
4850
|
+
contentResourceId: raw.resourceId
|
|
4851
|
+
};
|
|
4852
|
+
}
|
|
4853
|
+
if (!meshCount) throw new Error("Selected content has no mesh primitive.");
|
|
4854
|
+
} finally {
|
|
4855
|
+
runtime.destroy();
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4859
|
+
};
|
|
4860
|
+
return {
|
|
4861
|
+
source,
|
|
4862
|
+
spatialContext,
|
|
4863
|
+
geometricError: Math.max(...selections.map((selection) => selection.geometricError)) + SELECTED_MESH_LIMITS.maxPositionError
|
|
4864
|
+
};
|
|
4865
|
+
}
|
|
4866
|
+
var SelectedTiles3DSource = class extends import_tiles_3d4.Tiles3DSource {
|
|
4867
|
+
/** Parent frame; explicit, unknown or unresolved child declarations are never replaced. */
|
|
4868
|
+
inheritedSpatialReference;
|
|
4869
|
+
/** Creates a selected source with optional external-document frame inheritance. */
|
|
4870
|
+
constructor(input, options, inheritedSpatialReference) {
|
|
4871
|
+
super(input, options);
|
|
4872
|
+
this.inheritedSpatialReference = inheritedSpatialReference;
|
|
4873
|
+
}
|
|
4874
|
+
/** Returns source metadata with the same absent-CRS inheritance used by external tilesets. */
|
|
4875
|
+
getMetadata() {
|
|
4876
|
+
const metadata = super.getMetadata();
|
|
4877
|
+
const reference = metadata.spatialReference;
|
|
4878
|
+
if (this.inheritedSpatialReference && reference?.crs.state === "absent" && reference.coordinateEpoch === void 0 && !reference.verticalCrs && !reference.warnings.length)
|
|
4879
|
+
return { ...metadata, spatialReference: this.inheritedSpatialReference };
|
|
4880
|
+
return metadata;
|
|
4881
|
+
}
|
|
4882
|
+
};
|
|
4883
|
+
function createSelectedRuntime(document, data, signal, boundedFetcher, contentUrl, dracoLibraryOptions, inheritedSpatialReference) {
|
|
4884
|
+
const {
|
|
4885
|
+
modules,
|
|
4886
|
+
decoderProfile,
|
|
4887
|
+
CDN: contentDeliveryNetwork,
|
|
4888
|
+
useLocalLibraries
|
|
4889
|
+
} = dracoLibraryOptions;
|
|
4890
|
+
const loadOptions = {
|
|
4891
|
+
modules,
|
|
4892
|
+
core: {
|
|
4893
|
+
worker: false,
|
|
4894
|
+
fetch: boundedFetcher,
|
|
4895
|
+
CDN: contentDeliveryNetwork,
|
|
4896
|
+
useLocalLibraries
|
|
4897
|
+
},
|
|
4898
|
+
// Reuse the full decoder already bundled for output verification.
|
|
4899
|
+
draco: { decoderProfile },
|
|
4900
|
+
// Preserve authored UVs and the transform for the converter's qualified GLB writer.
|
|
4901
|
+
gltf: {
|
|
4902
|
+
loadImages: false,
|
|
4903
|
+
decompressMeshes: true,
|
|
4904
|
+
excludeExtensions: {
|
|
4905
|
+
KHR_texture_transform: false,
|
|
4906
|
+
// Only Draco input compression is qualified by this conversion profile.
|
|
4907
|
+
EXT_meshopt_compression: false,
|
|
4908
|
+
KHR_meshopt_compression: false
|
|
4909
|
+
}
|
|
4910
|
+
},
|
|
4911
|
+
"3d-tiles": { loadGLTF: true }
|
|
4912
|
+
};
|
|
4913
|
+
return new import_tiles_3d4.Tileset3D(
|
|
4914
|
+
new SelectedTiles3DSource(
|
|
4915
|
+
{
|
|
4916
|
+
url: contentUrl,
|
|
4917
|
+
loader: import_d_tiles2.Tiles3DLoader,
|
|
4918
|
+
resolver: {
|
|
4919
|
+
/** Normalizes the selected hierarchy through the format loader, retaining ancestor transforms. */
|
|
4920
|
+
loadRoot: async (_url, loader, options) => (0, import_core34.parse)(new TextEncoder().encode(JSON.stringify(document)), loader, options),
|
|
4921
|
+
/** Decodes only the selected bounded payload; no sibling content can be requested. */
|
|
4922
|
+
loadResource: async (_url, loader, options) => {
|
|
4923
|
+
signal.throwIfAborted();
|
|
4924
|
+
return (0, import_core34.parse)(data, loader, options, {
|
|
4925
|
+
url: contentUrl,
|
|
4926
|
+
baseUrl: contentUrl.startsWith("blob:") ? contentUrl : new URL(".", contentUrl).href,
|
|
4927
|
+
fetch: boundedFetcher,
|
|
4928
|
+
coreApi: import_core34.coreApi,
|
|
4929
|
+
_parse: import_core34.parse
|
|
4930
|
+
});
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
},
|
|
4934
|
+
loadOptions,
|
|
4935
|
+
inheritedSpatialReference
|
|
4936
|
+
)
|
|
4937
|
+
);
|
|
4938
|
+
}
|
|
4939
|
+
function measureMeshBytes(resource) {
|
|
4940
|
+
const featureBytes = resource.features ? resource.features.triangleFeatureIndices.byteLength + resource.features.batches.reduce(
|
|
4941
|
+
(bytes, batch) => bytes + batch.data.schema.fields.reduce(
|
|
4942
|
+
(columnBytes, field) => columnBytes + (batch.data.getChild(field.name)?.byteLength ?? 0),
|
|
4943
|
+
0
|
|
4944
|
+
),
|
|
4945
|
+
0
|
|
4946
|
+
) : 0;
|
|
4947
|
+
return Object.values(resource.mesh.attributes).reduce(
|
|
4948
|
+
(bytes, attribute) => bytes + attribute.value.byteLength,
|
|
4949
|
+
(resource.mesh.indices?.value.byteLength ?? 0) + (resource.material?.baseColorTexture?.data.byteLength ?? 0) + featureBytes
|
|
4950
|
+
);
|
|
4951
|
+
}
|
|
4952
|
+
|
|
4953
|
+
// src/v5/archive-source.ts
|
|
4954
|
+
var import_loader_utils = require("@loaders.gl/core/loader-utils");
|
|
4955
|
+
var import_i3s3 = require("@loaders.gl/tiles-3d/i3s");
|
|
4956
|
+
var import_d_tiles3 = require("@loaders.gl/tiles-3d/3d-tiles");
|
|
4957
|
+
var import_core36 = require("@loaders.gl/tile-converter/v5/core");
|
|
4958
|
+
async function openTileConversionArchive(descriptor, maxInputBytes, signal, fetcher = fetch) {
|
|
4959
|
+
signal.throwIfAborted();
|
|
4960
|
+
if (!["slpk", "3tz"].includes(descriptor.format))
|
|
4961
|
+
throw new import_core36.TileConversionError("INVALID_ARCHIVE_SOURCE", "Select SLPK or 3TZ archive input");
|
|
4962
|
+
validateArchiveEntityTag(descriptor.identity?.etag);
|
|
4963
|
+
const transport = createTileConversionResourceFetcher({
|
|
4964
|
+
maxInputBytes,
|
|
4965
|
+
signal,
|
|
4966
|
+
fetcher: async (url, options) => {
|
|
4967
|
+
const response = await fetcher(url, options);
|
|
4968
|
+
if (response.status !== 206) {
|
|
4969
|
+
await response.body?.cancel();
|
|
4970
|
+
throw new Error("Remote conversion archives require HTTP 206 byte-range responses.");
|
|
4971
|
+
}
|
|
4972
|
+
try {
|
|
4973
|
+
validateArchiveResponseValidator(response.headers);
|
|
4974
|
+
} catch (error) {
|
|
4975
|
+
await response.body?.cancel(error);
|
|
4976
|
+
throw error;
|
|
4977
|
+
}
|
|
4978
|
+
return response;
|
|
4979
|
+
}
|
|
4980
|
+
});
|
|
4981
|
+
let file;
|
|
4982
|
+
let closePromise;
|
|
4983
|
+
async function closeFile() {
|
|
4984
|
+
if (file) {
|
|
4985
|
+
closePromise ??= file.close();
|
|
4986
|
+
await closePromise;
|
|
4987
|
+
}
|
|
4988
|
+
}
|
|
4989
|
+
try {
|
|
4990
|
+
if (typeof descriptor.input === "string") {
|
|
4991
|
+
if (!["http:", "https:"].includes(new URL(descriptor.input).protocol))
|
|
4992
|
+
throw new Error("Use an HTTP(S) archive URL.");
|
|
4993
|
+
const remote = new import_loader_utils.HttpFile(descriptor.input, {
|
|
4994
|
+
fetch: (url, options) => transport(url, options),
|
|
4995
|
+
consistency: "strict",
|
|
4996
|
+
...descriptor.identity
|
|
4997
|
+
});
|
|
4998
|
+
file = remote;
|
|
4999
|
+
await remote.open(signal);
|
|
5000
|
+
} else file = new import_loader_utils.BlobFile(descriptor.input);
|
|
5001
|
+
const ownedFile = file;
|
|
5002
|
+
file = void 0;
|
|
5003
|
+
return await createTileConversionArchiveTransport(
|
|
5004
|
+
ownedFile,
|
|
5005
|
+
descriptor.format,
|
|
5006
|
+
maxInputBytes,
|
|
5007
|
+
signal,
|
|
5008
|
+
descriptor
|
|
5009
|
+
);
|
|
5010
|
+
} catch (error) {
|
|
5011
|
+
await closeFile();
|
|
5012
|
+
throw error;
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
async function createTileConversionArchiveTransport(file, format, maxInputBytes, signal, descriptor) {
|
|
5016
|
+
const rootUrl = `https://conversion.invalid/input.${format}`;
|
|
5017
|
+
let closed = false;
|
|
5018
|
+
let releasePromise;
|
|
5019
|
+
let closePromise;
|
|
5020
|
+
const closeFile = () => {
|
|
5021
|
+
closed = true;
|
|
5022
|
+
closePromise ??= file.close();
|
|
5023
|
+
return closePromise;
|
|
5024
|
+
};
|
|
5025
|
+
const checkOpen = () => {
|
|
5026
|
+
signal.throwIfAborted();
|
|
5027
|
+
if (closed)
|
|
5028
|
+
throw new import_core36.TileConversionError("ARCHIVE_SOURCE_CLOSED", "Conversion archive is closed");
|
|
5029
|
+
};
|
|
5030
|
+
try {
|
|
5031
|
+
checkOpen();
|
|
5032
|
+
if (!["slpk", "3tz"].includes(format) || !Number.isSafeInteger(maxInputBytes) || maxInputBytes < 1)
|
|
5033
|
+
throw new import_core36.TileConversionError(
|
|
5034
|
+
"INVALID_ARCHIVE_SOURCE",
|
|
5035
|
+
"Archive source requires a supported format and positive safe byte budget"
|
|
5036
|
+
);
|
|
5037
|
+
if (!Number.isSafeInteger(file.size) || file.size < 0 || file.size > maxInputBytes)
|
|
5038
|
+
throw new Error("Conversion archive input must be at most 16 MiB.");
|
|
5039
|
+
let readBytes = 0;
|
|
5040
|
+
const boundedFile = {
|
|
5041
|
+
handle: file.handle,
|
|
5042
|
+
size: file.size,
|
|
5043
|
+
bigsize: file.bigsize,
|
|
5044
|
+
url: file.url,
|
|
5045
|
+
...file.stat ? { stat: () => file.stat() } : {},
|
|
5046
|
+
close: closeFile,
|
|
5047
|
+
/** Checks indexed reads before allocation and forwards operation cancellation. */
|
|
5048
|
+
read: async (start, length) => {
|
|
5049
|
+
checkOpen();
|
|
5050
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > maxInputBytes - readBytes)
|
|
5051
|
+
throw new import_core36.TileConversionError(
|
|
5052
|
+
"INPUT_RESOURCE_TOO_LARGE",
|
|
5053
|
+
"Archive indexed reads exceed the input budget"
|
|
5054
|
+
);
|
|
5055
|
+
readBytes += length;
|
|
5056
|
+
const bytes = await file.read(start, length, signal);
|
|
5057
|
+
checkOpen();
|
|
5058
|
+
return bytes;
|
|
5059
|
+
}
|
|
5060
|
+
};
|
|
5061
|
+
const archive = format === "slpk" ? await (0, import_i3s3.parseSLPKArchive)(boundedFile) : await (0, import_d_tiles3.parse3DTilesArchive)(boundedFile);
|
|
5062
|
+
const root = new URL(rootUrl);
|
|
5063
|
+
return {
|
|
5064
|
+
rootUrl: format === "3tz" ? `${rootUrl}/tileset.json` : rootUrl,
|
|
5065
|
+
descriptor: descriptor ? {
|
|
5066
|
+
...descriptor,
|
|
5067
|
+
...file instanceof import_loader_utils.HttpFile ? { identity: file.getIdentitySnapshot() } : {}
|
|
5068
|
+
} : void 0,
|
|
5069
|
+
/** Resolves only resources belonging to this archive, without external network fallback. */
|
|
5070
|
+
fetcher: (async (input, options) => {
|
|
5071
|
+
checkOpen();
|
|
5072
|
+
options?.signal?.throwIfAborted();
|
|
5073
|
+
const url = new URL(input instanceof Request ? input.url : String(input));
|
|
5074
|
+
if (url.origin !== root.origin || url.pathname !== root.pathname && !url.pathname.startsWith(`${root.pathname}/`))
|
|
5075
|
+
throw new Error("Conversion dependencies must resolve inside the selected archive.");
|
|
5076
|
+
const path = decodeURIComponent(
|
|
5077
|
+
url.pathname.slice(root.pathname.length).replace(/^\//, "")
|
|
5078
|
+
);
|
|
5079
|
+
if (path.includes("\\") || path.split("/").some((segment) => segment === ".." || segment === "."))
|
|
5080
|
+
throw new Error("Archive dependency path cannot escape its root.");
|
|
5081
|
+
const bytes = format === "slpk" ? await archive.getFile(path, "http") : await archive.getFile(path);
|
|
5082
|
+
checkOpen();
|
|
5083
|
+
options?.signal?.throwIfAborted();
|
|
5084
|
+
return new Response(bytes);
|
|
5085
|
+
}),
|
|
5086
|
+
/** Releases the readable file on success, failure or cancellation. */
|
|
5087
|
+
close: async () => {
|
|
5088
|
+
closed = true;
|
|
5089
|
+
releasePromise ??= archive.close();
|
|
5090
|
+
await releasePromise;
|
|
5091
|
+
}
|
|
5092
|
+
};
|
|
5093
|
+
} catch (error) {
|
|
5094
|
+
await closeFile();
|
|
5095
|
+
throw error;
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
5098
|
+
function validateArchiveEntityTag(entityTag) {
|
|
5099
|
+
if (entityTag !== void 0 && entityTag !== null && !/^"[\x21\x23-\x7e\x80-\xff]*"$/.test(entityTag))
|
|
5100
|
+
throw new import_core36.TileConversionError(
|
|
5101
|
+
"ARCHIVE_VALIDATOR_UNSUPPORTED",
|
|
5102
|
+
"Remote archives require a strong quoted ETag, or Last-Modified without an ETag; weak/malformed ETags are unsupported"
|
|
5103
|
+
);
|
|
5104
|
+
}
|
|
5105
|
+
function validateArchiveResponseValidator(headers) {
|
|
5106
|
+
const entityTag = headers.get("etag");
|
|
5107
|
+
validateArchiveEntityTag(entityTag);
|
|
5108
|
+
if (entityTag !== null) return;
|
|
5109
|
+
const lastModified = headers.get("last-modified");
|
|
5110
|
+
const responseDate = headers.get("date");
|
|
5111
|
+
const modifiedTime = parseArchiveHttpDate(lastModified);
|
|
5112
|
+
const responseTime = parseArchiveHttpDate(responseDate);
|
|
5113
|
+
if (!Number.isFinite(modifiedTime) || !Number.isFinite(responseTime) || responseTime - modifiedTime < 6e4)
|
|
5114
|
+
throw new import_core36.TileConversionError(
|
|
5115
|
+
"ARCHIVE_VALIDATOR_UNSUPPORTED",
|
|
5116
|
+
"Last-Modified archive identity requires a valid Date at least 60 seconds later; otherwise supply a strong ETag"
|
|
5117
|
+
);
|
|
5118
|
+
}
|
|
5119
|
+
function parseArchiveHttpDate(value) {
|
|
5120
|
+
const milliseconds = value === null ? NaN : Date.parse(value);
|
|
5121
|
+
return Number.isFinite(milliseconds) && new Date(milliseconds).toUTCString() === value ? milliseconds : NaN;
|
|
5122
|
+
}
|
|
5123
|
+
|
|
5124
|
+
// src/v5/i3s-selection-source.ts
|
|
5125
|
+
var import_core37 = require("@loaders.gl/core");
|
|
5126
|
+
var import_i3s4 = require("@loaders.gl/tiles-3d/i3s");
|
|
5127
|
+
var import_tiles_3d5 = require("@loaders.gl/tiles-3d");
|
|
5128
|
+
var import_core38 = require("@loaders.gl/tile-converter/v5/core");
|
|
5129
|
+
function createRuntime(transport, layer, header) {
|
|
5130
|
+
const libraries = transport.dracoLibraryOptions;
|
|
5131
|
+
const sourceUrl = new URL(transport.rootUrl);
|
|
5132
|
+
sourceUrl.search = "";
|
|
5133
|
+
sourceUrl.hash = "";
|
|
5134
|
+
const options = {
|
|
5135
|
+
modules: libraries.modules,
|
|
5136
|
+
core: {
|
|
5137
|
+
worker: false,
|
|
5138
|
+
fetch: transport.fetcher,
|
|
5139
|
+
CDN: libraries.CDN,
|
|
5140
|
+
useLocalLibraries: libraries.useLocalLibraries
|
|
5141
|
+
},
|
|
5142
|
+
draco: { decoderProfile: libraries.decoderProfile },
|
|
5143
|
+
// The injected transport owns credential scope; runtime query parameters apply to every URL.
|
|
5144
|
+
i3s: { geometryMode: "source", decodeTextures: false, useCompressedTextures: false }
|
|
5145
|
+
};
|
|
5146
|
+
const loadResource = async (url, loader, loadOptions) => {
|
|
5147
|
+
const response = await transport.fetcher(url);
|
|
5148
|
+
const contextUrl = new URL(url);
|
|
5149
|
+
contextUrl.search = "";
|
|
5150
|
+
contextUrl.hash = "";
|
|
5151
|
+
return (0, import_core37.parse)(await response.arrayBuffer(), loader, loadOptions, {
|
|
5152
|
+
url: contextUrl.href,
|
|
5153
|
+
fetch: transport.fetcher,
|
|
5154
|
+
coreApi: import_core37.coreApi,
|
|
5155
|
+
_parse: import_core37.parse
|
|
5156
|
+
});
|
|
5157
|
+
};
|
|
5158
|
+
const source = new import_tiles_3d5.I3SSource(
|
|
5159
|
+
header && layer ? {
|
|
5160
|
+
...layer,
|
|
5161
|
+
root: { ...header, children: [] },
|
|
5162
|
+
type: "I3S",
|
|
5163
|
+
url: sourceUrl.href,
|
|
5164
|
+
basePath: sourceUrl.href,
|
|
5165
|
+
loader: import_i3s4.I3SLoader,
|
|
5166
|
+
coreApi: import_core37.coreApi
|
|
5167
|
+
} : {
|
|
5168
|
+
url: sourceUrl.href,
|
|
5169
|
+
loader: import_i3s4.I3SLoader,
|
|
5170
|
+
coreApi: import_core37.coreApi,
|
|
5171
|
+
resolver: { loadRoot: loadResource, loadResource }
|
|
5172
|
+
},
|
|
5173
|
+
options
|
|
5174
|
+
);
|
|
5175
|
+
source.coreApi = { ...import_core37.coreApi, load: loadResource };
|
|
5176
|
+
return new import_tiles_3d5.Tileset3D(source, { i3s: options.i3s });
|
|
5177
|
+
}
|
|
5178
|
+
async function inspectI3SMeshConversionSource(options) {
|
|
5179
|
+
const { signal } = options;
|
|
5180
|
+
validateSelectionOptions(options);
|
|
5181
|
+
signal.throwIfAborted();
|
|
5182
|
+
const transport = options;
|
|
5183
|
+
let runtime;
|
|
5184
|
+
try {
|
|
5185
|
+
const response = await transport.fetcher(transport.rootUrl);
|
|
5186
|
+
const layer = await response.json();
|
|
5187
|
+
if (!["3DObject", "IntegratedMesh"].includes(layer.layerType))
|
|
5188
|
+
throw new Error("Conversion accepts I3S 3DObject or IntegratedMesh layers.");
|
|
5189
|
+
const rootFetcher = transport.fetcher;
|
|
5190
|
+
const cachedFetcher = (url, options2) => {
|
|
5191
|
+
const requestUrl = new URL(url instanceof Request ? url.url : String(url));
|
|
5192
|
+
const root = new URL(transport.rootUrl);
|
|
5193
|
+
return requestUrl.origin === root.origin && requestUrl.pathname === root.pathname ? Promise.resolve(new Response(JSON.stringify(layer))) : rootFetcher(url, options2);
|
|
5194
|
+
};
|
|
5195
|
+
runtime = createRuntime({ ...transport, fetcher: cachedFetcher });
|
|
5196
|
+
await runtime.tilesetInitializationPromise;
|
|
5197
|
+
await createI3SMeshTilesetConversionSource(runtime).inspect(signal);
|
|
5198
|
+
const stack = [runtime.root];
|
|
5199
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
5200
|
+
const resources = [];
|
|
5201
|
+
while (stack.length) {
|
|
5202
|
+
signal.throwIfAborted();
|
|
5203
|
+
const tile = stack.pop();
|
|
5204
|
+
if (!["string", "number"].includes(typeof tile.header.id) || String(tile.header.id).length === 0)
|
|
5205
|
+
throw new Error("I3S nodes require a declared identity.");
|
|
5206
|
+
const identifier = String(tile.header.id);
|
|
5207
|
+
if (identifiers.has(identifier))
|
|
5208
|
+
throw new Error("Repeated I3S node identity is unsupported.");
|
|
5209
|
+
identifiers.add(identifier);
|
|
5210
|
+
if (identifiers.size + stack.length + (tile.header.children?.length ?? 0) > (options.maxInputResources ?? SELECTED_MESH_LIMITS.maxInputResources))
|
|
5211
|
+
throw new Error("I3S inspection exceeds the 1,000 node limit.");
|
|
5212
|
+
await runtime.source.loadTileChildrenForTraversal(tile, signal);
|
|
5213
|
+
if (!tile.children.length && tile.contentUrl)
|
|
5214
|
+
resources.push({
|
|
5215
|
+
resourceId: identifier,
|
|
5216
|
+
uri: tile.contentUrl,
|
|
5217
|
+
header: structuredClone(tile.header)
|
|
5218
|
+
});
|
|
5219
|
+
for (const child of [...tile.children].reverse()) stack.push(child);
|
|
5220
|
+
}
|
|
5221
|
+
signal.throwIfAborted();
|
|
5222
|
+
if (!resources.length) throw new Error("No supported leaf mesh nodes are declared.");
|
|
5223
|
+
return {
|
|
5224
|
+
kind: "i3s",
|
|
5225
|
+
rootUrl: transport.rootUrl,
|
|
5226
|
+
layer,
|
|
5227
|
+
resources,
|
|
5228
|
+
metadataBytes: 0
|
|
5229
|
+
};
|
|
5230
|
+
} finally {
|
|
5231
|
+
runtime?.destroy();
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
async function createSelectedI3SMeshConversionSource(inspection, options) {
|
|
5235
|
+
const { resourceIds, signal, features } = options;
|
|
5236
|
+
validateSelectionOptions(options);
|
|
5237
|
+
signal.throwIfAborted();
|
|
5238
|
+
if (inspection.rootUrl !== options.rootUrl)
|
|
5239
|
+
throw new import_core38.TileConversionError(
|
|
5240
|
+
"INVALID_I3S_SELECTION",
|
|
5241
|
+
"Selection transport must match the inspected layer"
|
|
5242
|
+
);
|
|
5243
|
+
if (!resourceIds.length || resourceIds.length > SELECTED_MESH_LIMITS.maxSelectedResources || new Set(resourceIds).size !== resourceIds.length || resourceIds.some(
|
|
5244
|
+
(identifier) => !inspection.resources.some((resource) => resource.resourceId === identifier)
|
|
5245
|
+
))
|
|
5246
|
+
throw new Error("Select between 1 and 64 distinct leaf mesh nodes.");
|
|
5247
|
+
const selected = inspection.resources.filter(
|
|
5248
|
+
(resource) => resourceIds.includes(resource.resourceId)
|
|
5249
|
+
);
|
|
5250
|
+
if (selected.some(
|
|
5251
|
+
(resource) => resource.header.children?.length || !resource.header.contentUrl || resource.uri !== resource.header.contentUrl
|
|
5252
|
+
))
|
|
5253
|
+
throw new Error("Only inspected leaf mesh headers can be converted.");
|
|
5254
|
+
const transport = options;
|
|
5255
|
+
let decodedBytes = 0;
|
|
5256
|
+
let meshCount = 0;
|
|
5257
|
+
{
|
|
5258
|
+
const qualification = createRuntime(transport, inspection.layer, selected[0].header);
|
|
5259
|
+
let spatialContext;
|
|
5260
|
+
try {
|
|
5261
|
+
await qualification.tilesetInitializationPromise;
|
|
5262
|
+
const metadata = await createI3SMeshTilesetConversionSource(qualification).inspect(signal);
|
|
5263
|
+
spatialContext = (0, import_core38.createI3SConversionSpatialContext)(metadata.spatialReference, {
|
|
5264
|
+
targetCrs: "EPSG:4978"
|
|
5265
|
+
});
|
|
5266
|
+
} finally {
|
|
5267
|
+
qualification.destroy();
|
|
5268
|
+
}
|
|
5269
|
+
const source = {
|
|
5270
|
+
/** Reuses only immutable inspected declarations. */
|
|
5271
|
+
inspect: async () => inspection,
|
|
5272
|
+
/** Releases each dedicated node runtime before moving on or closing early. */
|
|
5273
|
+
async *read() {
|
|
5274
|
+
for (const descriptor of selected) {
|
|
5275
|
+
const runtime = createRuntime(transport, inspection.layer, descriptor.header);
|
|
5276
|
+
try {
|
|
5277
|
+
await runtime.tilesetInitializationPromise;
|
|
5278
|
+
const meshSource = createI3SMeshTilesetConversionSource(runtime, {
|
|
5279
|
+
unloadContent: true,
|
|
5280
|
+
features,
|
|
5281
|
+
readExternalResource: async (uri) => new Uint8Array(
|
|
5282
|
+
await (await transport.fetcher(runtime.source.getTileUrl(uri))).arrayBuffer()
|
|
5283
|
+
)
|
|
5284
|
+
});
|
|
5285
|
+
const metadata = await meshSource.inspect(signal);
|
|
5286
|
+
for await (const resource of meshSource.read(metadata, signal)) {
|
|
5287
|
+
decodedBytes += measureMeshBytes(resource);
|
|
5288
|
+
if (++meshCount > SELECTED_MESH_LIMITS.maxMeshResources || decodedBytes > (options.maxInputBytes ?? SELECTED_MESH_LIMITS.maxInputBytes))
|
|
5289
|
+
throw new import_core38.TileConversionError(
|
|
5290
|
+
"INPUT_RESOURCE_TOO_LARGE",
|
|
5291
|
+
"Selected I3S decoded resources exceed the input/mesh budget"
|
|
5292
|
+
);
|
|
5293
|
+
yield { ...resource, id: descriptor.resourceId };
|
|
5294
|
+
}
|
|
5295
|
+
} finally {
|
|
5296
|
+
runtime.destroy();
|
|
5297
|
+
}
|
|
5298
|
+
}
|
|
5299
|
+
}
|
|
5300
|
+
};
|
|
5301
|
+
return { source, spatialContext };
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
function validateSelectionOptions(options) {
|
|
5305
|
+
for (const [value, maximum] of [
|
|
5306
|
+
[
|
|
5307
|
+
options.maxInputBytes ?? SELECTED_MESH_LIMITS.maxInputBytes,
|
|
5308
|
+
SELECTED_MESH_LIMITS.maxInputBytes
|
|
5309
|
+
],
|
|
5310
|
+
[
|
|
5311
|
+
options.maxInputResources ?? SELECTED_MESH_LIMITS.maxInputResources,
|
|
5312
|
+
SELECTED_MESH_LIMITS.maxInputResources
|
|
5313
|
+
]
|
|
5314
|
+
])
|
|
5315
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
|
|
5316
|
+
throw new import_core38.TileConversionError(
|
|
5317
|
+
"INVALID_I3S_SELECTION_LIMIT",
|
|
5318
|
+
"I3S selection limits must be positive safe integers within the bounded profile"
|
|
5319
|
+
);
|
|
5320
|
+
}
|