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