@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.
Files changed (83) hide show
  1. package/README.md +178 -2
  2. package/bin/converter-v5.js +2 -0
  3. package/dist/converter.min.cjs +162 -163
  4. package/dist/i3s-server/bin/i3s-server.min.cjs +73 -73
  5. package/dist/index.cjs +1749 -2152
  6. package/dist/index.js +1748 -2162
  7. package/dist/v5/adapters.cjs +1550 -128
  8. package/dist/v5/adapters.d.ts +16 -0
  9. package/dist/v5/adapters.js +1558 -117
  10. package/dist/v5/archive-source.d.ts +36 -0
  11. package/dist/v5/browser-tileset-source.d.ts +55 -11
  12. package/dist/v5/browser.cjs +18 -0
  13. package/dist/v5/browser.d.ts +14 -0
  14. package/dist/v5/browser.js +26 -0
  15. package/dist/v5/conversion-api.d.ts +9 -0
  16. package/dist/v5/converter-cli.d.ts +1 -0
  17. package/dist/v5/converter-cli.js +1214 -0
  18. package/dist/v5/core.cjs +471 -60
  19. package/dist/v5/core.d.ts +2 -0
  20. package/dist/v5/core.js +471 -60
  21. package/dist/v5/i3s-mesh-conversion.d.ts +4 -0
  22. package/dist/v5/i3s-selection-source.d.ts +70 -0
  23. package/dist/v5/index.cjs +10 -0
  24. package/dist/v5/index.d.ts +8 -0
  25. package/dist/v5/index.js +14 -0
  26. package/dist/v5/mesh-conversion.d.ts +18 -0
  27. package/dist/v5/mesh-hierarchy.d.ts +47 -0
  28. package/dist/v5/mesh-source-features.d.ts +2 -1
  29. package/dist/v5/mesh-source-schema.d.ts +12 -0
  30. package/dist/v5/mesh-source.d.ts +5 -2
  31. package/dist/v5/mesh-tileset-sink.d.ts +5 -0
  32. package/dist/v5/node-cli.d.ts +9 -0
  33. package/dist/v5/node-conversion.d.ts +32 -0
  34. package/dist/v5/node-feature-schema.d.ts +3 -0
  35. package/dist/v5/node-i3s-source.d.ts +41 -0
  36. package/dist/v5/node-source.d.ts +47 -0
  37. package/dist/v5/node.cjs +900 -0
  38. package/dist/v5/node.d.ts +6 -0
  39. package/dist/v5/node.js +877 -0
  40. package/dist/v5/point-cloud-file.d.ts +83 -0
  41. package/dist/v5/point-cloud-potree.d.ts +47 -0
  42. package/dist/v5/point-cloud-tileset.d.ts +44 -0
  43. package/dist/v5/resource-manifest.d.ts +10 -1
  44. package/dist/v5/selected-mesh-source.d.ts +53 -0
  45. package/package.json +33 -25
  46. package/src/index.ts +4 -1
  47. package/src/v4/3d-tiles-converter/helpers/load-i3s.ts +2 -4
  48. package/src/v4/converter-cli.ts +8 -2
  49. package/src/v4/i3s-converter/helpers/load-3d-tiles.ts +3 -51
  50. package/src/v4/i3s-converter/helpers/node-index-document.ts +5 -2
  51. package/src/v4/i3s-converter/helpers/preprocess-3d-tiles.ts +1 -1
  52. package/src/v4/i3s-converter/json-templates/store.ts +2 -1
  53. package/src/v5/adapters.ts +48 -0
  54. package/src/v5/archive-source.ts +235 -0
  55. package/src/v5/browser-tileset-source.ts +418 -48
  56. package/src/v5/browser.ts +45 -0
  57. package/src/v5/conversion-api.ts +69 -9
  58. package/src/v5/converter-cli.ts +28 -0
  59. package/src/v5/core.ts +6 -0
  60. package/src/v5/i3s-mesh-conversion.ts +40 -2
  61. package/src/v5/i3s-mesh-sink.ts +1 -1
  62. package/src/v5/i3s-mesh-source.ts +1 -0
  63. package/src/v5/i3s-selection-source.ts +298 -0
  64. package/src/v5/index.ts +26 -0
  65. package/src/v5/mesh-conversion.ts +20 -0
  66. package/src/v5/mesh-hierarchy.ts +648 -0
  67. package/src/v5/mesh-source-features.ts +39 -7
  68. package/src/v5/mesh-source-schema.ts +134 -0
  69. package/src/v5/mesh-source.ts +16 -4
  70. package/src/v5/mesh-tileset-sink.ts +1 -1
  71. package/src/v5/node-cli.ts +197 -0
  72. package/src/v5/node-conversion.ts +345 -0
  73. package/src/v5/node-feature-schema.ts +155 -0
  74. package/src/v5/node-i3s-source.ts +181 -0
  75. package/src/v5/node-source.ts +328 -0
  76. package/src/v5/node.ts +16 -0
  77. package/src/v5/point-cloud-file.ts +306 -0
  78. package/src/v5/point-cloud-potree.ts +130 -0
  79. package/src/v5/point-cloud-tileset.ts +137 -0
  80. package/src/v5/resource-fetcher.ts +4 -1
  81. package/src/v5/resource-manifest.ts +40 -5
  82. package/src/v5/selected-mesh-source.ts +384 -0
  83. package/src/v5/spatial-conversion.ts +88 -17
