@loaders.gl/tile-converter 5.0.0-alpha.11 → 5.0.0-alpha.12
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/README.md +178 -2
- package/bin/converter-v5.js +2 -0
- package/dist/converter.min.cjs +162 -163
- package/dist/i3s-server/bin/i3s-server.min.cjs +73 -73
- package/dist/index.cjs +1749 -2152
- package/dist/index.js +1748 -2162
- package/dist/v5/adapters.cjs +1550 -128
- package/dist/v5/adapters.d.ts +16 -0
- package/dist/v5/adapters.js +1558 -117
- 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 +14 -0
- package/dist/v5/browser.js +26 -0
- package/dist/v5/conversion-api.d.ts +9 -0
- package/dist/v5/converter-cli.d.ts +1 -0
- package/dist/v5/converter-cli.js +1214 -0
- package/dist/v5/core.cjs +471 -60
- package/dist/v5/core.d.ts +2 -0
- package/dist/v5/core.js +471 -60
- package/dist/v5/i3s-mesh-conversion.d.ts +4 -0
- package/dist/v5/i3s-selection-source.d.ts +70 -0
- package/dist/v5/index.cjs +10 -0
- package/dist/v5/index.d.ts +8 -0
- package/dist/v5/index.js +14 -0
- package/dist/v5/mesh-conversion.d.ts +18 -0
- package/dist/v5/mesh-hierarchy.d.ts +47 -0
- package/dist/v5/mesh-source-features.d.ts +2 -1
- package/dist/v5/mesh-source-schema.d.ts +12 -0
- package/dist/v5/mesh-source.d.ts +5 -2
- package/dist/v5/mesh-tileset-sink.d.ts +5 -0
- package/dist/v5/node-cli.d.ts +9 -0
- package/dist/v5/node-conversion.d.ts +32 -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 +900 -0
- package/dist/v5/node.d.ts +6 -0
- package/dist/v5/node.js +877 -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-tileset.d.ts +44 -0
- package/dist/v5/resource-manifest.d.ts +10 -1
- package/dist/v5/selected-mesh-source.d.ts +53 -0
- package/package.json +33 -25
- package/src/index.ts +4 -1
- package/src/v4/3d-tiles-converter/helpers/load-i3s.ts +2 -4
- package/src/v4/converter-cli.ts +8 -2
- package/src/v4/i3s-converter/helpers/load-3d-tiles.ts +3 -51
- package/src/v4/i3s-converter/helpers/node-index-document.ts +5 -2
- package/src/v4/i3s-converter/helpers/preprocess-3d-tiles.ts +1 -1
- package/src/v4/i3s-converter/json-templates/store.ts +2 -1
- package/src/v5/adapters.ts +48 -0
- package/src/v5/archive-source.ts +235 -0
- package/src/v5/browser-tileset-source.ts +418 -48
- package/src/v5/browser.ts +45 -0
- package/src/v5/conversion-api.ts +69 -9
- package/src/v5/converter-cli.ts +28 -0
- package/src/v5/core.ts +6 -0
- package/src/v5/i3s-mesh-conversion.ts +40 -2
- package/src/v5/i3s-mesh-sink.ts +1 -1
- package/src/v5/i3s-mesh-source.ts +1 -0
- package/src/v5/i3s-selection-source.ts +298 -0
- package/src/v5/index.ts +26 -0
- package/src/v5/mesh-conversion.ts +20 -0
- package/src/v5/mesh-hierarchy.ts +648 -0
- package/src/v5/mesh-source-features.ts +39 -7
- package/src/v5/mesh-source-schema.ts +134 -0
- package/src/v5/mesh-source.ts +16 -4
- package/src/v5/mesh-tileset-sink.ts +1 -1
- package/src/v5/node-cli.ts +197 -0
- package/src/v5/node-conversion.ts +345 -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-file.ts +306 -0
- package/src/v5/point-cloud-potree.ts +130 -0
- package/src/v5/point-cloud-tileset.ts +137 -0
- 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 +88 -17
package/dist/v5/adapters.cjs
CHANGED
|
@@ -20,26 +20,40 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/v5/adapters.ts
|
|
21
21
|
var adapters_exports = {};
|
|
22
22
|
__export(adapters_exports, {
|
|
23
|
+
SELECTED_MESH_LIMITS: () => SELECTED_MESH_LIMITS,
|
|
23
24
|
convertFeatureAttributesToArrowBatches: () => convertFeatureAttributesToArrowBatches,
|
|
24
25
|
convertPointCloudSource: () => convertPointCloudSource,
|
|
26
|
+
convertPointCloudToPotree: () => convertPointCloudToPotree,
|
|
27
|
+
convertPointCloudToTileset: () => convertPointCloudToTileset,
|
|
25
28
|
createI3SMeshConversionCodec: () => createI3SMeshConversionCodec,
|
|
26
29
|
createI3SMeshSink: () => createI3SMeshSink,
|
|
27
30
|
createI3SMeshTilesetConversionSource: () => createI3SMeshTilesetConversionSource,
|
|
28
31
|
createMeshConversionCodec: () => createMeshConversionCodec,
|
|
32
|
+
createMeshHierarchyTilesetSink: () => createMeshHierarchyTilesetSink,
|
|
33
|
+
createMeshSourceFeatureOptions: () => createMeshSourceFeatureOptions,
|
|
29
34
|
createMeshTilesetConversionSource: () => createMeshTilesetConversionSource,
|
|
30
35
|
createMeshTilesetSink: () => createMeshTilesetSink,
|
|
31
36
|
createPointCloudTilesetSink: () => createPointCloudTilesetSink,
|
|
37
|
+
createSelectedI3SMeshConversionSource: () => createSelectedI3SMeshConversionSource,
|
|
38
|
+
createSelectedMeshConversionSource: () => createSelectedMeshConversionSource,
|
|
32
39
|
createSingleMeshI3SSink: () => createSingleMeshI3SSink,
|
|
33
40
|
createSingleMeshTilesetArchive: () => createSingleMeshTilesetArchive,
|
|
34
41
|
createSingleMeshTilesetSink: () => createSingleMeshTilesetSink,
|
|
35
42
|
createTileConversionArchive: () => createTileConversionArchive,
|
|
43
|
+
createTileConversionArchiveTransport: () => createTileConversionArchiveTransport,
|
|
36
44
|
createTileConversionResourceFetcher: () => createTileConversionResourceFetcher,
|
|
37
45
|
encodeMeshTile: () => encodeMeshTile,
|
|
38
46
|
encodePointCloudSource: () => encodePointCloudSource,
|
|
39
47
|
encodePointCloudSourceTile: () => encodePointCloudSourceTile,
|
|
40
48
|
encodePointCloudTile: () => encodePointCloudTile,
|
|
41
49
|
encodePointCloudTileWithMetadata: () => encodePointCloudTileWithMetadata,
|
|
50
|
+
encodePotreeArchive: () => encodePotreeArchive,
|
|
42
51
|
encodeTileConversionArchiveInBatches: () => encodeTileConversionArchiveInBatches,
|
|
52
|
+
extractMeshVertices: () => extractMeshVertices,
|
|
53
|
+
inspectI3SMeshConversionSource: () => inspectI3SMeshConversionSource,
|
|
54
|
+
measureMeshBytes: () => measureMeshBytes,
|
|
55
|
+
openPointCloudFile: () => openPointCloudFile,
|
|
56
|
+
openTileConversionArchive: () => openTileConversionArchive,
|
|
43
57
|
transformPointCloudSourceTile: () => transformPointCloudSourceTile
|
|
44
58
|
});
|
|
45
59
|
module.exports = __toCommonJS(adapters_exports);
|
|
@@ -1676,6 +1690,8 @@ function createMeshConversionCodec(options) {
|
|
|
1676
1690
|
}
|
|
1677
1691
|
yield {
|
|
1678
1692
|
id: resource.id,
|
|
1693
|
+
contentResourceId: resource.contentResourceId,
|
|
1694
|
+
contentPrimitive: resource.contentPrimitive,
|
|
1679
1695
|
glb: encoded.glb,
|
|
1680
1696
|
origin,
|
|
1681
1697
|
boundingBox: [minimum, maximum],
|
|
@@ -1987,9 +2003,9 @@ async function createSingleMeshTilesetArchive(files, options) {
|
|
|
1987
2003
|
}
|
|
1988
2004
|
|
|
1989
2005
|
// src/v5/mesh-source.ts
|
|
1990
|
-
var
|
|
1991
|
-
var import_core19 = require("@loaders.gl/tile-converter/v5/core");
|
|
2006
|
+
var import_core19 = require("@math.gl/core");
|
|
1992
2007
|
var import_core20 = require("@loaders.gl/tile-converter/v5/core");
|
|
2008
|
+
var import_core21 = require("@loaders.gl/tile-converter/v5/core");
|
|
1993
2009
|
|
|
1994
2010
|
// src/v5/mesh-source-texture.ts
|
|
1995
2011
|
var import_images2 = require("@loaders.gl/images");
|
|
@@ -2097,99 +2113,194 @@ async function resolveMeshSourceTexture(textureInfo, readExternalResource, conte
|
|
|
2097
2113
|
return mapMeshSourceTexture(resolved);
|
|
2098
2114
|
}
|
|
2099
2115
|
|
|
2100
|
-
// src/v5/mesh-source-
|
|
2116
|
+
// src/v5/mesh-source-schema.ts
|
|
2101
2117
|
var import_core17 = require("@loaders.gl/tile-converter/v5/core");
|
|
2118
|
+
var SCALAR_TYPES = {
|
|
2119
|
+
INT8: "int8",
|
|
2120
|
+
UINT8: "uint8",
|
|
2121
|
+
INT16: "int16",
|
|
2122
|
+
UINT16: "uint16",
|
|
2123
|
+
INT32: "int32",
|
|
2124
|
+
UINT32: "uint32",
|
|
2125
|
+
INT64: "int64",
|
|
2126
|
+
UINT64: "uint64",
|
|
2127
|
+
FLOAT32: "float32",
|
|
2128
|
+
FLOAT64: "float64"
|
|
2129
|
+
};
|
|
2130
|
+
function createMeshSourceFeatureOptions(gltf, options) {
|
|
2131
|
+
const metadata = gltf.extensions?.EXT_structural_metadata;
|
|
2132
|
+
const table = metadata?.propertyTables?.[0];
|
|
2133
|
+
const properties = metadata?.schema?.classes?.[options.metadataClass]?.properties;
|
|
2134
|
+
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) {
|
|
2135
|
+
throw new import_core17.TileConversionError(
|
|
2136
|
+
"MESH_FEATURE_SCHEMA_REQUIRED",
|
|
2137
|
+
"Schema mapping requires one inline class, one matching property table and a positive uint32 row count"
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
if (Object.keys(table.properties || {}).some((name) => !Object.hasOwn(properties, name))) {
|
|
2141
|
+
throw new import_core17.TileConversionError(
|
|
2142
|
+
"MESH_FEATURE_PROPERTY_UNMAPPED",
|
|
2143
|
+
"Property table columns must be declared in the selected class"
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
const fields = Object.entries(properties).map(([name, property]) => {
|
|
2147
|
+
const declaration = property;
|
|
2148
|
+
const column = table.properties?.[name];
|
|
2149
|
+
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)) {
|
|
2150
|
+
throw new import_core17.TileConversionError(
|
|
2151
|
+
"MESH_FEATURE_PROPERTY_UNSUPPORTED",
|
|
2152
|
+
`Property ${name} requires an explicit separately qualified mapping`
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
const type = declaration.type === "STRING" ? "utf8" : Object.hasOwn(SCALAR_TYPES, declaration.componentType || "") ? SCALAR_TYPES[declaration.componentType] : void 0;
|
|
2156
|
+
if (!type) {
|
|
2157
|
+
throw new import_core17.TileConversionError(
|
|
2158
|
+
"MESH_FEATURE_PROPERTY_UNSUPPORTED",
|
|
2159
|
+
`Property ${name} has no supported declared scalar width`
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
return {
|
|
2163
|
+
name,
|
|
2164
|
+
type,
|
|
2165
|
+
nullable: declaration.required !== true,
|
|
2166
|
+
metadata: { "loaders.gl:structural-property": JSON.stringify(declaration) }
|
|
2167
|
+
};
|
|
2168
|
+
});
|
|
2169
|
+
const identifierName = options.featureIdField || "feature_id";
|
|
2170
|
+
const sourceIdentifier = options.sourceFeatureIdProperty;
|
|
2171
|
+
const sourceField = sourceIdentifier ? fields.find((field) => field.name === sourceIdentifier) : void 0;
|
|
2172
|
+
if (sourceIdentifier && !sourceField || fields.some((field) => field.name === identifierName && field !== sourceField) || sourceField && !["utf8", "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64"].includes(
|
|
2173
|
+
String(sourceField.type)
|
|
2174
|
+
)) {
|
|
2175
|
+
throw new import_core17.TileConversionError(
|
|
2176
|
+
"MESH_FEATURE_ID_PROPERTY_REQUIRED",
|
|
2177
|
+
"Select a declared string/integer identifier and a distinct output identifier name"
|
|
2178
|
+
);
|
|
2179
|
+
}
|
|
2180
|
+
return {
|
|
2181
|
+
...options,
|
|
2182
|
+
schema: {
|
|
2183
|
+
fields: [
|
|
2184
|
+
{
|
|
2185
|
+
...sourceField,
|
|
2186
|
+
name: identifierName,
|
|
2187
|
+
type: sourceField?.type || "uint32",
|
|
2188
|
+
nullable: false
|
|
2189
|
+
},
|
|
2190
|
+
...fields.filter((field) => field !== sourceField)
|
|
2191
|
+
],
|
|
2192
|
+
metadata: { "loaders.gl:source-schema": JSON.stringify(metadata.schema) }
|
|
2193
|
+
}
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
// src/v5/mesh-source-features.ts
|
|
2198
|
+
var import_core18 = require("@loaders.gl/tile-converter/v5/core");
|
|
2102
2199
|
function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
2103
2200
|
const declarations = primitive.extensions?.EXT_mesh_features?.featureIds;
|
|
2104
2201
|
const metadata = gltf.extensions?.EXT_structural_metadata;
|
|
2105
2202
|
const legacy = primitive.attributes._BATCHID;
|
|
2106
2203
|
if (!declarations && !legacy && !metadata && !Object.keys(payload.batchTableJson || {}).length && !payload.header?.batchLength) {
|
|
2107
2204
|
if (Object.keys(primitive.attributes).some((name) => /^_FEATURE_ID_\d+$/.test(name)))
|
|
2108
|
-
throw new
|
|
2205
|
+
throw new import_core18.TileConversionError(
|
|
2109
2206
|
"MESH_FEATURE_MAPPING_REQUIRED",
|
|
2110
2207
|
"Feature attributes require an EXT_mesh_features declaration"
|
|
2111
2208
|
);
|
|
2112
2209
|
return void 0;
|
|
2113
2210
|
}
|
|
2114
2211
|
if (!options)
|
|
2115
|
-
throw new
|
|
2212
|
+
throw new import_core18.TileConversionError(
|
|
2116
2213
|
"MESH_FEATURE_SCHEMA_REQUIRED",
|
|
2117
|
-
"Feature extraction requires
|
|
2214
|
+
"Feature extraction requires a class selection and a declared Arrow or inline source schema"
|
|
2118
2215
|
);
|
|
2119
2216
|
if (!options.metadataClass)
|
|
2120
|
-
throw new
|
|
2217
|
+
throw new import_core18.TileConversionError(
|
|
2121
2218
|
"MESH_FEATURE_CLASS_REQUIRED",
|
|
2122
2219
|
"Feature metadata class must be explicit"
|
|
2123
2220
|
);
|
|
2221
|
+
const mapping = "schema" in options ? options : createMeshSourceFeatureOptions(gltf, options);
|
|
2124
2222
|
let count;
|
|
2125
2223
|
let attribute = legacy;
|
|
2126
2224
|
let properties;
|
|
2127
2225
|
if (declarations) {
|
|
2128
2226
|
if (legacy || declarations.length !== 1 || metadata?.propertyTables?.length !== 1 || metadata.propertyTextures?.length || metadata.propertyAttributes?.length)
|
|
2129
|
-
throw new
|
|
2227
|
+
throw new import_core18.TileConversionError(
|
|
2130
2228
|
"MESH_FEATURE_MAPPING_UNSUPPORTED",
|
|
2131
2229
|
"Only one attribute-backed feature set and one property table are supported"
|
|
2132
2230
|
);
|
|
2133
2231
|
const declaration = declarations[0];
|
|
2134
2232
|
if (!Number.isInteger(declaration.attribute) || declaration.attribute < 0 || declaration.propertyTable !== 0 || declaration.texture || declaration.implicit || declaration.nullFeatureId !== void 0)
|
|
2135
|
-
throw new
|
|
2233
|
+
throw new import_core18.TileConversionError(
|
|
2136
2234
|
"MESH_FEATURE_MAPPING_UNSUPPORTED",
|
|
2137
2235
|
"Features require an attribute and property table 0 without null/texture/implicit IDs"
|
|
2138
2236
|
);
|
|
2139
2237
|
attribute = primitive.attributes[`_FEATURE_ID_${declaration.attribute}`];
|
|
2140
2238
|
const table = metadata.propertyTables[0];
|
|
2141
2239
|
if (table.class !== options.metadataClass || table.count !== declaration.featureCount)
|
|
2142
|
-
throw new
|
|
2240
|
+
throw new import_core18.TileConversionError(
|
|
2143
2241
|
"MESH_FEATURE_CLASS_MISMATCH",
|
|
2144
2242
|
"Feature class and declared table counts must agree"
|
|
2145
2243
|
);
|
|
2146
2244
|
count = table.count;
|
|
2245
|
+
if (!Number.isSafeInteger(count) || count <= 0 || count > mesh.attributes.POSITION.value.length / 3) {
|
|
2246
|
+
throw new import_core18.TileConversionError(
|
|
2247
|
+
"MESH_FEATURE_IDS_INVALID",
|
|
2248
|
+
"Feature row count exceeds the available mesh vertices"
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2147
2251
|
properties = {};
|
|
2148
2252
|
const classProperties = metadata.schema?.classes?.[table.class]?.properties;
|
|
2149
2253
|
if (!classProperties)
|
|
2150
|
-
throw new
|
|
2254
|
+
throw new import_core18.TileConversionError(
|
|
2151
2255
|
"MESH_FEATURE_SCHEMA_REQUIRED",
|
|
2152
2256
|
"A decoded inline structural metadata class is required"
|
|
2153
2257
|
);
|
|
2154
2258
|
if (Object.keys(table.properties || {}).some((name) => !Object.hasOwn(classProperties, name)))
|
|
2155
|
-
throw new
|
|
2259
|
+
throw new import_core18.TileConversionError(
|
|
2156
2260
|
"MESH_FEATURE_PROPERTY_UNMAPPED",
|
|
2157
2261
|
"Property table columns must be declared in the metadata class"
|
|
2158
2262
|
);
|
|
2159
2263
|
for (const [name, property] of Object.entries(classProperties)) {
|
|
2160
2264
|
const column = table.properties?.[name];
|
|
2161
2265
|
if (!["SCALAR", "STRING"].includes(property.type) || property.array || property.noData !== void 0 && (property.normalized || property.offset !== void 0 || property.scale !== void 0 || column?.offset !== void 0 || column?.scale !== void 0) || ["INT64", "UINT64"].includes(property.componentType || "") && (property.normalized || property.offset !== void 0 || property.scale !== void 0 || column?.offset !== void 0 || column?.scale !== void 0))
|
|
2162
|
-
throw new
|
|
2266
|
+
throw new import_core18.TileConversionError(
|
|
2163
2267
|
"MESH_FEATURE_PROPERTY_UNSUPPORTED",
|
|
2164
2268
|
`Property ${name} requires a separately qualified semantic mapping`
|
|
2165
2269
|
);
|
|
2166
2270
|
const data = column?.data;
|
|
2167
|
-
if (data === void 0 && property.
|
|
2168
|
-
throw new
|
|
2271
|
+
if (column !== void 0 && (data === void 0 || data === null) || data === void 0 && property.required === true)
|
|
2272
|
+
throw new import_core18.TileConversionError(
|
|
2169
2273
|
"MESH_FEATURE_PROPERTY_UNAVAILABLE",
|
|
2170
2274
|
`Property ${name} has not been decoded`
|
|
2171
2275
|
);
|
|
2172
|
-
const
|
|
2276
|
+
const integer64 = ["INT64", "UINT64"].includes(property.componentType || "");
|
|
2277
|
+
if (integer64 && property.default !== void 0 && (typeof property.default !== "number" || !Number.isSafeInteger(property.default))) {
|
|
2278
|
+
throw new import_core18.TileConversionError(
|
|
2279
|
+
"MESH_FEATURE_PROPERTY_UNSUPPORTED",
|
|
2280
|
+
`Property ${name} requires an exact 64-bit default`
|
|
2281
|
+
);
|
|
2282
|
+
}
|
|
2283
|
+
const defaultValue = integer64 && property.default !== void 0 ? BigInt(property.default) : property.default ?? null;
|
|
2284
|
+
const decoded = data ?? Array.from({ length: count }, () => defaultValue);
|
|
2173
2285
|
if (property.noData === void 0 || data === void 0) {
|
|
2174
2286
|
properties[name] = decoded;
|
|
2175
2287
|
continue;
|
|
2176
2288
|
}
|
|
2177
2289
|
const sentinel = property.noData;
|
|
2178
|
-
const integer64 = ["INT64", "UINT64"].includes(property.componentType || "");
|
|
2179
2290
|
if ((property.type === "STRING" ? typeof sentinel !== "string" : typeof sentinel !== "number" || !Number.isFinite(sentinel) || integer64 && !Number.isSafeInteger(sentinel)) || !decoded || !Array.isArray(decoded) && !ArrayBuffer.isView(decoded) || decoded.length !== count)
|
|
2180
|
-
throw new
|
|
2291
|
+
throw new import_core18.TileConversionError(
|
|
2181
2292
|
"MESH_FEATURE_PROPERTY_UNSUPPORTED",
|
|
2182
2293
|
`Property ${name} requires a valid decoded noData mapping`
|
|
2183
2294
|
);
|
|
2184
2295
|
const comparable = integer64 ? BigInt(sentinel) : sentinel;
|
|
2185
2296
|
properties[name] = Array.from(
|
|
2186
2297
|
decoded,
|
|
2187
|
-
(value) => value === comparable ?
|
|
2298
|
+
(value) => value === comparable ? defaultValue : value
|
|
2188
2299
|
);
|
|
2189
2300
|
}
|
|
2190
2301
|
} else {
|
|
2191
2302
|
if (metadata)
|
|
2192
|
-
throw new
|
|
2303
|
+
throw new import_core18.TileConversionError(
|
|
2193
2304
|
"MESH_FEATURE_MAPPING_UNSUPPORTED",
|
|
2194
2305
|
"Structural metadata requires an attribute-backed feature declaration"
|
|
2195
2306
|
);
|
|
@@ -2200,7 +2311,7 @@ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
|
2200
2311
|
if (!Number.isSafeInteger(count) || count <= 0 || !attribute || attribute.components !== 1 || attribute.normalized || attribute.value.length !== vertexCount || Array.from(attribute.value).some(
|
|
2201
2312
|
(value) => !Number.isSafeInteger(value) || value < 0 || value >= count
|
|
2202
2313
|
))
|
|
2203
|
-
throw new
|
|
2314
|
+
throw new import_core18.TileConversionError(
|
|
2204
2315
|
"MESH_FEATURE_IDS_INVALID",
|
|
2205
2316
|
"Feature IDs must be one valid property-table row index per vertex"
|
|
2206
2317
|
);
|
|
@@ -2208,20 +2319,20 @@ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
|
2208
2319
|
if (Object.keys(primitive.attributes).some(
|
|
2209
2320
|
(name) => (name === "_BATCHID" || /^_FEATURE_ID_\d+$/.test(name)) && name !== selectedAttribute
|
|
2210
2321
|
))
|
|
2211
|
-
throw new
|
|
2322
|
+
throw new import_core18.TileConversionError(
|
|
2212
2323
|
"MESH_FEATURE_MAPPING_UNSUPPORTED",
|
|
2213
2324
|
"Additional feature ID sets require separate mappings"
|
|
2214
2325
|
);
|
|
2215
2326
|
for (const [name, values] of Object.entries(properties)) {
|
|
2216
2327
|
if (!values || !Array.isArray(values) && !ArrayBuffer.isView(values) || values.length !== count)
|
|
2217
|
-
throw new
|
|
2328
|
+
throw new import_core18.TileConversionError(
|
|
2218
2329
|
"MESH_FEATURE_PROPERTY_UNAVAILABLE",
|
|
2219
2330
|
`Property ${name} must be a decoded column matching the table count`
|
|
2220
2331
|
);
|
|
2221
2332
|
}
|
|
2222
2333
|
const sourceIdentifier = options.sourceFeatureIdProperty;
|
|
2223
2334
|
if (sourceIdentifier && !properties[sourceIdentifier])
|
|
2224
|
-
throw new
|
|
2335
|
+
throw new import_core18.TileConversionError(
|
|
2225
2336
|
"MESH_FEATURE_ID_PROPERTY_REQUIRED",
|
|
2226
2337
|
"Stable source identifier property is missing"
|
|
2227
2338
|
);
|
|
@@ -2233,7 +2344,7 @@ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
|
2233
2344
|
Object.entries(properties).filter(([name]) => name !== sourceIdentifier).map(([name, values]) => [name, values[index]])
|
|
2234
2345
|
)
|
|
2235
2346
|
})),
|
|
2236
|
-
|
|
2347
|
+
mapping
|
|
2237
2348
|
);
|
|
2238
2349
|
const indices = mesh.indices?.value;
|
|
2239
2350
|
const triangleFeatureIndices = new Uint32Array((indices?.length ?? vertexCount) / 3);
|
|
@@ -2243,7 +2354,7 @@ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
|
2243
2354
|
);
|
|
2244
2355
|
const rows = corners.map((corner) => attribute.value[corner]);
|
|
2245
2356
|
if (rows.some((row) => row !== rows[0]) && new Set(corners).size === 3)
|
|
2246
|
-
throw new
|
|
2357
|
+
throw new import_core18.TileConversionError(
|
|
2247
2358
|
"MESH_FEATURE_TRIANGLE_MIXED",
|
|
2248
2359
|
"All vertices of a nondegenerate triangle must reference the same feature row"
|
|
2249
2360
|
);
|
|
@@ -2259,20 +2370,20 @@ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
|
2259
2370
|
|
|
2260
2371
|
// src/v5/mesh-source.ts
|
|
2261
2372
|
function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
2262
|
-
const source = (0,
|
|
2373
|
+
const source = (0, import_core21.createTilesetConversionSource)(tileset, options);
|
|
2263
2374
|
return {
|
|
2264
2375
|
/** Validates the source frame and supported dataset metadata before traversal. */
|
|
2265
2376
|
async inspect(signal) {
|
|
2266
2377
|
const metadata = await source.inspect(signal);
|
|
2267
2378
|
const reference = metadata.spatialReference;
|
|
2268
2379
|
if (metadata.type !== "TILES3D" || !reference || reference.status !== "native" || reference.sourceCrs !== "EPSG:4978" || reference.heightReference !== "ellipsoidal" || reference.axisOrder !== "xyz" || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter")) {
|
|
2269
|
-
throw new
|
|
2380
|
+
throw new import_core20.TileConversionError(
|
|
2270
2381
|
"MESH_SOURCE_FRAME_UNSUPPORTED",
|
|
2271
2382
|
"Mesh extraction requires native EPSG:4978 xyz coordinates in meters and ellipsoidal heights"
|
|
2272
2383
|
);
|
|
2273
2384
|
}
|
|
2274
2385
|
if (metadata.tileset.metadata || metadata.tileset.groups?.length) {
|
|
2275
|
-
throw new
|
|
2386
|
+
throw new import_core20.TileConversionError(
|
|
2276
2387
|
"MESH_SOURCE_METADATA_UNSUPPORTED",
|
|
2277
2388
|
"Tileset/group metadata requires a separate mapping"
|
|
2278
2389
|
);
|
|
@@ -2284,25 +2395,25 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
2284
2395
|
for await (const item of source.read(inspection, signal)) {
|
|
2285
2396
|
const placementReference = item.tile.header._spatialReference || tileset.spatialReference;
|
|
2286
2397
|
if (placementReference && (placementReference.status !== "native" || placementReference.sourceCrs !== "EPSG:4978"))
|
|
2287
|
-
throw new
|
|
2398
|
+
throw new import_core20.TileConversionError(
|
|
2288
2399
|
"MESH_SOURCE_FRAME_UNSUPPORTED",
|
|
2289
2400
|
"Nested mesh placements require native ECEF coordinates"
|
|
2290
2401
|
);
|
|
2291
2402
|
if (item.tile.header.metadata || item.tile.header.implicitMetadata)
|
|
2292
|
-
throw new
|
|
2403
|
+
throw new import_core20.TileConversionError(
|
|
2293
2404
|
"MESH_SOURCE_METADATA_UNSUPPORTED",
|
|
2294
2405
|
"Tile metadata requires a separate mapping"
|
|
2295
2406
|
);
|
|
2296
2407
|
for (const content of item.contents) {
|
|
2297
2408
|
signal?.throwIfAborted();
|
|
2298
2409
|
if (content.metadata || content.group !== void 0)
|
|
2299
|
-
throw new
|
|
2410
|
+
throw new import_core20.TileConversionError(
|
|
2300
2411
|
"MESH_SOURCE_METADATA_UNSUPPORTED",
|
|
2301
2412
|
"Content metadata requires a separate mapping"
|
|
2302
2413
|
);
|
|
2303
2414
|
const payload = content.payload;
|
|
2304
2415
|
if (!payload?.gltf || !["glTF", "gltf", "b3dm"].includes(payload.type || content.type || "")) {
|
|
2305
|
-
throw new
|
|
2416
|
+
throw new import_core20.TileConversionError(
|
|
2306
2417
|
"MESH_SOURCE_CONTENT_UNSUPPORTED",
|
|
2307
2418
|
"Mesh extraction requires decoded GLB/glTF or B3DM content"
|
|
2308
2419
|
);
|
|
@@ -2311,36 +2422,40 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
2311
2422
|
if (gltf.animations?.length || gltf.skins?.length || Object.keys(gltf.extensions || {}).some(
|
|
2312
2423
|
(name) => !["EXT_structural_metadata", "CESIUM_RTC"].includes(name)
|
|
2313
2424
|
)) {
|
|
2314
|
-
throw new
|
|
2425
|
+
throw new import_core20.TileConversionError(
|
|
2315
2426
|
"MESH_SOURCE_SCENE_UNSUPPORTED",
|
|
2316
2427
|
"Animated/skinned scenes and unmapped scene extensions are unsupported"
|
|
2317
2428
|
);
|
|
2318
2429
|
}
|
|
2319
2430
|
const axis = payload.gltfUpAxis || "Y";
|
|
2320
2431
|
if (!["X", "Y", "Z"].includes(axis))
|
|
2321
|
-
throw new
|
|
2322
|
-
const placement = new
|
|
2432
|
+
throw new import_core20.TileConversionError("MESH_SOURCE_AXIS_UNSUPPORTED", "Unknown glTF up axis");
|
|
2433
|
+
const placement = new import_core19.Matrix4(item.tile.computedTransform);
|
|
2323
2434
|
const rtc = payload.rtcCenter || gltf.extensions?.CESIUM_RTC?.center;
|
|
2324
2435
|
if (rtc) {
|
|
2325
2436
|
if (rtc.length !== 3 || rtc.some((value) => !Number.isFinite(value)))
|
|
2326
|
-
throw new
|
|
2437
|
+
throw new import_core20.TileConversionError(
|
|
2327
2438
|
"MESH_SOURCE_TRANSFORM_INVALID",
|
|
2328
2439
|
"RTC center must contain finite xyz coordinates"
|
|
2329
2440
|
);
|
|
2330
2441
|
placement.translate(rtc);
|
|
2331
2442
|
}
|
|
2332
2443
|
if (axis === "Y")
|
|
2333
|
-
placement.multiplyRight(new
|
|
2444
|
+
placement.multiplyRight(new import_core19.Matrix4([1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1]));
|
|
2334
2445
|
if (axis === "X")
|
|
2335
|
-
placement.multiplyRight(new
|
|
2446
|
+
placement.multiplyRight(new import_core19.Matrix4([0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1]));
|
|
2336
2447
|
const roots = gltf.scene?.nodes || (gltf.scenes?.length === 1 ? gltf.scenes[0].nodes : void 0);
|
|
2337
2448
|
if (!roots?.length)
|
|
2338
|
-
throw new
|
|
2449
|
+
throw new import_core20.TileConversionError(
|
|
2339
2450
|
"MESH_SOURCE_SCENE_UNSUPPORTED",
|
|
2340
2451
|
"A default scene or exactly one scene is required"
|
|
2341
2452
|
);
|
|
2342
2453
|
let primitiveIndex = 0;
|
|
2343
|
-
|
|
2454
|
+
const primitives = traverseMeshNodes(roots, placement);
|
|
2455
|
+
let current = primitives.next();
|
|
2456
|
+
while (!current.done) {
|
|
2457
|
+
const selected = current.value;
|
|
2458
|
+
const next = primitives.next();
|
|
2344
2459
|
const resource = await extractPrimitive(
|
|
2345
2460
|
selected.primitive,
|
|
2346
2461
|
selected.transform,
|
|
@@ -2356,10 +2471,15 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
2356
2471
|
resource.mesh,
|
|
2357
2472
|
options.features
|
|
2358
2473
|
);
|
|
2359
|
-
yield {
|
|
2474
|
+
yield {
|
|
2475
|
+
...resource,
|
|
2476
|
+
features,
|
|
2477
|
+
contentPrimitive: { index: primitiveIndex - 1, last: Boolean(next.done) }
|
|
2478
|
+
};
|
|
2479
|
+
current = next;
|
|
2360
2480
|
}
|
|
2361
2481
|
if (!primitiveIndex)
|
|
2362
|
-
throw new
|
|
2482
|
+
throw new import_core20.TileConversionError(
|
|
2363
2483
|
"MESH_SOURCE_CONTENT_EMPTY",
|
|
2364
2484
|
"Declared content contains no supported mesh primitive"
|
|
2365
2485
|
);
|
|
@@ -2371,16 +2491,16 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
2371
2491
|
function* traverseMeshNodes(nodes, parent, ancestors = /* @__PURE__ */ new Set()) {
|
|
2372
2492
|
for (const node of nodes) {
|
|
2373
2493
|
if (ancestors.has(node) || node.skin || node.weights || Object.keys(node.extensions || {}).length)
|
|
2374
|
-
throw new
|
|
2494
|
+
throw new import_core20.TileConversionError(
|
|
2375
2495
|
"MESH_SOURCE_NODE_UNSUPPORTED",
|
|
2376
2496
|
"Cyclic, skinned, morphed, or extended nodes are unsupported"
|
|
2377
2497
|
);
|
|
2378
|
-
const transform = new
|
|
2379
|
-
node.matrix ? new
|
|
2498
|
+
const transform = new import_core19.Matrix4(parent).multiplyRight(
|
|
2499
|
+
node.matrix ? new import_core19.Matrix4(node.matrix) : new import_core19.Matrix4().translate(node.translation || [0, 0, 0]).multiplyRight(new import_core19.Matrix4().fromQuaternion(node.rotation || [0, 0, 0, 1])).scale(node.scale || [1, 1, 1])
|
|
2380
2500
|
);
|
|
2381
2501
|
if (node.mesh) {
|
|
2382
2502
|
if (node.mesh.weights || Object.keys(node.mesh.extensions || {}).length)
|
|
2383
|
-
throw new
|
|
2503
|
+
throw new import_core20.TileConversionError(
|
|
2384
2504
|
"MESH_SOURCE_NODE_UNSUPPORTED",
|
|
2385
2505
|
"Morph weights and mesh extensions are unsupported"
|
|
2386
2506
|
);
|
|
@@ -2391,12 +2511,12 @@ function* traverseMeshNodes(nodes, parent, ancestors = /* @__PURE__ */ new Set()
|
|
|
2391
2511
|
}
|
|
2392
2512
|
async function extractPrimitive(primitive, transform, id, readExternalResource, contentUri, signal) {
|
|
2393
2513
|
if (primitive.targets?.length || Object.keys(primitive.extensions || {}).some((name) => name !== "EXT_mesh_features"))
|
|
2394
|
-
throw new
|
|
2514
|
+
throw new import_core20.TileConversionError(
|
|
2395
2515
|
"MESH_SOURCE_PRIMITIVE_UNSUPPORTED",
|
|
2396
2516
|
"Morph targets and unmapped primitive extensions are unsupported"
|
|
2397
2517
|
);
|
|
2398
2518
|
if (Array.from(transform).some((value) => !Number.isFinite(value)) || transform[3] !== 0 || transform[7] !== 0 || transform[11] !== 0 || transform[15] !== 1 || transform.determinant() <= 0)
|
|
2399
|
-
throw new
|
|
2519
|
+
throw new import_core20.TileConversionError(
|
|
2400
2520
|
"MESH_SOURCE_TRANSFORM_INVALID",
|
|
2401
2521
|
"Mesh placement requires a finite invertible affine transform with positive determinant"
|
|
2402
2522
|
);
|
|
@@ -2404,7 +2524,7 @@ async function extractPrimitive(primitive, transform, id, readExternalResource,
|
|
|
2404
2524
|
for (const [name, accessor] of Object.entries(primitive.attributes)) {
|
|
2405
2525
|
if (name === "_BATCHID" || /^_FEATURE_ID_\d+$/.test(name)) continue;
|
|
2406
2526
|
if (!["POSITION", "NORMAL", "COLOR_0", "TEXCOORD_0"].includes(name))
|
|
2407
|
-
throw new
|
|
2527
|
+
throw new import_core20.TileConversionError(
|
|
2408
2528
|
"MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
|
|
2409
2529
|
`Unsupported mesh attribute ${name}`
|
|
2410
2530
|
);
|
|
@@ -2440,7 +2560,7 @@ async function extractPrimitive(primitive, transform, id, readExternalResource,
|
|
|
2440
2560
|
mesh.attributes.POSITION = { value: positions, size: 3 };
|
|
2441
2561
|
if (mesh.attributes.NORMAL) {
|
|
2442
2562
|
const values = mesh.attributes.NORMAL.value;
|
|
2443
|
-
const normalMatrix = new
|
|
2563
|
+
const normalMatrix = new import_core19.Matrix4(transform).invert().transpose();
|
|
2444
2564
|
const normals = new Float32Array(values.length);
|
|
2445
2565
|
for (let index = 0; index < values.length; index += 3) {
|
|
2446
2566
|
const normal = normalMatrix.transformAsVector(Array.from(values.subarray(index, index + 3)));
|
|
@@ -2481,7 +2601,7 @@ async function mapMeshSourceMaterial(material, readExternalResource, contentUri,
|
|
|
2481
2601
|
) || Object.entries(pbr || {}).some(
|
|
2482
2602
|
([name, value]) => value !== void 0 && !["baseColorFactor", "baseColorTexture", "metallicFactor", "roughnessFactor"].includes(name)
|
|
2483
2603
|
) || material.emissiveFactor?.some((value) => value !== 0))
|
|
2484
|
-
throw new
|
|
2604
|
+
throw new import_core20.TileConversionError(
|
|
2485
2605
|
"MESH_SOURCE_MATERIAL_UNSUPPORTED",
|
|
2486
2606
|
"Only metallic-roughness factors and one embedded base-color image are supported"
|
|
2487
2607
|
);
|
|
@@ -2512,7 +2632,7 @@ function createTriangleIndices(primitive, vertexCount) {
|
|
|
2512
2632
|
const sourceIndices = validateMeshIndices(attribute, vertexCount, 5);
|
|
2513
2633
|
const indexCount = sourceIndices?.length ?? vertexCount;
|
|
2514
2634
|
if (!Number.isSafeInteger(indexCount) || indexCount < 3) {
|
|
2515
|
-
throw new
|
|
2635
|
+
throw new import_core20.TileConversionError(
|
|
2516
2636
|
"MESH_TRIANGLE_COUNT_INVALID",
|
|
2517
2637
|
"Triangle strips require at least three vertices"
|
|
2518
2638
|
);
|
|
@@ -2531,13 +2651,19 @@ function createTriangleIndices(primitive, vertexCount) {
|
|
|
2531
2651
|
|
|
2532
2652
|
// src/v5/i3s-mesh-conversion.ts
|
|
2533
2653
|
var import_i3s = require("@loaders.gl/i3s");
|
|
2534
|
-
var import_core21 = require("@loaders.gl/tile-converter/v5/core");
|
|
2535
2654
|
var import_core22 = require("@loaders.gl/tile-converter/v5/core");
|
|
2655
|
+
var import_core23 = require("@loaders.gl/tile-converter/v5/core");
|
|
2536
2656
|
function createI3SMeshConversionCodec(options) {
|
|
2657
|
+
if (options.integratedMeshPolicy !== void 0 && options.integratedMeshPolicy !== "as-3d-object") {
|
|
2658
|
+
throw new import_core22.TileConversionError(
|
|
2659
|
+
"I3S_LAYER_TYPE_POLICY_INVALID",
|
|
2660
|
+
"Unknown IntegratedMesh conversion policy"
|
|
2661
|
+
);
|
|
2662
|
+
}
|
|
2537
2663
|
const reference = options.spatialContext.spatialReference;
|
|
2538
2664
|
const height = reference.targetHeightReference === "native" ? reference.heightReference : reference.targetHeightReference;
|
|
2539
2665
|
if (!["native", "transformed"].includes(reference.status) || (reference.targetCrs || reference.sourceCrs) !== "EPSG:4978" || height !== "ellipsoidal" || reference.status === "native" && (!["xyz", "unknown"].includes(reference.axisOrder) || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter")))
|
|
2540
|
-
throw new
|
|
2666
|
+
throw new import_core22.TileConversionError(
|
|
2541
2667
|
"I3S_MESH_FRAME_UNSUPPORTED",
|
|
2542
2668
|
"I3S mesh authoring requires resolved ECEF coordinates and ellipsoidal heights"
|
|
2543
2669
|
);
|
|
@@ -2546,6 +2672,12 @@ function createI3SMeshConversionCodec(options) {
|
|
|
2546
2672
|
/** Places a source mesh in ECEF and authors one layer without modifying source arrays. */
|
|
2547
2673
|
async *convert(resource, _inspection, signal) {
|
|
2548
2674
|
signal?.throwIfAborted();
|
|
2675
|
+
if (resource.sourceLayerType === "IntegratedMesh" && options.integratedMeshPolicy !== "as-3d-object") {
|
|
2676
|
+
throw new import_core22.TileConversionError(
|
|
2677
|
+
"I3S_LAYER_TYPE_CHANGE_REQUIRED",
|
|
2678
|
+
"IntegratedMesh to 3DObject conversion requires integratedMeshPolicy=as-3d-object"
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2549
2681
|
const mesh = validateMeshGeometry(resource.mesh, true);
|
|
2550
2682
|
const positions = mesh.attributes.POSITION.value;
|
|
2551
2683
|
const context = options.spatialContext;
|
|
@@ -2562,6 +2694,7 @@ function createI3SMeshConversionCodec(options) {
|
|
|
2562
2694
|
if (mesh.attributes.NORMAL) mesh.attributes.NORMAL = { value: transformed.normals, size: 3 };
|
|
2563
2695
|
let layer;
|
|
2564
2696
|
try {
|
|
2697
|
+
await (0, import_i3s.preloadI3SMeshCompression)();
|
|
2565
2698
|
const writerOptions = {
|
|
2566
2699
|
...options,
|
|
2567
2700
|
material: resource.material,
|
|
@@ -2570,18 +2703,26 @@ function createI3SMeshConversionCodec(options) {
|
|
|
2570
2703
|
};
|
|
2571
2704
|
layer = options.draco === false ? (0, import_i3s.encodeI3SMeshLayer)(mesh, writerOptions) : await (0, import_i3s.encodeI3SMeshLayerWithDraco)(mesh, writerOptions, options.dracoLibraryOptions);
|
|
2572
2705
|
} catch (error) {
|
|
2573
|
-
throw new
|
|
2706
|
+
throw new import_core22.TileConversionError(
|
|
2574
2707
|
"I3S_MESH_PROFILE_UNSUPPORTED",
|
|
2575
2708
|
error instanceof Error ? error.message : String(error)
|
|
2576
2709
|
);
|
|
2577
2710
|
}
|
|
2578
2711
|
signal?.throwIfAborted();
|
|
2579
2712
|
objectIdOffset += resource.features?.batches.reduce((total, batch) => total + batch.data.numRows, 0) ?? 1;
|
|
2580
|
-
yield { id: resource.id, ...layer };
|
|
2713
|
+
yield { id: resource.id, ...layer, sourceLayerType: resource.sourceLayerType };
|
|
2581
2714
|
},
|
|
2582
2715
|
/** Reports measured precision and explicitly authorized integer representation changes. */
|
|
2583
2716
|
async validateOutput(resource) {
|
|
2584
2717
|
return [
|
|
2718
|
+
...resource.sourceLayerType === "IntegratedMesh" ? [
|
|
2719
|
+
{
|
|
2720
|
+
code: "I3S_LAYER_TYPE_CHANGED",
|
|
2721
|
+
message: "Explicitly authored IntegratedMesh geometry as I3S 1.7 3DObject; integrated surface layer semantics and source LOD are not preserved",
|
|
2722
|
+
severity: "warning",
|
|
2723
|
+
resourceId: resource.id
|
|
2724
|
+
}
|
|
2725
|
+
] : [],
|
|
2585
2726
|
...resource.maximumPositionError > 0 ? [
|
|
2586
2727
|
{
|
|
2587
2728
|
code: "I3S_MESH_POSITION_ROUNDING",
|
|
@@ -2601,7 +2742,7 @@ function createI3SMeshConversionCodec(options) {
|
|
|
2601
2742
|
};
|
|
2602
2743
|
}
|
|
2603
2744
|
function createSingleMeshI3SSink(options) {
|
|
2604
|
-
const memory = (0,
|
|
2745
|
+
const memory = (0, import_core23.createBoundedMemoryTileConversionSink)(options);
|
|
2605
2746
|
let state = "empty";
|
|
2606
2747
|
let completed = false;
|
|
2607
2748
|
return {
|
|
@@ -2611,7 +2752,7 @@ function createSingleMeshI3SSink(options) {
|
|
|
2611
2752
|
async write(resource, signal) {
|
|
2612
2753
|
signal?.throwIfAborted();
|
|
2613
2754
|
if (state !== "empty")
|
|
2614
|
-
throw new
|
|
2755
|
+
throw new import_core22.TileConversionError(
|
|
2615
2756
|
"SINGLE_I3S_MESH_UNAVAILABLE",
|
|
2616
2757
|
"The initial I3S sink accepts exactly one mesh layer"
|
|
2617
2758
|
);
|
|
@@ -2630,7 +2771,7 @@ function createSingleMeshI3SSink(options) {
|
|
|
2630
2771
|
/** Commits a complete single-layer result. */
|
|
2631
2772
|
async finalize(report) {
|
|
2632
2773
|
if (state !== "written")
|
|
2633
|
-
throw new
|
|
2774
|
+
throw new import_core22.TileConversionError(
|
|
2634
2775
|
"SINGLE_I3S_MESH_INCOMPLETE",
|
|
2635
2776
|
"Finalization requires exactly one successfully written layer"
|
|
2636
2777
|
);
|
|
@@ -2650,16 +2791,16 @@ function createSingleMeshI3SSink(options) {
|
|
|
2650
2791
|
// src/v5/i3s-mesh-sink.ts
|
|
2651
2792
|
var import_geospatial = require("@math.gl/geospatial");
|
|
2652
2793
|
var import_compression = require("@loaders.gl/compression");
|
|
2653
|
-
var
|
|
2794
|
+
var import_core24 = require("@loaders.gl/tile-converter/v5/core");
|
|
2654
2795
|
function createI3SMeshSink(options) {
|
|
2655
2796
|
const { maxMeshes, maxResourceBytes } = options;
|
|
2656
2797
|
if (!Number.isInteger(maxMeshes) || maxMeshes < 1 || maxMeshes > 64 || !Number.isSafeInteger(maxResourceBytes) || maxResourceBytes < 1)
|
|
2657
|
-
throw new
|
|
2798
|
+
throw new import_core24.TileConversionError(
|
|
2658
2799
|
"I3S_COLLECTION_OPTIONS_INVALID",
|
|
2659
2800
|
"I3S collections require 1\u201364 meshes and a positive resource byte limit"
|
|
2660
2801
|
);
|
|
2661
|
-
const memory = (0,
|
|
2662
|
-
const compressor = new import_compression.GZipCompressor({ useNative: false
|
|
2802
|
+
const memory = (0, import_core24.createBoundedMemoryTileConversionSink)(options);
|
|
2803
|
+
const compressor = new import_compression.GZipCompressor({ useNative: false });
|
|
2663
2804
|
const decompressor = new import_compression.GZipDecompressor({ useNative: false });
|
|
2664
2805
|
const leaves = [];
|
|
2665
2806
|
const identifiers = /* @__PURE__ */ new Set();
|
|
@@ -2671,7 +2812,7 @@ function createI3SMeshSink(options) {
|
|
|
2671
2812
|
async function readBytes(bytes) {
|
|
2672
2813
|
const output = await decompressor.decompress(bytes);
|
|
2673
2814
|
if (output.byteLength > maxResourceBytes)
|
|
2674
|
-
throw new
|
|
2815
|
+
throw new import_core24.TileConversionError(
|
|
2675
2816
|
"I3S_COLLECTION_RESOURCE_TOO_LARGE",
|
|
2676
2817
|
"I3S metadata exceeds maxResourceBytes"
|
|
2677
2818
|
);
|
|
@@ -2683,7 +2824,7 @@ function createI3SMeshSink(options) {
|
|
|
2683
2824
|
async function writeJson(resourceId, value) {
|
|
2684
2825
|
const bytes = new TextEncoder().encode(JSON.stringify(value));
|
|
2685
2826
|
if (bytes.byteLength > maxResourceBytes)
|
|
2686
|
-
throw new
|
|
2827
|
+
throw new import_core24.TileConversionError(
|
|
2687
2828
|
"I3S_COLLECTION_RESOURCE_TOO_LARGE",
|
|
2688
2829
|
"I3S index exceeds maxResourceBytes"
|
|
2689
2830
|
);
|
|
@@ -2700,7 +2841,7 @@ function createI3SMeshSink(options) {
|
|
|
2700
2841
|
async write(resource, signal) {
|
|
2701
2842
|
signal?.throwIfAborted();
|
|
2702
2843
|
if (state !== "open" || identifiers.has(resource.id) || leaves.length >= maxMeshes)
|
|
2703
|
-
throw new
|
|
2844
|
+
throw new import_core24.TileConversionError(
|
|
2704
2845
|
"I3S_COLLECTION_UNAVAILABLE",
|
|
2705
2846
|
"I3S collection is busy, closed, full, or repeats a placement"
|
|
2706
2847
|
);
|
|
@@ -2713,7 +2854,7 @@ function createI3SMeshSink(options) {
|
|
|
2713
2854
|
authored.store.defaultGeometrySchema
|
|
2714
2855
|
]);
|
|
2715
2856
|
if (schemaKey !== void 0 && key !== schemaKey)
|
|
2716
|
-
throw new
|
|
2857
|
+
throw new import_core24.TileConversionError(
|
|
2717
2858
|
"I3S_COLLECTION_SCHEMA_MISMATCH",
|
|
2718
2859
|
"Mesh nodes must share feature fields and the legacy geometry layout"
|
|
2719
2860
|
);
|
|
@@ -2725,7 +2866,7 @@ function createI3SMeshSink(options) {
|
|
|
2725
2866
|
if (objectIds.byteLength !== 4 + count * 4 || !count || objectIdRanges.some(([start, end]) => first < end && first + count > start) || Array.from({ length: count }, (_, index2) => objectIds.getUint32(4 + index2 * 4, true)).some(
|
|
2726
2867
|
(value, index2) => value !== first + index2
|
|
2727
2868
|
))
|
|
2728
|
-
throw new
|
|
2869
|
+
throw new import_core24.TileConversionError(
|
|
2729
2870
|
"I3S_COLLECTION_IDS_INVALID",
|
|
2730
2871
|
"Generated object IDs must be contiguous and disjoint across nodes"
|
|
2731
2872
|
);
|
|
@@ -2736,7 +2877,7 @@ function createI3SMeshSink(options) {
|
|
|
2736
2877
|
"nodes/1/3dNodeIndexDocument.json.gz"
|
|
2737
2878
|
);
|
|
2738
2879
|
if (node.obb.quaternion.join(",") !== "0,0,0,1" || [...node.obb.center, ...node.obb.halfSize].some((value) => !Number.isFinite(value)) || node.obb.halfSize.some((value) => value < 0))
|
|
2739
|
-
throw new
|
|
2880
|
+
throw new import_core24.TileConversionError(
|
|
2740
2881
|
"I3S_COLLECTION_BOUNDS_INVALID",
|
|
2741
2882
|
"Expected finite axis-aligned bounds from the I3S mesh writer"
|
|
2742
2883
|
);
|
|
@@ -2787,7 +2928,7 @@ function createI3SMeshSink(options) {
|
|
|
2787
2928
|
/** Rebuilds both indices and layer bounds only after every leaf has completed. */
|
|
2788
2929
|
async finalize(report) {
|
|
2789
2930
|
if (state !== "open" || !layer || !leaves.length)
|
|
2790
|
-
throw new
|
|
2931
|
+
throw new import_core24.TileConversionError(
|
|
2791
2932
|
"I3S_COLLECTION_INCOMPLETE",
|
|
2792
2933
|
"Finalization requires successfully written mesh nodes"
|
|
2793
2934
|
);
|
|
@@ -2812,7 +2953,7 @@ function createI3SMeshSink(options) {
|
|
|
2812
2953
|
quaternion: [0, 0, 0, 1]
|
|
2813
2954
|
};
|
|
2814
2955
|
if ([...obb.center, ...obb.halfSize].some((value) => !Number.isFinite(value)))
|
|
2815
|
-
throw new
|
|
2956
|
+
throw new import_core24.TileConversionError(
|
|
2816
2957
|
"I3S_COLLECTION_BOUNDS_INVALID",
|
|
2817
2958
|
"Collection bounds cannot be represented"
|
|
2818
2959
|
);
|
|
@@ -2872,7 +3013,7 @@ function createI3SMeshSink(options) {
|
|
|
2872
3013
|
}
|
|
2873
3014
|
|
|
2874
3015
|
// src/v5/conversion-api.ts
|
|
2875
|
-
var
|
|
3016
|
+
var TileConversionError18 = class extends Error {
|
|
2876
3017
|
/** Stable machine-readable failure code. */
|
|
2877
3018
|
code;
|
|
2878
3019
|
/** Diagnostics associated with this failure. */
|
|
@@ -2890,7 +3031,7 @@ var TileConversionError17 = class extends Error {
|
|
|
2890
3031
|
function createTileConversionResourceFetcher(options) {
|
|
2891
3032
|
let consumed = options.initialInputBytes ?? 0;
|
|
2892
3033
|
if (!Number.isSafeInteger(options.maxInputBytes) || options.maxInputBytes < 1 || !Number.isSafeInteger(consumed) || consumed < 0 || consumed > options.maxInputBytes)
|
|
2893
|
-
throw new
|
|
3034
|
+
throw new TileConversionError18(
|
|
2894
3035
|
"INVALID_INPUT_MEMORY_LIMIT",
|
|
2895
3036
|
"Resource fetch requires a positive aggregate byte budget and valid initial bytes"
|
|
2896
3037
|
);
|
|
@@ -2899,7 +3040,7 @@ function createTileConversionResourceFetcher(options) {
|
|
|
2899
3040
|
options.signal?.throwIfAborted();
|
|
2900
3041
|
const url = new URL(input instanceof Request ? input.url : String(input));
|
|
2901
3042
|
if (!["http:", "https:", "blob:", "data:"].includes(url.protocol))
|
|
2902
|
-
throw new
|
|
3043
|
+
throw new TileConversionError18(
|
|
2903
3044
|
"EXTERNAL_RESOURCE_UNSUPPORTED",
|
|
2904
3045
|
"Conversion transport requires HTTP(S), Blob or embedded data resources"
|
|
2905
3046
|
);
|
|
@@ -2908,14 +3049,14 @@ function createTileConversionResourceFetcher(options) {
|
|
|
2908
3049
|
const response = await fetcher(input, { ...requestOptions, signal });
|
|
2909
3050
|
if (!response.ok) {
|
|
2910
3051
|
await response.body?.cancel();
|
|
2911
|
-
throw new
|
|
3052
|
+
throw new TileConversionError18(
|
|
2912
3053
|
"BROWSER_SOURCE_FETCH_FAILED",
|
|
2913
3054
|
`Could not fetch conversion resource (${response.status})`
|
|
2914
3055
|
);
|
|
2915
3056
|
}
|
|
2916
3057
|
const reader = response.body?.getReader();
|
|
2917
3058
|
if (!reader)
|
|
2918
|
-
throw new
|
|
3059
|
+
throw new TileConversionError18(
|
|
2919
3060
|
"BROWSER_SOURCE_STREAM_UNAVAILABLE",
|
|
2920
3061
|
"Conversion resources require readable response streams"
|
|
2921
3062
|
);
|
|
@@ -2933,7 +3074,7 @@ function createTileConversionResourceFetcher(options) {
|
|
|
2933
3074
|
signal?.throwIfAborted();
|
|
2934
3075
|
if (done) break;
|
|
2935
3076
|
if (value.byteLength > options.maxInputBytes - consumed)
|
|
2936
|
-
throw new
|
|
3077
|
+
throw new TileConversionError18(
|
|
2937
3078
|
"INPUT_RESOURCE_TOO_LARGE",
|
|
2938
3079
|
"Selected contents and external dependencies exceed the aggregate input byte budget"
|
|
2939
3080
|
);
|
|
@@ -2954,25 +3095,27 @@ function createTileConversionResourceFetcher(options) {
|
|
|
2954
3095
|
bytes.set(part, offset);
|
|
2955
3096
|
offset += part.byteLength;
|
|
2956
3097
|
}
|
|
2957
|
-
|
|
3098
|
+
const boundedResponse = new Response(bytes, {
|
|
2958
3099
|
status: response.status,
|
|
2959
3100
|
statusText: response.statusText,
|
|
2960
3101
|
headers: response.headers
|
|
2961
3102
|
});
|
|
3103
|
+
Object.defineProperty(boundedResponse, "url", { value: response.url });
|
|
3104
|
+
return boundedResponse;
|
|
2962
3105
|
};
|
|
2963
3106
|
}
|
|
2964
3107
|
|
|
2965
3108
|
// src/v5/point-cloud-tileset-sink.ts
|
|
2966
|
-
var
|
|
3109
|
+
var import_core25 = require("@loaders.gl/tile-converter/v5/core");
|
|
2967
3110
|
function createPointCloudTilesetSink(options) {
|
|
2968
3111
|
const { maxTiles, geometricError } = options;
|
|
2969
3112
|
if (!Number.isSafeInteger(maxTiles) || maxTiles < 1 || !Number.isFinite(geometricError) || geometricError < 0) {
|
|
2970
|
-
throw new
|
|
3113
|
+
throw new import_core25.TileConversionError(
|
|
2971
3114
|
"POINT_CLOUD_TILESET_OPTIONS_INVALID",
|
|
2972
3115
|
"Positive maxTiles and finite nonnegative geometricError are required"
|
|
2973
3116
|
);
|
|
2974
3117
|
}
|
|
2975
|
-
const memory = (0,
|
|
3118
|
+
const memory = (0, import_core25.createBoundedMemoryTileConversionSink)(options);
|
|
2976
3119
|
const children = [];
|
|
2977
3120
|
const identifiers = /* @__PURE__ */ new Set();
|
|
2978
3121
|
const minimum = [Infinity, Infinity, Infinity];
|
|
@@ -2986,12 +3129,12 @@ function createPointCloudTilesetSink(options) {
|
|
|
2986
3129
|
async write(resource, signal) {
|
|
2987
3130
|
signal?.throwIfAborted();
|
|
2988
3131
|
if (state !== "open")
|
|
2989
|
-
throw new
|
|
3132
|
+
throw new import_core25.TileConversionError(
|
|
2990
3133
|
"POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
|
|
2991
3134
|
"Point sink is busy or closed"
|
|
2992
3135
|
);
|
|
2993
3136
|
if (!resource.id || identifiers.has(resource.id) || children.length >= maxTiles) {
|
|
2994
|
-
throw new
|
|
3137
|
+
throw new import_core25.TileConversionError(
|
|
2995
3138
|
"POINT_CLOUD_TILESET_PLACEMENT_INVALID",
|
|
2996
3139
|
"Placements require unique nonempty identifiers within maxTiles"
|
|
2997
3140
|
);
|
|
@@ -3004,7 +3147,7 @@ function createPointCloudTilesetSink(options) {
|
|
|
3004
3147
|
signal
|
|
3005
3148
|
);
|
|
3006
3149
|
if (state !== "writing")
|
|
3007
|
-
throw new
|
|
3150
|
+
throw new import_core25.TileConversionError(
|
|
3008
3151
|
"POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
|
|
3009
3152
|
"Point sink was aborted during writing"
|
|
3010
3153
|
);
|
|
@@ -3023,7 +3166,7 @@ function createPointCloudTilesetSink(options) {
|
|
|
3023
3166
|
/** Serializes the enclosing root after all placements have completed. */
|
|
3024
3167
|
async finalize(report) {
|
|
3025
3168
|
if (state !== "open" || !children.length)
|
|
3026
|
-
throw new
|
|
3169
|
+
throw new import_core25.TileConversionError(
|
|
3027
3170
|
"POINT_CLOUD_TILESET_SINK_INCOMPLETE",
|
|
3028
3171
|
"Finalization requires successfully written point tiles"
|
|
3029
3172
|
);
|
|
@@ -3033,7 +3176,7 @@ function createPointCloudTilesetSink(options) {
|
|
|
3033
3176
|
Math.hypot(...minimum.map((value, axis) => maximum[axis] - value))
|
|
3034
3177
|
);
|
|
3035
3178
|
if (!Number.isFinite(rootError))
|
|
3036
|
-
throw new
|
|
3179
|
+
throw new import_core25.TileConversionError(
|
|
3037
3180
|
"POINT_CLOUD_TILESET_BOUNDS_INVALID",
|
|
3038
3181
|
"Enclosing bounds must have a finite diagonal"
|
|
3039
3182
|
);
|
|
@@ -3053,13 +3196,13 @@ function createPointCloudTilesetSink(options) {
|
|
|
3053
3196
|
contentType: "application/json"
|
|
3054
3197
|
});
|
|
3055
3198
|
if (state !== "writing")
|
|
3056
|
-
throw new
|
|
3199
|
+
throw new import_core25.TileConversionError(
|
|
3057
3200
|
"POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
|
|
3058
3201
|
"Point sink was aborted during finalization"
|
|
3059
3202
|
);
|
|
3060
3203
|
await memory.finalize(report);
|
|
3061
3204
|
if (state !== "writing")
|
|
3062
|
-
throw new
|
|
3205
|
+
throw new import_core25.TileConversionError(
|
|
3063
3206
|
"POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
|
|
3064
3207
|
"Point sink was aborted during finalization"
|
|
3065
3208
|
);
|
|
@@ -3080,13 +3223,13 @@ function getPointTileBounds(resource, geometricError) {
|
|
|
3080
3223
|
const reference = resource.spatialReference;
|
|
3081
3224
|
const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
3082
3225
|
if (!reference || !["native", "transformed"].includes(reference.status) || (reference.targetCrs || reference.sourceCrs) !== "EPSG:4978" || (reference.targetHeightReference === "native" ? reference.heightReference : reference.targetHeightReference) !== "ellipsoidal" || resource.coordinateSystem !== "cartesian" || resource.cartographicOrigin.length !== 3 || resource.cartographicOrigin.some((value) => value !== 0) || resource.modelMatrix && (resource.modelMatrix.length !== 16 || resource.modelMatrix.some((value, index) => value !== identity[index])) || reference.status === "native" && (reference.coordinateFrame !== "geocentric" || reference.axisOrder !== "xyz" || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter"))) {
|
|
3083
|
-
throw new
|
|
3226
|
+
throw new import_core25.TileConversionError(
|
|
3084
3227
|
"POINT_CLOUD_TILESET_FRAME_UNSUPPORTED",
|
|
3085
3228
|
"Point tiles require declared EPSG:4978 xyz meters, ellipsoidal heights and no additional source placement"
|
|
3086
3229
|
);
|
|
3087
3230
|
}
|
|
3088
3231
|
if (!Number.isFinite(resource.maximumPositionError) || resource.maximumPositionError < 0 || resource.maximumPositionError > geometricError) {
|
|
3089
|
-
throw new
|
|
3232
|
+
throw new import_core25.TileConversionError(
|
|
3090
3233
|
"POINT_CLOUD_GEOMETRIC_ERROR_INVALID",
|
|
3091
3234
|
"geometricError must include measured position reconstruction error"
|
|
3092
3235
|
);
|
|
@@ -3094,7 +3237,7 @@ function getPointTileBounds(resource, geometricError) {
|
|
|
3094
3237
|
const [minimum, maximum] = resource.localBoundingBox;
|
|
3095
3238
|
const center = resource.rtcCenter;
|
|
3096
3239
|
if (center.length !== 3 || minimum.length !== 3 || maximum.length !== 3 || [...center, ...minimum, ...maximum].some((value) => !Number.isFinite(value)) || minimum.some((value, axis) => value > maximum[axis])) {
|
|
3097
|
-
throw new
|
|
3240
|
+
throw new import_core25.TileConversionError(
|
|
3098
3241
|
"POINT_CLOUD_TILESET_BOUNDS_INVALID",
|
|
3099
3242
|
"RTC center and encoded xyz bounds must be finite and ordered"
|
|
3100
3243
|
);
|
|
@@ -3102,7 +3245,7 @@ function getPointTileBounds(resource, geometricError) {
|
|
|
3102
3245
|
const lower = minimum.map((value, axis) => value + center[axis]);
|
|
3103
3246
|
const upper = maximum.map((value, axis) => value + center[axis]);
|
|
3104
3247
|
if ([...lower, ...upper].some((value) => !Number.isFinite(value)))
|
|
3105
|
-
throw new
|
|
3248
|
+
throw new import_core25.TileConversionError(
|
|
3106
3249
|
"POINT_CLOUD_TILESET_BOUNDS_INVALID",
|
|
3107
3250
|
"Reconstructed ECEF bounds must be finite"
|
|
3108
3251
|
);
|
|
@@ -3115,11 +3258,11 @@ function createPointBoundsBox(minimum, maximum) {
|
|
|
3115
3258
|
}
|
|
3116
3259
|
|
|
3117
3260
|
// src/v5/i3s-mesh-source.ts
|
|
3118
|
-
var
|
|
3261
|
+
var import_core28 = require("@loaders.gl/tile-converter/v5/core");
|
|
3119
3262
|
|
|
3120
3263
|
// src/v5/i3s-mesh-source-material.ts
|
|
3121
3264
|
var import_images3 = require("@loaders.gl/images");
|
|
3122
|
-
var
|
|
3265
|
+
var import_core26 = require("@loaders.gl/tile-converter/v5/core");
|
|
3123
3266
|
async function mapI3SMeshSourceMaterial(content, header, readExternalResource, contentUri, signal) {
|
|
3124
3267
|
const material = Object.fromEntries(
|
|
3125
3268
|
Object.entries(content.material || {}).filter(([, value]) => value !== void 0)
|
|
@@ -3129,7 +3272,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3129
3272
|
) : {};
|
|
3130
3273
|
material.pbrMetallicRoughness = metallicRoughness;
|
|
3131
3274
|
if (material.cullFace !== void 0 && material.cullFace !== "back") {
|
|
3132
|
-
throw new
|
|
3275
|
+
throw new import_core26.TileConversionError(
|
|
3133
3276
|
"I3S_MESH_SOURCE_MATERIAL_UNSUPPORTED",
|
|
3134
3277
|
"Only back-face culling is supported"
|
|
3135
3278
|
);
|
|
@@ -3148,7 +3291,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3148
3291
|
}
|
|
3149
3292
|
const encodedTextures = content.textures || {};
|
|
3150
3293
|
if (!textureInfo && (resources.length || content.texture || Object.keys(encodedTextures).length)) {
|
|
3151
|
-
throw new
|
|
3294
|
+
throw new import_core26.TileConversionError(
|
|
3152
3295
|
"I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
3153
3296
|
"Texture resources require a base-color mapping"
|
|
3154
3297
|
);
|
|
@@ -3161,7 +3304,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3161
3304
|
) || ["wrapS", "wrapT"].some(
|
|
3162
3305
|
(name) => textureInfo[name] !== void 0 && (typeof textureInfo[name] !== "string" || !Object.hasOwn(wrappingModes, textureInfo[name]))
|
|
3163
3306
|
) || resources.length > 1 || resources.some((resource) => resource.textureSetDefinitionId !== definitionId) || Object.keys(encodedTextures).some((key) => key !== String(definitionId))) {
|
|
3164
|
-
throw new
|
|
3307
|
+
throw new import_core26.TileConversionError(
|
|
3165
3308
|
"I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
3166
3309
|
"Only one base-color texture set on TEXCOORD_0 with qualified wrapping is supported"
|
|
3167
3310
|
);
|
|
@@ -3169,7 +3312,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3169
3312
|
let encoded = encodedTextures[String(definitionId)] || content.texture || textureInfo.texture?.source?.image;
|
|
3170
3313
|
if (!encoded) {
|
|
3171
3314
|
if (!resources.length || !readExternalResource) {
|
|
3172
|
-
throw new
|
|
3315
|
+
throw new import_core26.TileConversionError(
|
|
3173
3316
|
"I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
3174
3317
|
"Encoded texture bytes or an explicit bounded resource reader are required"
|
|
3175
3318
|
);
|
|
@@ -3188,7 +3331,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3188
3331
|
}
|
|
3189
3332
|
const declaredFormat = resources[0]?.textureFormat;
|
|
3190
3333
|
if (!image || !["image/png", "image/jpeg"].includes(image.mimeType) || declaredFormat && (declaredFormat === "png" ? image.mimeType !== "image/png" : declaredFormat === "jpg" ? image.mimeType !== "image/jpeg" : true)) {
|
|
3191
|
-
throw new
|
|
3334
|
+
throw new import_core26.TileConversionError(
|
|
3192
3335
|
"I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
3193
3336
|
"I3S base-color bytes must have matching PNG/JPEG headers"
|
|
3194
3337
|
);
|
|
@@ -3211,7 +3354,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3211
3354
|
signal
|
|
3212
3355
|
);
|
|
3213
3356
|
if (mapped?.baseColorTexture && !content.sourceAttributes?.uv0) {
|
|
3214
|
-
throw new
|
|
3357
|
+
throw new import_core26.TileConversionError(
|
|
3215
3358
|
"MESH_TEXCOORD_REQUIRED",
|
|
3216
3359
|
"Base-color textures require TEXCOORD_0"
|
|
3217
3360
|
);
|
|
@@ -3221,7 +3364,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
|
|
|
3221
3364
|
|
|
3222
3365
|
// src/v5/i3s-mesh-source-features.ts
|
|
3223
3366
|
var import_i3s2 = require("@loaders.gl/i3s");
|
|
3224
|
-
var
|
|
3367
|
+
var import_core27 = require("@loaders.gl/tile-converter/v5/core");
|
|
3225
3368
|
async function extractI3SMeshFeatures(content, item, metadata, options, readExternalResource, contentUri, signal) {
|
|
3226
3369
|
const maximumAttributeBytes = options.maxAttributeBytes;
|
|
3227
3370
|
const storage = metadata.tileset.attributeStorageInfo;
|
|
@@ -3229,7 +3372,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3229
3372
|
if (!options.metadataClass || !options.objectIdProperty || options.rawMetadataField || !Number.isSafeInteger(maximumAttributeBytes) || maximumAttributeBytes <= 0 || !storage?.length || attributeUrls?.length !== storage.length || !readExternalResource || new Set(storage.map((attribute2) => attribute2.name)).size !== storage.length || new Set(storage.map((attribute2) => attribute2.key)).size !== storage.length || !storage.some((attribute2) => attribute2.name === options.objectIdProperty) || (metadata.tileset.fields || []).some(
|
|
3230
3373
|
(field) => field.domain || field.type === "esriFieldTypeDate" || !storage.some((attribute2) => attribute2.name === field.name)
|
|
3231
3374
|
)) {
|
|
3232
|
-
throw new
|
|
3375
|
+
throw new import_core27.TileConversionError(
|
|
3233
3376
|
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3234
3377
|
"I3S attributes require an explicit scalar schema, object ID column, byte budget and resource reader; domains/dates/raw metadata need a custom mapper"
|
|
3235
3378
|
);
|
|
@@ -3252,7 +3395,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3252
3395
|
) || attribute2.ordering && JSON.stringify(
|
|
3253
3396
|
attribute2.ordering.map((value) => value === "ObjectIds" ? "objectIds" : value)
|
|
3254
3397
|
) !== JSON.stringify(expectedOrdering) || type === "String" && (values.encoding !== "UTF-8" || attribute2.attributeByteCounts?.valueType !== "UInt32" || attribute2.attributeByteCounts.valuesPerElement !== 1) || !attributeUrls[index]) {
|
|
3255
|
-
throw new
|
|
3398
|
+
throw new import_core27.TileConversionError(
|
|
3256
3399
|
"I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
|
|
3257
3400
|
`Attribute ${attribute2.name} requires a standard scalar/string resource layout`
|
|
3258
3401
|
);
|
|
@@ -3261,7 +3404,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3261
3404
|
signal?.throwIfAborted();
|
|
3262
3405
|
inputBytes += bytes instanceof Uint8Array ? bytes.byteLength : Infinity;
|
|
3263
3406
|
if (!(bytes instanceof Uint8Array) || inputBytes > maximumAttributeBytes) {
|
|
3264
|
-
throw new
|
|
3407
|
+
throw new import_core27.TileConversionError(
|
|
3265
3408
|
"INPUT_RESOURCE_TOO_LARGE",
|
|
3266
3409
|
"I3S node attributes exceed maxAttributeBytes"
|
|
3267
3410
|
);
|
|
@@ -3274,14 +3417,14 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3274
3417
|
i3s: { attributeValues: "exact" }
|
|
3275
3418
|
});
|
|
3276
3419
|
} catch (error) {
|
|
3277
|
-
throw new
|
|
3420
|
+
throw new import_core27.TileConversionError(
|
|
3278
3421
|
"I3S_MESH_SOURCE_ATTRIBUTE_INVALID",
|
|
3279
3422
|
`Invalid attribute ${attribute2.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
3280
3423
|
);
|
|
3281
3424
|
}
|
|
3282
3425
|
const column = parsed[attribute2.name];
|
|
3283
3426
|
if (!column || count !== void 0 && column.length !== count || !column.length) {
|
|
3284
|
-
throw new
|
|
3427
|
+
throw new import_core27.TileConversionError(
|
|
3285
3428
|
"I3S_MESH_SOURCE_ATTRIBUTE_COUNT_INVALID",
|
|
3286
3429
|
"Every attribute must have the same nonzero row count"
|
|
3287
3430
|
);
|
|
@@ -3289,7 +3432,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3289
3432
|
if (Array.from(column).some(
|
|
3290
3433
|
(value) => typeof value === "number" && !Number.isFinite(value)
|
|
3291
3434
|
)) {
|
|
3292
|
-
throw new
|
|
3435
|
+
throw new import_core27.TileConversionError(
|
|
3293
3436
|
"I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
|
|
3294
3437
|
"Numeric noData values require an explicit custom mapper"
|
|
3295
3438
|
);
|
|
@@ -3302,7 +3445,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3302
3445
|
for (let index = 0; index < count; index++) {
|
|
3303
3446
|
const identifier = identifiers[index];
|
|
3304
3447
|
if (!(typeof identifier === "bigint" || typeof identifier === "number" && Number.isSafeInteger(identifier)) || attributeRows.has(BigInt(identifier))) {
|
|
3305
|
-
throw new
|
|
3448
|
+
throw new import_core27.TileConversionError(
|
|
3306
3449
|
"I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
|
|
3307
3450
|
"Object IDs must be unique exact integers"
|
|
3308
3451
|
);
|
|
@@ -3313,7 +3456,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3313
3456
|
const geometryIdentifiers = content.featureIds;
|
|
3314
3457
|
const attribute = content.sourceAttributes?.id;
|
|
3315
3458
|
if (geometryIdentifiers.length !== vertexCount || attribute?.size !== 1 || attribute.normalized || attribute.value.length !== vertexCount || Array.from(geometryIdentifiers).some((value, index) => value !== attribute.value[index])) {
|
|
3316
|
-
throw new
|
|
3459
|
+
throw new import_core27.TileConversionError(
|
|
3317
3460
|
"I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
|
|
3318
3461
|
"Geometry requires one exact unnormalized object ID per vertex"
|
|
3319
3462
|
);
|
|
@@ -3324,7 +3467,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3324
3467
|
const covered = new Uint8Array(triangleFeatureIndices.length);
|
|
3325
3468
|
for (const range of content.drawRanges) {
|
|
3326
3469
|
if (!Number.isSafeInteger(range.firstPrimitive) || range.firstPrimitive < 0 || !Number.isSafeInteger(range.primitiveCount) || range.primitiveCount <= 0 || range.firstPrimitive + range.primitiveCount > covered.length) {
|
|
3327
|
-
throw new
|
|
3470
|
+
throw new import_core27.TileConversionError(
|
|
3328
3471
|
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3329
3472
|
"Feature draw ranges must cover valid triangles"
|
|
3330
3473
|
);
|
|
@@ -3332,7 +3475,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3332
3475
|
for (let triangle = range.firstPrimitive; triangle < range.firstPrimitive + range.primitiveCount; triangle++) {
|
|
3333
3476
|
const vertex = indices?.[triangle * 3] ?? triangle * 3;
|
|
3334
3477
|
if (covered[triangle] || geometryIdentifiers[vertex] !== range.featureId) {
|
|
3335
|
-
throw new
|
|
3478
|
+
throw new import_core27.TileConversionError(
|
|
3336
3479
|
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3337
3480
|
"Feature draw ranges must agree with decoded ownership without overlapping"
|
|
3338
3481
|
);
|
|
@@ -3341,7 +3484,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3341
3484
|
}
|
|
3342
3485
|
}
|
|
3343
3486
|
if (covered.some((value) => value === 0)) {
|
|
3344
|
-
throw new
|
|
3487
|
+
throw new import_core27.TileConversionError(
|
|
3345
3488
|
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3346
3489
|
"Feature draw ranges must cover every triangle"
|
|
3347
3490
|
);
|
|
@@ -3355,7 +3498,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3355
3498
|
const featureIds = corners.map((corner) => geometryIdentifiers[corner]);
|
|
3356
3499
|
const row = attributeRows.get(BigInt(featureIds[0]));
|
|
3357
3500
|
if (row === void 0 || featureIds.some((identifier) => identifier !== featureIds[0])) {
|
|
3358
|
-
throw new
|
|
3501
|
+
throw new import_core27.TileConversionError(
|
|
3359
3502
|
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3360
3503
|
"Each triangle must reference one declared attribute row"
|
|
3361
3504
|
);
|
|
@@ -3364,14 +3507,14 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3364
3507
|
usedRows.add(row);
|
|
3365
3508
|
}
|
|
3366
3509
|
if (usedRows.size !== count) {
|
|
3367
|
-
throw new
|
|
3510
|
+
throw new import_core27.TileConversionError(
|
|
3368
3511
|
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3369
3512
|
"Attribute rows without geometry require a separately qualified mapping"
|
|
3370
3513
|
);
|
|
3371
3514
|
}
|
|
3372
3515
|
const stableProperty = options.sourceFeatureIdProperty ?? options.objectIdProperty;
|
|
3373
3516
|
if (!Object.hasOwn(columns, stableProperty)) {
|
|
3374
|
-
throw new
|
|
3517
|
+
throw new import_core27.TileConversionError(
|
|
3375
3518
|
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3376
3519
|
"Stable identifier column is missing"
|
|
3377
3520
|
);
|
|
@@ -3392,7 +3535,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3392
3535
|
for (let index = 0; index < column.length; index++) {
|
|
3393
3536
|
const identifier = column.get(index);
|
|
3394
3537
|
if (stableIds.has(identifier)) {
|
|
3395
|
-
throw new
|
|
3538
|
+
throw new import_core27.TileConversionError(
|
|
3396
3539
|
"I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
|
|
3397
3540
|
"Stable IDs must be unique"
|
|
3398
3541
|
);
|
|
@@ -3410,25 +3553,25 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
|
|
|
3410
3553
|
|
|
3411
3554
|
// src/v5/i3s-mesh-source.ts
|
|
3412
3555
|
function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
3413
|
-
const source = (0,
|
|
3556
|
+
const source = (0, import_core28.createTilesetConversionSource)(tileset, options);
|
|
3414
3557
|
return {
|
|
3415
3558
|
/** Validates the layer profile and explicit source decode policy before reading. */
|
|
3416
3559
|
async inspect(signal) {
|
|
3417
3560
|
if (options.features && options.getFeatures) {
|
|
3418
|
-
throw new
|
|
3561
|
+
throw new import_core28.TileConversionError(
|
|
3419
3562
|
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3420
3563
|
"Choose explicit resource features or a custom getFeatures mapper, not both"
|
|
3421
3564
|
);
|
|
3422
3565
|
}
|
|
3423
3566
|
const metadata = await source.inspect(signal);
|
|
3424
3567
|
if (metadata.type !== "I3S" || !["3DObject", "IntegratedMesh"].includes(metadata.tileset.layerType) || tileset.options.i3s?.geometryMode !== "source") {
|
|
3425
|
-
throw new
|
|
3568
|
+
throw new import_core28.TileConversionError(
|
|
3426
3569
|
"I3S_MESH_SOURCE_PROFILE_UNSUPPORTED",
|
|
3427
3570
|
"I3S mesh extraction requires a 3DObject/IntegratedMesh runtime with geometryMode=source"
|
|
3428
3571
|
);
|
|
3429
3572
|
}
|
|
3430
3573
|
if (!metadata.spatialReference?.sourceCrs) {
|
|
3431
|
-
throw new
|
|
3574
|
+
throw new import_core28.TileConversionError(
|
|
3432
3575
|
"I3S_MESH_SOURCE_REFERENCE_REQUIRED",
|
|
3433
3576
|
"I3S conversion requires an explicit source CRS"
|
|
3434
3577
|
);
|
|
@@ -3456,7 +3599,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3456
3599
|
].some(
|
|
3457
3600
|
(field) => JSON.stringify(content.spatialReference?.[field]) !== JSON.stringify(metadata.spatialReference?.[field])
|
|
3458
3601
|
)) {
|
|
3459
|
-
throw new
|
|
3602
|
+
throw new import_core28.TileConversionError(
|
|
3460
3603
|
"I3S_MESH_SOURCE_GEOMETRY_UNSUPPORTED",
|
|
3461
3604
|
"I3S extraction requires original source-frame Float64 triangle geometry"
|
|
3462
3605
|
);
|
|
@@ -3464,7 +3607,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3464
3607
|
if (!["earth-centered", "vertex-reference-frame"].includes(
|
|
3465
3608
|
content.normalReferenceFrame || ""
|
|
3466
3609
|
) || content.normalReferenceFrame !== (metadata.tileset.store?.normalReferenceFrame || "earth-centered")) {
|
|
3467
|
-
throw new
|
|
3610
|
+
throw new import_core28.TileConversionError(
|
|
3468
3611
|
"I3S_MESH_SOURCE_NORMAL_FRAME_UNSUPPORTED",
|
|
3469
3612
|
"Unknown I3S vector basis"
|
|
3470
3613
|
);
|
|
@@ -3472,20 +3615,20 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3472
3615
|
if (content.meshSegmentation || Object.entries(attributes).some(
|
|
3473
3616
|
([name, value]) => value && !["position", "normal", "uv0", "id", "faceRange"].includes(name)
|
|
3474
3617
|
)) {
|
|
3475
|
-
throw new
|
|
3618
|
+
throw new import_core28.TileConversionError(
|
|
3476
3619
|
"I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
|
|
3477
3620
|
"Only positions, normals, TEXCOORD_0 and explicitly mapped feature associations are supported"
|
|
3478
3621
|
);
|
|
3479
3622
|
}
|
|
3480
3623
|
const hasFeatures = content.featureIds.length > 0 || metadata.tileset.fields?.length || metadata.tileset.attributeStorageInfo?.length;
|
|
3481
3624
|
if (Array.from(content.featureIds).some((value) => !Number.isSafeInteger(value))) {
|
|
3482
|
-
throw new
|
|
3625
|
+
throw new import_core28.TileConversionError(
|
|
3483
3626
|
"I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
|
|
3484
3627
|
"Decoded numeric feature IDs must be exact safe integers"
|
|
3485
3628
|
);
|
|
3486
3629
|
}
|
|
3487
3630
|
if (hasFeatures && !options.getFeatures && !options.features) {
|
|
3488
|
-
throw new
|
|
3631
|
+
throw new import_core28.TileConversionError(
|
|
3489
3632
|
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3490
3633
|
"I3S feature data requires explicit resource features or a custom Arrow mapper"
|
|
3491
3634
|
);
|
|
@@ -3504,7 +3647,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3504
3647
|
true
|
|
3505
3648
|
);
|
|
3506
3649
|
if (mesh.attributes.POSITION.value.length / 3 !== content.vertexCount) {
|
|
3507
|
-
throw new
|
|
3650
|
+
throw new import_core28.TileConversionError(
|
|
3508
3651
|
"I3S_MESH_SOURCE_COUNT_INVALID",
|
|
3509
3652
|
"I3S vertexCount must match the decoded geometry"
|
|
3510
3653
|
);
|
|
@@ -3520,7 +3663,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3520
3663
|
) : void 0;
|
|
3521
3664
|
signal?.throwIfAborted();
|
|
3522
3665
|
if (hasFeatures && !features) {
|
|
3523
|
-
throw new
|
|
3666
|
+
throw new import_core28.TileConversionError(
|
|
3524
3667
|
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3525
3668
|
"The feature mapper must return all declared feature data"
|
|
3526
3669
|
);
|
|
@@ -3536,6 +3679,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3536
3679
|
yield {
|
|
3537
3680
|
id: `${item.tile.id}/${entry.index}`,
|
|
3538
3681
|
origin: [0, 0, 0],
|
|
3682
|
+
sourceLayerType: metadata.tileset.layerType,
|
|
3539
3683
|
mesh,
|
|
3540
3684
|
normalReferenceFrame: content.normalReferenceFrame === "vertex-reference-frame" ? "vertex-reference-frame" : "earth-centered",
|
|
3541
3685
|
features,
|
|
@@ -3546,3 +3690,1281 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
3546
3690
|
}
|
|
3547
3691
|
};
|
|
3548
3692
|
}
|
|
3693
|
+
|
|
3694
|
+
// src/v5/point-cloud-tileset.ts
|
|
3695
|
+
var import_point_cloud_tile_source = require("@loaders.gl/tiles/point-cloud-tile-source");
|
|
3696
|
+
var import_core29 = require("@loaders.gl/tile-converter/v5/core");
|
|
3697
|
+
async function convertPointCloudToTileset(input, options) {
|
|
3698
|
+
const reference = options.spatialReference;
|
|
3699
|
+
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")) {
|
|
3700
|
+
throw new import_core29.TileConversionError(
|
|
3701
|
+
"POINT_CLOUD_TILESET_FRAME_UNSUPPORTED",
|
|
3702
|
+
"Point authoring requires explicit native EPSG:4978 xyz meters and ellipsoidal heights"
|
|
3703
|
+
);
|
|
3704
|
+
}
|
|
3705
|
+
if (options.archive && (!Number.isSafeInteger(options.archive.maxArchiveBytes) || options.archive.maxArchiveBytes < 0)) {
|
|
3706
|
+
throw new import_core29.TileConversionError(
|
|
3707
|
+
"INVALID_ARCHIVE_BYTE_LIMIT",
|
|
3708
|
+
"Archive byte limit must be a non-negative safe integer"
|
|
3709
|
+
);
|
|
3710
|
+
}
|
|
3711
|
+
options.signal?.throwIfAborted();
|
|
3712
|
+
const sink = createPointCloudTilesetSink(options);
|
|
3713
|
+
let source;
|
|
3714
|
+
try {
|
|
3715
|
+
source = new import_point_cloud_tile_source.PointCloudTileSource(input, {
|
|
3716
|
+
pointCloudTiler: { ...options.tiling, signal: options.signal, spatialReference: reference }
|
|
3717
|
+
});
|
|
3718
|
+
const { pointCount } = await source.getMetadata();
|
|
3719
|
+
const report = await convertPointCloudSource(source, {
|
|
3720
|
+
sink,
|
|
3721
|
+
signal: options.signal,
|
|
3722
|
+
onProgress: options.onProgress,
|
|
3723
|
+
measureInputBytes: (tile) => {
|
|
3724
|
+
const table = tile.content?.data.data;
|
|
3725
|
+
return table ? table.schema.fields.reduce(
|
|
3726
|
+
(bytes, _field, index) => bytes + (table.getChildAt(index)?.byteLength ?? 0),
|
|
3727
|
+
0
|
|
3728
|
+
) : 0;
|
|
3729
|
+
},
|
|
3730
|
+
getTileEncodingOptions: (tile) => ({
|
|
3731
|
+
rtcCenter: [
|
|
3732
|
+
tile.header.boundingVolume.center[0],
|
|
3733
|
+
tile.header.boundingVolume.center[1],
|
|
3734
|
+
tile.header.boundingVolume.center[2]
|
|
3735
|
+
],
|
|
3736
|
+
maxPositionError: options.geometricError
|
|
3737
|
+
})
|
|
3738
|
+
});
|
|
3739
|
+
options.signal?.throwIfAborted();
|
|
3740
|
+
const files = sink.getFiles();
|
|
3741
|
+
const archive = options.archive ? await createTileConversionArchive(files, {
|
|
3742
|
+
format: "3tz",
|
|
3743
|
+
maxArchiveBytes: options.archive.maxArchiveBytes,
|
|
3744
|
+
signal: options.signal
|
|
3745
|
+
}) : void 0;
|
|
3746
|
+
options.signal?.throwIfAborted();
|
|
3747
|
+
return { pointCount, report, files, archive };
|
|
3748
|
+
} catch (error) {
|
|
3749
|
+
await sink.abort(error);
|
|
3750
|
+
throw error;
|
|
3751
|
+
} finally {
|
|
3752
|
+
source?.close();
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
|
|
3756
|
+
// src/v5/point-cloud-file.ts
|
|
3757
|
+
var import_copc = require("@loaders.gl/copc");
|
|
3758
|
+
var import_potree = require("@loaders.gl/potree");
|
|
3759
|
+
var import_schema_utils5 = require("@loaders.gl/schema-utils");
|
|
3760
|
+
var import_zip = require("@loaders.gl/zip");
|
|
3761
|
+
async function encodePotreeArchive(input, options) {
|
|
3762
|
+
validateLimit(options.maxArchiveBytes);
|
|
3763
|
+
const dataset = await (0, import_potree.encodePotreeDataset)(input, { potree: options.potree });
|
|
3764
|
+
return encodePotreeDatasetArchive(dataset, options.maxArchiveBytes, options.potree?.signal);
|
|
3765
|
+
}
|
|
3766
|
+
async function encodePotreeDatasetArchive(dataset, maxArchiveBytes, signal) {
|
|
3767
|
+
validateLimit(maxArchiveBytes);
|
|
3768
|
+
for (const path of dataset.files.keys()) validatePath(path);
|
|
3769
|
+
signal?.throwIfAborted();
|
|
3770
|
+
const archive = await (0, import_zip.encodeZip)(Object.fromEntries(dataset.files), {
|
|
3771
|
+
compression: "store",
|
|
3772
|
+
zip64: "never",
|
|
3773
|
+
maxEntries: 65535,
|
|
3774
|
+
maxArchiveBytes: BigInt(maxArchiveBytes),
|
|
3775
|
+
signal
|
|
3776
|
+
});
|
|
3777
|
+
signal?.throwIfAborted();
|
|
3778
|
+
return new Blob([archive], { type: "application/zip" });
|
|
3779
|
+
}
|
|
3780
|
+
async function openPointCloudFile(input, options) {
|
|
3781
|
+
validateLimit(options.maxFileBytes);
|
|
3782
|
+
if (input.size > options.maxFileBytes) throw new RangeError("Point file input budget exceeded");
|
|
3783
|
+
options.signal?.throwIfAborted();
|
|
3784
|
+
if (options.format === "copc") {
|
|
3785
|
+
const source2 = import_copc.COPCSourceLoader.createDataSource(input, options.sourceOptions || {});
|
|
3786
|
+
try {
|
|
3787
|
+
await source2.metadata;
|
|
3788
|
+
options.signal?.throwIfAborted();
|
|
3789
|
+
return { source: source2, close: () => source2.close() };
|
|
3790
|
+
} catch (error) {
|
|
3791
|
+
await source2.close();
|
|
3792
|
+
throw error;
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
if (options.format !== "potree-zip") throw new TypeError("Unsupported point file format");
|
|
3796
|
+
const maxEntries = options.maxEntries ?? 1e5;
|
|
3797
|
+
const maxResourceBytes = options.maxResourceBytes ?? 64 * 1024 * 1024;
|
|
3798
|
+
validateLimit(maxEntries);
|
|
3799
|
+
validateLimit(maxResourceBytes);
|
|
3800
|
+
const controller = new AbortController();
|
|
3801
|
+
const signals = [controller.signal, options.signal, options.sourceOptions?.potree?.signal].filter(
|
|
3802
|
+
(value) => Boolean(value)
|
|
3803
|
+
);
|
|
3804
|
+
const signal = AbortSignal.any(signals);
|
|
3805
|
+
let archive;
|
|
3806
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3807
|
+
let source;
|
|
3808
|
+
try {
|
|
3809
|
+
archive = await (0, import_zip.openZipArchive)(input, {
|
|
3810
|
+
maxArchiveBytes: BigInt(options.maxFileBytes),
|
|
3811
|
+
maxEntries,
|
|
3812
|
+
maxReadBytes: Math.max(65557, maxResourceBytes),
|
|
3813
|
+
maxCompressedBytes: BigInt(maxResourceBytes),
|
|
3814
|
+
maxDecodedBytes: BigInt(maxResourceBytes),
|
|
3815
|
+
signal
|
|
3816
|
+
});
|
|
3817
|
+
for await (const entry of archive.entries()) {
|
|
3818
|
+
if (entry.directory) continue;
|
|
3819
|
+
validatePath(entry.path);
|
|
3820
|
+
if (entry.compression !== "store")
|
|
3821
|
+
throw new Error("Potree bundles require unencrypted STORE entries");
|
|
3822
|
+
entries.set(entry.path, entry);
|
|
3823
|
+
}
|
|
3824
|
+
const candidates = ["metadata.json", "cloud.js"].filter((path) => entries.has(path));
|
|
3825
|
+
const metadataPath = options.metadataPath || (candidates.length === 1 ? candidates[0] : "");
|
|
3826
|
+
validatePath(metadataPath);
|
|
3827
|
+
if (!entries.has(metadataPath) || !/(^|\/)(metadata\.json|cloud\.js)$/.test(metadataPath))
|
|
3828
|
+
throw new Error("Potree ZIP requires one metadata entry or an explicit metadataPath");
|
|
3829
|
+
const prefix = "https://point-bundle.loaders.gl.invalid/";
|
|
3830
|
+
const fetchResource = async (resource, request = {}) => {
|
|
3831
|
+
signal.throwIfAborted();
|
|
3832
|
+
request.signal?.throwIfAborted();
|
|
3833
|
+
const url = new URL(resource);
|
|
3834
|
+
if (url.origin !== new URL(prefix).origin || url.search || url.hash)
|
|
3835
|
+
throw new Error("Resource outside the Potree bundle");
|
|
3836
|
+
const path = decodeURIComponent(url.pathname.slice(1));
|
|
3837
|
+
validatePath(path);
|
|
3838
|
+
const entry = entries.get(path);
|
|
3839
|
+
if (!entry) throw new Error(`Missing Potree bundle resource: ${path}`);
|
|
3840
|
+
const range = new Headers(request.headers).get("range");
|
|
3841
|
+
const match = range?.match(/^bytes=(\d+)-(\d+)$/);
|
|
3842
|
+
if (range && !match) throw new RangeError("Unsupported Potree bundle byte range");
|
|
3843
|
+
const begin = match ? Number(match[1]) : 0;
|
|
3844
|
+
const end = match ? Number(match[2]) + 1 : Number(entry.size);
|
|
3845
|
+
if (!Number.isSafeInteger(begin) || !Number.isSafeInteger(end) || begin < 0 || end < begin || BigInt(end) > entry.size || end - begin > maxResourceBytes)
|
|
3846
|
+
throw new RangeError("Potree bundle resource/range budget exceeded");
|
|
3847
|
+
const headers = new Headers({
|
|
3848
|
+
"content-length": String(end - begin),
|
|
3849
|
+
"accept-ranges": "bytes"
|
|
3850
|
+
});
|
|
3851
|
+
if (match) headers.set("content-range", `bytes ${begin}-${end - 1}/${entry.size}`);
|
|
3852
|
+
const bytes = match ? await archive.readRange(entry, {
|
|
3853
|
+
start: BigInt(begin),
|
|
3854
|
+
end: BigInt(end),
|
|
3855
|
+
signal: request.signal ?? void 0
|
|
3856
|
+
}) : await archive.read(entry, { signal: request.signal ?? void 0 });
|
|
3857
|
+
const response = new Response(bytes, {
|
|
3858
|
+
status: match ? 206 : 200,
|
|
3859
|
+
headers
|
|
3860
|
+
});
|
|
3861
|
+
Object.defineProperty(response, "url", { value: url.href });
|
|
3862
|
+
signal.throwIfAborted();
|
|
3863
|
+
return response;
|
|
3864
|
+
};
|
|
3865
|
+
source = import_potree.PotreeSourceLoader.createDataSource(`${prefix}${metadataPath}`, {
|
|
3866
|
+
...options.sourceOptions,
|
|
3867
|
+
core: { ...options.sourceOptions?.core, fetch: fetchResource },
|
|
3868
|
+
potree: { ...options.sourceOptions?.potree, signal }
|
|
3869
|
+
});
|
|
3870
|
+
await source.initialize();
|
|
3871
|
+
signal.throwIfAborted();
|
|
3872
|
+
return {
|
|
3873
|
+
source,
|
|
3874
|
+
async close() {
|
|
3875
|
+
controller.abort();
|
|
3876
|
+
source?.close();
|
|
3877
|
+
await archive?.close();
|
|
3878
|
+
}
|
|
3879
|
+
};
|
|
3880
|
+
} catch (error) {
|
|
3881
|
+
controller.abort();
|
|
3882
|
+
source?.close();
|
|
3883
|
+
await archive?.close();
|
|
3884
|
+
throw error;
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
function extractMeshVertices(mesh, options) {
|
|
3888
|
+
validateLimit(options.maxPoints);
|
|
3889
|
+
const position = mesh.attributes.POSITION;
|
|
3890
|
+
const pointCount = position?.value.length / 3;
|
|
3891
|
+
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)))
|
|
3892
|
+
throw new Error(
|
|
3893
|
+
"Mesh vertex harvesting requires packed finite triangle XYZ within the point budget"
|
|
3894
|
+
);
|
|
3895
|
+
const attributes = { POSITION: position };
|
|
3896
|
+
for (const name of options.attributes) {
|
|
3897
|
+
const attribute = mesh.attributes[name];
|
|
3898
|
+
if (!attribute || attribute.transform || attribute.byteStride || attribute.byteOffset || !Number.isSafeInteger(attribute.size) || attribute.size < 1 || attribute.value.length !== pointCount * attribute.size)
|
|
3899
|
+
throw new Error(`Invalid selected mesh vertex attribute: ${name}`);
|
|
3900
|
+
attributes[name] = attribute;
|
|
3901
|
+
}
|
|
3902
|
+
return { topology: "point-list", mode: 0, attributes, schema: (0, import_schema_utils5.deduceMeshSchema)(attributes) };
|
|
3903
|
+
}
|
|
3904
|
+
function validatePath(path) {
|
|
3905
|
+
if (!path || !/^[A-Za-z0-9_.\/-]+$/.test(path) || path.split("/").some((part) => !part || part === "." || part === ".."))
|
|
3906
|
+
throw new Error("Potree bundle paths must be canonical relative ASCII paths");
|
|
3907
|
+
}
|
|
3908
|
+
function validateLimit(value) {
|
|
3909
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
3910
|
+
throw new RangeError("Point file limits must be positive safe integers");
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
// src/v5/point-cloud-potree.ts
|
|
3914
|
+
var import_schema_utils6 = require("@loaders.gl/schema-utils");
|
|
3915
|
+
var import_potree2 = require("@loaders.gl/potree");
|
|
3916
|
+
async function convertPointCloudToPotree(input, options) {
|
|
3917
|
+
if (typeof options.projection !== "string" || !options.projection.trim()) {
|
|
3918
|
+
throw new Error("Potree authoring requires an explicit native CRS declaration");
|
|
3919
|
+
}
|
|
3920
|
+
for (const limit of [
|
|
3921
|
+
options.maxGatheredBytes,
|
|
3922
|
+
options.maxOutputBytes,
|
|
3923
|
+
options.archive?.maxArchiveBytes
|
|
3924
|
+
]) {
|
|
3925
|
+
if (limit !== void 0 && (!Number.isSafeInteger(limit) || limit < 1)) {
|
|
3926
|
+
throw new RangeError("Potree authoring byte limits must be positive safe integers");
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
if (options.maxGatheredBytes === void 0 || options.maxOutputBytes === void 0) {
|
|
3930
|
+
throw new RangeError("Potree authoring requires gathered and encoded byte limits");
|
|
3931
|
+
}
|
|
3932
|
+
options.signal?.throwIfAborted();
|
|
3933
|
+
const tiler = new import_schema_utils6.PointCloudTiler(input, {
|
|
3934
|
+
...options.tiling,
|
|
3935
|
+
maxNodes: Math.min(options.tiling?.maxNodes ?? 1e5, options.potree?.maxNodes ?? 1e5),
|
|
3936
|
+
maxTileBytes: Math.min(
|
|
3937
|
+
options.tiling?.maxTileBytes ?? 64 * 1024 * 1024,
|
|
3938
|
+
options.maxGatheredBytes
|
|
3939
|
+
),
|
|
3940
|
+
signal: options.signal
|
|
3941
|
+
});
|
|
3942
|
+
const nodes = [];
|
|
3943
|
+
let gatheredBytes = 0;
|
|
3944
|
+
let pointByteLength = 0;
|
|
3945
|
+
let pointCount;
|
|
3946
|
+
try {
|
|
3947
|
+
const pending = [await tiler.getRootNode()];
|
|
3948
|
+
pointCount = tiler.pointCount;
|
|
3949
|
+
while (pending.length) {
|
|
3950
|
+
options.signal?.throwIfAborted();
|
|
3951
|
+
const node = pending.pop();
|
|
3952
|
+
if (node.pointCount * pointByteLength > options.maxGatheredBytes - gatheredBytes) {
|
|
3953
|
+
throw new RangeError("Potree gathered node byte budget exceeded");
|
|
3954
|
+
}
|
|
3955
|
+
const mesh = await tiler.getTileMesh(node.id);
|
|
3956
|
+
const nodeBytes = Array.from(
|
|
3957
|
+
new Set(Object.values(mesh.attributes).map((attribute) => attribute.value.buffer))
|
|
3958
|
+
).reduce((bytes, buffer) => bytes + buffer.byteLength, 0);
|
|
3959
|
+
if (nodeBytes > options.maxGatheredBytes - gatheredBytes) {
|
|
3960
|
+
throw new RangeError("Potree gathered node byte budget exceeded");
|
|
3961
|
+
}
|
|
3962
|
+
if (node.pointCount) pointByteLength = nodeBytes / node.pointCount;
|
|
3963
|
+
gatheredBytes += nodeBytes;
|
|
3964
|
+
nodes.push({ id: node.id, mesh });
|
|
3965
|
+
const children = await tiler.getChildNodes(node.id);
|
|
3966
|
+
for (let index = children.length - 1; index >= 0; index--) pending.push(children[index]);
|
|
3967
|
+
}
|
|
3968
|
+
} finally {
|
|
3969
|
+
tiler.close();
|
|
3970
|
+
}
|
|
3971
|
+
const dataset = await (0, import_potree2.encodePotreeDataset)(nodes, {
|
|
3972
|
+
potree: {
|
|
3973
|
+
...options.potree,
|
|
3974
|
+
projection: options.projection,
|
|
3975
|
+
maxOutputBytes: options.maxOutputBytes,
|
|
3976
|
+
signal: options.signal
|
|
3977
|
+
}
|
|
3978
|
+
});
|
|
3979
|
+
const archive = options.archive ? await encodePotreeDatasetArchive(dataset, options.archive.maxArchiveBytes, options.signal) : void 0;
|
|
3980
|
+
options.signal?.throwIfAborted();
|
|
3981
|
+
return { pointCount, nodeCount: nodes.length, dataset, archive };
|
|
3982
|
+
}
|
|
3983
|
+
|
|
3984
|
+
// src/v5/mesh-hierarchy.ts
|
|
3985
|
+
var import_core30 = require("@math.gl/core");
|
|
3986
|
+
var import_tiles3 = require("@loaders.gl/tiles");
|
|
3987
|
+
var import_core31 = require("@loaders.gl/tile-converter/v5/core");
|
|
3988
|
+
function prepareMeshHierarchy(inspection, resourceIds) {
|
|
3989
|
+
const rejectHierarchy = (message) => {
|
|
3990
|
+
throw new import_core31.TileConversionError("MESH_HIERARCHY_UNSUPPORTED", message);
|
|
3991
|
+
};
|
|
3992
|
+
if (!inspection.documents?.length || inspection.documents.length > 32)
|
|
3993
|
+
rejectHierarchy(
|
|
3994
|
+
"Hierarchy preservation requires expanded metadata inspection of at most 32 explicit document placements"
|
|
3995
|
+
);
|
|
3996
|
+
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)))
|
|
3997
|
+
rejectHierarchy(
|
|
3998
|
+
"Hierarchy preservation requires every content placement, without duplicates (at most 64)"
|
|
3999
|
+
);
|
|
4000
|
+
const entryTransform = new import_core30.Matrix4();
|
|
4001
|
+
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]))
|
|
4002
|
+
rejectHierarchy("The entry document must have identity parent placement");
|
|
4003
|
+
const documents = inspection.documents.map((placement) => placement.tileset);
|
|
4004
|
+
const document = documents[0];
|
|
4005
|
+
const externalDocuments = /* @__PURE__ */ new Map();
|
|
4006
|
+
for (let index = 1; index < inspection.documents.length; index++) {
|
|
4007
|
+
const identifier = inspection.documents[index].parentResourceId;
|
|
4008
|
+
if (!identifier || externalDocuments.has(identifier))
|
|
4009
|
+
rejectHierarchy(
|
|
4010
|
+
"Every external document requires a distinct inspected parent content placement"
|
|
4011
|
+
);
|
|
4012
|
+
externalDocuments.set(identifier, index);
|
|
4013
|
+
}
|
|
4014
|
+
for (const document2 of documents) {
|
|
4015
|
+
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"))
|
|
4016
|
+
rejectHierarchy(
|
|
4017
|
+
"Dataset metadata and unmapped extensions require a separate hierarchy profile"
|
|
4018
|
+
);
|
|
4019
|
+
if (!["1.0", "1.1"].includes(document2.asset?.version ?? ""))
|
|
4020
|
+
rejectHierarchy("Hierarchy preservation requires a 3D Tiles 1.0 or 1.1 document");
|
|
4021
|
+
if (document2.geometricError !== void 0 && (typeof document2.geometricError !== "number" || !Number.isFinite(document2.geometricError) || document2.geometricError < 0))
|
|
4022
|
+
rejectHierarchy("Dataset geometricError must be finite nonnegative meters");
|
|
4023
|
+
}
|
|
4024
|
+
const spatialReferences = documents.map(
|
|
4025
|
+
(document2) => (0, import_tiles3.get3DTilesSpatialReference)(document2)
|
|
4026
|
+
);
|
|
4027
|
+
const spatialReference = (0, import_core31.createTiles3DConversionSpatialContext)(
|
|
4028
|
+
spatialReferences[0]
|
|
4029
|
+
).spatialReference;
|
|
4030
|
+
if (spatialReference.status !== "native" || spatialReference.sourceCrs !== "EPSG:4978" || spatialReference.heightReference !== "ellipsoidal" || spatialReference.axisOrder !== "xyz" || spatialReference.verticalUnitScale !== 1 || spatialReference.units?.some((unit) => unit !== "meter"))
|
|
4031
|
+
rejectHierarchy(
|
|
4032
|
+
"Hierarchy preservation requires native EPSG:4978 xyz meters and ellipsoidal heights"
|
|
4033
|
+
);
|
|
4034
|
+
for (const reference of spatialReferences.slice(1)) {
|
|
4035
|
+
const inherited = reference.crs.state === "absent" && reference.coordinateEpoch === void 0 && !reference.verticalCrs && !reference.warnings.length;
|
|
4036
|
+
const context = (0, import_core31.createTiles3DConversionSpatialContext)(inherited ? spatialReference : reference);
|
|
4037
|
+
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)
|
|
4038
|
+
rejectHierarchy("External document CRS and coordinate epoch must match the entry frame");
|
|
4039
|
+
}
|
|
4040
|
+
const nodes = [];
|
|
4041
|
+
const visitedDocuments = /* @__PURE__ */ new Set([0]);
|
|
4042
|
+
const stack = [
|
|
4043
|
+
{
|
|
4044
|
+
tile: document.root,
|
|
4045
|
+
documentIndex: 0,
|
|
4046
|
+
prefix: "",
|
|
4047
|
+
path: [],
|
|
4048
|
+
parent: -1,
|
|
4049
|
+
transform: new import_core30.Matrix4(),
|
|
4050
|
+
refine: void 0
|
|
4051
|
+
}
|
|
4052
|
+
];
|
|
4053
|
+
while (stack.length) {
|
|
4054
|
+
const {
|
|
4055
|
+
tile,
|
|
4056
|
+
path,
|
|
4057
|
+
parent,
|
|
4058
|
+
documentIndex,
|
|
4059
|
+
prefix,
|
|
4060
|
+
transform: parentTransform,
|
|
4061
|
+
refine: inheritedRefine
|
|
4062
|
+
} = stack.pop();
|
|
4063
|
+
const document2 = documents[documentIndex];
|
|
4064
|
+
if (nodes.length >= 256) rejectHierarchy("Hierarchy preservation supports at most 256 tiles");
|
|
4065
|
+
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))
|
|
4066
|
+
rejectHierarchy(
|
|
4067
|
+
"Hierarchy tiles require explicit children, exclusive content/contents declarations and no unmapped metadata, extensions or request volumes"
|
|
4068
|
+
);
|
|
4069
|
+
const refine = tile.refine ?? inheritedRefine;
|
|
4070
|
+
if (refine !== "ADD" && refine !== "REPLACE")
|
|
4071
|
+
rejectHierarchy("Root refinement must be explicit ADD or REPLACE; children may inherit");
|
|
4072
|
+
const matrix = tile.transform;
|
|
4073
|
+
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))
|
|
4074
|
+
rejectHierarchy("Hierarchy transforms must be finite affine matrices");
|
|
4075
|
+
const transform = new import_core30.Matrix4(parentTransform).multiplyRight(matrix ?? new import_core30.Matrix4());
|
|
4076
|
+
const columns = [0, 1, 2].map(
|
|
4077
|
+
(column) => [0, 1, 2].reduce((sum, row) => sum + Math.abs(transform[column * 4 + row]), 0)
|
|
4078
|
+
);
|
|
4079
|
+
const rows = [0, 1, 2].map(
|
|
4080
|
+
(row) => [0, 1, 2].reduce((sum, column) => sum + Math.abs(transform[column * 4 + row]), 0)
|
|
4081
|
+
);
|
|
4082
|
+
const scale = document2.asset?.version === "1.0" ? 1 : Math.sqrt(Math.max(...columns)) * Math.sqrt(Math.max(...rows));
|
|
4083
|
+
const geometricError2 = (tile.geometricError ?? NaN) * scale;
|
|
4084
|
+
if (typeof tile.geometricError !== "number" || (tile.geometricError ?? -1) < 0 || !Number.isFinite(geometricError2) || Array.from(transform).some((value) => !Number.isFinite(value)))
|
|
4085
|
+
rejectHierarchy("Every hierarchy tile must declare a finite nonnegative metric error");
|
|
4086
|
+
const contents = tile.contents ?? (tile.content ? [tile.content] : []);
|
|
4087
|
+
const identifiers = contents.map(
|
|
4088
|
+
(_, contentIndex) => `${prefix}tile-${path.length ? path.join("-") : "root"}-content-${contentIndex}`
|
|
4089
|
+
);
|
|
4090
|
+
const externalIndexes = identifiers.map((identifier) => externalDocuments.get(identifier));
|
|
4091
|
+
const externalIndex = externalIndexes.find((index2) => index2 !== void 0);
|
|
4092
|
+
for (const [contentIndex, content] of contents.entries()) {
|
|
4093
|
+
if (!content || typeof content !== "object" || Array.isArray(content) || content.metadata !== void 0 || content.group !== void 0 || content.extensions !== void 0)
|
|
4094
|
+
rejectHierarchy("Content metadata, groups and extensions require a separate mapping");
|
|
4095
|
+
if (externalIndexes[contentIndex] === void 0 && !inspection.resources.some(
|
|
4096
|
+
(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])
|
|
4097
|
+
))
|
|
4098
|
+
rejectHierarchy("Hierarchy content must match the inspected placement");
|
|
4099
|
+
}
|
|
4100
|
+
const resourceId = identifiers[0];
|
|
4101
|
+
if (externalIndex !== void 0) {
|
|
4102
|
+
if (contents.length !== 1 || tile.contents !== void 0 || tile.children?.length || visitedDocuments.has(externalIndex))
|
|
4103
|
+
rejectHierarchy(
|
|
4104
|
+
"External references require a single content, no source children and a unique document placement"
|
|
4105
|
+
);
|
|
4106
|
+
visitedDocuments.add(externalIndex);
|
|
4107
|
+
const placement = inspection.documents[externalIndex];
|
|
4108
|
+
if (!Array.isArray(placement.parentTransform) || placement.parentTransform.length !== 16 || placement.parentTransform.some((value, index2) => value !== transform[index2]))
|
|
4109
|
+
rejectHierarchy("External document placement must match its referencing tile transform");
|
|
4110
|
+
const contentUri = tile.content?.uri ?? tile.content?.url;
|
|
4111
|
+
if (!contentUri || !new URL(contentUri, inspection.documents[documentIndex].url).pathname.toLowerCase().endsWith(".json"))
|
|
4112
|
+
rejectHierarchy("External documents require an inspected .json content reference");
|
|
4113
|
+
}
|
|
4114
|
+
const index = nodes.length;
|
|
4115
|
+
nodes.push({
|
|
4116
|
+
children: [],
|
|
4117
|
+
resourceIds: externalIndex === void 0 ? identifiers : [],
|
|
4118
|
+
geometricError: Math.max(
|
|
4119
|
+
geometricError2,
|
|
4120
|
+
externalIndex === void 0 ? 0 : documents[externalIndex].geometricError ?? 0
|
|
4121
|
+
),
|
|
4122
|
+
refine
|
|
4123
|
+
});
|
|
4124
|
+
if (parent !== -1) nodes[parent].children.push(index);
|
|
4125
|
+
if (externalIndex !== void 0) {
|
|
4126
|
+
stack.push({
|
|
4127
|
+
tile: documents[externalIndex].root,
|
|
4128
|
+
path: [],
|
|
4129
|
+
parent: index,
|
|
4130
|
+
documentIndex: externalIndex,
|
|
4131
|
+
prefix: resourceId + "/",
|
|
4132
|
+
transform,
|
|
4133
|
+
refine: void 0
|
|
4134
|
+
});
|
|
4135
|
+
}
|
|
4136
|
+
if (nodes.length + stack.length + (tile.children?.length ?? 0) > 256)
|
|
4137
|
+
rejectHierarchy("Hierarchy preservation supports at most 256 tiles");
|
|
4138
|
+
for (let child = (tile.children?.length ?? 0) - 1; child >= 0; child--)
|
|
4139
|
+
stack.push({
|
|
4140
|
+
tile: tile.children[child],
|
|
4141
|
+
documentIndex,
|
|
4142
|
+
prefix,
|
|
4143
|
+
path: [...path, child],
|
|
4144
|
+
parent: index,
|
|
4145
|
+
transform,
|
|
4146
|
+
refine
|
|
4147
|
+
});
|
|
4148
|
+
}
|
|
4149
|
+
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))
|
|
4150
|
+
rejectHierarchy("Contentless leaves or unmatched content placements cannot be preserved");
|
|
4151
|
+
const geometricError = document.geometricError ?? nodes[0].geometricError;
|
|
4152
|
+
if (typeof geometricError !== "number" || !Number.isFinite(geometricError) || geometricError < 0)
|
|
4153
|
+
rejectHierarchy("Dataset geometricError must be finite nonnegative meters");
|
|
4154
|
+
return {
|
|
4155
|
+
nodes,
|
|
4156
|
+
geometricError: Math.max(geometricError, nodes[0].geometricError),
|
|
4157
|
+
coordinateEpoch: spatialReference.coordinateEpoch
|
|
4158
|
+
};
|
|
4159
|
+
}
|
|
4160
|
+
function createMeshHierarchyTilesetSink(options) {
|
|
4161
|
+
const hierarchy = prepareMeshHierarchy(options.inspection, options.resourceIds);
|
|
4162
|
+
const extensions = structuredClone(
|
|
4163
|
+
options.inspection.tileset.extensions
|
|
4164
|
+
);
|
|
4165
|
+
const extensionsRequired = structuredClone(
|
|
4166
|
+
options.inspection.tileset.extensionsRequired
|
|
4167
|
+
);
|
|
4168
|
+
const { maxPositionError } = options;
|
|
4169
|
+
if (!Number.isFinite(maxPositionError) || maxPositionError < 0 || hierarchy.nodes.some((node) => !Number.isFinite(node.geometricError + maxPositionError)) || !Number.isFinite(hierarchy.geometricError + maxPositionError))
|
|
4170
|
+
throw new import_core31.TileConversionError(
|
|
4171
|
+
"MESH_GEOMETRIC_ERROR_INVALID",
|
|
4172
|
+
"Hierarchy position allowance and resulting errors must be finite nonnegative meters"
|
|
4173
|
+
);
|
|
4174
|
+
const memory = (0, import_core31.createBoundedMemoryTileConversionSink)(options);
|
|
4175
|
+
const placements = /* @__PURE__ */ new Map();
|
|
4176
|
+
const completedContents = /* @__PURE__ */ new Set();
|
|
4177
|
+
const primitiveIdentifiers = /* @__PURE__ */ new Set();
|
|
4178
|
+
let state = "open";
|
|
4179
|
+
let completed = false;
|
|
4180
|
+
return {
|
|
4181
|
+
/** Exposes files only after complete finalization. */
|
|
4182
|
+
getFiles: () => completed ? memory.getFiles() : [],
|
|
4183
|
+
/** Retains ordered codec outputs and requires a terminal primitive for each content. */
|
|
4184
|
+
async write(resource, signal) {
|
|
4185
|
+
signal?.throwIfAborted();
|
|
4186
|
+
const identifier = resource.contentResourceId;
|
|
4187
|
+
const { index: primitiveIndex, last: lastPrimitive } = resource.contentPrimitive ?? {
|
|
4188
|
+
index: 0,
|
|
4189
|
+
last: true
|
|
4190
|
+
};
|
|
4191
|
+
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)))
|
|
4192
|
+
throw new import_core31.TileConversionError(
|
|
4193
|
+
"MESH_HIERARCHY_PLACEMENT_INVALID",
|
|
4194
|
+
"Hierarchy writes require ordered, distinct primitives for declared contents before close (at most 64)"
|
|
4195
|
+
);
|
|
4196
|
+
if (resource.spatialReference.coordinateEpoch !== hierarchy.coordinateEpoch)
|
|
4197
|
+
throw new import_core31.TileConversionError(
|
|
4198
|
+
"MESH_SOURCE_EPOCH_MISMATCH",
|
|
4199
|
+
"Hierarchy output coordinate epoch must match its source"
|
|
4200
|
+
);
|
|
4201
|
+
const tile = createMeshTileset(resource, maxPositionError).root;
|
|
4202
|
+
const nodeIndex = hierarchy.nodes.findIndex((node2) => node2.resourceIds.includes(identifier));
|
|
4203
|
+
const node = hierarchy.nodes[nodeIndex];
|
|
4204
|
+
const sibling = node.resourceIds.map((identifier2) => placements.get(identifier2)?.[0]).find(Boolean);
|
|
4205
|
+
if (sibling && sibling.origin.some((value, axis) => value !== resource.origin[axis]))
|
|
4206
|
+
throw new import_core31.TileConversionError(
|
|
4207
|
+
"MESH_HIERARCHY_PLACEMENT_INVALID",
|
|
4208
|
+
"Contents on one tile must share an encoded origin"
|
|
4209
|
+
);
|
|
4210
|
+
const suffix = node.resourceIds.length > 1 ? "-" + node.resourceIds.indexOf(identifier) : "";
|
|
4211
|
+
const primitiveSuffix = primitiveIndex > 0 || !lastPrimitive ? "-primitive-" + primitiveIndex : "";
|
|
4212
|
+
const uri = `meshes/${nodeIndex}${suffix}${primitiveSuffix}.glb`;
|
|
4213
|
+
state = "writing";
|
|
4214
|
+
await memory.write(
|
|
4215
|
+
{ resourceId: uri, parts: [resource.glb], contentType: "model/gltf-binary" },
|
|
4216
|
+
signal
|
|
4217
|
+
);
|
|
4218
|
+
const contentPlacements = placements.get(identifier) ?? [];
|
|
4219
|
+
contentPlacements.push({
|
|
4220
|
+
transform: [...tile.transform],
|
|
4221
|
+
origin: [...resource.origin],
|
|
4222
|
+
localBoundingBox: resource.localBoundingBox.map((bound) => [...bound]),
|
|
4223
|
+
uri
|
|
4224
|
+
});
|
|
4225
|
+
placements.set(identifier, contentPlacements);
|
|
4226
|
+
primitiveIdentifiers.add(resource.id);
|
|
4227
|
+
if (lastPrimitive) completedContents.add(identifier);
|
|
4228
|
+
state = "open";
|
|
4229
|
+
},
|
|
4230
|
+
/** Publishes original topology only after every source content has completed. */
|
|
4231
|
+
async finalize(report) {
|
|
4232
|
+
if (state !== "open" || completedContents.size !== hierarchy.nodes.reduce((count, node) => count + node.resourceIds.length, 0))
|
|
4233
|
+
throw new import_core31.TileConversionError(
|
|
4234
|
+
"MESH_HIERARCHY_INCOMPLETE",
|
|
4235
|
+
"Finalization requires every declared hierarchy content"
|
|
4236
|
+
);
|
|
4237
|
+
state = "writing";
|
|
4238
|
+
const tiles = new Array(hierarchy.nodes.length);
|
|
4239
|
+
const bounds = new Array(hierarchy.nodes.length);
|
|
4240
|
+
const transforms = hierarchy.nodes.map((node) => {
|
|
4241
|
+
const placement = placements.get(node.resourceIds[0])?.[0];
|
|
4242
|
+
return placement ? new import_core30.Matrix4(placement.transform) : new import_core30.Matrix4();
|
|
4243
|
+
});
|
|
4244
|
+
for (let index = hierarchy.nodes.length - 1; index >= 0; index--) {
|
|
4245
|
+
const node = hierarchy.nodes[index];
|
|
4246
|
+
const placement = placements.get(node.resourceIds[0])?.[0];
|
|
4247
|
+
const minimum = [Infinity, Infinity, Infinity];
|
|
4248
|
+
const maximum = [-Infinity, -Infinity, -Infinity];
|
|
4249
|
+
const contributors = node.children.map((child) => bounds[child]);
|
|
4250
|
+
const nodePlacements = node.resourceIds.flatMap((identifier) => placements.get(identifier));
|
|
4251
|
+
for (const placement2 of nodePlacements) {
|
|
4252
|
+
contributors.push(
|
|
4253
|
+
placement2.localBoundingBox.map(
|
|
4254
|
+
(bound, boundIndex) => bound.map((value, axis) => {
|
|
4255
|
+
const padding = 8 * Number.EPSILON * Math.max(
|
|
4256
|
+
1,
|
|
4257
|
+
Math.abs(placement2.origin[axis]),
|
|
4258
|
+
...placement2.localBoundingBox.map((limit) => Math.abs(limit[axis]))
|
|
4259
|
+
);
|
|
4260
|
+
return value + placement2.origin[axis] + (boundIndex ? padding : -padding);
|
|
4261
|
+
})
|
|
4262
|
+
)
|
|
4263
|
+
);
|
|
4264
|
+
}
|
|
4265
|
+
for (const [lower, upper] of contributors)
|
|
4266
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
4267
|
+
minimum[axis] = Math.min(minimum[axis], lower[axis]);
|
|
4268
|
+
maximum[axis] = Math.max(maximum[axis], upper[axis]);
|
|
4269
|
+
}
|
|
4270
|
+
if ([...minimum, ...maximum].some((value) => !Number.isFinite(value)))
|
|
4271
|
+
throw new import_core31.TileConversionError(
|
|
4272
|
+
"MESH_TILESET_BOUNDS_INVALID",
|
|
4273
|
+
"Hierarchy absolute bounds must remain finite"
|
|
4274
|
+
);
|
|
4275
|
+
bounds[index] = [minimum, maximum];
|
|
4276
|
+
const inverse = new import_core30.Matrix4(transforms[index]).invert();
|
|
4277
|
+
const center = Array.from(
|
|
4278
|
+
inverse.transformAsPoint(minimum.map((value, axis) => value / 2 + maximum[axis] / 2))
|
|
4279
|
+
);
|
|
4280
|
+
const halfSize = minimum.map((value, axis) => maximum[axis] / 2 - value / 2);
|
|
4281
|
+
const halfAxes = [0, 1, 2].flatMap(
|
|
4282
|
+
(axis) => Array.from(
|
|
4283
|
+
inverse.transformAsVector(
|
|
4284
|
+
halfSize.map((value, coordinate) => coordinate === axis ? value : 0)
|
|
4285
|
+
)
|
|
4286
|
+
)
|
|
4287
|
+
);
|
|
4288
|
+
if ([...center, ...halfAxes].some((value) => !Number.isFinite(value)))
|
|
4289
|
+
throw new import_core31.TileConversionError(
|
|
4290
|
+
"MESH_TILESET_BOUNDS_INVALID",
|
|
4291
|
+
"Hierarchy local bounds must remain finite"
|
|
4292
|
+
);
|
|
4293
|
+
tiles[index] = {
|
|
4294
|
+
transform: Array.from(transforms[index]),
|
|
4295
|
+
boundingVolume: { box: [...center, ...halfAxes] },
|
|
4296
|
+
geometricError: node.geometricError + maxPositionError,
|
|
4297
|
+
refine: node.refine,
|
|
4298
|
+
...nodePlacements.length > 1 ? { contents: nodePlacements.map((placement2) => ({ uri: placement2.uri })) } : placement ? { content: { uri: placement.uri } } : {},
|
|
4299
|
+
...node.children.length ? {
|
|
4300
|
+
children: node.children.map((child) => {
|
|
4301
|
+
tiles[child].transform = Array.from(
|
|
4302
|
+
new import_core30.Matrix4(inverse).multiplyRight(transforms[child])
|
|
4303
|
+
);
|
|
4304
|
+
if (tiles[child].transform.some((value) => !Number.isFinite(value)))
|
|
4305
|
+
throw new import_core31.TileConversionError(
|
|
4306
|
+
"MESH_TILESET_BOUNDS_INVALID",
|
|
4307
|
+
"Relative hierarchy transforms must remain finite"
|
|
4308
|
+
);
|
|
4309
|
+
return tiles[child];
|
|
4310
|
+
})
|
|
4311
|
+
} : {}
|
|
4312
|
+
};
|
|
4313
|
+
}
|
|
4314
|
+
const json = JSON.stringify({
|
|
4315
|
+
asset: { version: "1.1" },
|
|
4316
|
+
...extensions && Object.keys(extensions).length ? { extensions, extensionsUsed: Object.keys(extensions) } : {},
|
|
4317
|
+
...extensionsRequired?.length ? { extensionsRequired } : {},
|
|
4318
|
+
geometricError: hierarchy.geometricError + maxPositionError,
|
|
4319
|
+
root: tiles[0]
|
|
4320
|
+
});
|
|
4321
|
+
await memory.write({
|
|
4322
|
+
resourceId: "tileset.json",
|
|
4323
|
+
parts: [json],
|
|
4324
|
+
contentType: "application/json"
|
|
4325
|
+
});
|
|
4326
|
+
await memory.finalize(report);
|
|
4327
|
+
state = "closed";
|
|
4328
|
+
completed = true;
|
|
4329
|
+
},
|
|
4330
|
+
/** Discards retained resources and hides all files after failure. */
|
|
4331
|
+
async abort(error) {
|
|
4332
|
+
state = "closed";
|
|
4333
|
+
completed = false;
|
|
4334
|
+
placements.clear();
|
|
4335
|
+
completedContents.clear();
|
|
4336
|
+
primitiveIdentifiers.clear();
|
|
4337
|
+
await memory.abort(error);
|
|
4338
|
+
}
|
|
4339
|
+
};
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
// src/v5/selected-mesh-source.ts
|
|
4343
|
+
var import_core32 = require("@loaders.gl/core");
|
|
4344
|
+
var import_d_tiles2 = require("@loaders.gl/3d-tiles");
|
|
4345
|
+
var import_tiles4 = require("@loaders.gl/tiles");
|
|
4346
|
+
var import_core33 = require("@loaders.gl/tile-converter/v5/core");
|
|
4347
|
+
var import_adapters = require("@loaders.gl/tile-converter/v5/adapters");
|
|
4348
|
+
var SELECTED_MESH_LIMITS = {
|
|
4349
|
+
/** Root JSON plus selected content transport bytes; also the decoded geometry/image/feature byte gate. */
|
|
4350
|
+
maxInputBytes: 16 * 1024 * 1024,
|
|
4351
|
+
/** Maximum declared content placements inspected. */
|
|
4352
|
+
maxInputResources: 1e3,
|
|
4353
|
+
/** Maximum inspected root/external document placements. */
|
|
4354
|
+
maxTilesetDocuments: 32,
|
|
4355
|
+
/** Maximum explicitly selected content placements in one partial archive. */
|
|
4356
|
+
maxSelectedResources: 64,
|
|
4357
|
+
/** Maximum primitive placements authored across all selected contents. */
|
|
4358
|
+
maxMeshResources: 64,
|
|
4359
|
+
/** Maximum retained file bytes and final archive bytes. */
|
|
4360
|
+
maxOutputBytes: 32 * 1024 * 1024,
|
|
4361
|
+
/** Maximum measured reconstruction error in meters. */
|
|
4362
|
+
maxPositionError: 0.01
|
|
4363
|
+
};
|
|
4364
|
+
async function createSelectedMeshConversionSource(inspection, options) {
|
|
4365
|
+
const {
|
|
4366
|
+
resourceIds,
|
|
4367
|
+
signal,
|
|
4368
|
+
dracoLibraryOptions,
|
|
4369
|
+
features,
|
|
4370
|
+
preserveHierarchy,
|
|
4371
|
+
fetcher = fetch
|
|
4372
|
+
} = options;
|
|
4373
|
+
const maxInputBytes = options.maxInputBytes ?? SELECTED_MESH_LIMITS.maxInputBytes;
|
|
4374
|
+
if (!Number.isSafeInteger(maxInputBytes) || maxInputBytes < 1 || maxInputBytes > SELECTED_MESH_LIMITS.maxInputBytes)
|
|
4375
|
+
throw new import_core33.TileConversionError(
|
|
4376
|
+
"INVALID_SELECTED_MESH_OPTIONS",
|
|
4377
|
+
"Selected mesh input budget must be a positive safe integer at most 16 MiB"
|
|
4378
|
+
);
|
|
4379
|
+
if (!resourceIds.length || resourceIds.length > SELECTED_MESH_LIMITS.maxSelectedResources || new Set(resourceIds).size !== resourceIds.length || resourceIds.some(
|
|
4380
|
+
(identifier) => !inspection.resources.some((resource) => resource.resourceId === identifier)
|
|
4381
|
+
))
|
|
4382
|
+
throw new Error("Select between 1 and 64 distinct content placements.");
|
|
4383
|
+
if (preserveHierarchy !== void 0 && typeof preserveHierarchy !== "boolean")
|
|
4384
|
+
throw new import_core33.TileConversionError(
|
|
4385
|
+
"MESH_HIERARCHY_UNSUPPORTED",
|
|
4386
|
+
"preserveHierarchy must be a boolean"
|
|
4387
|
+
);
|
|
4388
|
+
if (preserveHierarchy) prepareMeshHierarchy(inspection, resourceIds);
|
|
4389
|
+
const descriptors = inspection.resources.filter(
|
|
4390
|
+
(resource) => resourceIds.includes(resource.resourceId)
|
|
4391
|
+
);
|
|
4392
|
+
const selections = descriptors.map(
|
|
4393
|
+
(descriptor) => (0, import_core33.selectBrowserTilesetContent)(inspection, descriptor)
|
|
4394
|
+
);
|
|
4395
|
+
const selectedContents = new Map(
|
|
4396
|
+
descriptors.map((descriptor, index) => [descriptor.resourceId, selections[index]])
|
|
4397
|
+
);
|
|
4398
|
+
if (!preserveHierarchy && resourceIds.length > 1 && selections.some((selection) => !selection.isLeaf))
|
|
4399
|
+
throw new Error(
|
|
4400
|
+
"Multi-selection requires leaf contents; source LOD hierarchy is not exported."
|
|
4401
|
+
);
|
|
4402
|
+
signal.throwIfAborted();
|
|
4403
|
+
const boundedFetcher = (0, import_adapters.createTileConversionResourceFetcher)({
|
|
4404
|
+
maxInputBytes,
|
|
4405
|
+
initialInputBytes: inspection.tilesetBytes,
|
|
4406
|
+
fetcher,
|
|
4407
|
+
signal
|
|
4408
|
+
});
|
|
4409
|
+
const qualification = createSelectedRuntime(
|
|
4410
|
+
inspection.tileset,
|
|
4411
|
+
new Uint8Array(),
|
|
4412
|
+
signal,
|
|
4413
|
+
boundedFetcher,
|
|
4414
|
+
inspection.rootUrl,
|
|
4415
|
+
dracoLibraryOptions
|
|
4416
|
+
);
|
|
4417
|
+
let spatialContext;
|
|
4418
|
+
try {
|
|
4419
|
+
await qualification.tilesetInitializationPromise;
|
|
4420
|
+
const metadata = await (0, import_adapters.createMeshTilesetConversionSource)(qualification).inspect(signal);
|
|
4421
|
+
spatialContext = (0, import_core33.createTiles3DConversionSpatialContext)(metadata.spatialReference);
|
|
4422
|
+
} finally {
|
|
4423
|
+
qualification.destroy();
|
|
4424
|
+
}
|
|
4425
|
+
const rawSource = options.source ?? (0, import_core33.createBrowserTilesetConversionSource)({
|
|
4426
|
+
input: inspection.rootUrl,
|
|
4427
|
+
...SELECTED_MESH_LIMITS,
|
|
4428
|
+
maxInputBytes,
|
|
4429
|
+
fetcher: boundedFetcher
|
|
4430
|
+
});
|
|
4431
|
+
const selectedInspection = { ...inspection, resources: descriptors };
|
|
4432
|
+
const source = {
|
|
4433
|
+
/** Reuses inspected declarations without fetching the root again. */
|
|
4434
|
+
inspect: async (inspectionSignal) => {
|
|
4435
|
+
signal.throwIfAborted();
|
|
4436
|
+
inspectionSignal?.throwIfAborted();
|
|
4437
|
+
return selectedInspection;
|
|
4438
|
+
},
|
|
4439
|
+
/** Decodes and releases each selected placement in declaration order. */
|
|
4440
|
+
async *read(_inspection, readSignal) {
|
|
4441
|
+
const signal2 = readSignal ? AbortSignal.any([options.signal, readSignal]) : options.signal;
|
|
4442
|
+
signal2.throwIfAborted();
|
|
4443
|
+
let decodedBytes = 0;
|
|
4444
|
+
let primitiveCount = 0;
|
|
4445
|
+
const tileOrigins = /* @__PURE__ */ new Map();
|
|
4446
|
+
for await (const raw of rawSource.read(selectedInspection, signal2)) {
|
|
4447
|
+
const selection = selectedContents.get(raw.resourceId);
|
|
4448
|
+
if (!selection)
|
|
4449
|
+
throw new import_core33.TileConversionError(
|
|
4450
|
+
"INVALID_BROWSER_SELECTION",
|
|
4451
|
+
"Source yielded an uninspected content placement"
|
|
4452
|
+
);
|
|
4453
|
+
const contentUrl = raw.contentUrl ?? selection.contentUrl;
|
|
4454
|
+
const runtime = createSelectedRuntime(
|
|
4455
|
+
selection.tileset,
|
|
4456
|
+
raw.data,
|
|
4457
|
+
signal2,
|
|
4458
|
+
boundedFetcher,
|
|
4459
|
+
contentUrl,
|
|
4460
|
+
dracoLibraryOptions,
|
|
4461
|
+
spatialContext.spatialReference
|
|
4462
|
+
);
|
|
4463
|
+
try {
|
|
4464
|
+
await runtime.tilesetInitializationPromise;
|
|
4465
|
+
const meshSource = (0, import_adapters.createMeshTilesetConversionSource)(runtime, {
|
|
4466
|
+
unloadContent: true,
|
|
4467
|
+
features,
|
|
4468
|
+
readExternalResource: async (uri, _contentUri, resourceSignal) => {
|
|
4469
|
+
const response = await boundedFetcher(new URL(uri, contentUrl), {
|
|
4470
|
+
signal: resourceSignal
|
|
4471
|
+
});
|
|
4472
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
4473
|
+
}
|
|
4474
|
+
});
|
|
4475
|
+
const metadata = await meshSource.inspect(signal2);
|
|
4476
|
+
const selectedContext = (0, import_core33.createTiles3DConversionSpatialContext)(metadata.spatialReference);
|
|
4477
|
+
if (selectedContext.spatialReference.coordinateEpoch !== spatialContext.spatialReference.coordinateEpoch)
|
|
4478
|
+
throw new import_core33.TileConversionError(
|
|
4479
|
+
"MESH_SOURCE_EPOCH_MISMATCH",
|
|
4480
|
+
"Selected content coordinate epoch must match the root; epoch transformations are unsupported"
|
|
4481
|
+
);
|
|
4482
|
+
let meshCount = 0;
|
|
4483
|
+
for await (const resource of meshSource.read(metadata, signal2)) {
|
|
4484
|
+
meshCount++;
|
|
4485
|
+
if (++primitiveCount > SELECTED_MESH_LIMITS.maxMeshResources)
|
|
4486
|
+
throw new Error("Selected contents exceed the mesh primitive placement limit.");
|
|
4487
|
+
decodedBytes += measureMeshBytes(resource);
|
|
4488
|
+
if (decodedBytes > maxInputBytes)
|
|
4489
|
+
throw new import_core33.TileConversionError(
|
|
4490
|
+
"INPUT_RESOURCE_TOO_LARGE",
|
|
4491
|
+
"Selected decoded geometry and encoded images exceed the aggregate input byte limit."
|
|
4492
|
+
);
|
|
4493
|
+
const tileIdentifier = raw.resourceId.replace(/content-\d+$/, "");
|
|
4494
|
+
if (preserveHierarchy && !tileOrigins.has(tileIdentifier))
|
|
4495
|
+
tileOrigins.set(tileIdentifier, [...resource.origin]);
|
|
4496
|
+
yield {
|
|
4497
|
+
...resource,
|
|
4498
|
+
origin: preserveHierarchy ? tileOrigins.get(tileIdentifier) : resource.origin,
|
|
4499
|
+
id: `${raw.resourceId}/${resource.id}`,
|
|
4500
|
+
contentResourceId: raw.resourceId
|
|
4501
|
+
};
|
|
4502
|
+
}
|
|
4503
|
+
if (!meshCount) throw new Error("Selected content has no mesh primitive.");
|
|
4504
|
+
} finally {
|
|
4505
|
+
runtime.destroy();
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
};
|
|
4510
|
+
return {
|
|
4511
|
+
source,
|
|
4512
|
+
spatialContext,
|
|
4513
|
+
geometricError: Math.max(...selections.map((selection) => selection.geometricError)) + SELECTED_MESH_LIMITS.maxPositionError
|
|
4514
|
+
};
|
|
4515
|
+
}
|
|
4516
|
+
var SelectedTiles3DSource = class extends import_tiles4.Tiles3DSource {
|
|
4517
|
+
/** Parent frame; explicit, unknown or unresolved child declarations are never replaced. */
|
|
4518
|
+
inheritedSpatialReference;
|
|
4519
|
+
/** Creates a selected source with optional external-document frame inheritance. */
|
|
4520
|
+
constructor(input, options, inheritedSpatialReference) {
|
|
4521
|
+
super(input, options);
|
|
4522
|
+
this.inheritedSpatialReference = inheritedSpatialReference;
|
|
4523
|
+
}
|
|
4524
|
+
/** Returns source metadata with the same absent-CRS inheritance used by external tilesets. */
|
|
4525
|
+
getMetadata() {
|
|
4526
|
+
const metadata = super.getMetadata();
|
|
4527
|
+
const reference = metadata.spatialReference;
|
|
4528
|
+
if (this.inheritedSpatialReference && reference?.crs.state === "absent" && reference.coordinateEpoch === void 0 && !reference.verticalCrs && !reference.warnings.length)
|
|
4529
|
+
return { ...metadata, spatialReference: this.inheritedSpatialReference };
|
|
4530
|
+
return metadata;
|
|
4531
|
+
}
|
|
4532
|
+
};
|
|
4533
|
+
function createSelectedRuntime(document, data, signal, boundedFetcher, contentUrl, dracoLibraryOptions, inheritedSpatialReference) {
|
|
4534
|
+
const {
|
|
4535
|
+
modules,
|
|
4536
|
+
decoderProfile,
|
|
4537
|
+
CDN: contentDeliveryNetwork,
|
|
4538
|
+
useLocalLibraries
|
|
4539
|
+
} = dracoLibraryOptions;
|
|
4540
|
+
const loadOptions = {
|
|
4541
|
+
modules,
|
|
4542
|
+
core: {
|
|
4543
|
+
worker: false,
|
|
4544
|
+
fetch: boundedFetcher,
|
|
4545
|
+
CDN: contentDeliveryNetwork,
|
|
4546
|
+
useLocalLibraries
|
|
4547
|
+
},
|
|
4548
|
+
// Reuse the full decoder already bundled for output verification.
|
|
4549
|
+
draco: { decoderProfile },
|
|
4550
|
+
// Preserve authored UVs and the transform for the converter's qualified GLB writer.
|
|
4551
|
+
gltf: {
|
|
4552
|
+
loadImages: false,
|
|
4553
|
+
decompressMeshes: true,
|
|
4554
|
+
excludeExtensions: {
|
|
4555
|
+
KHR_texture_transform: false,
|
|
4556
|
+
// Only Draco input compression is qualified by this conversion profile.
|
|
4557
|
+
EXT_meshopt_compression: false,
|
|
4558
|
+
KHR_meshopt_compression: false
|
|
4559
|
+
}
|
|
4560
|
+
},
|
|
4561
|
+
"3d-tiles": { loadGLTF: true }
|
|
4562
|
+
};
|
|
4563
|
+
return new import_tiles4.Tileset3D(
|
|
4564
|
+
new SelectedTiles3DSource(
|
|
4565
|
+
{
|
|
4566
|
+
url: contentUrl,
|
|
4567
|
+
loader: import_d_tiles2.Tiles3DLoader,
|
|
4568
|
+
resolver: {
|
|
4569
|
+
/** Normalizes the selected hierarchy through the format loader, retaining ancestor transforms. */
|
|
4570
|
+
loadRoot: async (_url, loader, options) => (0, import_core32.parse)(new TextEncoder().encode(JSON.stringify(document)), loader, options),
|
|
4571
|
+
/** Decodes only the selected bounded payload; no sibling content can be requested. */
|
|
4572
|
+
loadResource: async (_url, loader, options) => {
|
|
4573
|
+
signal.throwIfAborted();
|
|
4574
|
+
return (0, import_core32.parse)(data, loader, options, {
|
|
4575
|
+
url: contentUrl,
|
|
4576
|
+
baseUrl: contentUrl.startsWith("blob:") ? contentUrl : new URL(".", contentUrl).href,
|
|
4577
|
+
fetch: boundedFetcher,
|
|
4578
|
+
coreApi: import_core32.coreApi,
|
|
4579
|
+
_parse: import_core32.parse
|
|
4580
|
+
});
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
},
|
|
4584
|
+
loadOptions,
|
|
4585
|
+
inheritedSpatialReference
|
|
4586
|
+
)
|
|
4587
|
+
);
|
|
4588
|
+
}
|
|
4589
|
+
function measureMeshBytes(resource) {
|
|
4590
|
+
const featureBytes = resource.features ? resource.features.triangleFeatureIndices.byteLength + resource.features.batches.reduce(
|
|
4591
|
+
(bytes, batch) => bytes + batch.data.schema.fields.reduce(
|
|
4592
|
+
(columnBytes, field) => columnBytes + (batch.data.getChild(field.name)?.byteLength ?? 0),
|
|
4593
|
+
0
|
|
4594
|
+
),
|
|
4595
|
+
0
|
|
4596
|
+
) : 0;
|
|
4597
|
+
return Object.values(resource.mesh.attributes).reduce(
|
|
4598
|
+
(bytes, attribute) => bytes + attribute.value.byteLength,
|
|
4599
|
+
(resource.mesh.indices?.value.byteLength ?? 0) + (resource.material?.baseColorTexture?.data.byteLength ?? 0) + featureBytes
|
|
4600
|
+
);
|
|
4601
|
+
}
|
|
4602
|
+
|
|
4603
|
+
// src/v5/archive-source.ts
|
|
4604
|
+
var import_loader_utils = require("@loaders.gl/loader-utils");
|
|
4605
|
+
var import_i3s3 = require("@loaders.gl/i3s");
|
|
4606
|
+
var import_d_tiles3 = require("@loaders.gl/3d-tiles");
|
|
4607
|
+
var import_core34 = require("@loaders.gl/tile-converter/v5/core");
|
|
4608
|
+
async function openTileConversionArchive(descriptor, maxInputBytes, signal, fetcher = fetch) {
|
|
4609
|
+
signal.throwIfAborted();
|
|
4610
|
+
if (!["slpk", "3tz"].includes(descriptor.format))
|
|
4611
|
+
throw new import_core34.TileConversionError("INVALID_ARCHIVE_SOURCE", "Select SLPK or 3TZ archive input");
|
|
4612
|
+
validateArchiveEntityTag(descriptor.identity?.etag);
|
|
4613
|
+
const transport = createTileConversionResourceFetcher({
|
|
4614
|
+
maxInputBytes,
|
|
4615
|
+
signal,
|
|
4616
|
+
fetcher: async (url, options) => {
|
|
4617
|
+
const response = await fetcher(url, options);
|
|
4618
|
+
if (response.status !== 206) {
|
|
4619
|
+
await response.body?.cancel();
|
|
4620
|
+
throw new Error("Remote conversion archives require HTTP 206 byte-range responses.");
|
|
4621
|
+
}
|
|
4622
|
+
try {
|
|
4623
|
+
validateArchiveResponseValidator(response.headers);
|
|
4624
|
+
} catch (error) {
|
|
4625
|
+
await response.body?.cancel(error);
|
|
4626
|
+
throw error;
|
|
4627
|
+
}
|
|
4628
|
+
return response;
|
|
4629
|
+
}
|
|
4630
|
+
});
|
|
4631
|
+
let file;
|
|
4632
|
+
let closePromise;
|
|
4633
|
+
async function closeFile() {
|
|
4634
|
+
if (file) {
|
|
4635
|
+
closePromise ??= file.close();
|
|
4636
|
+
await closePromise;
|
|
4637
|
+
}
|
|
4638
|
+
}
|
|
4639
|
+
try {
|
|
4640
|
+
if (typeof descriptor.input === "string") {
|
|
4641
|
+
if (!["http:", "https:"].includes(new URL(descriptor.input).protocol))
|
|
4642
|
+
throw new Error("Use an HTTP(S) archive URL.");
|
|
4643
|
+
const remote = new import_loader_utils.HttpFile(descriptor.input, {
|
|
4644
|
+
fetch: (url, options) => transport(url, options),
|
|
4645
|
+
consistency: "strict",
|
|
4646
|
+
...descriptor.identity
|
|
4647
|
+
});
|
|
4648
|
+
file = remote;
|
|
4649
|
+
await remote.open(signal);
|
|
4650
|
+
} else file = new import_loader_utils.BlobFile(descriptor.input);
|
|
4651
|
+
const ownedFile = file;
|
|
4652
|
+
file = void 0;
|
|
4653
|
+
return await createTileConversionArchiveTransport(
|
|
4654
|
+
ownedFile,
|
|
4655
|
+
descriptor.format,
|
|
4656
|
+
maxInputBytes,
|
|
4657
|
+
signal,
|
|
4658
|
+
descriptor
|
|
4659
|
+
);
|
|
4660
|
+
} catch (error) {
|
|
4661
|
+
await closeFile();
|
|
4662
|
+
throw error;
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
async function createTileConversionArchiveTransport(file, format, maxInputBytes, signal, descriptor) {
|
|
4666
|
+
const rootUrl = `https://conversion.invalid/input.${format}`;
|
|
4667
|
+
let closed = false;
|
|
4668
|
+
let releasePromise;
|
|
4669
|
+
let closePromise;
|
|
4670
|
+
const closeFile = () => {
|
|
4671
|
+
closed = true;
|
|
4672
|
+
closePromise ??= file.close();
|
|
4673
|
+
return closePromise;
|
|
4674
|
+
};
|
|
4675
|
+
const checkOpen = () => {
|
|
4676
|
+
signal.throwIfAborted();
|
|
4677
|
+
if (closed)
|
|
4678
|
+
throw new import_core34.TileConversionError("ARCHIVE_SOURCE_CLOSED", "Conversion archive is closed");
|
|
4679
|
+
};
|
|
4680
|
+
try {
|
|
4681
|
+
checkOpen();
|
|
4682
|
+
if (!["slpk", "3tz"].includes(format) || !Number.isSafeInteger(maxInputBytes) || maxInputBytes < 1)
|
|
4683
|
+
throw new import_core34.TileConversionError(
|
|
4684
|
+
"INVALID_ARCHIVE_SOURCE",
|
|
4685
|
+
"Archive source requires a supported format and positive safe byte budget"
|
|
4686
|
+
);
|
|
4687
|
+
if (!Number.isSafeInteger(file.size) || file.size < 0 || file.size > maxInputBytes)
|
|
4688
|
+
throw new Error("Conversion archive input must be at most 16 MiB.");
|
|
4689
|
+
let readBytes = 0;
|
|
4690
|
+
const boundedFile = {
|
|
4691
|
+
handle: file.handle,
|
|
4692
|
+
size: file.size,
|
|
4693
|
+
bigsize: file.bigsize,
|
|
4694
|
+
url: file.url,
|
|
4695
|
+
...file.stat ? { stat: () => file.stat() } : {},
|
|
4696
|
+
close: closeFile,
|
|
4697
|
+
/** Checks indexed reads before allocation and forwards operation cancellation. */
|
|
4698
|
+
read: async (start, length) => {
|
|
4699
|
+
checkOpen();
|
|
4700
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > maxInputBytes - readBytes)
|
|
4701
|
+
throw new import_core34.TileConversionError(
|
|
4702
|
+
"INPUT_RESOURCE_TOO_LARGE",
|
|
4703
|
+
"Archive indexed reads exceed the input budget"
|
|
4704
|
+
);
|
|
4705
|
+
readBytes += length;
|
|
4706
|
+
const bytes = await file.read(start, length, signal);
|
|
4707
|
+
checkOpen();
|
|
4708
|
+
return bytes;
|
|
4709
|
+
}
|
|
4710
|
+
};
|
|
4711
|
+
const archive = format === "slpk" ? await (0, import_i3s3.parseSLPKArchive)(boundedFile) : await (0, import_d_tiles3.parse3DTilesArchive)(boundedFile);
|
|
4712
|
+
const root = new URL(rootUrl);
|
|
4713
|
+
return {
|
|
4714
|
+
rootUrl: format === "3tz" ? `${rootUrl}/tileset.json` : rootUrl,
|
|
4715
|
+
descriptor: descriptor ? {
|
|
4716
|
+
...descriptor,
|
|
4717
|
+
...file instanceof import_loader_utils.HttpFile ? { identity: file.getIdentitySnapshot() } : {}
|
|
4718
|
+
} : void 0,
|
|
4719
|
+
/** Resolves only resources belonging to this archive, without external network fallback. */
|
|
4720
|
+
fetcher: (async (input, options) => {
|
|
4721
|
+
checkOpen();
|
|
4722
|
+
options?.signal?.throwIfAborted();
|
|
4723
|
+
const url = new URL(input instanceof Request ? input.url : String(input));
|
|
4724
|
+
if (url.origin !== root.origin || url.pathname !== root.pathname && !url.pathname.startsWith(`${root.pathname}/`))
|
|
4725
|
+
throw new Error("Conversion dependencies must resolve inside the selected archive.");
|
|
4726
|
+
const path = decodeURIComponent(
|
|
4727
|
+
url.pathname.slice(root.pathname.length).replace(/^\//, "")
|
|
4728
|
+
);
|
|
4729
|
+
if (path.includes("\\") || path.split("/").some((segment) => segment === ".." || segment === "."))
|
|
4730
|
+
throw new Error("Archive dependency path cannot escape its root.");
|
|
4731
|
+
const bytes = format === "slpk" ? await archive.getFile(path, "http") : await archive.getFile(path);
|
|
4732
|
+
checkOpen();
|
|
4733
|
+
options?.signal?.throwIfAborted();
|
|
4734
|
+
return new Response(bytes);
|
|
4735
|
+
}),
|
|
4736
|
+
/** Releases the readable file on success, failure or cancellation. */
|
|
4737
|
+
close: async () => {
|
|
4738
|
+
closed = true;
|
|
4739
|
+
releasePromise ??= archive.close();
|
|
4740
|
+
await releasePromise;
|
|
4741
|
+
}
|
|
4742
|
+
};
|
|
4743
|
+
} catch (error) {
|
|
4744
|
+
await closeFile();
|
|
4745
|
+
throw error;
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
function validateArchiveEntityTag(entityTag) {
|
|
4749
|
+
if (entityTag !== void 0 && entityTag !== null && !/^"[\x21\x23-\x7e\x80-\xff]*"$/.test(entityTag))
|
|
4750
|
+
throw new import_core34.TileConversionError(
|
|
4751
|
+
"ARCHIVE_VALIDATOR_UNSUPPORTED",
|
|
4752
|
+
"Remote archives require a strong quoted ETag, or Last-Modified without an ETag; weak/malformed ETags are unsupported"
|
|
4753
|
+
);
|
|
4754
|
+
}
|
|
4755
|
+
function validateArchiveResponseValidator(headers) {
|
|
4756
|
+
const entityTag = headers.get("etag");
|
|
4757
|
+
validateArchiveEntityTag(entityTag);
|
|
4758
|
+
if (entityTag !== null) return;
|
|
4759
|
+
const lastModified = headers.get("last-modified");
|
|
4760
|
+
const responseDate = headers.get("date");
|
|
4761
|
+
const modifiedTime = parseArchiveHttpDate(lastModified);
|
|
4762
|
+
const responseTime = parseArchiveHttpDate(responseDate);
|
|
4763
|
+
if (!Number.isFinite(modifiedTime) || !Number.isFinite(responseTime) || responseTime - modifiedTime < 6e4)
|
|
4764
|
+
throw new import_core34.TileConversionError(
|
|
4765
|
+
"ARCHIVE_VALIDATOR_UNSUPPORTED",
|
|
4766
|
+
"Last-Modified archive identity requires a valid Date at least 60 seconds later; otherwise supply a strong ETag"
|
|
4767
|
+
);
|
|
4768
|
+
}
|
|
4769
|
+
function parseArchiveHttpDate(value) {
|
|
4770
|
+
const milliseconds = value === null ? NaN : Date.parse(value);
|
|
4771
|
+
return Number.isFinite(milliseconds) && new Date(milliseconds).toUTCString() === value ? milliseconds : NaN;
|
|
4772
|
+
}
|
|
4773
|
+
|
|
4774
|
+
// src/v5/i3s-selection-source.ts
|
|
4775
|
+
var import_core35 = require("@loaders.gl/core");
|
|
4776
|
+
var import_i3s4 = require("@loaders.gl/i3s");
|
|
4777
|
+
var import_tiles5 = require("@loaders.gl/tiles");
|
|
4778
|
+
var import_core36 = require("@loaders.gl/tile-converter/v5/core");
|
|
4779
|
+
function createRuntime(transport, layer, header) {
|
|
4780
|
+
const libraries = transport.dracoLibraryOptions;
|
|
4781
|
+
const sourceUrl = new URL(transport.rootUrl);
|
|
4782
|
+
sourceUrl.search = "";
|
|
4783
|
+
sourceUrl.hash = "";
|
|
4784
|
+
const options = {
|
|
4785
|
+
modules: libraries.modules,
|
|
4786
|
+
core: {
|
|
4787
|
+
worker: false,
|
|
4788
|
+
fetch: transport.fetcher,
|
|
4789
|
+
CDN: libraries.CDN,
|
|
4790
|
+
useLocalLibraries: libraries.useLocalLibraries
|
|
4791
|
+
},
|
|
4792
|
+
draco: { decoderProfile: libraries.decoderProfile },
|
|
4793
|
+
// The injected transport owns credential scope; runtime query parameters apply to every URL.
|
|
4794
|
+
i3s: { geometryMode: "source", decodeTextures: false, useCompressedTextures: false }
|
|
4795
|
+
};
|
|
4796
|
+
const loadResource = async (url, loader, loadOptions) => {
|
|
4797
|
+
const response = await transport.fetcher(url);
|
|
4798
|
+
const contextUrl = new URL(url);
|
|
4799
|
+
contextUrl.search = "";
|
|
4800
|
+
contextUrl.hash = "";
|
|
4801
|
+
return (0, import_core35.parse)(await response.arrayBuffer(), loader, loadOptions, {
|
|
4802
|
+
url: contextUrl.href,
|
|
4803
|
+
fetch: transport.fetcher,
|
|
4804
|
+
coreApi: import_core35.coreApi,
|
|
4805
|
+
_parse: import_core35.parse
|
|
4806
|
+
});
|
|
4807
|
+
};
|
|
4808
|
+
const source = new import_tiles5.I3SSource(
|
|
4809
|
+
header && layer ? {
|
|
4810
|
+
...layer,
|
|
4811
|
+
root: { ...header, children: [] },
|
|
4812
|
+
type: "I3S",
|
|
4813
|
+
url: sourceUrl.href,
|
|
4814
|
+
basePath: sourceUrl.href,
|
|
4815
|
+
loader: import_i3s4.I3SLoader,
|
|
4816
|
+
coreApi: import_core35.coreApi
|
|
4817
|
+
} : {
|
|
4818
|
+
url: sourceUrl.href,
|
|
4819
|
+
loader: import_i3s4.I3SLoader,
|
|
4820
|
+
coreApi: import_core35.coreApi,
|
|
4821
|
+
resolver: { loadRoot: loadResource, loadResource }
|
|
4822
|
+
},
|
|
4823
|
+
options
|
|
4824
|
+
);
|
|
4825
|
+
source.coreApi = { ...import_core35.coreApi, load: loadResource };
|
|
4826
|
+
return new import_tiles5.Tileset3D(source, { i3s: options.i3s });
|
|
4827
|
+
}
|
|
4828
|
+
async function inspectI3SMeshConversionSource(options) {
|
|
4829
|
+
const { signal } = options;
|
|
4830
|
+
validateSelectionOptions(options);
|
|
4831
|
+
signal.throwIfAborted();
|
|
4832
|
+
const transport = options;
|
|
4833
|
+
let runtime;
|
|
4834
|
+
try {
|
|
4835
|
+
const response = await transport.fetcher(transport.rootUrl);
|
|
4836
|
+
const layer = await response.json();
|
|
4837
|
+
if (!["3DObject", "IntegratedMesh"].includes(layer.layerType))
|
|
4838
|
+
throw new Error("Conversion accepts I3S 3DObject or IntegratedMesh layers.");
|
|
4839
|
+
const rootFetcher = transport.fetcher;
|
|
4840
|
+
const cachedFetcher = (url, options2) => {
|
|
4841
|
+
const requestUrl = new URL(url instanceof Request ? url.url : String(url));
|
|
4842
|
+
const root = new URL(transport.rootUrl);
|
|
4843
|
+
return requestUrl.origin === root.origin && requestUrl.pathname === root.pathname ? Promise.resolve(new Response(JSON.stringify(layer))) : rootFetcher(url, options2);
|
|
4844
|
+
};
|
|
4845
|
+
runtime = createRuntime({ ...transport, fetcher: cachedFetcher });
|
|
4846
|
+
await runtime.tilesetInitializationPromise;
|
|
4847
|
+
await createI3SMeshTilesetConversionSource(runtime).inspect(signal);
|
|
4848
|
+
const stack = [runtime.root];
|
|
4849
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
4850
|
+
const resources = [];
|
|
4851
|
+
while (stack.length) {
|
|
4852
|
+
signal.throwIfAborted();
|
|
4853
|
+
const tile = stack.pop();
|
|
4854
|
+
if (!["string", "number"].includes(typeof tile.header.id) || String(tile.header.id).length === 0)
|
|
4855
|
+
throw new Error("I3S nodes require a declared identity.");
|
|
4856
|
+
const identifier = String(tile.header.id);
|
|
4857
|
+
if (identifiers.has(identifier))
|
|
4858
|
+
throw new Error("Repeated I3S node identity is unsupported.");
|
|
4859
|
+
identifiers.add(identifier);
|
|
4860
|
+
if (identifiers.size + stack.length + (tile.header.children?.length ?? 0) > (options.maxInputResources ?? SELECTED_MESH_LIMITS.maxInputResources))
|
|
4861
|
+
throw new Error("I3S inspection exceeds the 1,000 node limit.");
|
|
4862
|
+
await runtime.source.loadTileChildrenForTraversal(tile, signal);
|
|
4863
|
+
if (!tile.children.length && tile.contentUrl)
|
|
4864
|
+
resources.push({
|
|
4865
|
+
resourceId: identifier,
|
|
4866
|
+
uri: tile.contentUrl,
|
|
4867
|
+
header: structuredClone(tile.header)
|
|
4868
|
+
});
|
|
4869
|
+
for (const child of [...tile.children].reverse()) stack.push(child);
|
|
4870
|
+
}
|
|
4871
|
+
signal.throwIfAborted();
|
|
4872
|
+
if (!resources.length) throw new Error("No supported leaf mesh nodes are declared.");
|
|
4873
|
+
return {
|
|
4874
|
+
kind: "i3s",
|
|
4875
|
+
rootUrl: transport.rootUrl,
|
|
4876
|
+
layer,
|
|
4877
|
+
resources,
|
|
4878
|
+
metadataBytes: 0
|
|
4879
|
+
};
|
|
4880
|
+
} finally {
|
|
4881
|
+
runtime?.destroy();
|
|
4882
|
+
}
|
|
4883
|
+
}
|
|
4884
|
+
async function createSelectedI3SMeshConversionSource(inspection, options) {
|
|
4885
|
+
const { resourceIds, signal, features } = options;
|
|
4886
|
+
validateSelectionOptions(options);
|
|
4887
|
+
signal.throwIfAborted();
|
|
4888
|
+
if (inspection.rootUrl !== options.rootUrl)
|
|
4889
|
+
throw new import_core36.TileConversionError(
|
|
4890
|
+
"INVALID_I3S_SELECTION",
|
|
4891
|
+
"Selection transport must match the inspected layer"
|
|
4892
|
+
);
|
|
4893
|
+
if (!resourceIds.length || resourceIds.length > SELECTED_MESH_LIMITS.maxSelectedResources || new Set(resourceIds).size !== resourceIds.length || resourceIds.some(
|
|
4894
|
+
(identifier) => !inspection.resources.some((resource) => resource.resourceId === identifier)
|
|
4895
|
+
))
|
|
4896
|
+
throw new Error("Select between 1 and 64 distinct leaf mesh nodes.");
|
|
4897
|
+
const selected = inspection.resources.filter(
|
|
4898
|
+
(resource) => resourceIds.includes(resource.resourceId)
|
|
4899
|
+
);
|
|
4900
|
+
if (selected.some(
|
|
4901
|
+
(resource) => resource.header.children?.length || !resource.header.contentUrl || resource.uri !== resource.header.contentUrl
|
|
4902
|
+
))
|
|
4903
|
+
throw new Error("Only inspected leaf mesh headers can be converted.");
|
|
4904
|
+
const transport = options;
|
|
4905
|
+
let decodedBytes = 0;
|
|
4906
|
+
let meshCount = 0;
|
|
4907
|
+
{
|
|
4908
|
+
const qualification = createRuntime(transport, inspection.layer, selected[0].header);
|
|
4909
|
+
let spatialContext;
|
|
4910
|
+
try {
|
|
4911
|
+
await qualification.tilesetInitializationPromise;
|
|
4912
|
+
const metadata = await createI3SMeshTilesetConversionSource(qualification).inspect(signal);
|
|
4913
|
+
spatialContext = (0, import_core36.createI3SConversionSpatialContext)(metadata.spatialReference, {
|
|
4914
|
+
targetCrs: "EPSG:4978"
|
|
4915
|
+
});
|
|
4916
|
+
} finally {
|
|
4917
|
+
qualification.destroy();
|
|
4918
|
+
}
|
|
4919
|
+
const source = {
|
|
4920
|
+
/** Reuses only immutable inspected declarations. */
|
|
4921
|
+
inspect: async () => inspection,
|
|
4922
|
+
/** Releases each dedicated node runtime before moving on or closing early. */
|
|
4923
|
+
async *read() {
|
|
4924
|
+
for (const descriptor of selected) {
|
|
4925
|
+
const runtime = createRuntime(transport, inspection.layer, descriptor.header);
|
|
4926
|
+
try {
|
|
4927
|
+
await runtime.tilesetInitializationPromise;
|
|
4928
|
+
const meshSource = createI3SMeshTilesetConversionSource(runtime, {
|
|
4929
|
+
unloadContent: true,
|
|
4930
|
+
features,
|
|
4931
|
+
readExternalResource: async (uri) => new Uint8Array(
|
|
4932
|
+
await (await transport.fetcher(runtime.source.getTileUrl(uri))).arrayBuffer()
|
|
4933
|
+
)
|
|
4934
|
+
});
|
|
4935
|
+
const metadata = await meshSource.inspect(signal);
|
|
4936
|
+
for await (const resource of meshSource.read(metadata, signal)) {
|
|
4937
|
+
decodedBytes += measureMeshBytes(resource);
|
|
4938
|
+
if (++meshCount > SELECTED_MESH_LIMITS.maxMeshResources || decodedBytes > (options.maxInputBytes ?? SELECTED_MESH_LIMITS.maxInputBytes))
|
|
4939
|
+
throw new import_core36.TileConversionError(
|
|
4940
|
+
"INPUT_RESOURCE_TOO_LARGE",
|
|
4941
|
+
"Selected I3S decoded resources exceed the input/mesh budget"
|
|
4942
|
+
);
|
|
4943
|
+
yield { ...resource, id: descriptor.resourceId };
|
|
4944
|
+
}
|
|
4945
|
+
} finally {
|
|
4946
|
+
runtime.destroy();
|
|
4947
|
+
}
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
};
|
|
4951
|
+
return { source, spatialContext };
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
function validateSelectionOptions(options) {
|
|
4955
|
+
for (const [value, maximum] of [
|
|
4956
|
+
[
|
|
4957
|
+
options.maxInputBytes ?? SELECTED_MESH_LIMITS.maxInputBytes,
|
|
4958
|
+
SELECTED_MESH_LIMITS.maxInputBytes
|
|
4959
|
+
],
|
|
4960
|
+
[
|
|
4961
|
+
options.maxInputResources ?? SELECTED_MESH_LIMITS.maxInputResources,
|
|
4962
|
+
SELECTED_MESH_LIMITS.maxInputResources
|
|
4963
|
+
]
|
|
4964
|
+
])
|
|
4965
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
|
|
4966
|
+
throw new import_core36.TileConversionError(
|
|
4967
|
+
"INVALID_I3S_SELECTION_LIMIT",
|
|
4968
|
+
"I3S selection limits must be positive safe integers within the bounded profile"
|
|
4969
|
+
);
|
|
4970
|
+
}
|