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