@@ -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 TileConversionError14 } from "@loaders.gl/tile-converter/v5/core";
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-features.ts
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 TileConversionError13(
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 TileConversionError13(
2155
+ throw new TileConversionError14(
2073
2156
  "MESH_FEATURE_SCHEMA_REQUIRED",
2074
- "Feature extraction requires an explicit Arrow schema and class mapping"
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 TileConversionError13(
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 TileConversionError13(
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 TileConversionError13(
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 TileConversionError13(
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 TileConversionError13(
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 TileConversionError13(
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 TileConversionError13(
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.default === void 0)
2125
- throw new TileConversionError13(
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 decoded = data ?? Array.from({ length: count }, () => property.default);
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 TileConversionError13(
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 ? property.default ?? null : value
2241
+ (value) => value === comparable ? defaultValue : value
2145
2242
  );
2146
2243
  }
2147
2244
  } else {
2148
2245
  if (metadata)
2149
- throw new TileConversionError13(
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 TileConversionError13(
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 TileConversionError13(
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 TileConversionError13(
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 TileConversionError13(
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
- options
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 TileConversionError13(
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 TileConversionError14(
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 TileConversionError14(
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 TileConversionError14(
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 TileConversionError14(
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 TileConversionError14(
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 TileConversionError14(
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 TileConversionError14(
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 TileConversionError14("MESH_SOURCE_AXIS_UNSUPPORTED", "Unknown glTF up axis");
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 TileConversionError14(
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 TileConversionError14(
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
- for (const selected of traverseMeshNodes(roots, placement)) {
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 { ...resource, features };
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 TileConversionError14(
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 TileConversionError14(
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 TileConversionError14(
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 TileConversionError14(
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 TileConversionError14(
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 TileConversionError14(
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 TileConversionError14(
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 TileConversionError14(
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 { encodeI3SMeshLayer, encodeI3SMeshLayerWithDraco } from "@loaders.gl/i3s";
2491
- import { TileConversionError as TileConversionError15 } from "@loaders.gl/tile-converter/v5/core";
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 TileConversionError15(
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 TileConversionError15(
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 TileConversionError15(
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 TileConversionError15(
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 TileConversionError16,
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 TileConversionError16(
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, gzip: { mtime: 0 } });
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 TileConversionError16(
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 TileConversionError16(
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 TileConversionError16(
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 TileConversionError16(
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 TileConversionError16(
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 TileConversionError16(
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 TileConversionError16(
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 TileConversionError16(
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 TileConversionError17 = class extends Error {
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 TileConversionError17(
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 TileConversionError17(
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 TileConversionError17(
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 TileConversionError17(
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 TileConversionError17(
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
- return new Response(bytes, {
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 TileConversionError18
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 TileConversionError18(
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 TileConversionError18(
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 TileConversionError18(
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 TileConversionError18(
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 TileConversionError18(
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 TileConversionError18(
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 TileConversionError18(
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 TileConversionError18(
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 TileConversionError18(
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 TileConversionError18(
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 TileConversionError18(
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 TileConversionError18(
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 TileConversionError21,
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 TileConversionError19 } from "@loaders.gl/tile-converter/v5/core";
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 TileConversionError19(
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 TileConversionError19(
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 TileConversionError19(
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 TileConversionError19(
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 TileConversionError19(
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 TileConversionError19(
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 TileConversionError20 } from "@loaders.gl/tile-converter/v5/core";
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError20(
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 TileConversionError21(
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 TileConversionError21(
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 TileConversionError21(
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 TileConversionError21(
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 TileConversionError21(
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 TileConversionError21(
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 TileConversionError21(
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 TileConversionError21(
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 TileConversionError21(
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 TileConversionError21(
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
  };