@loaders.gl/tile-converter 5.0.0-alpha.1 → 5.0.0-alpha.11

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 (141) hide show
  1. package/LICENSE +14 -3
  2. package/README.md +497 -0
  3. package/dist/converter.min.cjs +210 -282
  4. package/dist/i3s-server/bin/i3s-server.min.cjs +106 -341
  5. package/dist/index.cjs +38586 -0
  6. package/dist/index.js +38557 -0
  7. package/dist/v5/adapters.cjs +3548 -0
  8. package/dist/v5/adapters.d.ts +33 -0
  9. package/dist/v5/adapters.js +3537 -0
  10. package/dist/v5/browser-sink.d.ts +37 -0
  11. package/dist/v5/browser-source.d.ts +38 -0
  12. package/dist/v5/browser-tileset-source.d.ts +70 -0
  13. package/dist/v5/browser.cjs +76 -0
  14. package/dist/v5/browser.d.ts +44 -0
  15. package/dist/v5/browser.js +75 -0
  16. package/dist/v5/conversion-api.d.ts +122 -0
  17. package/dist/v5/conversion-archive.d.ts +32 -0
  18. package/dist/v5/core.cjs +917 -0
  19. package/dist/v5/core.d.ts +16 -0
  20. package/dist/v5/core.js +899 -0
  21. package/dist/v5/feature-arrow.d.ts +36 -0
  22. package/dist/v5/i3s-mesh-conversion.d.ts +32 -0
  23. package/dist/v5/i3s-mesh-sink.d.ts +25 -0
  24. package/dist/v5/i3s-mesh-source-features.d.ts +13 -0
  25. package/dist/v5/i3s-mesh-source-material.d.ts +5 -0
  26. package/dist/v5/i3s-mesh-source.d.ts +30 -0
  27. package/dist/v5/index.cjs +73 -0
  28. package/dist/v5/index.d.ts +40 -0
  29. package/dist/v5/index.js +75 -0
  30. package/dist/v5/mesh-conversion.d.ts +71 -0
  31. package/dist/v5/mesh-draco.d.ts +8 -0
  32. package/dist/v5/mesh-features.d.ts +9 -0
  33. package/dist/v5/mesh-source-features.d.ts +21 -0
  34. package/dist/v5/mesh-source-texture.d.ts +6 -0
  35. package/dist/v5/mesh-source.d.ts +41 -0
  36. package/dist/v5/mesh-tileset-archive.d.ts +18 -0
  37. package/dist/v5/mesh-tileset-sink.d.ts +51 -0
  38. package/dist/v5/mesh.d.ts +86 -0
  39. package/dist/v5/point-cloud-conversion.d.ts +31 -0
  40. package/dist/v5/point-cloud-source-encoder.d.ts +46 -0
  41. package/dist/v5/point-cloud-source.d.ts +25 -0
  42. package/dist/v5/point-cloud-spatial.d.ts +22 -0
  43. package/dist/v5/point-cloud-tileset-sink.d.ts +28 -0
  44. package/dist/v5/point-cloud.d.ts +49 -0
  45. package/dist/v5/resource-fetcher.d.ts +18 -0
  46. package/dist/v5/resource-manifest.d.ts +58 -0
  47. package/dist/v5/spatial-conversion.d.ts +61 -0
  48. package/dist/v5/tileset-source.d.ts +21 -0
  49. package/package.json +52 -26
  50. package/src/common/README.md +5 -0
  51. package/src/index.ts +1 -2
  52. package/src/{3d-tiles-converter → v4/3d-tiles-converter}/3d-tiles-converter.ts +13 -2
  53. package/src/v4/3d-tiles-converter/helpers/validate-i3s-spatial-reference.ts +124 -0
  54. package/src/{deps-installer → v4/deps-installer}/deps-installer.ts +10 -7
  55. package/src/{i3s-converter → v4/i3s-converter}/helpers/batch-ids-extensions.ts +1 -1
  56. package/src/v4/i3s-converter/helpers/encode-string-attribute.ts +23 -0
  57. package/src/{i3s-converter → v4/i3s-converter}/helpers/geometry-converter.ts +30 -55
  58. package/src/{i3s-converter → v4/i3s-converter}/helpers/gltf-attributes.ts +5 -4
  59. package/src/{i3s-converter → v4/i3s-converter}/helpers/load-3d-tiles.ts +5 -9
  60. package/src/{i3s-converter → v4/i3s-converter}/i3s-converter.ts +6 -7
  61. package/src/{i3s-server → v4/i3s-server}/README.md +2 -2
  62. package/src/{i3s-server → v4/i3s-server}/routes/slpk-router.ts +1 -0
  63. package/src/v4/index.ts +2 -0
  64. package/src/{lib → v4/lib}/utils/compress-util.ts +8 -7
  65. package/src/v5/adapters.ts +75 -0
  66. package/src/v5/browser-sink.ts +130 -0
  67. package/src/v5/browser-source.ts +155 -0
  68. package/src/v5/browser-tileset-source.ts +328 -0
  69. package/src/v5/browser.ts +139 -0
  70. package/src/v5/conversion-api.ts +287 -0
  71. package/src/v5/conversion-archive.ts +85 -0
  72. package/src/v5/core.ts +61 -0
  73. package/src/v5/feature-arrow.ts +429 -0
  74. package/src/v5/i3s-mesh-conversion.ts +192 -0
  75. package/src/v5/i3s-mesh-sink.ts +351 -0
  76. package/src/v5/i3s-mesh-source-features.ts +279 -0
  77. package/src/v5/i3s-mesh-source-material.ts +157 -0
  78. package/src/v5/i3s-mesh-source.ts +227 -0
  79. package/src/v5/index.ts +131 -0
  80. package/src/v5/mesh-conversion.ts +230 -0
  81. package/src/v5/mesh-draco.ts +182 -0
  82. package/src/v5/mesh-features.ts +319 -0
  83. package/src/v5/mesh-source-features.ts +263 -0
  84. package/src/v5/mesh-source-texture.ts +169 -0
  85. package/src/v5/mesh-source.ts +422 -0
  86. package/src/v5/mesh-tileset-archive.ts +50 -0
  87. package/src/v5/mesh-tileset-sink.ts +324 -0
  88. package/src/v5/mesh.ts +526 -0
  89. package/src/v5/point-cloud-conversion.ts +88 -0
  90. package/src/v5/point-cloud-source-encoder.ts +116 -0
  91. package/src/v5/point-cloud-source.ts +84 -0
  92. package/src/v5/point-cloud-spatial.ts +280 -0
  93. package/src/v5/point-cloud-tileset-sink.ts +234 -0
  94. package/src/v5/point-cloud.ts +398 -0
  95. package/src/v5/resource-fetcher.ts +110 -0
  96. package/src/v5/resource-manifest.ts +213 -0
  97. package/src/v5/spatial-conversion.ts +389 -0
  98. package/src/v5/tileset-source.ts +54 -0
  99. /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/3d-tiles-content-converter.ts +0 -0
  100. /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/i3s-obb-to-3d-tiles-obb.ts +0 -0
  101. /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/load-i3s.ts +0 -0
  102. /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/texture-atlas.ts +0 -0
  103. /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/json-templates/tileset.ts +0 -0
  104. /package/src/{constants.ts → v4/constants.ts} +0 -0
  105. /package/src/{converter-cli.ts → v4/converter-cli.ts} +0 -0
  106. /package/src/{i3s-converter → v4/i3s-converter}/helpers/attribute-metadata-info.ts +0 -0
  107. /package/src/{i3s-converter → v4/i3s-converter}/helpers/coordinate-converter.ts +0 -0
  108. /package/src/{i3s-converter → v4/i3s-converter}/helpers/create-scene-server-path.ts +0 -0
  109. /package/src/{i3s-converter → v4/i3s-converter}/helpers/feature-attributes.ts +0 -0
  110. /package/src/{i3s-converter → v4/i3s-converter}/helpers/geometry-attributes.ts +0 -0
  111. /package/src/{i3s-converter → v4/i3s-converter}/helpers/node-debug.ts +0 -0
  112. /package/src/{i3s-converter → v4/i3s-converter}/helpers/node-index-document.ts +0 -0
  113. /package/src/{i3s-converter → v4/i3s-converter}/helpers/node-pages.ts +0 -0
  114. /package/src/{i3s-converter → v4/i3s-converter}/helpers/preprocess-3d-tiles.ts +0 -0
  115. /package/src/{i3s-converter → v4/i3s-converter}/helpers/progress.ts +0 -0
  116. /package/src/{i3s-converter → v4/i3s-converter}/helpers/tileset-traversal.ts +0 -0
  117. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/geometry-definitions.ts +0 -0
  118. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/layers.ts +0 -0
  119. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/metadata.ts +0 -0
  120. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/node.ts +0 -0
  121. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/scene-server.ts +0 -0
  122. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/shared-resources.ts +0 -0
  123. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/store.ts +0 -0
  124. /package/src/{i3s-converter → v4/i3s-converter}/types.ts +0 -0
  125. /package/src/{i3s-server → v4/i3s-server}/app.ts +0 -0
  126. /package/src/{i3s-server → v4/i3s-server}/bin/www.ts +0 -0
  127. /package/src/{i3s-server → v4/i3s-server}/certs/cert.pem +0 -0
  128. /package/src/{i3s-server → v4/i3s-server}/certs/key.pem +0 -0
  129. /package/src/{i3s-server → v4/i3s-server}/controllers/slpk-controller.ts +0 -0
  130. /package/src/{i3s-server → v4/i3s-server}/utils/create-scene-server.ts +0 -0
  131. /package/src/{i3s-server → v4/i3s-server}/utils/server-utils.ts +0 -0
  132. /package/src/{lib → v4/lib}/json-schemas/conversion-dump-json-schema.ts +0 -0
  133. /package/src/{lib → v4/lib}/utils/cli-utils.ts +0 -0
  134. /package/src/{lib → v4/lib}/utils/conversion-dump.ts +0 -0
  135. /package/src/{lib → v4/lib}/utils/file-utils.ts +0 -0
  136. /package/src/{lib → v4/lib}/utils/geometry-utils.ts +0 -0
  137. /package/src/{lib → v4/lib}/utils/lod-conversion-utils.ts +0 -0
  138. /package/src/{lib → v4/lib}/utils/queue.ts +0 -0
  139. /package/src/{lib → v4/lib}/utils/statistic-utils.ts +0 -0
  140. /package/src/{lib → v4/lib}/utils/write-queue.ts +0 -0
  141. /package/src/{pgm-loader.ts → v4/pgm-loader.ts} +0 -0
@@ -0,0 +1,3548 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/v5/adapters.ts
21
+ var adapters_exports = {};
22
+ __export(adapters_exports, {
23
+ convertFeatureAttributesToArrowBatches: () => convertFeatureAttributesToArrowBatches,
24
+ convertPointCloudSource: () => convertPointCloudSource,
25
+ createI3SMeshConversionCodec: () => createI3SMeshConversionCodec,
26
+ createI3SMeshSink: () => createI3SMeshSink,
27
+ createI3SMeshTilesetConversionSource: () => createI3SMeshTilesetConversionSource,
28
+ createMeshConversionCodec: () => createMeshConversionCodec,
29
+ createMeshTilesetConversionSource: () => createMeshTilesetConversionSource,
30
+ createMeshTilesetSink: () => createMeshTilesetSink,
31
+ createPointCloudTilesetSink: () => createPointCloudTilesetSink,
32
+ createSingleMeshI3SSink: () => createSingleMeshI3SSink,
33
+ createSingleMeshTilesetArchive: () => createSingleMeshTilesetArchive,
34
+ createSingleMeshTilesetSink: () => createSingleMeshTilesetSink,
35
+ createTileConversionArchive: () => createTileConversionArchive,
36
+ createTileConversionResourceFetcher: () => createTileConversionResourceFetcher,
37
+ encodeMeshTile: () => encodeMeshTile,
38
+ encodePointCloudSource: () => encodePointCloudSource,
39
+ encodePointCloudSourceTile: () => encodePointCloudSourceTile,
40
+ encodePointCloudTile: () => encodePointCloudTile,
41
+ encodePointCloudTileWithMetadata: () => encodePointCloudTileWithMetadata,
42
+ encodeTileConversionArchiveInBatches: () => encodeTileConversionArchiveInBatches,
43
+ transformPointCloudSourceTile: () => transformPointCloudSourceTile
44
+ });
45
+ module.exports = __toCommonJS(adapters_exports);
46
+
47
+ // src/v5/feature-arrow.ts
48
+ var import_schema_utils = require("@loaders.gl/schema-utils");
49
+ var import_core = require("@loaders.gl/tile-converter/v5/core");
50
+ var DEFAULT_FEATURE_ID_FIELD = "feature_id";
51
+ var FEATURE_CLASS_METADATA_KEY = "loaders.gl:feature-class";
52
+ function convertFeatureAttributesToArrowBatches(features, options) {
53
+ const featureIdField = options.featureIdField || DEFAULT_FEATURE_ID_FIELD;
54
+ const batchSize = options.batchSize ?? 65536;
55
+ if (!Number.isInteger(batchSize) || batchSize <= 0) {
56
+ throw new import_core.TileConversionError(
57
+ "INVALID_FEATURE_BATCH_SIZE",
58
+ "Feature Arrow batch size must be a positive integer"
59
+ );
60
+ }
61
+ const featureClasses = new Set(features.map((feature) => feature.metadataClass));
62
+ if (featureClasses.size > 1) {
63
+ throw new import_core.TileConversionError(
64
+ "MULTIPLE_FEATURE_CLASSES",
65
+ "Build separate Arrow batches for each source metadata class"
66
+ );
67
+ }
68
+ const metadataClass = features[0]?.metadataClass;
69
+ const schema = makeFeatureSchema(
70
+ options.schema,
71
+ featureIdField,
72
+ options.rawMetadataField,
73
+ metadataClass
74
+ );
75
+ const fieldsByName = new Map(schema.fields.map((field) => [field.name, field]));
76
+ const propertyFieldNames = new Set(
77
+ schema.fields.filter((field) => field.name !== featureIdField && field.name !== options.rawMetadataField).map((field) => field.name)
78
+ );
79
+ for (const feature of features) {
80
+ validateFeatureIdentifier(feature.featureId, fieldsByName.get(featureIdField)?.type);
81
+ if (feature.rawMetadata && !options.rawMetadataField) {
82
+ throw new import_core.TileConversionError(
83
+ "RAW_METADATA_FIELD_REQUIRED",
84
+ "A rawMetadataField must be declared to preserve encoded source metadata"
85
+ );
86
+ }
87
+ for (const propertyName of Object.keys(feature.properties)) {
88
+ if (!propertyFieldNames.has(propertyName)) {
89
+ throw new import_core.TileConversionError(
90
+ "FEATURE_PROPERTY_NOT_MAPPED",
91
+ `Feature property "${propertyName}" is not declared in the Arrow schema`
92
+ );
93
+ }
94
+ }
95
+ }
96
+ const batches = [];
97
+ for (let batchStart = 0; batchStart < features.length; batchStart += batchSize) {
98
+ const tableBuilder = new import_schema_utils.ArrowTableBuilder(schema);
99
+ const batchFeatures = features.slice(batchStart, batchStart + batchSize);
100
+ for (const feature of batchFeatures) {
101
+ const row = {
102
+ ...feature.properties,
103
+ [featureIdField]: feature.featureId
104
+ };
105
+ if (options.rawMetadataField) {
106
+ row[options.rawMetadataField] = feature.rawMetadata || null;
107
+ }
108
+ for (const field of schema.fields) {
109
+ if (row[field.name] === void 0) {
110
+ row[field.name] = null;
111
+ }
112
+ validateFeatureValue(field, row[field.name], field.name);
113
+ }
114
+ tableBuilder.addObjectRow(row);
115
+ }
116
+ const batch = tableBuilder.finishBatch();
117
+ if (batch) {
118
+ batches.push(batch);
119
+ }
120
+ }
121
+ return batches;
122
+ }
123
+ function makeFeatureSchema(sourceSchema, featureIdField, rawMetadataField, metadataClass) {
124
+ const fieldsByName = new Map(sourceSchema.fields.map((field) => [field.name, field]));
125
+ if (fieldsByName.size !== sourceSchema.fields.length) {
126
+ throw new import_core.TileConversionError(
127
+ "DUPLICATE_FEATURE_FIELDS",
128
+ "Arrow schema fields must have unique names"
129
+ );
130
+ }
131
+ for (const field of sourceSchema.fields) {
132
+ validateFeatureSchemaType(field);
133
+ }
134
+ const featureIdType = fieldsByName.get(featureIdField)?.type;
135
+ if (!featureIdType) {
136
+ throw new import_core.TileConversionError(
137
+ "FEATURE_ID_FIELD_REQUIRED",
138
+ `Arrow schema must declare the feature identifier field "${featureIdField}"`
139
+ );
140
+ }
141
+ if (rawMetadataField && !fieldsByName.has(rawMetadataField)) {
142
+ throw new import_core.TileConversionError(
143
+ "RAW_METADATA_FIELD_NOT_DECLARED",
144
+ `Arrow schema does not declare the raw metadata field "${rawMetadataField}"`
145
+ );
146
+ }
147
+ if (rawMetadataField === featureIdField) {
148
+ throw new import_core.TileConversionError(
149
+ "FEATURE_FIELDS_COLLIDE",
150
+ "The feature identifier and raw metadata require separate Arrow fields"
151
+ );
152
+ }
153
+ if (rawMetadataField && !isBinaryType(fieldsByName.get(rawMetadataField)?.type)) {
154
+ throw new import_core.TileConversionError(
155
+ "RAW_METADATA_FIELD_NOT_BINARY",
156
+ `Arrow schema field "${rawMetadataField}" must use binary or fixed-size-binary type`
157
+ );
158
+ }
159
+ return {
160
+ ...sourceSchema,
161
+ metadata: {
162
+ ...sourceSchema.metadata || {},
163
+ ...metadataClass ? { [FEATURE_CLASS_METADATA_KEY]: metadataClass } : {}
164
+ }
165
+ };
166
+ }
167
+ function validateFeatureIdentifier(featureId, dataType) {
168
+ const isStringIdentifier = typeof featureId === "string";
169
+ const isIntegerIdentifier2 = typeof featureId === "bigint" || Number.isSafeInteger(featureId);
170
+ const isStringType = dataType === "utf8" || dataType === "utf8-view";
171
+ if (isStringIdentifier && isStringType || isIntegerIdentifier2 && isIntegerDataType(dataType)) {
172
+ return;
173
+ }
174
+ throw new import_core.TileConversionError(
175
+ "FEATURE_ID_TYPE_MISMATCH",
176
+ `Feature identifier ${String(featureId)} is not compatible with the declared Arrow type`
177
+ );
178
+ }
179
+ function validateFeatureValue(field, value, fieldPath) {
180
+ if (value === null || value === void 0) {
181
+ if (field.nullable !== true) {
182
+ throw new import_core.TileConversionError(
183
+ "FEATURE_VALUE_REQUIRED",
184
+ `Required Arrow field "${fieldPath}" has no value`
185
+ );
186
+ }
187
+ return;
188
+ }
189
+ const dataType = field.type;
190
+ if (typeof dataType === "object") {
191
+ if (dataType.type === "dictionary") {
192
+ validateFeatureValue({ ...field, type: dataType.dictionary }, value, fieldPath);
193
+ return;
194
+ }
195
+ if (dataType.type === "list" || dataType.type === "large-list") {
196
+ if (!Array.isArray(value)) {
197
+ throw createFeatureValueTypeError(fieldPath, "an array");
198
+ }
199
+ const childField = dataType.children[0];
200
+ for (let index = 0; index < value.length; index++) {
201
+ validateFeatureValue(childField, value[index], `${fieldPath}[${index}]`);
202
+ }
203
+ return;
204
+ }
205
+ if (dataType.type === "fixed-size-list") {
206
+ if (!Array.isArray(value) || value.length !== dataType.listSize) {
207
+ throw createFeatureValueTypeError(fieldPath, `an array of length ${dataType.listSize}`);
208
+ }
209
+ const childField = dataType.children[0];
210
+ for (let index = 0; index < value.length; index++) {
211
+ validateFeatureValue(childField, value[index], `${fieldPath}[${index}]`);
212
+ }
213
+ return;
214
+ }
215
+ if (dataType.type === "struct") {
216
+ if (!isRecord(value)) {
217
+ throw createFeatureValueTypeError(fieldPath, "an object");
218
+ }
219
+ const childNames = new Set(dataType.children.map((childField) => childField.name));
220
+ const unknownNames = Object.keys(value).filter((name) => !childNames.has(name));
221
+ if (unknownNames.length > 0) {
222
+ throw new import_core.TileConversionError(
223
+ "FEATURE_PROPERTY_NOT_MAPPED",
224
+ `Nested feature properties "${unknownNames.join(", ")}" are not declared under "${fieldPath}"`
225
+ );
226
+ }
227
+ for (const childField of dataType.children) {
228
+ validateFeatureValue(childField, value[childField.name], `${fieldPath}.${childField.name}`);
229
+ }
230
+ return;
231
+ }
232
+ if (dataType.type === "decimal") {
233
+ if (typeof value !== "bigint") {
234
+ throw createFeatureValueTypeError(fieldPath, "an unscaled bigint decimal value");
235
+ }
236
+ if (value <= -(10n ** BigInt(dataType.precision)) || value >= 10n ** BigInt(dataType.precision)) {
237
+ throw new import_core.TileConversionError(
238
+ "FEATURE_VALUE_OUT_OF_RANGE",
239
+ `Value for Arrow decimal field "${fieldPath}" exceeds precision ${dataType.precision}`
240
+ );
241
+ }
242
+ return;
243
+ }
244
+ if (dataType.type === "fixed-size-binary") {
245
+ const byteLength = getByteLength(value);
246
+ if (byteLength !== dataType.byteWidth) {
247
+ throw createFeatureValueTypeError(fieldPath, `exactly ${dataType.byteWidth} bytes`);
248
+ }
249
+ return;
250
+ }
251
+ throw new import_core.TileConversionError(
252
+ "FEATURE_VALUE_MAPPING_UNSUPPORTED",
253
+ `Arrow schema type "${dataType.type}" has no feature value mapping for "${fieldPath}"`
254
+ );
255
+ }
256
+ if (isIntegerDataType(dataType)) {
257
+ if (!isIntegerIdentifier(value)) {
258
+ throw createFeatureValueTypeError(fieldPath, "an integer");
259
+ }
260
+ const [minimum, maximum] = getIntegerBounds(dataType);
261
+ const integerValue = typeof value === "bigint" ? value : BigInt(value);
262
+ if (integerValue < minimum || integerValue > maximum) {
263
+ throw new import_core.TileConversionError(
264
+ "FEATURE_VALUE_OUT_OF_RANGE",
265
+ `Value for Arrow field "${fieldPath}" is outside the range of ${dataType}`
266
+ );
267
+ }
268
+ return;
269
+ }
270
+ if (dataType === "utf8" || dataType === "utf8-view") {
271
+ if (typeof value !== "string") throw createFeatureValueTypeError(fieldPath, "a string");
272
+ if (new TextDecoder().decode(new TextEncoder().encode(value)) !== value)
273
+ throw createFeatureValueTypeError(fieldPath, "a valid Unicode string");
274
+ return;
275
+ }
276
+ if (dataType === "bool") {
277
+ if (typeof value !== "boolean") throw createFeatureValueTypeError(fieldPath, "a boolean");
278
+ return;
279
+ }
280
+ if (dataType === "binary" || dataType === "binary-view") {
281
+ if (getByteLength(value) < 0) throw createFeatureValueTypeError(fieldPath, "binary data");
282
+ return;
283
+ }
284
+ if (dataType === "float" || dataType === "float16" || dataType === "float32" || dataType === "float64") {
285
+ if (typeof value !== "number") throw createFeatureValueTypeError(fieldPath, "a number");
286
+ return;
287
+ }
288
+ if (dataType === "null") {
289
+ throw createFeatureValueTypeError(fieldPath, "null");
290
+ }
291
+ if (dataType === "date-day" || dataType === "date-millisecond" || dataType.startsWith("timestamp-")) {
292
+ if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {
293
+ throw createFeatureValueTypeError(fieldPath, "a valid Date");
294
+ }
295
+ return;
296
+ }
297
+ throw new import_core.TileConversionError(
298
+ "FEATURE_VALUE_MAPPING_UNSUPPORTED",
299
+ `Arrow schema type "${dataType}" has no feature value mapping for "${fieldPath}"`
300
+ );
301
+ }
302
+ function isIntegerIdentifier(value) {
303
+ return typeof value === "bigint" || typeof value === "number" && Number.isSafeInteger(value);
304
+ }
305
+ function isIntegerDataType(dataType) {
306
+ return dataType === "int8" || dataType === "int16" || dataType === "int32" || dataType === "int64" || dataType === "uint8" || dataType === "uint16" || dataType === "uint32" || dataType === "uint64";
307
+ }
308
+ function validateFeatureSchemaType(field) {
309
+ const dataType = field.type;
310
+ if (dataType === "int") {
311
+ throw new import_core.TileConversionError(
312
+ "FEATURE_INTEGER_TYPE_UNSUPPORTED",
313
+ `Arrow field "${field.name}" must declare an explicit integer width and signedness`
314
+ );
315
+ }
316
+ if (typeof dataType !== "object") {
317
+ return;
318
+ }
319
+ let childFields = [];
320
+ if (dataType.type === "dictionary" && dataType.indices) {
321
+ childFields = [{ name: `${field.name} index`, type: dataType.indices, nullable: false }];
322
+ } else if ("children" in dataType) {
323
+ childFields = dataType.children;
324
+ }
325
+ for (const childField of childFields) {
326
+ validateFeatureSchemaType(childField);
327
+ }
328
+ }
329
+ function getIntegerBounds(dataType) {
330
+ switch (dataType) {
331
+ case "int8":
332
+ return [-128n, 127n];
333
+ case "uint8":
334
+ return [0n, 255n];
335
+ case "int16":
336
+ return [-32768n, 32767n];
337
+ case "uint16":
338
+ return [0n, 65535n];
339
+ case "int32":
340
+ return [-2147483648n, 2147483647n];
341
+ case "uint32":
342
+ return [0n, 4294967295n];
343
+ case "int64":
344
+ return [-(2n ** 63n), 2n ** 63n - 1n];
345
+ case "uint64":
346
+ return [0n, 2n ** 64n - 1n];
347
+ default:
348
+ return [-(2n ** 31n), 2n ** 31n - 1n];
349
+ }
350
+ }
351
+ function createFeatureValueTypeError(fieldPath, expected) {
352
+ return new import_core.TileConversionError(
353
+ "FEATURE_VALUE_TYPE_MISMATCH",
354
+ `Arrow field "${fieldPath}" requires ${expected}`
355
+ );
356
+ }
357
+ function isRecord(value) {
358
+ return typeof value === "object" && value !== null && !Array.isArray(value);
359
+ }
360
+ function getByteLength(value) {
361
+ if (value instanceof Uint8Array) return value.byteLength;
362
+ if (value instanceof ArrayBuffer) return value.byteLength;
363
+ return -1;
364
+ }
365
+ function isBinaryType(dataType) {
366
+ return dataType === "binary" || dataType === "binary-view" || typeof dataType === "object" && dataType.type === "fixed-size-binary";
367
+ }
368
+
369
+ // src/v5/point-cloud.ts
370
+ var import_d_tiles = require("@loaders.gl/3d-tiles");
371
+ var import_schema_utils2 = require("@loaders.gl/schema-utils");
372
+ var import_core2 = require("@loaders.gl/tile-converter/v5/core");
373
+ function encodePointCloudTile(pointBatch, options = {}) {
374
+ return encodePointCloudTileWithMetadata(pointBatch, options).pnts;
375
+ }
376
+ function encodePointCloudTileWithMetadata(pointBatch, options = {}) {
377
+ const mesh = "attributes" in pointBatch ? pointBatch : (0, import_schema_utils2.convertTableToMesh)(pointBatch);
378
+ validatePointAttributes(mesh.attributes);
379
+ const positionAttribute = mesh.attributes.POSITION;
380
+ validatePointCloudPosition(positionAttribute);
381
+ const placement = encodePointPositions(positionAttribute, options);
382
+ const { positions } = placement;
383
+ const pointCount = positions.length / 3;
384
+ const colors = getColorAttribute(mesh.attributes.COLOR_0 || mesh.attributes.COLOR, pointCount);
385
+ const constantRGBA = colors ? null : getConstantRgba(options.constantRGBA);
386
+ const normals = getNormalAttribute(mesh.attributes.NORMAL, pointCount);
387
+ const batchIds = getBatchIds(mesh.attributes.BATCH_ID, pointCount);
388
+ const batchTableJson = validateBatchTable(options.batchTableJson, batchIds);
389
+ const pnts = import_d_tiles.Tile3DWriter.encodeSync(
390
+ {
391
+ type: import_d_tiles.TILE3D_TYPE.POINT_CLOUD,
392
+ featureTableJson: options.rtcCenter || constantRGBA ? {
393
+ ...options.rtcCenter ? { RTC_CENTER: [...options.rtcCenter] } : {},
394
+ ...constantRGBA ? { CONSTANT_RGBA: constantRGBA } : {}
395
+ } : void 0,
396
+ batchTableJson,
397
+ attributes: { positions, colors, normals, batchIds }
398
+ },
399
+ {}
400
+ );
401
+ return {
402
+ pnts,
403
+ pointCount,
404
+ rtcCenter: placement.rtcCenter,
405
+ localBoundingBox: placement.localBoundingBox,
406
+ maximumPositionError: placement.maximumPositionError
407
+ };
408
+ }
409
+ function validatePointAttributes(attributes) {
410
+ const supportedAttributes = /* @__PURE__ */ new Set([
411
+ "POSITION",
412
+ "NORMAL",
413
+ "BATCH_ID",
414
+ attributes.COLOR_0 ? "COLOR_0" : "COLOR"
415
+ ]);
416
+ const unsupportedAttributes = Object.keys(attributes).filter(
417
+ (name) => !supportedAttributes.has(name)
418
+ );
419
+ if (unsupportedAttributes.length > 0) {
420
+ throw new import_core2.TileConversionError(
421
+ "POINT_CLOUD_ATTRIBUTE_UNSUPPORTED",
422
+ `PNTS encoding does not support these point attributes: ${unsupportedAttributes.join(", ")}`
423
+ );
424
+ }
425
+ }
426
+ function getConstantRgba(constantRGBA) {
427
+ if (!constantRGBA) {
428
+ return null;
429
+ }
430
+ if (constantRGBA.length !== 4 || constantRGBA.some((value) => !Number.isInteger(value) || value < 0 || value > 255)) {
431
+ throw new import_core2.TileConversionError(
432
+ "POINT_CLOUD_CONSTANT_RGBA_INVALID",
433
+ "Point cloud constantRGBA must contain four unsigned byte values"
434
+ );
435
+ }
436
+ return [constantRGBA[0], constantRGBA[1], constantRGBA[2], constantRGBA[3]];
437
+ }
438
+ function validatePointCloudPosition(attribute) {
439
+ if (!attribute || attribute.size !== 3 || attribute.byteOffset || attribute.byteStride || attribute.normalized || attribute.componentType) {
440
+ throw new import_core2.TileConversionError(
441
+ "POINT_CLOUD_POSITION_REQUIRED",
442
+ "Point cloud batches must contain packed, non-normalized three-component POSITION values"
443
+ );
444
+ }
445
+ if (!attribute.value.length || attribute.value.length % 3) {
446
+ throw new import_core2.TileConversionError(
447
+ "POINT_CLOUD_POSITION_COUNT_INVALID",
448
+ "Point cloud POSITION must contain one or more xyz triples"
449
+ );
450
+ }
451
+ const transform = attribute.transform;
452
+ if (transform && (transform.type !== "quantization" || !Number.isInteger(transform.bits) || transform.bits < 1 || transform.bits > 32 || transform.origin.length !== 3 || transform.origin.some((value) => !Number.isFinite(value)) || !Number.isFinite(transform.range) || transform.range < 0)) {
453
+ throw new import_core2.TileConversionError(
454
+ "POINT_CLOUD_POSITION_TRANSFORM_INVALID",
455
+ "Position quantization requires finite xyz origin, nonnegative range and 1\u201332 bits"
456
+ );
457
+ }
458
+ }
459
+ function decodePointCloudPositions(attribute) {
460
+ validatePointCloudPosition(attribute);
461
+ const transform = attribute.transform;
462
+ return Float64Array.from(
463
+ attribute.value,
464
+ (value, index) => transform ? transform.origin[index % 3] + value / (2 ** transform.bits - 1) * transform.range : value
465
+ );
466
+ }
467
+ function encodePointPositions(attribute, options) {
468
+ const { rtcCenter = [0, 0, 0], maxPositionError = Infinity } = options;
469
+ if (rtcCenter.length !== 3 || rtcCenter.some((value) => !Number.isFinite(value))) {
470
+ throw new import_core2.TileConversionError(
471
+ "POINT_CLOUD_RTC_CENTER_INVALID",
472
+ "Point cloud RTC_CENTER must contain three finite values"
473
+ );
474
+ }
475
+ if (options.maxPositionError !== void 0 && (!Number.isFinite(maxPositionError) || maxPositionError < 0)) {
476
+ throw new import_core2.TileConversionError(
477
+ "POINT_CLOUD_PRECISION_INVALID",
478
+ "maxPositionError must be finite and nonnegative"
479
+ );
480
+ }
481
+ const values = attribute.value;
482
+ const positions = new Float32Array(values.length);
483
+ const minimum = [Infinity, Infinity, Infinity];
484
+ const maximum = [-Infinity, -Infinity, -Infinity];
485
+ let maximumPositionError = 0;
486
+ const transform = attribute.transform;
487
+ const errors = [0, 0, 0];
488
+ for (let point = 0; point < values.length; point += 3) {
489
+ for (let axis = 0; axis < 3; axis++) {
490
+ const value = values[point + axis];
491
+ const absoluteValue = transform ? transform.origin[axis] + value / (2 ** transform.bits - 1) * transform.range : value;
492
+ const relativeValue = absoluteValue - rtcCenter[axis];
493
+ positions[point + axis] = relativeValue;
494
+ const encodedValue = positions[point + axis];
495
+ const reconstructed = encodedValue + rtcCenter[axis];
496
+ if (!Number.isFinite(absoluteValue) || !Number.isFinite(relativeValue) || !Number.isFinite(encodedValue) || !Number.isFinite(reconstructed)) {
497
+ throw new import_core2.TileConversionError(
498
+ "POINT_CLOUD_POSITION_INVALID",
499
+ "Point positions and their float32 reconstruction must be finite"
500
+ );
501
+ }
502
+ errors[axis] = reconstructed - absoluteValue;
503
+ minimum[axis] = Math.min(minimum[axis], encodedValue);
504
+ maximum[axis] = Math.max(maximum[axis], encodedValue);
505
+ }
506
+ maximumPositionError = Math.max(maximumPositionError, Math.hypot(...errors));
507
+ }
508
+ if (!Number.isFinite(maximumPositionError) || maximumPositionError > maxPositionError) {
509
+ throw new import_core2.TileConversionError(
510
+ "POINT_CLOUD_PRECISION_EXCEEDED",
511
+ "Float32 position reconstruction exceeds maxPositionError; select a closer RTC center"
512
+ );
513
+ }
514
+ return {
515
+ positions,
516
+ rtcCenter: [...rtcCenter],
517
+ localBoundingBox: [minimum, maximum],
518
+ maximumPositionError
519
+ };
520
+ }
521
+ function getColorAttribute(attribute, pointCount) {
522
+ if (!attribute) {
523
+ return null;
524
+ }
525
+ if (attribute.size !== 3 && attribute.size !== 4) {
526
+ throw new import_core2.TileConversionError(
527
+ "POINT_CLOUD_COLOR_SIZE_UNSUPPORTED",
528
+ "Point cloud color attributes must contain three or four components"
529
+ );
530
+ }
531
+ if (attribute.value.length !== pointCount * attribute.size) {
532
+ throw new import_core2.TileConversionError(
533
+ "POINT_CLOUD_COLOR_COUNT_MISMATCH",
534
+ "Point cloud color count does not match the POSITION count"
535
+ );
536
+ }
537
+ const colors = new Uint8Array(attribute.value.length);
538
+ const maximum = getIntegerColorMaximum(attribute.value);
539
+ for (let index = 0; index < attribute.value.length; index++) {
540
+ const value = attribute.value[index];
541
+ const scaledValue = maximum ? value / maximum * 255 : value <= 1 ? value * 255 : value;
542
+ if (!Number.isFinite(scaledValue) || scaledValue < 0 || scaledValue > 255) {
543
+ throw new import_core2.TileConversionError(
544
+ "POINT_CLOUD_COLOR_VALUE_INVALID",
545
+ "Point cloud color components must be finite values from 0 through 255"
546
+ );
547
+ }
548
+ colors[index] = Math.round(scaledValue);
549
+ }
550
+ return { type: 5121, value: colors, size: attribute.size, normalized: false };
551
+ }
552
+ function getIntegerColorMaximum(values) {
553
+ if (values instanceof Uint8Array || values instanceof Uint8ClampedArray) {
554
+ return 255;
555
+ }
556
+ if (values instanceof Uint16Array) {
557
+ return 65535;
558
+ }
559
+ if (values instanceof Uint32Array) {
560
+ return 4294967295;
561
+ }
562
+ return null;
563
+ }
564
+ function getNormalAttribute(attribute, pointCount) {
565
+ if (!attribute) {
566
+ return null;
567
+ }
568
+ if (attribute.size !== 3 || attribute.value.length !== pointCount * 3) {
569
+ throw new import_core2.TileConversionError(
570
+ "POINT_CLOUD_NORMAL_COUNT_MISMATCH",
571
+ "Point cloud normals must contain one xyz triple per position"
572
+ );
573
+ }
574
+ return { type: 5126, value: Float32Array.from(attribute.value), size: 3 };
575
+ }
576
+ function getBatchIds(attribute, pointCount) {
577
+ if (!attribute) {
578
+ return null;
579
+ }
580
+ if (attribute.size !== 1 || attribute.value.length !== pointCount) {
581
+ throw new import_core2.TileConversionError(
582
+ "POINT_CLOUD_BATCH_ID_COUNT_MISMATCH",
583
+ "Point cloud BATCH_ID must contain one value per position"
584
+ );
585
+ }
586
+ const batchIds = new Uint16Array(pointCount);
587
+ for (let index = 0; index < pointCount; index++) {
588
+ const batchId = attribute.value[index];
589
+ if (!Number.isInteger(batchId) || batchId < 0 || batchId > 65535) {
590
+ throw new import_core2.TileConversionError(
591
+ "POINT_CLOUD_BATCH_ID_OUT_OF_RANGE",
592
+ "Point cloud BATCH_ID values must be unsigned 16-bit integers"
593
+ );
594
+ }
595
+ batchIds[index] = batchId;
596
+ }
597
+ return batchIds;
598
+ }
599
+ function validateBatchTable(batchTableJson, batchIds) {
600
+ if (!batchTableJson) {
601
+ if (batchIds) {
602
+ throw new import_core2.TileConversionError(
603
+ "POINT_CLOUD_BATCH_TABLE_REQUIRED",
604
+ "BATCH_ID values require matching batchTableJson properties"
605
+ );
606
+ }
607
+ return void 0;
608
+ }
609
+ if (!batchIds) {
610
+ throw new import_core2.TileConversionError(
611
+ "POINT_CLOUD_BATCH_ID_REQUIRED",
612
+ "batchTableJson requires a BATCH_ID attribute"
613
+ );
614
+ }
615
+ let batchLength = 0;
616
+ for (const batchId of batchIds) {
617
+ batchLength = Math.max(batchLength, batchId + 1);
618
+ }
619
+ const normalizedBatchTable = {};
620
+ for (const [name, values] of Object.entries(batchTableJson)) {
621
+ if (values.length !== batchLength) {
622
+ throw new import_core2.TileConversionError(
623
+ "POINT_CLOUD_BATCH_TABLE_LENGTH_MISMATCH",
624
+ `Batch table property "${name}" must contain ${batchLength} values`
625
+ );
626
+ }
627
+ normalizedBatchTable[name] = [...values];
628
+ }
629
+ return normalizedBatchTable;
630
+ }
631
+
632
+ // src/v5/point-cloud-spatial.ts
633
+ var import_core3 = require("@math.gl/core");
634
+ var import_tiles = require("@loaders.gl/tiles");
635
+ var import_schema_utils3 = require("@loaders.gl/schema-utils");
636
+ var import_core4 = require("@loaders.gl/tile-converter/v5/core");
637
+ async function transformPointCloudSourceTile(sourceTile, options) {
638
+ options.signal?.throwIfAborted();
639
+ const { content } = sourceTile;
640
+ if (!content) return sourceTile;
641
+ const reference = options.spatialContext.spatialReference;
642
+ const contentReference = content.spatialReference;
643
+ const retainTransformedEcef = contentReference?.status === "transformed" && (contentReference.targetCrs || contentReference.sourceCrs) === "EPSG:4978" && reference.status === "native" && reference.sourceCrs === "EPSG:4978" && reference.verticalUnitScale === 1 && (reference.units?.every((unit) => unit === "meter") ?? true);
644
+ const sourceReference = retainTransformedEcef ? (0, import_tiles.createTilesetSpatialReference)({
645
+ sourceCrs: "EPSG:4978",
646
+ coordinateFrame: "geocentric",
647
+ axisOrder: "xyz",
648
+ units: reference.units,
649
+ coordinateEpoch: contentReference.coordinateEpoch,
650
+ heightReference: contentReference.targetHeightReference === "native" ? contentReference.heightReference : contentReference.targetHeightReference
651
+ }) : contentReference;
652
+ if (sourceReference && (sourceReference.status === "transformed" || sourceReference.status === "unresolved" || JSON.stringify(sourceReference.sourceCrs) !== JSON.stringify(reference.sourceCrs) || sourceReference.heightReference !== reference.heightReference || sourceReference.verticalUnitScale !== reference.verticalUnitScale || sourceReference.coordinateEpoch !== reference.coordinateEpoch || JSON.stringify(sourceReference.units) !== JSON.stringify(reference.units) || sourceReference.elevationMode !== reference.elevationMode || sourceReference.elevationOffset !== reference.elevationOffset || sourceReference.elevationUnitScale !== reference.elevationUnitScale)) {
653
+ throw new import_core4.TileConversionError(
654
+ "POINT_CLOUD_SPATIAL_REFERENCE_MISMATCH",
655
+ "Point content and context must agree on the input CRS, units, height reference and epoch"
656
+ );
657
+ }
658
+ const sourceFrame = reference.coordinateFrame === "unknown" && reference.sourceCrs ? (0, import_tiles.getSpatialCoordinateFrame)(reference.sourceCrs) : reference.coordinateFrame;
659
+ const geographic = sourceFrame === "geographic";
660
+ if (!["xy", "xyz", "unknown"].includes(reference.axisOrder) || (geographic ? !["lnglat", "lnglat-offsets"].includes(content.coordinateSystem) : content.coordinateSystem !== "cartesian" || !["projected", "geocentric", "cartesian", "local"].includes(sourceFrame))) {
661
+ throw new import_core4.TileConversionError(
662
+ "POINT_CLOUD_SOURCE_FRAME_UNSUPPORTED",
663
+ "Spatial preparation requires explicit Cartesian or longitude/latitude positions in source xyz order"
664
+ );
665
+ }
666
+ if (content.cartographicOrigin.length !== 3 || content.cartographicOrigin.some((value) => !Number.isFinite(value)) || content.coordinateSystem !== "lnglat-offsets" && content.cartographicOrigin.some((value) => value !== 0)) {
667
+ throw new import_core4.TileConversionError(
668
+ "POINT_CLOUD_SOURCE_ORIGIN_INVALID",
669
+ "Only longitude/latitude offsets use a nonzero cartographic origin"
670
+ );
671
+ }
672
+ if (content.modelMatrix && (content.modelMatrix.length !== 16 || Array.from(content.modelMatrix).some((value) => !Number.isFinite(value)))) {
673
+ throw new import_core4.TileConversionError(
674
+ "POINT_CLOUD_SOURCE_PLACEMENT_UNSUPPORTED",
675
+ "Point placement must contain sixteen finite matrix values"
676
+ );
677
+ }
678
+ const matrix = new import_core3.Matrix4(content.modelMatrix ? Array.from(content.modelMatrix) : void 0);
679
+ const determinant = matrix.determinant();
680
+ if (matrix[3] !== 0 || matrix[7] !== 0 || matrix[11] !== 0 || matrix[15] !== 1 || !Number.isFinite(determinant) || determinant === 0 || geographic && !matrix.equals(new import_core3.Matrix4())) {
681
+ throw new import_core4.TileConversionError(
682
+ "POINT_CLOUD_SOURCE_PLACEMENT_UNSUPPORTED",
683
+ "Cartesian placement must be finite, affine and invertible; geographic placement must be identity"
684
+ );
685
+ }
686
+ const mesh = (0, import_schema_utils3.convertTableToMesh)(content.data);
687
+ const positionAttribute = mesh.attributes.POSITION;
688
+ const declaredTransform = mesh.schema?.fields.find((field) => field.name === "POSITION")?.metadata?.["loaders.gl.transform"];
689
+ if (declaredTransform && !positionAttribute?.transform) {
690
+ throw new import_core4.TileConversionError(
691
+ "POINT_CLOUD_POSITION_TRANSFORM_INVALID",
692
+ "Arrow position transform metadata is unsupported or invalid"
693
+ );
694
+ }
695
+ const sourcePositions = decodePointCloudPositions(positionAttribute);
696
+ if (sourcePositions.some((value) => !Number.isFinite(value))) {
697
+ throw new import_core4.TileConversionError(
698
+ "POINT_CLOUD_POSITION_INVALID",
699
+ "Decoded point positions must be finite"
700
+ );
701
+ }
702
+ const translationOnly = Array.from(matrix).every(
703
+ (value, index) => index >= 12 && index <= 14 || value === (index % 5 === 0 ? 1 : 0)
704
+ );
705
+ for (let index = 0; index < sourcePositions.length; index += 3) {
706
+ if (geographic || translationOnly) {
707
+ for (let axis = 0; axis < 3; axis++) {
708
+ sourcePositions[index + axis] += geographic ? content.cartographicOrigin[axis] : matrix[12 + axis];
709
+ }
710
+ } else {
711
+ sourcePositions.set(
712
+ matrix.transformAsPoint(Array.from(sourcePositions.subarray(index, index + 3))),
713
+ index
714
+ );
715
+ }
716
+ }
717
+ const normalAttribute = mesh.attributes.NORMAL;
718
+ let normals;
719
+ if (normalAttribute) {
720
+ if (!options.normalReferenceFrame || normalAttribute.size !== 3 || normalAttribute.value.length !== sourcePositions.length || normalAttribute.transform || normalAttribute.normalized || normalAttribute.byteOffset || normalAttribute.byteStride || normalAttribute.componentType || !translationOnly) {
721
+ throw new import_core4.TileConversionError(
722
+ "POINT_CLOUD_NORMAL_FRAME_REQUIRED",
723
+ "Spatial point normals require a declared vector frame and packed xyz values"
724
+ );
725
+ }
726
+ normals = new Float32Array(normalAttribute.value.length);
727
+ for (let index = 0; index < normals.length; index += 3) {
728
+ const normal = Array.from(normalAttribute.value.slice(index, index + 3));
729
+ const length = Math.hypot(...normal);
730
+ if (!Number.isFinite(length) || length === 0) {
731
+ throw new import_core4.TileConversionError(
732
+ "POINT_CLOUD_NORMAL_INVALID",
733
+ "Spatial point normals must be finite and nonzero"
734
+ );
735
+ }
736
+ normals.set(
737
+ normal.map((value) => value / length),
738
+ index
739
+ );
740
+ }
741
+ }
742
+ const transformed = await options.spatialContext.transformGeometryAsync(
743
+ sourcePositions,
744
+ normals,
745
+ options.normalReferenceFrame
746
+ );
747
+ options.signal?.throwIfAborted();
748
+ if (transformed.positions.length !== sourcePositions.length) {
749
+ throw new import_core4.TileConversionError(
750
+ "POINT_CLOUD_POSITION_INVALID",
751
+ "Spatial conversion must return one xyz triple per point"
752
+ );
753
+ }
754
+ const minimum = [Infinity, Infinity, Infinity];
755
+ const maximum = [-Infinity, -Infinity, -Infinity];
756
+ for (let index = 0; index < transformed.positions.length; index++) {
757
+ const value = transformed.positions[index];
758
+ if (!Number.isFinite(value)) {
759
+ throw new import_core4.TileConversionError(
760
+ "POINT_CLOUD_POSITION_INVALID",
761
+ "Transformed point positions must be finite"
762
+ );
763
+ }
764
+ const axis = index % 3;
765
+ minimum[axis] = Math.min(minimum[axis], value);
766
+ maximum[axis] = Math.max(maximum[axis], value);
767
+ }
768
+ const center = minimum.map((value, axis) => value / 2 + maximum[axis] / 2);
769
+ const targetFrame = reference.status === "native" ? sourceFrame : (0, import_tiles.getSpatialCoordinateFrame)(reference.targetCrs || reference.sourceCrs);
770
+ const targetIsGeographic = targetFrame === "geographic";
771
+ const attributes = {
772
+ ...mesh.attributes,
773
+ POSITION: { value: transformed.positions, size: 3 },
774
+ ...transformed.normals ? { NORMAL: { value: transformed.normals, size: 3 } } : {}
775
+ };
776
+ return {
777
+ header: sourceTile.header,
778
+ content: {
779
+ ...content,
780
+ data: (0, import_schema_utils3.makeMeshArrowTable)(attributes, {
781
+ topology: "point-list",
782
+ boundingBox: [minimum, maximum],
783
+ schema: mesh.schema ? {
784
+ ...mesh.schema,
785
+ metadata: {
786
+ ...mesh.schema.metadata,
787
+ topology: "point-list",
788
+ boundingBox: JSON.stringify([minimum, maximum])
789
+ },
790
+ fields: mesh.schema.fields.map(
791
+ (field) => field.name === "POSITION" || transformed.normals && field.name === "NORMAL" ? { ...field, metadata: {} } : field
792
+ )
793
+ } : void 0
794
+ }),
795
+ coordinateSystem: targetIsGeographic ? "lnglat" : "cartesian",
796
+ cartographicOrigin: [0, 0, 0],
797
+ modelMatrix: void 0,
798
+ spatialReference: reference,
799
+ spatialBoundingVolume: {
800
+ cartographicBounds: [minimum, maximum],
801
+ coordinateFrame: targetIsGeographic ? "geographic" : "cartesian",
802
+ center,
803
+ radius: Math.hypot(...maximum.map((value, axis) => value - center[axis]))
804
+ }
805
+ }
806
+ };
807
+ }
808
+
809
+ // src/v5/point-cloud-source-encoder.ts
810
+ var import_core5 = require("@loaders.gl/tile-converter/v5/core");
811
+ async function* encodePointCloudSource(source, options = {}) {
812
+ for await (const inputTile of (0, import_core5.traversePointCloudSource)(source, options)) {
813
+ const sourceTile = options.spatialContext ? await transformPointCloudSourceTile(inputTile, {
814
+ ...options,
815
+ spatialContext: options.spatialContext
816
+ }) : inputTile;
817
+ const encodingOptions = sourceTile.content ? options.getTileEncodingOptions?.(sourceTile) : void 0;
818
+ const encodedTile = encodePointCloudSourceTile(sourceTile, encodingOptions);
819
+ if (encodedTile) {
820
+ yield encodedTile;
821
+ }
822
+ }
823
+ }
824
+ function encodePointCloudSourceTile(sourceTile, options = {}) {
825
+ const { header, content } = sourceTile;
826
+ if (!content) {
827
+ return null;
828
+ }
829
+ const encoded = encodePointCloudTileWithMetadata(content.data, {
830
+ ...options,
831
+ constantRGBA: options.constantRGBA ?? content.constantRGBA
832
+ });
833
+ if (content.pointCount !== encoded.pointCount) {
834
+ throw new import_core5.TileConversionError(
835
+ "POINT_CLOUD_SOURCE_COUNT_INVALID",
836
+ "Source pointCount must match the encoded Arrow point rows"
837
+ );
838
+ }
839
+ return {
840
+ id: header.id,
841
+ header,
842
+ ...encoded,
843
+ coordinateSystem: content.coordinateSystem,
844
+ cartographicOrigin: [...content.cartographicOrigin],
845
+ modelMatrix: content.modelMatrix ? Array.from(content.modelMatrix) : void 0,
846
+ spatialReference: content.spatialReference,
847
+ spatialBoundingVolume: content.spatialBoundingVolume || header.spatialBoundingVolume
848
+ };
849
+ }
850
+
851
+ // src/v5/point-cloud-conversion.ts
852
+ var import_core6 = require("@loaders.gl/tile-converter/v5/core");
853
+ var import_core7 = require("@loaders.gl/tile-converter/v5/core");
854
+ function convertPointCloudSource(source, options) {
855
+ return (0, import_core6.convertTileset)({
856
+ source: {
857
+ async inspect() {
858
+ if (!source.isReady) {
859
+ await source.initialize();
860
+ }
861
+ },
862
+ read: (_inspection, signal) => (0, import_core7.traversePointCloudSource)(source, { maxDepth: options.maxDepth, signal })
863
+ },
864
+ codec: {
865
+ async *convert(inputTile, _inspection, signal) {
866
+ const sourceTile = options.spatialContext ? await transformPointCloudSourceTile(inputTile, {
867
+ ...options,
868
+ spatialContext: options.spatialContext,
869
+ signal
870
+ }) : inputTile;
871
+ const encodingOptions = sourceTile.content ? options.getTileEncodingOptions?.(sourceTile) : void 0;
872
+ const encodedTile = encodePointCloudSourceTile(sourceTile, encodingOptions);
873
+ if (encodedTile) {
874
+ yield encodedTile;
875
+ }
876
+ }
877
+ },
878
+ sink: options.sink,
879
+ measureInputBytes: options.measureInputBytes,
880
+ measureOutputBytes: (resource) => resource.pnts.byteLength,
881
+ maxInputResourceBytes: options.maxInputResourceBytes,
882
+ maxOutputResourceBytes: options.maxOutputResourceBytes,
883
+ signal: options.signal,
884
+ onProgress: options.onProgress
885
+ });
886
+ }
887
+
888
+ // src/v5/mesh.ts
889
+ var import_gltf = require("@loaders.gl/gltf");
890
+ var import_images = require("@loaders.gl/images");
891
+
892
+ // src/v5/mesh-features.ts
893
+ var import_schema_utils4 = require("@loaders.gl/schema-utils");
894
+ var import_core8 = require("@loaders.gl/tile-converter/v5/core");
895
+ function prepareMeshFeatureGeometry(mesh, features) {
896
+ if (!features) return mesh;
897
+ const count = features.batches.reduce((total, batch) => total + batch.data.numRows, 0);
898
+ const indices = mesh.indices?.value;
899
+ const vertexCount = indices?.length ?? mesh.attributes.POSITION.value.length / 3;
900
+ const associations = features.triangleFeatureIndices;
901
+ if (!(associations instanceof Uint32Array) || associations.length !== vertexCount / 3 || associations.some((row) => row >= count) || new Set(associations).size !== count) {
902
+ throw new import_core8.TileConversionError(
903
+ "MESH_FEATURE_ASSOCIATION_INVALID",
904
+ "Every feature row must own a triangle and every triangle must reference a valid row"
905
+ );
906
+ }
907
+ if (count > 2 ** 24)
908
+ throw new import_core8.TileConversionError(
909
+ "MESH_FEATURE_COUNT_UNSUPPORTED",
910
+ "Feature row indices must be exactly representable in a glTF vertex attribute"
911
+ );
912
+ const attributes = Object.fromEntries(
913
+ Object.entries(mesh.attributes).map(([name, attribute]) => {
914
+ const values = attribute.value;
915
+ const Constructor = values.constructor;
916
+ const output = new Constructor(vertexCount * attribute.size);
917
+ for (let vertex = 0; vertex < vertexCount; vertex++) {
918
+ const sourceVertex = indices?.[vertex] ?? vertex;
919
+ for (let component = 0; component < attribute.size; component++)
920
+ output[vertex * attribute.size + component] = Number(
921
+ values[sourceVertex * attribute.size + component]
922
+ );
923
+ }
924
+ return [name, { ...attribute, value: output }];
925
+ })
926
+ );
927
+ const featureIndices = count <= 65536 ? new Uint16Array(vertexCount) : new Float32Array(vertexCount);
928
+ for (let vertex = 0; vertex < vertexCount; vertex++)
929
+ featureIndices[vertex] = associations[Math.floor(vertex / 3)];
930
+ attributes._FEATURE_ID_0 = { value: featureIndices, size: 1 };
931
+ return { ...mesh, indices: void 0, attributes };
932
+ }
933
+ function encodeMeshFeatures(scenegraph, meshIndex, features) {
934
+ if (!features) return;
935
+ const { batches, featureIdField } = features;
936
+ const schema = batches[0]?.schema;
937
+ const count = batches.reduce((total, batch) => total + batch.data.numRows, 0);
938
+ const metadataClass = schema?.metadata?.["loaders.gl:feature-class"] || "features";
939
+ if (!schema || !count || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(metadataClass) || !schema.fields.some((field) => field.name === featureIdField && field.nullable !== true) || new Set(schema.fields.map((field) => field.name)).size !== schema.fields.length) {
940
+ throw new import_core8.TileConversionError(
941
+ "MESH_FEATURE_SCHEMA_INVALID",
942
+ "Features require a single named class, unique fields, and a non-null stable identifier"
943
+ );
944
+ }
945
+ for (const batch of batches) {
946
+ const actual = (0, import_schema_utils4.convertArrowToSchema)(batch.data.schema).fields;
947
+ if (JSON.stringify(batch.schema) !== JSON.stringify(schema) || batch.length !== batch.data.numRows || JSON.stringify(actual.map((field) => [field.name, field.type])) !== JSON.stringify(schema.fields.map((field) => [field.name, field.type]))) {
948
+ throw new import_core8.TileConversionError(
949
+ "MESH_FEATURE_SCHEMA_INVALID",
950
+ "Declared feature schemas and Arrow column types must agree across batches"
951
+ );
952
+ }
953
+ }
954
+ const properties = /* @__PURE__ */ Object.create(null);
955
+ const columns = /* @__PURE__ */ Object.create(null);
956
+ for (const field of schema.fields) {
957
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(field.name))
958
+ throw new import_core8.TileConversionError(
959
+ "MESH_FEATURE_SCHEMA_INVALID",
960
+ "Property names must be metadata identifiers"
961
+ );
962
+ const values = batches.flatMap((batch) => {
963
+ const column = batch.data.getChild(field.name);
964
+ if (!column)
965
+ throw new import_core8.TileConversionError(
966
+ "MESH_FEATURE_SCHEMA_INVALID",
967
+ `Missing Arrow column ${field.name}`
968
+ );
969
+ return Array.from({ length: column.length }, (_, index) => column.get(index));
970
+ });
971
+ if (field.name === featureIdField && (values.some(
972
+ (value) => !(typeof value === "string" || typeof value === "bigint" || Number.isSafeInteger(value))
973
+ ) || new Set(values).size !== count))
974
+ throw new import_core8.TileConversionError(
975
+ "MESH_FEATURE_IDS_INVALID",
976
+ "Stable identifiers must be unique strings or exact integers"
977
+ );
978
+ const encoded = encodeFeatureColumn(field, values);
979
+ properties[field.name] = encoded.property;
980
+ columns[field.name] = {
981
+ values: addMetadataBuffer(scenegraph, encoded.data),
982
+ ...encoded.offsets ? {
983
+ stringOffsets: addMetadataBuffer(scenegraph, encoded.offsets),
984
+ stringOffsetType: "UINT32"
985
+ } : {}
986
+ };
987
+ }
988
+ scenegraph.json.extensions ||= {};
989
+ scenegraph.json.extensions.EXT_structural_metadata = {
990
+ schema: { id: "loaders_gl_features", classes: { [metadataClass]: { properties } } },
991
+ propertyTables: [{ class: metadataClass, count, properties: columns }]
992
+ };
993
+ const primitive = scenegraph.json.meshes[meshIndex].primitives[0];
994
+ primitive.extensions ||= {};
995
+ primitive.extensions.EXT_mesh_features = {
996
+ featureIds: [{ featureCount: count, attribute: 0, propertyTable: 0 }]
997
+ };
998
+ scenegraph.registerUsedExtension("EXT_structural_metadata");
999
+ scenegraph.registerUsedExtension("EXT_mesh_features");
1000
+ }
1001
+ function addMetadataBuffer(scenegraph, data) {
1002
+ if (scenegraph.byteLength % 8) scenegraph.addBufferView(new Uint8Array(4));
1003
+ return scenegraph.addBufferView(data);
1004
+ }
1005
+ function encodeFeatureColumn(field, values) {
1006
+ const type = field.type;
1007
+ const hasNull = values.includes(null);
1008
+ if (hasNull && field.nullable !== true)
1009
+ throw new import_core8.TileConversionError(
1010
+ "MESH_FEATURE_VALUE_INVALID",
1011
+ `Required field ${field.name} contains null`
1012
+ );
1013
+ const present = values.filter((value) => value !== null);
1014
+ let noData;
1015
+ if (type === "utf8") {
1016
+ if (present.some(
1017
+ (value) => typeof value !== "string" || new TextDecoder().decode(new TextEncoder().encode(value)) !== value
1018
+ ))
1019
+ throw new import_core8.TileConversionError("MESH_FEATURE_VALUE_INVALID", "Strings require valid Unicode");
1020
+ if (hasNull) {
1021
+ noData = "";
1022
+ const strings = new Set(present);
1023
+ while (strings.has(noData)) noData += "_";
1024
+ }
1025
+ const encoded = values.map(
1026
+ (value) => new TextEncoder().encode(value === null ? noData : value)
1027
+ );
1028
+ const offsets = new Uint32Array(values.length + 1);
1029
+ for (let index = 0; index < values.length; index++)
1030
+ offsets[index + 1] = offsets[index] + encoded[index].length;
1031
+ const data2 = new Uint8Array(offsets[values.length]);
1032
+ encoded.forEach((bytes, index) => data2.set(bytes, offsets[index]));
1033
+ return {
1034
+ property: { type: "STRING", required: field.nullable !== true, ...hasNull ? { noData } : {} },
1035
+ data: data2,
1036
+ offsets
1037
+ };
1038
+ }
1039
+ const widths = {
1040
+ int8: 1,
1041
+ uint8: 1,
1042
+ int16: 2,
1043
+ uint16: 2,
1044
+ int32: 4,
1045
+ uint32: 4,
1046
+ int64: 8,
1047
+ uint64: 8,
1048
+ float32: 4,
1049
+ float64: 8
1050
+ };
1051
+ const width = widths[String(type)];
1052
+ if (!width)
1053
+ throw new import_core8.TileConversionError(
1054
+ "MESH_FEATURE_TYPE_UNSUPPORTED",
1055
+ `No qualified GLB property mapping for ${String(type)}`
1056
+ );
1057
+ const isInteger64 = type === "int64" || type === "uint64";
1058
+ const isFloat = type === "float32" || type === "float64";
1059
+ const minimum = isFloat ? -Infinity : String(type).startsWith("uint") ? 0n : -(1n << BigInt(width * 8 - 1));
1060
+ const maximum = isFloat ? Infinity : (1n << BigInt(width * 8 - (String(type).startsWith("uint") ? 0 : 1))) - 1n;
1061
+ if (present.some(
1062
+ (value) => isInteger64 ? typeof value !== "bigint" || value < minimum || value > maximum : typeof value !== "number" || !Number.isFinite(value) || !isFloat && (!Number.isSafeInteger(value) || BigInt(value) < minimum || BigInt(value) > maximum)
1063
+ ))
1064
+ throw new import_core8.TileConversionError(
1065
+ "MESH_FEATURE_VALUE_INVALID",
1066
+ `Values do not fit ${String(type)}`
1067
+ );
1068
+ if (hasNull) {
1069
+ const used = new Set(present.map(String));
1070
+ noData = 0;
1071
+ while (used.has(String(noData))) noData++;
1072
+ if (noData > maximum && minimum < 0) {
1073
+ noData = -1;
1074
+ while (used.has(String(noData))) noData--;
1075
+ }
1076
+ if (noData > maximum || noData < minimum)
1077
+ throw new import_core8.TileConversionError(
1078
+ "MESH_FEATURE_NULL_UNREPRESENTABLE",
1079
+ "No unused value remains for null without losing a real value"
1080
+ );
1081
+ }
1082
+ const data = new Uint8Array(values.length * width);
1083
+ const view = new DataView(data.buffer);
1084
+ values.forEach((value, index) => {
1085
+ const selected = value === null ? isInteger64 ? BigInt(noData) : noData : value;
1086
+ const offset = index * width;
1087
+ switch (type) {
1088
+ case "int8":
1089
+ view.setInt8(offset, selected);
1090
+ break;
1091
+ case "uint8":
1092
+ view.setUint8(offset, selected);
1093
+ break;
1094
+ case "int16":
1095
+ view.setInt16(offset, selected, true);
1096
+ break;
1097
+ case "uint16":
1098
+ view.setUint16(offset, selected, true);
1099
+ break;
1100
+ case "int32":
1101
+ view.setInt32(offset, selected, true);
1102
+ break;
1103
+ case "uint32":
1104
+ view.setUint32(offset, selected, true);
1105
+ break;
1106
+ case "int64":
1107
+ view.setBigInt64(offset, selected, true);
1108
+ break;
1109
+ case "uint64":
1110
+ view.setBigUint64(offset, selected, true);
1111
+ break;
1112
+ case "float32":
1113
+ view.setFloat32(offset, selected, true);
1114
+ break;
1115
+ case "float64":
1116
+ view.setFloat64(offset, selected, true);
1117
+ break;
1118
+ }
1119
+ if (isFloat && !Number.isFinite(
1120
+ type === "float32" ? view.getFloat32(offset, true) : view.getFloat64(offset, true)
1121
+ ))
1122
+ throw new import_core8.TileConversionError(
1123
+ "MESH_FEATURE_VALUE_INVALID",
1124
+ "Floating point property overflow"
1125
+ );
1126
+ });
1127
+ return {
1128
+ property: {
1129
+ type: "SCALAR",
1130
+ componentType: String(type).toUpperCase(),
1131
+ required: field.nullable !== true,
1132
+ ...hasNull ? { noData } : {}
1133
+ },
1134
+ data
1135
+ };
1136
+ }
1137
+
1138
+ // src/v5/mesh.ts
1139
+ var import_core9 = require("@loaders.gl/tile-converter/v5/core");
1140
+ function encodeMeshTile(mesh, options = {}) {
1141
+ const geometry = prepareMeshFeatureGeometry(validateMeshGeometry(mesh), options.features);
1142
+ const { scenegraph, materialIndex } = createMeshTileScenegraph(options, geometry);
1143
+ const meshIndex = scenegraph.addMesh({
1144
+ attributes: geometry.attributes,
1145
+ indices: geometry.indices?.value,
1146
+ material: materialIndex,
1147
+ mode: 4
1148
+ });
1149
+ encodeMeshFeatures(scenegraph, meshIndex, options.features);
1150
+ return finalizeMeshTileScenegraph(scenegraph, meshIndex);
1151
+ }
1152
+ function createMeshTileScenegraph(options, geometry) {
1153
+ const scenegraph = new import_gltf.GLTFScenegraph({ json: { asset: { version: "2.0", generator: "loaders.gl" } } });
1154
+ const material = options.material === void 0 ? void 0 : validateMeshMaterial(options.material, scenegraph, "TEXCOORD_0" in geometry.attributes);
1155
+ return { scenegraph, materialIndex: material ? scenegraph.addMaterial(material) : void 0 };
1156
+ }
1157
+ function finalizeMeshTileScenegraph(scenegraph, meshIndex) {
1158
+ const nodeIndex = scenegraph.addNode({ meshIndex });
1159
+ const sceneIndex = scenegraph.addScene({ nodeIndices: [nodeIndex] });
1160
+ scenegraph.setDefaultScene(sceneIndex);
1161
+ scenegraph.createBinaryChunk();
1162
+ return import_gltf.GLTFWriter.encodeSync(scenegraph.gltf);
1163
+ }
1164
+ function validateMeshGeometry(mesh, allowFloat64Positions = false) {
1165
+ if (mesh.topology !== "triangle-list" || mesh.mode !== 4) {
1166
+ throw new import_core9.TileConversionError(
1167
+ "MESH_TOPOLOGY_UNSUPPORTED",
1168
+ "Only triangle-list mode 4 is supported"
1169
+ );
1170
+ }
1171
+ for (const name of Object.keys(mesh.attributes)) {
1172
+ if (name !== "POSITION" && name !== "NORMAL" && name !== "COLOR_0" && name !== "TEXCOORD_0") {
1173
+ throw new import_core9.TileConversionError(
1174
+ "MESH_ATTRIBUTE_UNSUPPORTED",
1175
+ `Mesh attribute ${name} is unsupported`
1176
+ );
1177
+ }
1178
+ }
1179
+ const positions = getFloatAttribute(mesh.attributes.POSITION, "POSITION", allowFloat64Positions);
1180
+ const attributes = {
1181
+ POSITION: { value: positions, size: 3 }
1182
+ };
1183
+ if (mesh.attributes.NORMAL) {
1184
+ const normals = getFloatAttribute(mesh.attributes.NORMAL, "NORMAL");
1185
+ if (normals.length !== positions.length) {
1186
+ throw new import_core9.TileConversionError(
1187
+ "MESH_NORMAL_COUNT_MISMATCH",
1188
+ "NORMAL count must match POSITION count"
1189
+ );
1190
+ }
1191
+ for (let index = 0; index < normals.length; index += 3) {
1192
+ if (Math.abs(Math.hypot(normals[index], normals[index + 1], normals[index + 2]) - 1) > 1e-4) {
1193
+ throw new import_core9.TileConversionError(
1194
+ "MESH_NORMAL_INVALID",
1195
+ "NORMAL vectors must have unit length"
1196
+ );
1197
+ }
1198
+ }
1199
+ attributes.NORMAL = { value: normals, size: 3 };
1200
+ }
1201
+ if ("COLOR_0" in mesh.attributes) {
1202
+ const colors = mesh.attributes.COLOR_0;
1203
+ const normalized = isNormalizedUnsignedAttribute(colors);
1204
+ if (!colors || !(colors.value instanceof Float32Array || normalized) || colors.size !== 3 && colors.size !== 4 || colors.value.length / colors.size !== positions.length / 3 || colors.value instanceof Float32Array && colors.value.some((value) => !Number.isFinite(value) || value < 0 || value > 1)) {
1205
+ throw new import_core9.TileConversionError(
1206
+ "MESH_COLOR_INVALID",
1207
+ "COLOR_0 requires packed linear RGB/RGBA: Float32 in [0, 1] or normalized Uint8/Uint16, one per vertex"
1208
+ );
1209
+ }
1210
+ validateAttributeLayout(colors, "COLOR_0", normalized);
1211
+ attributes.COLOR_0 = {
1212
+ value: colors.value,
1213
+ size: colors.size,
1214
+ ...normalized ? { normalized: true } : {}
1215
+ };
1216
+ }
1217
+ if ("TEXCOORD_0" in mesh.attributes) {
1218
+ const textureCoordinates = mesh.attributes.TEXCOORD_0;
1219
+ const normalized = isNormalizedUnsignedAttribute(textureCoordinates);
1220
+ if (!textureCoordinates || !(textureCoordinates.value instanceof Float32Array || normalized) || textureCoordinates.size !== 2 || textureCoordinates.value.length / 2 !== positions.length / 3 || textureCoordinates.value.some((value) => !Number.isFinite(value))) {
1221
+ throw new import_core9.TileConversionError(
1222
+ "MESH_TEXCOORD_INVALID",
1223
+ "TEXCOORD_0 requires packed finite Float32 or normalized Uint8/Uint16 UV pairs, one per vertex"
1224
+ );
1225
+ }
1226
+ validateAttributeLayout(textureCoordinates, "TEXCOORD_0", normalized);
1227
+ attributes.TEXCOORD_0 = {
1228
+ value: textureCoordinates.value,
1229
+ size: 2,
1230
+ ...normalized ? { normalized: true } : {}
1231
+ };
1232
+ }
1233
+ const indices = validateMeshIndices(mesh.indices, positions.length / 3);
1234
+ if (!indices && positions.length % 9 !== 0) {
1235
+ throw new import_core9.TileConversionError(
1236
+ "MESH_TRIANGLE_COUNT_INVALID",
1237
+ "Nonindexed meshes require complete triangles"
1238
+ );
1239
+ }
1240
+ return {
1241
+ topology: "triangle-list",
1242
+ mode: 4,
1243
+ attributes,
1244
+ indices: indices ? { value: indices, size: 1 } : void 0
1245
+ };
1246
+ }
1247
+ function getFloatAttribute(attribute, name, allowFloat64 = false) {
1248
+ if (!attribute || !(attribute.value instanceof Float32Array || allowFloat64 && attribute.value instanceof Float64Array) || attribute.size !== 3 || attribute.value.length === 0 || attribute.value.length % 3 !== 0) {
1249
+ throw new import_core9.TileConversionError(
1250
+ "MESH_ATTRIBUTE_INVALID",
1251
+ `${name} must contain packed ${allowFloat64 ? "Float32 or Float64" : "Float32"} xyz triples`
1252
+ );
1253
+ }
1254
+ validateAttributeLayout(attribute, name);
1255
+ if (attribute.value.some((value) => !Number.isFinite(value))) {
1256
+ throw new import_core9.TileConversionError("MESH_ATTRIBUTE_NONFINITE", `${name} must contain finite values`);
1257
+ }
1258
+ return attribute.value;
1259
+ }
1260
+ function validateMeshIndices(attribute, vertexCount, primitiveMode = 4) {
1261
+ if (!attribute) return void 0;
1262
+ validateAttributeLayout(attribute, "indices");
1263
+ const values = attribute.value;
1264
+ if (attribute.size !== 1 || !(values instanceof Uint8Array || values instanceof Uint16Array || values instanceof Uint32Array)) {
1265
+ throw new import_core9.TileConversionError(
1266
+ "MESH_INDICES_INVALID",
1267
+ "Indices must contain packed unsigned integer scalars"
1268
+ );
1269
+ }
1270
+ if (primitiveMode === 5 ? values.length < 3 : values.length === 0 || values.length % 3 !== 0) {
1271
+ throw new import_core9.TileConversionError(
1272
+ "MESH_TRIANGLE_COUNT_INVALID",
1273
+ "Indices must contain complete triangles or at least three strip vertices"
1274
+ );
1275
+ }
1276
+ const restartIndex = 2 ** (values.BYTES_PER_ELEMENT * 8) - 1;
1277
+ if (values.some((value) => value >= vertexCount || value === restartIndex)) {
1278
+ throw new import_core9.TileConversionError(
1279
+ "MESH_INDEX_OUT_OF_RANGE",
1280
+ "Indices must reference existing vertices and exclude primitive-restart values"
1281
+ );
1282
+ }
1283
+ return values;
1284
+ }
1285
+ function isNormalizedUnsignedAttribute(attribute) {
1286
+ return attribute?.normalized === true && (attribute.value instanceof Uint8Array || attribute.value instanceof Uint16Array);
1287
+ }
1288
+ function validateAttributeLayout(attribute, name, allowNormalized = false) {
1289
+ if ((attribute.byteOffset ?? 0) !== 0 || (attribute.byteStride ?? 0) !== 0 || attribute.normalized && !allowNormalized || attribute.transform || attribute.componentType) {
1290
+ throw new import_core9.TileConversionError(
1291
+ "MESH_ATTRIBUTE_LAYOUT_UNSUPPORTED",
1292
+ `${name} must use packed contiguous storage with supported normalization`
1293
+ );
1294
+ }
1295
+ }
1296
+ function validateMeshMaterial(material, scenegraph, hasTextureCoordinates) {
1297
+ if (!material || typeof material !== "object" || Array.isArray(material)) {
1298
+ throw new import_core9.TileConversionError("MESH_MATERIAL_INVALID", "Material must be an object");
1299
+ }
1300
+ const allowedProperties = [
1301
+ "baseColorFactor",
1302
+ "baseColorTexture",
1303
+ "metallicFactor",
1304
+ "roughnessFactor",
1305
+ "alphaMode",
1306
+ "alphaCutoff",
1307
+ "doubleSided"
1308
+ ];
1309
+ for (const name of Object.keys(material)) {
1310
+ if (!allowedProperties.includes(name)) {
1311
+ throw new import_core9.TileConversionError(
1312
+ "MESH_MATERIAL_UNSUPPORTED",
1313
+ `Material property ${name} is unsupported`
1314
+ );
1315
+ }
1316
+ }
1317
+ const { baseColorFactor, metallicFactor, roughnessFactor, alphaMode, alphaCutoff, doubleSided } = material;
1318
+ if (baseColorFactor !== void 0 && (!Array.isArray(baseColorFactor) || baseColorFactor.length !== 4 || Array.from(baseColorFactor).some(
1319
+ (value) => !Number.isFinite(value) || value < 0 || value > 1
1320
+ )) || [metallicFactor, roughnessFactor].some(
1321
+ (value) => value !== void 0 && (!Number.isFinite(value) || value < 0 || value > 1)
1322
+ ) || alphaMode !== void 0 && !["OPAQUE", "MASK", "BLEND"].includes(alphaMode) || alphaCutoff !== void 0 && (alphaMode !== "MASK" || !Number.isFinite(alphaCutoff) || alphaCutoff < 0) || doubleSided !== void 0 && typeof doubleSided !== "boolean") {
1323
+ throw new import_core9.TileConversionError(
1324
+ "MESH_MATERIAL_INVALID",
1325
+ "Material values must satisfy the selected glTF profile"
1326
+ );
1327
+ }
1328
+ return {
1329
+ pbrMetallicRoughness: {
1330
+ baseColorFactor: baseColorFactor ? [...baseColorFactor] : void 0,
1331
+ metallicFactor,
1332
+ roughnessFactor,
1333
+ baseColorTexture: "baseColorTexture" in material ? encodeBaseColorTexture(material.baseColorTexture, scenegraph, hasTextureCoordinates) : void 0
1334
+ },
1335
+ alphaMode,
1336
+ alphaCutoff,
1337
+ doubleSided
1338
+ };
1339
+ }
1340
+ function encodeBaseColorTexture(texture, scenegraph, hasTextureCoordinates) {
1341
+ if (!texture || Object.keys(texture).some(
1342
+ (name) => name !== "data" && name !== "mimeType" && name !== "sampler" && name !== "transform"
1343
+ ) || !(texture.data instanceof Uint8Array) || !["image/png", "image/jpeg"].includes(texture.mimeType)) {
1344
+ throw new import_core9.TileConversionError(
1345
+ "MESH_TEXTURE_INVALID",
1346
+ "Base-color texture must supply Uint8Array data, image/png or image/jpeg mimeType, and optional sampling/transform controls"
1347
+ );
1348
+ }
1349
+ if (!hasTextureCoordinates) {
1350
+ throw new import_core9.TileConversionError(
1351
+ "MESH_TEXCOORD_REQUIRED",
1352
+ "Base-color texture requires TEXCOORD_0"
1353
+ );
1354
+ }
1355
+ const image = new DataView(texture.data.buffer, texture.data.byteOffset, texture.data.byteLength);
1356
+ let metadata;
1357
+ try {
1358
+ metadata = (0, import_images.getBinaryImageMetadata)(image);
1359
+ } catch {
1360
+ }
1361
+ if (!metadata || metadata.mimeType !== texture.mimeType || metadata.width <= 0 || metadata.height <= 0) {
1362
+ throw new import_core9.TileConversionError(
1363
+ "MESH_TEXTURE_INVALID",
1364
+ "Image header must match mimeType and declare positive dimensions"
1365
+ );
1366
+ }
1367
+ const samplerIndex = "sampler" in texture ? scenegraph.addSampler(validateMeshSampler(texture.sampler)) : void 0;
1368
+ const transform = "transform" in texture ? validateMeshTextureTransform(texture.transform) : void 0;
1369
+ if (transform) scenegraph.registerRequiredExtension("KHR_texture_transform");
1370
+ return {
1371
+ ...transform ? { extensions: { KHR_texture_transform: transform } } : {},
1372
+ index: scenegraph.addTexture({
1373
+ imageIndex: scenegraph.addImage(image, texture.mimeType),
1374
+ samplerIndex
1375
+ })
1376
+ };
1377
+ }
1378
+ function validateMeshSampler(sampler) {
1379
+ if (!sampler || typeof sampler !== "object" || Array.isArray(sampler)) {
1380
+ throw new import_core9.TileConversionError("MESH_SAMPLER_INVALID", "Sampler must be an object");
1381
+ }
1382
+ const allowedValues = {
1383
+ wrapS: [33071, 33648, 10497],
1384
+ wrapT: [33071, 33648, 10497],
1385
+ magFilter: [9728, 9729],
1386
+ minFilter: [9728, 9729, 9984, 9985, 9986, 9987]
1387
+ };
1388
+ for (const name in sampler) {
1389
+ if (!Object.hasOwn(allowedValues, name)) {
1390
+ throw new import_core9.TileConversionError(
1391
+ "MESH_SAMPLER_UNSUPPORTED",
1392
+ `Sampler property ${name} is unsupported`
1393
+ );
1394
+ }
1395
+ }
1396
+ const selectedSampler = {
1397
+ wrapS: sampler.wrapS,
1398
+ wrapT: sampler.wrapT,
1399
+ minFilter: sampler.minFilter,
1400
+ magFilter: sampler.magFilter
1401
+ };
1402
+ for (const name of Object.keys(allowedValues)) {
1403
+ const property = name;
1404
+ if (selectedSampler[property] !== void 0 && !allowedValues[property].includes(selectedSampler[property])) {
1405
+ throw new import_core9.TileConversionError(
1406
+ "MESH_SAMPLER_INVALID",
1407
+ `Sampler ${name} must use a glTF wrapping/filtering value`
1408
+ );
1409
+ }
1410
+ }
1411
+ return selectedSampler;
1412
+ }
1413
+ function validateMeshTextureTransform(transform) {
1414
+ if (!transform || typeof transform !== "object" || Array.isArray(transform)) {
1415
+ throw new import_core9.TileConversionError(
1416
+ "MESH_TEXTURE_TRANSFORM_INVALID",
1417
+ "Texture transform must be an object"
1418
+ );
1419
+ }
1420
+ for (const name in transform) {
1421
+ if (name !== "offset" && name !== "rotation" && name !== "scale") {
1422
+ throw new import_core9.TileConversionError(
1423
+ "MESH_TEXTURE_TRANSFORM_UNSUPPORTED",
1424
+ `Texture transform property ${name} is unsupported`
1425
+ );
1426
+ }
1427
+ }
1428
+ const { offset, rotation, scale } = transform;
1429
+ const offsetValues = Array.isArray(offset) && offset.length === 2 ? [offset[0], offset[1]] : offset;
1430
+ const scaleValues = Array.isArray(scale) && scale.length === 2 ? [scale[0], scale[1]] : scale;
1431
+ if ([offsetValues, scaleValues].some(
1432
+ (value) => value !== void 0 && (!Array.isArray(value) || value.length !== 2 || value.some((component) => !Number.isFinite(component)))
1433
+ ) || rotation !== void 0 && !Number.isFinite(rotation)) {
1434
+ throw new import_core9.TileConversionError(
1435
+ "MESH_TEXTURE_TRANSFORM_INVALID",
1436
+ "Texture transform requires finite UV pairs and a finite rotation"
1437
+ );
1438
+ }
1439
+ return {
1440
+ offset: offsetValues === void 0 ? void 0 : [offsetValues[0], offsetValues[1]],
1441
+ rotation,
1442
+ scale: scaleValues === void 0 ? void 0 : [scaleValues[0], scaleValues[1]]
1443
+ };
1444
+ }
1445
+
1446
+ // src/v5/mesh-conversion.ts
1447
+ var import_tiles2 = require("@loaders.gl/tiles");
1448
+ var import_core11 = require("@loaders.gl/tile-converter/v5/core");
1449
+
1450
+ // src/v5/mesh-draco.ts
1451
+ var import_draco = require("@loaders.gl/draco");
1452
+ var import_core10 = require("@loaders.gl/tile-converter/v5/core");
1453
+ var DRACO_EXTENSION = "KHR_draco_mesh_compression";
1454
+ async function encodeDracoMeshTile(geometry, options, libraryOptions = {}) {
1455
+ geometry = prepareMeshFeatureGeometry(validateMeshGeometry(geometry), options.features);
1456
+ if (!geometry.indices) {
1457
+ geometry.indices = {
1458
+ size: 1,
1459
+ value: Uint32Array.from(
1460
+ { length: geometry.attributes.POSITION.value.length / 3 },
1461
+ (_, index) => index
1462
+ )
1463
+ };
1464
+ }
1465
+ const { modules, useLocalLibraries, CDN: contentDeliveryNetwork } = libraryOptions;
1466
+ const runtimeOptions = { modules, core: { useLocalLibraries, CDN: contentDeliveryNetwork } };
1467
+ const { scenegraph, materialIndex } = createMeshTileScenegraph(options, geometry);
1468
+ const encoded = await (0, import_draco.encodeDraco)(geometry, {
1469
+ ...runtimeOptions,
1470
+ draco: { method: "MESH_EDGEBREAKER_ENCODING" }
1471
+ });
1472
+ const loaderOptions = { ...runtimeOptions, draco: { shape: "mesh" } };
1473
+ const loader = await import_draco.DracoLoader.preload("", loaderOptions);
1474
+ const decoded = await loader.parse(encoded.data, loaderOptions);
1475
+ const positions = decoded.attributes.POSITION.value;
1476
+ if (!(positions instanceof Float32Array) || positions.length !== encoded.report.pointCount * 3 || encoded.report.pointCount === 0 || !decoded.indices || decoded.indices.value.length !== encoded.report.faceCount * 3 || encoded.report.faceCount * 3 !== (geometry.indices?.value.length ?? geometry.attributes.POSITION.value.length / 3)) {
1477
+ throw new import_core10.TileConversionError(
1478
+ "MESH_DRACO_ENCODING_INVALID",
1479
+ "Draco must preserve the triangle count and emit finite positions"
1480
+ );
1481
+ }
1482
+ const inputPositions = geometry.attributes.POSITION.value;
1483
+ const positionKeys = /* @__PURE__ */ new Set();
1484
+ for (let index = 0; index < inputPositions.length; index += 3) {
1485
+ positionKeys.add(getPositionKey(inputPositions, index));
1486
+ }
1487
+ for (let index = 0; index < positions.length; index += 3) {
1488
+ if (!positionKeys.has(getPositionKey(positions, index))) {
1489
+ throw new import_core10.TileConversionError(
1490
+ "MESH_DRACO_POSITION_CHANGED",
1491
+ "Lossless Draco must preserve the encoded Float32 positions"
1492
+ );
1493
+ }
1494
+ }
1495
+ if (JSON.stringify(getMeshTriangles(geometry)) !== JSON.stringify(getMeshTriangles(decoded))) {
1496
+ throw new import_core10.TileConversionError(
1497
+ "MESH_DRACO_ATTRIBUTE_CHANGED",
1498
+ "Lossless Draco must preserve oriented triangles and every attribute, including feature ownership"
1499
+ );
1500
+ }
1501
+ const bufferView = scenegraph.addBufferView(encoded.data);
1502
+ const attributes = {};
1503
+ const attributeIdentifiers = {};
1504
+ scenegraph.json.accessors = [];
1505
+ for (const [name, attribute] of Object.entries(geometry.attributes)) {
1506
+ const decodedAttribute = decoded.attributes[name];
1507
+ if (!decodedAttribute || decodedAttribute.value.length !== encoded.report.pointCount * attribute.size) {
1508
+ throw new import_core10.TileConversionError("MESH_DRACO_ENCODING_INVALID", `Draco must preserve ${name}`);
1509
+ }
1510
+ const accessor = {
1511
+ componentType: attribute.value instanceof Uint8Array ? 5121 : attribute.value instanceof Uint16Array ? 5123 : attribute.value instanceof Uint32Array ? 5125 : 5126,
1512
+ count: encoded.report.pointCount,
1513
+ type: attribute.size === 1 ? "SCALAR" : attribute.size === 2 ? "VEC2" : attribute.size === 3 ? "VEC3" : "VEC4",
1514
+ ...attribute.normalized ? { normalized: true } : {}
1515
+ };
1516
+ if (name === "POSITION") {
1517
+ const minimum = [Infinity, Infinity, Infinity];
1518
+ const maximum = [-Infinity, -Infinity, -Infinity];
1519
+ for (let index = 0; index < positions.length; index++) {
1520
+ const axis = index % 3;
1521
+ minimum[axis] = Math.min(minimum[axis], positions[index]);
1522
+ maximum[axis] = Math.max(maximum[axis], positions[index]);
1523
+ }
1524
+ accessor.min = minimum;
1525
+ accessor.max = maximum;
1526
+ }
1527
+ attributes[name] = scenegraph.json.accessors.push(accessor) - 1;
1528
+ attributeIdentifiers[name] = encoded.report.attributes[name].id;
1529
+ }
1530
+ const indices = scenegraph.json.accessors.push({
1531
+ componentType: decoded.indices.value instanceof Uint16Array ? 5123 : 5125,
1532
+ count: encoded.report.faceCount * 3,
1533
+ type: "SCALAR"
1534
+ }) - 1;
1535
+ scenegraph.json.meshes = [
1536
+ {
1537
+ primitives: [
1538
+ {
1539
+ attributes,
1540
+ indices,
1541
+ mode: 4,
1542
+ ...materialIndex === void 0 ? {} : { material: materialIndex },
1543
+ extensions: { [DRACO_EXTENSION]: { bufferView, attributes: attributeIdentifiers } }
1544
+ }
1545
+ ]
1546
+ }
1547
+ ];
1548
+ encodeMeshFeatures(scenegraph, 0, options.features);
1549
+ scenegraph.registerRequiredExtension(DRACO_EXTENSION);
1550
+ return { glb: finalizeMeshTileScenegraph(scenegraph, 0), positions };
1551
+ }
1552
+ function getPositionKey(positions, index) {
1553
+ return `${positions[index]},${positions[index + 1]},${positions[index + 2]}`;
1554
+ }
1555
+ function getMeshTriangles(mesh) {
1556
+ const names = Object.keys(mesh.attributes).sort();
1557
+ const indices = mesh.indices.value;
1558
+ const triangles = [];
1559
+ for (let index = 0; index < indices.length; index += 3) {
1560
+ const vertices = [0, 1, 2].map(
1561
+ (corner) => names.map((name) => {
1562
+ const attribute = mesh.attributes[name];
1563
+ return Array.from(
1564
+ { length: attribute.size },
1565
+ (_, component) => attribute.value[indices[index + corner] * attribute.size + component]
1566
+ ).join(",");
1567
+ }).join("|")
1568
+ );
1569
+ triangles.push(
1570
+ [0, 1, 2].map((corner) => [...vertices.slice(corner), ...vertices.slice(0, corner)].join(";")).sort()[0]
1571
+ );
1572
+ }
1573
+ return triangles.sort();
1574
+ }
1575
+
1576
+ // src/v5/mesh-conversion.ts
1577
+ function createMeshConversionCodec(options) {
1578
+ const { spatialContext, maxPositionError, draco = true, dracoLibraryOptions } = options;
1579
+ if (typeof draco !== "boolean") {
1580
+ throw new import_core11.TileConversionError("MESH_DRACO_OPTIONS_INVALID", "draco must be a boolean");
1581
+ }
1582
+ if (!Number.isFinite(maxPositionError) || maxPositionError < 0) {
1583
+ throw new import_core11.TileConversionError(
1584
+ "MESH_PRECISION_LIMIT_INVALID",
1585
+ "maxPositionError must be finite and nonnegative"
1586
+ );
1587
+ }
1588
+ const spatialReference = spatialContext.spatialReference;
1589
+ const outputCrs = spatialReference.targetCrs || spatialReference.sourceCrs;
1590
+ const outputFrame = spatialReference.status === "native" ? spatialReference.coordinateFrame : outputCrs ? (0, import_tiles2.getSpatialCoordinateFrame)(outputCrs) : "unknown";
1591
+ if (!["native", "transformed"].includes(spatialReference.status) || !["projected", "geocentric", "local", "cartesian"].includes(outputFrame)) {
1592
+ throw new import_core11.TileConversionError(
1593
+ "MESH_OUTPUT_FRAME_UNSUPPORTED",
1594
+ "Mesh output requires a resolved Cartesian coordinate frame"
1595
+ );
1596
+ }
1597
+ return {
1598
+ /** Prepares and encodes one geometry while honoring cancellation. */
1599
+ async *convert(resource, _inspection, signal) {
1600
+ signal?.throwIfAborted();
1601
+ const geometry = validateMeshGeometry(resource.mesh, true);
1602
+ const sourcePositions = geometry.attributes.POSITION.value;
1603
+ const origin = [...resource.origin];
1604
+ if (origin.length !== 3 || origin.some((value) => !Number.isFinite(value))) {
1605
+ throw new import_core11.TileConversionError(
1606
+ "MESH_ORIGIN_INVALID",
1607
+ "Target origin must contain three finite coordinates"
1608
+ );
1609
+ }
1610
+ const transformed = "transformGeometryAsync" in spatialContext ? await spatialContext.transformGeometryAsync(
1611
+ sourcePositions,
1612
+ geometry.attributes.NORMAL?.value,
1613
+ resource.normalReferenceFrame ?? options.normalReferenceFrame
1614
+ ) : {
1615
+ positions: spatialContext.transformPositions(sourcePositions),
1616
+ normals: geometry.attributes.NORMAL ? spatialContext.transformNormals(geometry.attributes.NORMAL.value, sourcePositions) : void 0
1617
+ };
1618
+ signal?.throwIfAborted();
1619
+ const targetPositions = transformed.positions;
1620
+ if (targetPositions.length !== sourcePositions.length || targetPositions.some((value) => !Number.isFinite(value))) {
1621
+ throw new import_core11.TileConversionError(
1622
+ "MESH_TRANSFORM_INVALID",
1623
+ "Spatial transformation must return one finite xyz position per vertex"
1624
+ );
1625
+ }
1626
+ if (options.autoOrigin) {
1627
+ const minimum2 = [Infinity, Infinity, Infinity];
1628
+ const maximum2 = [-Infinity, -Infinity, -Infinity];
1629
+ for (let index = 0; index < targetPositions.length; index++) {
1630
+ const axis = index % 3;
1631
+ minimum2[axis] = Math.min(minimum2[axis], targetPositions[index]);
1632
+ maximum2[axis] = Math.max(maximum2[axis], targetPositions[index]);
1633
+ }
1634
+ for (let axis = 0; axis < 3; axis++) origin[axis] = minimum2[axis] / 2 + maximum2[axis] / 2;
1635
+ }
1636
+ const localPositions = new Float32Array(targetPositions.length);
1637
+ const minimum = [Infinity, Infinity, Infinity];
1638
+ const maximum = [-Infinity, -Infinity, -Infinity];
1639
+ const localMinimum = [Infinity, Infinity, Infinity];
1640
+ const localMaximum = [-Infinity, -Infinity, -Infinity];
1641
+ let maximumPositionError = 0;
1642
+ for (let index = 0; index < targetPositions.length; index += 3) {
1643
+ for (let axis = 0; axis < 3; axis++) {
1644
+ localPositions[index + axis] = targetPositions[index + axis] - origin[axis];
1645
+ }
1646
+ const positionError = Math.hypot(
1647
+ localPositions[index] + origin[0] - targetPositions[index],
1648
+ localPositions[index + 1] + origin[1] - targetPositions[index + 1],
1649
+ localPositions[index + 2] + origin[2] - targetPositions[index + 2]
1650
+ );
1651
+ if (!Number.isFinite(positionError) || positionError > maxPositionError) {
1652
+ throw new import_core11.TileConversionError(
1653
+ "MESH_POSITION_PRECISION_EXCEEDED",
1654
+ "Float32 mesh positions exceed the configured reconstruction error budget"
1655
+ );
1656
+ }
1657
+ maximumPositionError = Math.max(maximumPositionError, positionError);
1658
+ }
1659
+ geometry.attributes.POSITION = { value: localPositions, size: 3 };
1660
+ if (geometry.attributes.NORMAL) {
1661
+ geometry.attributes.NORMAL = {
1662
+ value: transformed.normals,
1663
+ size: 3
1664
+ };
1665
+ }
1666
+ const appearance = { material: resource.material, features: resource.features };
1667
+ const encoded = draco ? await encodeDracoMeshTile(geometry, appearance, dracoLibraryOptions) : { glb: encodeMeshTile(geometry, appearance), positions: localPositions };
1668
+ signal?.throwIfAborted();
1669
+ for (let index = 0; index < encoded.positions.length; index++) {
1670
+ const axis = index % 3;
1671
+ const position = encoded.positions[index];
1672
+ localMinimum[axis] = Math.min(localMinimum[axis], position);
1673
+ localMaximum[axis] = Math.max(localMaximum[axis], position);
1674
+ minimum[axis] = Math.min(minimum[axis], position + origin[axis]);
1675
+ maximum[axis] = Math.max(maximum[axis], position + origin[axis]);
1676
+ }
1677
+ yield {
1678
+ id: resource.id,
1679
+ glb: encoded.glb,
1680
+ origin,
1681
+ boundingBox: [minimum, maximum],
1682
+ localBoundingBox: [localMinimum, localMaximum],
1683
+ spatialReference,
1684
+ maximumPositionError
1685
+ };
1686
+ },
1687
+ /** Reports position rounding authorized by the configured precision budget. */
1688
+ async validateOutput(resource) {
1689
+ return resource.maximumPositionError > 0 ? [
1690
+ {
1691
+ code: "MESH_POSITION_ROUNDING",
1692
+ message: `Encoded positions have up to ${resource.maximumPositionError} target-unit reconstruction error`,
1693
+ severity: "info",
1694
+ resourceId: resource.id
1695
+ }
1696
+ ] : [];
1697
+ }
1698
+ };
1699
+ }
1700
+
1701
+ // src/v5/mesh-tileset-sink.ts
1702
+ var import_core12 = require("@loaders.gl/tile-converter/v5/core");
1703
+ var import_core13 = require("@loaders.gl/tile-converter/v5/core");
1704
+ function createSingleMeshTilesetSink(options) {
1705
+ const { geometricError } = options;
1706
+ if (!Number.isFinite(geometricError) || geometricError < 0) {
1707
+ throw new import_core12.TileConversionError(
1708
+ "MESH_GEOMETRIC_ERROR_INVALID",
1709
+ "geometricError must be finite and nonnegative, in meters"
1710
+ );
1711
+ }
1712
+ const memorySink = (0, import_core13.createBoundedMemoryTileConversionSink)(options);
1713
+ let state = "empty";
1714
+ let completed = false;
1715
+ return {
1716
+ getFiles() {
1717
+ return completed ? memorySink.getFiles() : [];
1718
+ },
1719
+ async write(resource, signal) {
1720
+ signal?.throwIfAborted();
1721
+ if (state !== "empty") {
1722
+ throw new import_core12.TileConversionError(
1723
+ "SINGLE_MESH_SINK_UNAVAILABLE",
1724
+ "The sink accepts exactly one mesh before finalization or abort"
1725
+ );
1726
+ }
1727
+ const json = JSON.stringify(createMeshTileset(resource, geometricError));
1728
+ state = "writing";
1729
+ await memorySink.write(
1730
+ { resourceId: "mesh.glb", parts: [resource.glb], contentType: "model/gltf-binary" },
1731
+ signal
1732
+ );
1733
+ await memorySink.write(
1734
+ { resourceId: "tileset.json", parts: [json], contentType: "application/json" },
1735
+ signal
1736
+ );
1737
+ state = "written";
1738
+ },
1739
+ async finalize(report) {
1740
+ if (state !== "written") {
1741
+ throw new import_core12.TileConversionError(
1742
+ "SINGLE_MESH_SINK_INCOMPLETE",
1743
+ "Finalization requires exactly one successfully written mesh"
1744
+ );
1745
+ }
1746
+ await memorySink.finalize(report);
1747
+ state = "closed";
1748
+ completed = true;
1749
+ },
1750
+ async abort(error) {
1751
+ state = "closed";
1752
+ completed = false;
1753
+ await memorySink.abort(error);
1754
+ }
1755
+ };
1756
+ }
1757
+ function createMeshTileset(resource, geometricError) {
1758
+ const reference = resource.spatialReference;
1759
+ const outputCrs = reference.targetCrs || reference.sourceCrs;
1760
+ const heightReference = reference.targetHeightReference === "native" ? reference.heightReference : reference.targetHeightReference;
1761
+ if (!["native", "transformed"].includes(reference.status) || outputCrs !== "EPSG:4978" || heightReference !== "ellipsoidal" || reference.status === "native" && (reference.coordinateFrame !== "geocentric" || reference.axisOrder !== "xyz" || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter"))) {
1762
+ throw new import_core12.TileConversionError(
1763
+ "MESH_TILESET_FRAME_UNSUPPORTED",
1764
+ "Single-mesh tilesets require resolved EPSG:4978 xyz coordinates in meters with ellipsoidal heights"
1765
+ );
1766
+ }
1767
+ if (!Number.isFinite(resource.maximumPositionError) || resource.maximumPositionError < 0 || resource.maximumPositionError > geometricError) {
1768
+ throw new import_core12.TileConversionError(
1769
+ "MESH_GEOMETRIC_ERROR_INVALID",
1770
+ "geometricError must include the measured position reconstruction error"
1771
+ );
1772
+ }
1773
+ const origin = [...resource.origin];
1774
+ const [minimum, maximum] = resource.localBoundingBox.map((bound) => [...bound]);
1775
+ if (origin.length !== 3 || minimum.length !== 3 || maximum.length !== 3 || [...origin, ...minimum, ...maximum].some((value) => !Number.isFinite(value)) || minimum.some((value, axis) => value > maximum[axis])) {
1776
+ throw new import_core12.TileConversionError(
1777
+ "MESH_TILESET_BOUNDS_INVALID",
1778
+ "Origin and local bounds must be finite xyz coordinates with ordered bounds"
1779
+ );
1780
+ }
1781
+ const center = minimum.map((value, axis) => value / 2 + maximum[axis] / 2);
1782
+ const halfSize = minimum.map((value, axis) => maximum[axis] / 2 - value / 2);
1783
+ return {
1784
+ asset: { version: "1.1" },
1785
+ geometricError,
1786
+ root: {
1787
+ // 3D Tiles applies y-up -> z-up to GLB content before the tile transform.
1788
+ transform: [1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, ...origin, 1],
1789
+ boundingVolume: {
1790
+ // Bounds use the same y-up -> z-up rotation before the inverse tile rotation.
1791
+ box: [
1792
+ center[0],
1793
+ -center[2],
1794
+ center[1],
1795
+ halfSize[0],
1796
+ 0,
1797
+ 0,
1798
+ 0,
1799
+ 0,
1800
+ halfSize[1],
1801
+ 0,
1802
+ -halfSize[2],
1803
+ 0
1804
+ ]
1805
+ },
1806
+ geometricError,
1807
+ refine: "REPLACE",
1808
+ content: { uri: "mesh.glb" }
1809
+ }
1810
+ };
1811
+ }
1812
+ function createMeshTilesetSink(options) {
1813
+ const { geometricError, maxMeshes } = options;
1814
+ if (!Number.isFinite(geometricError) || geometricError < 0 || !Number.isSafeInteger(maxMeshes) || maxMeshes < 1) {
1815
+ throw new import_core12.TileConversionError(
1816
+ "MESH_TILESET_OPTIONS_INVALID",
1817
+ "A leaf collection requires finite nonnegative geometricError and positive maxMeshes"
1818
+ );
1819
+ }
1820
+ const memory = (0, import_core13.createBoundedMemoryTileConversionSink)(options);
1821
+ const children = [];
1822
+ const identifiers = /* @__PURE__ */ new Set();
1823
+ const minimum = [Infinity, Infinity, Infinity];
1824
+ const maximum = [-Infinity, -Infinity, -Infinity];
1825
+ let state = "open";
1826
+ let completed = false;
1827
+ return {
1828
+ /** Exposes the complete package atomically. */
1829
+ getFiles: () => completed ? memory.getFiles() : [],
1830
+ /** Retains one placement and its corresponding encoded GLB. */
1831
+ async write(resource, signal) {
1832
+ signal?.throwIfAborted();
1833
+ if (state !== "open")
1834
+ throw new import_core12.TileConversionError(
1835
+ "MESH_TILESET_SINK_UNAVAILABLE",
1836
+ "The mesh sink is busy or closed"
1837
+ );
1838
+ if (children.length >= maxMeshes || identifiers.has(resource.id))
1839
+ throw new import_core12.TileConversionError(
1840
+ "MESH_TILESET_PLACEMENT_INVALID",
1841
+ "Mesh count exceeds maxMeshes or a placement identifier is repeated"
1842
+ );
1843
+ const tile = createMeshTileset(resource, geometricError).root;
1844
+ const lower = resource.localBoundingBox[0].map(
1845
+ (value, axis) => value + resource.origin[axis]
1846
+ );
1847
+ const upper = resource.localBoundingBox[1].map(
1848
+ (value, axis) => value + resource.origin[axis]
1849
+ );
1850
+ if ([...lower, ...upper].some((value) => !Number.isFinite(value)))
1851
+ throw new import_core12.TileConversionError(
1852
+ "MESH_TILESET_BOUNDS_INVALID",
1853
+ "Absolute bounds must be finite"
1854
+ );
1855
+ const resourceId = `meshes/${children.length}.glb`;
1856
+ tile.content = { uri: resourceId };
1857
+ state = "writing";
1858
+ await memory.write(
1859
+ { resourceId, parts: [resource.glb], contentType: "model/gltf-binary" },
1860
+ signal
1861
+ );
1862
+ children.push(tile);
1863
+ identifiers.add(resource.id);
1864
+ for (let axis = 0; axis < 3; axis++) {
1865
+ minimum[axis] = Math.min(minimum[axis], lower[axis]);
1866
+ maximum[axis] = Math.max(maximum[axis], upper[axis]);
1867
+ }
1868
+ state = "open";
1869
+ },
1870
+ /** Writes the enclosing root only when every leaf has completed. */
1871
+ async finalize(report) {
1872
+ if (state !== "open" || !children.length)
1873
+ throw new import_core12.TileConversionError(
1874
+ "MESH_TILESET_SINK_INCOMPLETE",
1875
+ "Finalization requires written mesh leaves"
1876
+ );
1877
+ state = "writing";
1878
+ const center = minimum.map((value, axis) => value / 2 + maximum[axis] / 2);
1879
+ const halfSize = minimum.map((value, axis) => maximum[axis] / 2 - value / 2);
1880
+ const rootGeometricError = Math.max(geometricError, 2 * Math.hypot(...halfSize));
1881
+ if (!Number.isFinite(rootGeometricError))
1882
+ throw new import_core12.TileConversionError(
1883
+ "MESH_TILESET_BOUNDS_INVALID",
1884
+ "Root bounds must have a finite diagonal"
1885
+ );
1886
+ const json = JSON.stringify({
1887
+ asset: { version: "1.1" },
1888
+ geometricError: rootGeometricError,
1889
+ root: {
1890
+ boundingVolume: {
1891
+ box: [
1892
+ center[0],
1893
+ center[1],
1894
+ center[2],
1895
+ halfSize[0],
1896
+ 0,
1897
+ 0,
1898
+ 0,
1899
+ halfSize[1],
1900
+ 0,
1901
+ 0,
1902
+ 0,
1903
+ halfSize[2]
1904
+ ]
1905
+ },
1906
+ geometricError: rootGeometricError,
1907
+ refine: "ADD",
1908
+ children
1909
+ }
1910
+ });
1911
+ await memory.write({
1912
+ resourceId: "tileset.json",
1913
+ parts: [json],
1914
+ contentType: "application/json"
1915
+ });
1916
+ await memory.finalize(report);
1917
+ state = "closed";
1918
+ completed = true;
1919
+ },
1920
+ /** Discards files and hierarchy metadata on failure or cancellation. */
1921
+ async abort(error) {
1922
+ state = "closed";
1923
+ completed = false;
1924
+ children.length = 0;
1925
+ identifiers.clear();
1926
+ await memory.abort(error);
1927
+ }
1928
+ };
1929
+ }
1930
+
1931
+ // src/v5/conversion-archive.ts
1932
+ var import_d_tiles_archive_writer = require("@loaders.gl/3d-tiles/3d-tiles-archive-writer");
1933
+ var import_i3s_slpk_writer = require("@loaders.gl/i3s/i3s-slpk-writer");
1934
+ var import_core14 = require("@loaders.gl/tile-converter/v5/core");
1935
+ async function createTileConversionArchive(files, options) {
1936
+ const { resources, format, maxArchiveBytes, signal } = prepareArchiveInput(files, options);
1937
+ const writer = format === "3tz" ? import_d_tiles_archive_writer.Tiles3DArchiveWriter : import_i3s_slpk_writer.SLPKWriter;
1938
+ const archive = await writer.encode(resources, { [format]: { maxArchiveBytes, signal } });
1939
+ return new Blob([archive], { type: writer.mimeTypes[0] });
1940
+ }
1941
+ async function* encodeTileConversionArchiveInBatches(files, options) {
1942
+ const { resources, format, maxArchiveBytes, signal } = prepareArchiveInput(files, options);
1943
+ const encode = format === "3tz" ? import_d_tiles_archive_writer.encodeTiles3DArchiveInBatches : import_i3s_slpk_writer.encodeSLPKArchiveInBatches;
1944
+ yield* encode(resources, { [format]: { maxArchiveBytes, signal } });
1945
+ }
1946
+ function prepareArchiveInput(files, options) {
1947
+ const { format, maxArchiveBytes, signal } = options;
1948
+ if (!Number.isSafeInteger(maxArchiveBytes) || maxArchiveBytes < 0) {
1949
+ throw new import_core14.TileConversionError(
1950
+ "INVALID_ARCHIVE_BYTE_LIMIT",
1951
+ "Archive byte limit must be a non-negative safe integer"
1952
+ );
1953
+ }
1954
+ if (format !== "3tz" && format !== "slpk") {
1955
+ throw new import_core14.TileConversionError("INVALID_ARCHIVE_FORMAT", "Expected 3tz or slpk archive format");
1956
+ }
1957
+ const resources = Object.fromEntries(files.map((file) => [file.resourceId, file.blob]));
1958
+ if (Object.keys(resources).length !== files.length) {
1959
+ throw new import_core14.TileConversionError(
1960
+ "DUPLICATE_ARCHIVE_RESOURCE",
1961
+ "Archive resource identifiers must be unique"
1962
+ );
1963
+ }
1964
+ return { resources, format, maxArchiveBytes, signal };
1965
+ }
1966
+
1967
+ // src/v5/mesh-tileset-archive.ts
1968
+ var import_d_tiles_archive_writer2 = require("@loaders.gl/3d-tiles/3d-tiles-archive-writer");
1969
+ var import_core15 = require("@loaders.gl/tile-converter/v5/core");
1970
+ async function createSingleMeshTilesetArchive(files, options) {
1971
+ const { maxArchiveBytes, signal } = options;
1972
+ if (!Number.isSafeInteger(maxArchiveBytes) || maxArchiveBytes < 0) {
1973
+ throw new import_core15.TileConversionError(
1974
+ "INVALID_ARCHIVE_BYTE_LIMIT",
1975
+ "Archive byte limit must be a non-negative safe integer"
1976
+ );
1977
+ }
1978
+ if (files.length !== 2 || !files.some((file) => file.resourceId === "mesh.glb") || !files.some((file) => file.resourceId === "tileset.json")) {
1979
+ throw new import_core15.TileConversionError(
1980
+ "INVALID_SINGLE_MESH_ARCHIVE",
1981
+ "Expected mesh.glb and tileset.json from a finalized single-mesh sink"
1982
+ );
1983
+ }
1984
+ const resources = Object.fromEntries(files.map((file) => [file.resourceId, file.blob]));
1985
+ const archive = await import_d_tiles_archive_writer2.Tiles3DArchiveWriter.encode(resources, { "3tz": { maxArchiveBytes, signal } });
1986
+ return new Blob([archive], { type: import_d_tiles_archive_writer2.Tiles3DArchiveWriter.mimeTypes[0] });
1987
+ }
1988
+
1989
+ // src/v5/mesh-source.ts
1990
+ var import_core18 = require("@math.gl/core");
1991
+ var import_core19 = require("@loaders.gl/tile-converter/v5/core");
1992
+ var import_core20 = require("@loaders.gl/tile-converter/v5/core");
1993
+
1994
+ // src/v5/mesh-source-texture.ts
1995
+ var import_images2 = require("@loaders.gl/images");
1996
+ var import_core16 = require("@loaders.gl/tile-converter/v5/core");
1997
+ function mapMeshSourceTexture(textureInfo) {
1998
+ const texture = textureInfo?.texture;
1999
+ const image = texture?.source;
2000
+ const sampler = texture?.sampler;
2001
+ const extensions = textureInfo?.extensions || {};
2002
+ const hasTransform = Object.hasOwn(extensions, "KHR_texture_transform");
2003
+ const transform = extensions.KHR_texture_transform;
2004
+ if (!textureInfo || textureInfo.texCoord !== void 0 && textureInfo.texCoord !== 0 || Object.keys(extensions).some((name) => name !== "KHR_texture_transform") || hasTransform && (!transform || typeof transform !== "object" || Array.isArray(transform) || transform.texCoord !== void 0 && transform.texCoord !== 0 || Object.keys(transform.extensions || {}).length || Object.keys(transform).some(
2005
+ (name) => !["offset", "rotation", "scale", "texCoord", "extensions", "extras"].includes(name)
2006
+ )) || Object.keys(textureInfo).some(
2007
+ (name) => !["id", "index", "texture", "texCoord", "extensions", "extras"].includes(name)
2008
+ ) || !texture || Object.keys(texture).some(
2009
+ (name) => !["id", "source", "sampler", "name", "extensions", "extras"].includes(name)
2010
+ ) || Object.keys(texture.extensions || {}).length || !image || image.uri !== void 0 && image.bufferView !== void 0 || Object.keys(image.extensions || {}).length || image.uri === void 0 && (!(image.bufferView?.data instanceof Uint8Array) || Object.keys(image.bufferView.extensions || {}).length || !["image/png", "image/jpeg"].includes(image.mimeType || "")) || sampler && (Object.keys(sampler.extensions || {}).length || Object.keys(sampler).some(
2011
+ (name) => ![
2012
+ "id",
2013
+ "parameters",
2014
+ "wrapS",
2015
+ "wrapT",
2016
+ "magFilter",
2017
+ "minFilter",
2018
+ "name",
2019
+ "extensions",
2020
+ "extras"
2021
+ ].includes(name)
2022
+ ))) {
2023
+ throw new import_core16.TileConversionError(
2024
+ "MESH_SOURCE_TEXTURE_UNSUPPORTED",
2025
+ "Base-color textures require a PNG/JPEG bufferView or base64 data URI, TEXCOORD_0, glTF sampling and an optional KHR_texture_transform on TEXCOORD_0"
2026
+ );
2027
+ }
2028
+ const selectedSampler = sampler ? {
2029
+ wrapS: sampler.wrapS,
2030
+ wrapT: sampler.wrapT,
2031
+ magFilter: sampler.magFilter,
2032
+ minFilter: sampler.minFilter
2033
+ } : void 0;
2034
+ return {
2035
+ ...mapMeshSourceImage(image),
2036
+ ...hasTransform ? {
2037
+ transform: {
2038
+ offset: transform.offset,
2039
+ rotation: transform.rotation,
2040
+ scale: transform.scale
2041
+ }
2042
+ } : {},
2043
+ ...selectedSampler && Object.values(selectedSampler).some((value) => value !== void 0) ? { sampler: selectedSampler } : {}
2044
+ };
2045
+ }
2046
+ function mapMeshSourceImage(image) {
2047
+ if (image.uri === void 0) {
2048
+ return { data: image.bufferView.data, mimeType: image.mimeType };
2049
+ }
2050
+ const match = typeof image.uri === "string" ? /^data:(image\/(?:png|jpeg));base64,([A-Za-z0-9+/]+={0,2})$/.exec(image.uri) : null;
2051
+ if (!match || image.mimeType !== void 0 && image.mimeType !== match[1]) {
2052
+ throw new import_core16.TileConversionError(
2053
+ "MESH_SOURCE_TEXTURE_UNSUPPORTED",
2054
+ "Inline images require a PNG/JPEG base64 data URI and matching optional mimeType"
2055
+ );
2056
+ }
2057
+ let encodedBytes;
2058
+ try {
2059
+ encodedBytes = atob(match[2]);
2060
+ } catch {
2061
+ throw new import_core16.TileConversionError("MESH_SOURCE_TEXTURE_UNSUPPORTED", "Invalid inline image base64");
2062
+ }
2063
+ return {
2064
+ data: Uint8Array.from(encodedBytes, (character) => character.charCodeAt(0)),
2065
+ mimeType: match[1]
2066
+ };
2067
+ }
2068
+ async function resolveMeshSourceTexture(textureInfo, readExternalResource, contentUri, signal) {
2069
+ const image = textureInfo?.texture?.source;
2070
+ if (!image || image.uri === void 0 || image.uri.startsWith("data:"))
2071
+ return mapMeshSourceTexture(textureInfo);
2072
+ if (!readExternalResource || image.bufferView !== void 0)
2073
+ throw new import_core16.TileConversionError(
2074
+ "MESH_SOURCE_TEXTURE_UNSUPPORTED",
2075
+ "External images require an explicit bounded resource reader"
2076
+ );
2077
+ signal?.throwIfAborted();
2078
+ const data = await readExternalResource(image.uri, contentUri, signal);
2079
+ signal?.throwIfAborted();
2080
+ let metadata;
2081
+ try {
2082
+ metadata = (0, import_images2.getBinaryImageMetadata)(new DataView(data.buffer, data.byteOffset, data.byteLength));
2083
+ } catch {
2084
+ }
2085
+ if (!metadata || !["image/png", "image/jpeg"].includes(metadata.mimeType) || image.mimeType !== void 0 && image.mimeType !== metadata.mimeType)
2086
+ throw new import_core16.TileConversionError(
2087
+ "MESH_SOURCE_TEXTURE_UNSUPPORTED",
2088
+ "External images require matching PNG/JPEG headers"
2089
+ );
2090
+ const resolved = {
2091
+ ...textureInfo,
2092
+ texture: {
2093
+ ...textureInfo.texture,
2094
+ source: { ...image, uri: void 0, mimeType: metadata.mimeType, bufferView: { data } }
2095
+ }
2096
+ };
2097
+ return mapMeshSourceTexture(resolved);
2098
+ }
2099
+
2100
+ // src/v5/mesh-source-features.ts
2101
+ var import_core17 = require("@loaders.gl/tile-converter/v5/core");
2102
+ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
2103
+ const declarations = primitive.extensions?.EXT_mesh_features?.featureIds;
2104
+ const metadata = gltf.extensions?.EXT_structural_metadata;
2105
+ const legacy = primitive.attributes._BATCHID;
2106
+ if (!declarations && !legacy && !metadata && !Object.keys(payload.batchTableJson || {}).length && !payload.header?.batchLength) {
2107
+ if (Object.keys(primitive.attributes).some((name) => /^_FEATURE_ID_\d+$/.test(name)))
2108
+ throw new import_core17.TileConversionError(
2109
+ "MESH_FEATURE_MAPPING_REQUIRED",
2110
+ "Feature attributes require an EXT_mesh_features declaration"
2111
+ );
2112
+ return void 0;
2113
+ }
2114
+ if (!options)
2115
+ throw new import_core17.TileConversionError(
2116
+ "MESH_FEATURE_SCHEMA_REQUIRED",
2117
+ "Feature extraction requires an explicit Arrow schema and class mapping"
2118
+ );
2119
+ if (!options.metadataClass)
2120
+ throw new import_core17.TileConversionError(
2121
+ "MESH_FEATURE_CLASS_REQUIRED",
2122
+ "Feature metadata class must be explicit"
2123
+ );
2124
+ let count;
2125
+ let attribute = legacy;
2126
+ let properties;
2127
+ if (declarations) {
2128
+ if (legacy || declarations.length !== 1 || metadata?.propertyTables?.length !== 1 || metadata.propertyTextures?.length || metadata.propertyAttributes?.length)
2129
+ throw new import_core17.TileConversionError(
2130
+ "MESH_FEATURE_MAPPING_UNSUPPORTED",
2131
+ "Only one attribute-backed feature set and one property table are supported"
2132
+ );
2133
+ const declaration = declarations[0];
2134
+ if (!Number.isInteger(declaration.attribute) || declaration.attribute < 0 || declaration.propertyTable !== 0 || declaration.texture || declaration.implicit || declaration.nullFeatureId !== void 0)
2135
+ throw new import_core17.TileConversionError(
2136
+ "MESH_FEATURE_MAPPING_UNSUPPORTED",
2137
+ "Features require an attribute and property table 0 without null/texture/implicit IDs"
2138
+ );
2139
+ attribute = primitive.attributes[`_FEATURE_ID_${declaration.attribute}`];
2140
+ const table = metadata.propertyTables[0];
2141
+ if (table.class !== options.metadataClass || table.count !== declaration.featureCount)
2142
+ throw new import_core17.TileConversionError(
2143
+ "MESH_FEATURE_CLASS_MISMATCH",
2144
+ "Feature class and declared table counts must agree"
2145
+ );
2146
+ count = table.count;
2147
+ properties = {};
2148
+ const classProperties = metadata.schema?.classes?.[table.class]?.properties;
2149
+ if (!classProperties)
2150
+ throw new import_core17.TileConversionError(
2151
+ "MESH_FEATURE_SCHEMA_REQUIRED",
2152
+ "A decoded inline structural metadata class is required"
2153
+ );
2154
+ if (Object.keys(table.properties || {}).some((name) => !Object.hasOwn(classProperties, name)))
2155
+ throw new import_core17.TileConversionError(
2156
+ "MESH_FEATURE_PROPERTY_UNMAPPED",
2157
+ "Property table columns must be declared in the metadata class"
2158
+ );
2159
+ for (const [name, property] of Object.entries(classProperties)) {
2160
+ const column = table.properties?.[name];
2161
+ if (!["SCALAR", "STRING"].includes(property.type) || property.array || property.noData !== void 0 && (property.normalized || property.offset !== void 0 || property.scale !== void 0 || column?.offset !== void 0 || column?.scale !== void 0) || ["INT64", "UINT64"].includes(property.componentType || "") && (property.normalized || property.offset !== void 0 || property.scale !== void 0 || column?.offset !== void 0 || column?.scale !== void 0))
2162
+ throw new import_core17.TileConversionError(
2163
+ "MESH_FEATURE_PROPERTY_UNSUPPORTED",
2164
+ `Property ${name} requires a separately qualified semantic mapping`
2165
+ );
2166
+ const data = column?.data;
2167
+ if (data === void 0 && property.default === void 0)
2168
+ throw new import_core17.TileConversionError(
2169
+ "MESH_FEATURE_PROPERTY_UNAVAILABLE",
2170
+ `Property ${name} has not been decoded`
2171
+ );
2172
+ const decoded = data ?? Array.from({ length: count }, () => property.default);
2173
+ if (property.noData === void 0 || data === void 0) {
2174
+ properties[name] = decoded;
2175
+ continue;
2176
+ }
2177
+ const sentinel = property.noData;
2178
+ const integer64 = ["INT64", "UINT64"].includes(property.componentType || "");
2179
+ if ((property.type === "STRING" ? typeof sentinel !== "string" : typeof sentinel !== "number" || !Number.isFinite(sentinel) || integer64 && !Number.isSafeInteger(sentinel)) || !decoded || !Array.isArray(decoded) && !ArrayBuffer.isView(decoded) || decoded.length !== count)
2180
+ throw new import_core17.TileConversionError(
2181
+ "MESH_FEATURE_PROPERTY_UNSUPPORTED",
2182
+ `Property ${name} requires a valid decoded noData mapping`
2183
+ );
2184
+ const comparable = integer64 ? BigInt(sentinel) : sentinel;
2185
+ properties[name] = Array.from(
2186
+ decoded,
2187
+ (value) => value === comparable ? property.default ?? null : value
2188
+ );
2189
+ }
2190
+ } else {
2191
+ if (metadata)
2192
+ throw new import_core17.TileConversionError(
2193
+ "MESH_FEATURE_MAPPING_UNSUPPORTED",
2194
+ "Structural metadata requires an attribute-backed feature declaration"
2195
+ );
2196
+ count = payload.header?.batchLength ?? Number(payload.featureTableJson?.BATCH_LENGTH);
2197
+ properties = payload.batchTableJson || {};
2198
+ }
2199
+ const vertexCount = mesh.attributes.POSITION.value.length / 3;
2200
+ if (!Number.isSafeInteger(count) || count <= 0 || !attribute || attribute.components !== 1 || attribute.normalized || attribute.value.length !== vertexCount || Array.from(attribute.value).some(
2201
+ (value) => !Number.isSafeInteger(value) || value < 0 || value >= count
2202
+ ))
2203
+ throw new import_core17.TileConversionError(
2204
+ "MESH_FEATURE_IDS_INVALID",
2205
+ "Feature IDs must be one valid property-table row index per vertex"
2206
+ );
2207
+ const selectedAttribute = declarations ? `_FEATURE_ID_${declarations[0].attribute}` : "_BATCHID";
2208
+ if (Object.keys(primitive.attributes).some(
2209
+ (name) => (name === "_BATCHID" || /^_FEATURE_ID_\d+$/.test(name)) && name !== selectedAttribute
2210
+ ))
2211
+ throw new import_core17.TileConversionError(
2212
+ "MESH_FEATURE_MAPPING_UNSUPPORTED",
2213
+ "Additional feature ID sets require separate mappings"
2214
+ );
2215
+ for (const [name, values] of Object.entries(properties)) {
2216
+ if (!values || !Array.isArray(values) && !ArrayBuffer.isView(values) || values.length !== count)
2217
+ throw new import_core17.TileConversionError(
2218
+ "MESH_FEATURE_PROPERTY_UNAVAILABLE",
2219
+ `Property ${name} must be a decoded column matching the table count`
2220
+ );
2221
+ }
2222
+ const sourceIdentifier = options.sourceFeatureIdProperty;
2223
+ if (sourceIdentifier && !properties[sourceIdentifier])
2224
+ throw new import_core17.TileConversionError(
2225
+ "MESH_FEATURE_ID_PROPERTY_REQUIRED",
2226
+ "Stable source identifier property is missing"
2227
+ );
2228
+ const batches = convertFeatureAttributesToArrowBatches(
2229
+ Array.from({ length: count }, (_, index) => ({
2230
+ featureId: sourceIdentifier ? properties[sourceIdentifier][index] : index,
2231
+ metadataClass: options.metadataClass,
2232
+ properties: Object.fromEntries(
2233
+ Object.entries(properties).filter(([name]) => name !== sourceIdentifier).map(([name, values]) => [name, values[index]])
2234
+ )
2235
+ })),
2236
+ options
2237
+ );
2238
+ const indices = mesh.indices?.value;
2239
+ const triangleFeatureIndices = new Uint32Array((indices?.length ?? vertexCount) / 3);
2240
+ for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
2241
+ const corners = [0, 1, 2].map(
2242
+ (corner) => indices?.[triangle * 3 + corner] ?? triangle * 3 + corner
2243
+ );
2244
+ const rows = corners.map((corner) => attribute.value[corner]);
2245
+ if (rows.some((row) => row !== rows[0]) && new Set(corners).size === 3)
2246
+ throw new import_core17.TileConversionError(
2247
+ "MESH_FEATURE_TRIANGLE_MIXED",
2248
+ "All vertices of a nondegenerate triangle must reference the same feature row"
2249
+ );
2250
+ triangleFeatureIndices[triangle] = Number(rows[0]);
2251
+ }
2252
+ return {
2253
+ batches,
2254
+ triangleFeatureIndices,
2255
+ featureIdField: options.featureIdField || "feature_id",
2256
+ integer64Encoding: options.integer64Encoding
2257
+ };
2258
+ }
2259
+
2260
+ // src/v5/mesh-source.ts
2261
+ function createMeshTilesetConversionSource(tileset, options = {}) {
2262
+ const source = (0, import_core20.createTilesetConversionSource)(tileset, options);
2263
+ return {
2264
+ /** Validates the source frame and supported dataset metadata before traversal. */
2265
+ async inspect(signal) {
2266
+ const metadata = await source.inspect(signal);
2267
+ const reference = metadata.spatialReference;
2268
+ if (metadata.type !== "TILES3D" || !reference || reference.status !== "native" || reference.sourceCrs !== "EPSG:4978" || reference.heightReference !== "ellipsoidal" || reference.axisOrder !== "xyz" || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter")) {
2269
+ throw new import_core19.TileConversionError(
2270
+ "MESH_SOURCE_FRAME_UNSUPPORTED",
2271
+ "Mesh extraction requires native EPSG:4978 xyz coordinates in meters and ellipsoidal heights"
2272
+ );
2273
+ }
2274
+ if (metadata.tileset.metadata || metadata.tileset.groups?.length) {
2275
+ throw new import_core19.TileConversionError(
2276
+ "MESH_SOURCE_METADATA_UNSUPPORTED",
2277
+ "Tileset/group metadata requires a separate mapping"
2278
+ );
2279
+ }
2280
+ return metadata;
2281
+ },
2282
+ /** Yields each primitive placement while the shared traversal owns content lifetime. */
2283
+ async *read(inspection, signal) {
2284
+ for await (const item of source.read(inspection, signal)) {
2285
+ const placementReference = item.tile.header._spatialReference || tileset.spatialReference;
2286
+ if (placementReference && (placementReference.status !== "native" || placementReference.sourceCrs !== "EPSG:4978"))
2287
+ throw new import_core19.TileConversionError(
2288
+ "MESH_SOURCE_FRAME_UNSUPPORTED",
2289
+ "Nested mesh placements require native ECEF coordinates"
2290
+ );
2291
+ if (item.tile.header.metadata || item.tile.header.implicitMetadata)
2292
+ throw new import_core19.TileConversionError(
2293
+ "MESH_SOURCE_METADATA_UNSUPPORTED",
2294
+ "Tile metadata requires a separate mapping"
2295
+ );
2296
+ for (const content of item.contents) {
2297
+ signal?.throwIfAborted();
2298
+ if (content.metadata || content.group !== void 0)
2299
+ throw new import_core19.TileConversionError(
2300
+ "MESH_SOURCE_METADATA_UNSUPPORTED",
2301
+ "Content metadata requires a separate mapping"
2302
+ );
2303
+ const payload = content.payload;
2304
+ if (!payload?.gltf || !["glTF", "gltf", "b3dm"].includes(payload.type || content.type || "")) {
2305
+ throw new import_core19.TileConversionError(
2306
+ "MESH_SOURCE_CONTENT_UNSUPPORTED",
2307
+ "Mesh extraction requires decoded GLB/glTF or B3DM content"
2308
+ );
2309
+ }
2310
+ const gltf = payload.gltf;
2311
+ if (gltf.animations?.length || gltf.skins?.length || Object.keys(gltf.extensions || {}).some(
2312
+ (name) => !["EXT_structural_metadata", "CESIUM_RTC"].includes(name)
2313
+ )) {
2314
+ throw new import_core19.TileConversionError(
2315
+ "MESH_SOURCE_SCENE_UNSUPPORTED",
2316
+ "Animated/skinned scenes and unmapped scene extensions are unsupported"
2317
+ );
2318
+ }
2319
+ const axis = payload.gltfUpAxis || "Y";
2320
+ if (!["X", "Y", "Z"].includes(axis))
2321
+ throw new import_core19.TileConversionError("MESH_SOURCE_AXIS_UNSUPPORTED", "Unknown glTF up axis");
2322
+ const placement = new import_core18.Matrix4(item.tile.computedTransform);
2323
+ const rtc = payload.rtcCenter || gltf.extensions?.CESIUM_RTC?.center;
2324
+ if (rtc) {
2325
+ if (rtc.length !== 3 || rtc.some((value) => !Number.isFinite(value)))
2326
+ throw new import_core19.TileConversionError(
2327
+ "MESH_SOURCE_TRANSFORM_INVALID",
2328
+ "RTC center must contain finite xyz coordinates"
2329
+ );
2330
+ placement.translate(rtc);
2331
+ }
2332
+ if (axis === "Y")
2333
+ placement.multiplyRight(new import_core18.Matrix4([1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1]));
2334
+ if (axis === "X")
2335
+ placement.multiplyRight(new import_core18.Matrix4([0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1]));
2336
+ const roots = gltf.scene?.nodes || (gltf.scenes?.length === 1 ? gltf.scenes[0].nodes : void 0);
2337
+ if (!roots?.length)
2338
+ throw new import_core19.TileConversionError(
2339
+ "MESH_SOURCE_SCENE_UNSUPPORTED",
2340
+ "A default scene or exactly one scene is required"
2341
+ );
2342
+ let primitiveIndex = 0;
2343
+ for (const selected of traverseMeshNodes(roots, placement)) {
2344
+ const resource = await extractPrimitive(
2345
+ selected.primitive,
2346
+ selected.transform,
2347
+ `${item.tile.id}/${content.index}/${primitiveIndex++}`,
2348
+ options.readExternalResource,
2349
+ content.uri,
2350
+ signal
2351
+ );
2352
+ const features = extractMeshFeatures(
2353
+ gltf,
2354
+ selected.primitive,
2355
+ payload,
2356
+ resource.mesh,
2357
+ options.features
2358
+ );
2359
+ yield { ...resource, features };
2360
+ }
2361
+ if (!primitiveIndex)
2362
+ throw new import_core19.TileConversionError(
2363
+ "MESH_SOURCE_CONTENT_EMPTY",
2364
+ "Declared content contains no supported mesh primitive"
2365
+ );
2366
+ }
2367
+ }
2368
+ }
2369
+ };
2370
+ }
2371
+ function* traverseMeshNodes(nodes, parent, ancestors = /* @__PURE__ */ new Set()) {
2372
+ for (const node of nodes) {
2373
+ if (ancestors.has(node) || node.skin || node.weights || Object.keys(node.extensions || {}).length)
2374
+ throw new import_core19.TileConversionError(
2375
+ "MESH_SOURCE_NODE_UNSUPPORTED",
2376
+ "Cyclic, skinned, morphed, or extended nodes are unsupported"
2377
+ );
2378
+ const transform = new import_core18.Matrix4(parent).multiplyRight(
2379
+ node.matrix ? new import_core18.Matrix4(node.matrix) : new import_core18.Matrix4().translate(node.translation || [0, 0, 0]).multiplyRight(new import_core18.Matrix4().fromQuaternion(node.rotation || [0, 0, 0, 1])).scale(node.scale || [1, 1, 1])
2380
+ );
2381
+ if (node.mesh) {
2382
+ if (node.mesh.weights || Object.keys(node.mesh.extensions || {}).length)
2383
+ throw new import_core19.TileConversionError(
2384
+ "MESH_SOURCE_NODE_UNSUPPORTED",
2385
+ "Morph weights and mesh extensions are unsupported"
2386
+ );
2387
+ for (const primitive of node.mesh.primitives) yield { primitive, transform };
2388
+ }
2389
+ yield* traverseMeshNodes(node.children || [], transform, /* @__PURE__ */ new Set([...ancestors, node]));
2390
+ }
2391
+ }
2392
+ async function extractPrimitive(primitive, transform, id, readExternalResource, contentUri, signal) {
2393
+ if (primitive.targets?.length || Object.keys(primitive.extensions || {}).some((name) => name !== "EXT_mesh_features"))
2394
+ throw new import_core19.TileConversionError(
2395
+ "MESH_SOURCE_PRIMITIVE_UNSUPPORTED",
2396
+ "Morph targets and unmapped primitive extensions are unsupported"
2397
+ );
2398
+ if (Array.from(transform).some((value) => !Number.isFinite(value)) || transform[3] !== 0 || transform[7] !== 0 || transform[11] !== 0 || transform[15] !== 1 || transform.determinant() <= 0)
2399
+ throw new import_core19.TileConversionError(
2400
+ "MESH_SOURCE_TRANSFORM_INVALID",
2401
+ "Mesh placement requires a finite invertible affine transform with positive determinant"
2402
+ );
2403
+ const attributes = {};
2404
+ for (const [name, accessor] of Object.entries(primitive.attributes)) {
2405
+ if (name === "_BATCHID" || /^_FEATURE_ID_\d+$/.test(name)) continue;
2406
+ if (!["POSITION", "NORMAL", "COLOR_0", "TEXCOORD_0"].includes(name))
2407
+ throw new import_core19.TileConversionError(
2408
+ "MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
2409
+ `Unsupported mesh attribute ${name}`
2410
+ );
2411
+ attributes[name] = {
2412
+ value: accessor.value,
2413
+ size: accessor.components,
2414
+ ...accessor.normalized ? { normalized: true } : {}
2415
+ };
2416
+ }
2417
+ const mesh = validateMeshGeometry(
2418
+ {
2419
+ topology: "triangle-list",
2420
+ mode: primitive.mode === 5 ? 4 : primitive.mode ?? 4,
2421
+ attributes,
2422
+ indices: createTriangleIndices(primitive, (attributes.POSITION?.value.length ?? 0) / 3)
2423
+ },
2424
+ true
2425
+ );
2426
+ const sourcePositions = mesh.attributes.POSITION.value;
2427
+ const positions = new Float64Array(sourcePositions.length);
2428
+ const minimum = [Infinity, Infinity, Infinity];
2429
+ const maximum = [-Infinity, -Infinity, -Infinity];
2430
+ for (let index = 0; index < positions.length; index += 3) {
2431
+ const placed = transform.transformAsPoint(
2432
+ Array.from(sourcePositions.subarray(index, index + 3))
2433
+ );
2434
+ positions.set(placed, index);
2435
+ for (let axis = 0; axis < 3; axis++) {
2436
+ minimum[axis] = Math.min(minimum[axis], placed[axis]);
2437
+ maximum[axis] = Math.max(maximum[axis], placed[axis]);
2438
+ }
2439
+ }
2440
+ mesh.attributes.POSITION = { value: positions, size: 3 };
2441
+ if (mesh.attributes.NORMAL) {
2442
+ const values = mesh.attributes.NORMAL.value;
2443
+ const normalMatrix = new import_core18.Matrix4(transform).invert().transpose();
2444
+ const normals = new Float32Array(values.length);
2445
+ for (let index = 0; index < values.length; index += 3) {
2446
+ const normal = normalMatrix.transformAsVector(Array.from(values.subarray(index, index + 3)));
2447
+ const length = Math.hypot(...normal);
2448
+ normals.set(
2449
+ normal.map((value) => value / length),
2450
+ index
2451
+ );
2452
+ }
2453
+ mesh.attributes.NORMAL = { value: normals, size: 3 };
2454
+ }
2455
+ return {
2456
+ id,
2457
+ mesh,
2458
+ origin: minimum.map((value, axis) => value / 2 + maximum[axis] / 2),
2459
+ material: await mapMeshSourceMaterial(
2460
+ primitive.material,
2461
+ readExternalResource,
2462
+ contentUri,
2463
+ signal
2464
+ )
2465
+ };
2466
+ }
2467
+ async function mapMeshSourceMaterial(material, readExternalResource, contentUri, signal) {
2468
+ if (!material) return void 0;
2469
+ const pbr = material.pbrMetallicRoughness;
2470
+ if (Object.entries(material).some(
2471
+ ([name, value]) => value !== void 0 && ![
2472
+ "id",
2473
+ "name",
2474
+ "pbrMetallicRoughness",
2475
+ "alphaMode",
2476
+ "alphaCutoff",
2477
+ "doubleSided",
2478
+ "emissiveFactor",
2479
+ "extras"
2480
+ ].includes(name)
2481
+ ) || Object.entries(pbr || {}).some(
2482
+ ([name, value]) => value !== void 0 && !["baseColorFactor", "baseColorTexture", "metallicFactor", "roughnessFactor"].includes(name)
2483
+ ) || material.emissiveFactor?.some((value) => value !== 0))
2484
+ throw new import_core19.TileConversionError(
2485
+ "MESH_SOURCE_MATERIAL_UNSUPPORTED",
2486
+ "Only metallic-roughness factors and one embedded base-color image are supported"
2487
+ );
2488
+ return {
2489
+ baseColorFactor: pbr?.baseColorFactor,
2490
+ ..."baseColorTexture" in (pbr || {}) ? {
2491
+ baseColorTexture: await resolveMeshSourceTexture(
2492
+ pbr.baseColorTexture,
2493
+ readExternalResource,
2494
+ contentUri,
2495
+ signal
2496
+ )
2497
+ } : {},
2498
+ metallicFactor: pbr?.metallicFactor,
2499
+ roughnessFactor: pbr?.roughnessFactor,
2500
+ alphaMode: material.alphaMode,
2501
+ ...material.alphaMode === "MASK" ? { alphaCutoff: material.alphaCutoff ?? 0.5 } : {},
2502
+ doubleSided: material.doubleSided
2503
+ };
2504
+ }
2505
+ function createTriangleIndices(primitive, vertexCount) {
2506
+ const attribute = primitive.indices ? {
2507
+ value: primitive.indices.value,
2508
+ size: primitive.indices.components,
2509
+ normalized: primitive.indices.normalized
2510
+ } : void 0;
2511
+ if (primitive.mode !== 5) return attribute;
2512
+ const sourceIndices = validateMeshIndices(attribute, vertexCount, 5);
2513
+ const indexCount = sourceIndices?.length ?? vertexCount;
2514
+ if (!Number.isSafeInteger(indexCount) || indexCount < 3) {
2515
+ throw new import_core19.TileConversionError(
2516
+ "MESH_TRIANGLE_COUNT_INVALID",
2517
+ "Triangle strips require at least three vertices"
2518
+ );
2519
+ }
2520
+ const IndexArray = vertexCount < 65536 ? Uint16Array : Uint32Array;
2521
+ const indices = new IndexArray((indexCount - 2) * 3);
2522
+ for (let triangle = 0; triangle < indexCount - 2; triangle++) {
2523
+ const second = triangle + 1 + triangle % 2;
2524
+ const third = triangle + 2 - triangle % 2;
2525
+ indices[triangle * 3] = sourceIndices?.[triangle] ?? triangle;
2526
+ indices[triangle * 3 + 1] = sourceIndices?.[second] ?? second;
2527
+ indices[triangle * 3 + 2] = sourceIndices?.[third] ?? third;
2528
+ }
2529
+ return { value: indices, size: 1 };
2530
+ }
2531
+
2532
+ // src/v5/i3s-mesh-conversion.ts
2533
+ var import_i3s = require("@loaders.gl/i3s");
2534
+ var import_core21 = require("@loaders.gl/tile-converter/v5/core");
2535
+ var import_core22 = require("@loaders.gl/tile-converter/v5/core");
2536
+ function createI3SMeshConversionCodec(options) {
2537
+ const reference = options.spatialContext.spatialReference;
2538
+ const height = reference.targetHeightReference === "native" ? reference.heightReference : reference.targetHeightReference;
2539
+ if (!["native", "transformed"].includes(reference.status) || (reference.targetCrs || reference.sourceCrs) !== "EPSG:4978" || height !== "ellipsoidal" || reference.status === "native" && (!["xyz", "unknown"].includes(reference.axisOrder) || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter")))
2540
+ throw new import_core21.TileConversionError(
2541
+ "I3S_MESH_FRAME_UNSUPPORTED",
2542
+ "I3S mesh authoring requires resolved ECEF coordinates and ellipsoidal heights"
2543
+ );
2544
+ let objectIdOffset = 0;
2545
+ return {
2546
+ /** Places a source mesh in ECEF and authors one layer without modifying source arrays. */
2547
+ async *convert(resource, _inspection, signal) {
2548
+ signal?.throwIfAborted();
2549
+ const mesh = validateMeshGeometry(resource.mesh, true);
2550
+ const positions = mesh.attributes.POSITION.value;
2551
+ const context = options.spatialContext;
2552
+ const transformed = "transformGeometryAsync" in context ? await context.transformGeometryAsync(
2553
+ positions,
2554
+ mesh.attributes.NORMAL?.value,
2555
+ resource.normalReferenceFrame ?? options.normalReferenceFrame
2556
+ ) : {
2557
+ positions: context.transformPositions(positions),
2558
+ normals: mesh.attributes.NORMAL ? context.transformNormals(mesh.attributes.NORMAL.value, positions) : void 0
2559
+ };
2560
+ signal?.throwIfAborted();
2561
+ mesh.attributes.POSITION = { value: transformed.positions, size: 3 };
2562
+ if (mesh.attributes.NORMAL) mesh.attributes.NORMAL = { value: transformed.normals, size: 3 };
2563
+ let layer;
2564
+ try {
2565
+ const writerOptions = {
2566
+ ...options,
2567
+ material: resource.material,
2568
+ features: resource.features,
2569
+ objectIdOffset
2570
+ };
2571
+ layer = options.draco === false ? (0, import_i3s.encodeI3SMeshLayer)(mesh, writerOptions) : await (0, import_i3s.encodeI3SMeshLayerWithDraco)(mesh, writerOptions, options.dracoLibraryOptions);
2572
+ } catch (error) {
2573
+ throw new import_core21.TileConversionError(
2574
+ "I3S_MESH_PROFILE_UNSUPPORTED",
2575
+ error instanceof Error ? error.message : String(error)
2576
+ );
2577
+ }
2578
+ signal?.throwIfAborted();
2579
+ objectIdOffset += resource.features?.batches.reduce((total, batch) => total + batch.data.numRows, 0) ?? 1;
2580
+ yield { id: resource.id, ...layer };
2581
+ },
2582
+ /** Reports measured precision and explicitly authorized integer representation changes. */
2583
+ async validateOutput(resource) {
2584
+ return [
2585
+ ...resource.maximumPositionError > 0 ? [
2586
+ {
2587
+ code: "I3S_MESH_POSITION_ROUNDING",
2588
+ message: `I3S positions have up to ${resource.maximumPositionError} meters reconstruction error`,
2589
+ severity: "info",
2590
+ resourceId: resource.id
2591
+ }
2592
+ ] : [],
2593
+ ...resource.decimalStringFields.map((field) => ({
2594
+ code: "I3S_INTEGER_DECIMAL_STRING",
2595
+ message: `Explicitly represented 64-bit integer field ${field} as exact decimal text`,
2596
+ severity: "info",
2597
+ resourceId: resource.id
2598
+ }))
2599
+ ];
2600
+ }
2601
+ };
2602
+ }
2603
+ function createSingleMeshI3SSink(options) {
2604
+ const memory = (0, import_core22.createBoundedMemoryTileConversionSink)(options);
2605
+ let state = "empty";
2606
+ let completed = false;
2607
+ return {
2608
+ /** Exposes resources only after successful finalization. */
2609
+ getFiles: () => completed ? memory.getFiles() : [],
2610
+ /** Writes exactly one layer within the retained-resource budget. */
2611
+ async write(resource, signal) {
2612
+ signal?.throwIfAborted();
2613
+ if (state !== "empty")
2614
+ throw new import_core21.TileConversionError(
2615
+ "SINGLE_I3S_MESH_UNAVAILABLE",
2616
+ "The initial I3S sink accepts exactly one mesh layer"
2617
+ );
2618
+ state = "writing";
2619
+ for (const [resourceId, bytes] of Object.entries(resource.files))
2620
+ await memory.write(
2621
+ {
2622
+ resourceId,
2623
+ parts: [bytes],
2624
+ contentType: resourceId.endsWith(".png") ? "image/png" : resourceId.endsWith(".jpg") ? "image/jpeg" : "application/gzip"
2625
+ },
2626
+ signal
2627
+ );
2628
+ state = "written";
2629
+ },
2630
+ /** Commits a complete single-layer result. */
2631
+ async finalize(report) {
2632
+ if (state !== "written")
2633
+ throw new import_core21.TileConversionError(
2634
+ "SINGLE_I3S_MESH_INCOMPLETE",
2635
+ "Finalization requires exactly one successfully written layer"
2636
+ );
2637
+ await memory.finalize(report);
2638
+ state = "closed";
2639
+ completed = true;
2640
+ },
2641
+ /** Discards all resources after any conversion failure. */
2642
+ async abort(error) {
2643
+ completed = false;
2644
+ state = "closed";
2645
+ await memory.abort(error);
2646
+ }
2647
+ };
2648
+ }
2649
+
2650
+ // src/v5/i3s-mesh-sink.ts
2651
+ var import_geospatial = require("@math.gl/geospatial");
2652
+ var import_compression = require("@loaders.gl/compression");
2653
+ var import_core23 = require("@loaders.gl/tile-converter/v5/core");
2654
+ function createI3SMeshSink(options) {
2655
+ const { maxMeshes, maxResourceBytes } = options;
2656
+ if (!Number.isInteger(maxMeshes) || maxMeshes < 1 || maxMeshes > 64 || !Number.isSafeInteger(maxResourceBytes) || maxResourceBytes < 1)
2657
+ throw new import_core23.TileConversionError(
2658
+ "I3S_COLLECTION_OPTIONS_INVALID",
2659
+ "I3S collections require 1\u201364 meshes and a positive resource byte limit"
2660
+ );
2661
+ const memory = (0, import_core23.createBoundedMemoryTileConversionSink)(options);
2662
+ const compressor = new import_compression.GZipCompressor({ useNative: false, gzip: { mtime: 0 } });
2663
+ const decompressor = new import_compression.GZipDecompressor({ useNative: false });
2664
+ const leaves = [];
2665
+ const identifiers = /* @__PURE__ */ new Set();
2666
+ const objectIdRanges = [];
2667
+ let layer;
2668
+ let schemaKey;
2669
+ let state = "open";
2670
+ let completed = false;
2671
+ async function readBytes(bytes) {
2672
+ const output = await decompressor.decompress(bytes);
2673
+ if (output.byteLength > maxResourceBytes)
2674
+ throw new import_core23.TileConversionError(
2675
+ "I3S_COLLECTION_RESOURCE_TOO_LARGE",
2676
+ "I3S metadata exceeds maxResourceBytes"
2677
+ );
2678
+ return output;
2679
+ }
2680
+ async function readJson(resource, name) {
2681
+ return JSON.parse(new TextDecoder().decode(await readBytes(resource.files[name])));
2682
+ }
2683
+ async function writeJson(resourceId, value) {
2684
+ const bytes = new TextEncoder().encode(JSON.stringify(value));
2685
+ if (bytes.byteLength > maxResourceBytes)
2686
+ throw new import_core23.TileConversionError(
2687
+ "I3S_COLLECTION_RESOURCE_TOO_LARGE",
2688
+ "I3S index exceeds maxResourceBytes"
2689
+ );
2690
+ await memory.write({
2691
+ resourceId,
2692
+ parts: [await compressor.compress(bytes.buffer)],
2693
+ contentType: "application/gzip"
2694
+ });
2695
+ }
2696
+ return {
2697
+ /** Exposes the whole collection atomically. */
2698
+ getFiles: () => completed ? memory.getFiles() : [],
2699
+ /** Retains one leaf's binary resources and validates its shared schema and IDs. */
2700
+ async write(resource, signal) {
2701
+ signal?.throwIfAborted();
2702
+ if (state !== "open" || identifiers.has(resource.id) || leaves.length >= maxMeshes)
2703
+ throw new import_core23.TileConversionError(
2704
+ "I3S_COLLECTION_UNAVAILABLE",
2705
+ "I3S collection is busy, closed, full, or repeats a placement"
2706
+ );
2707
+ state = "writing";
2708
+ const authored = await readJson(resource, "3dSceneLayer.json.gz");
2709
+ const key = JSON.stringify([
2710
+ authored.fields,
2711
+ authored.attributeStorageInfo,
2712
+ authored.description,
2713
+ authored.store.defaultGeometrySchema
2714
+ ]);
2715
+ if (schemaKey !== void 0 && key !== schemaKey)
2716
+ throw new import_core23.TileConversionError(
2717
+ "I3S_COLLECTION_SCHEMA_MISMATCH",
2718
+ "Mesh nodes must share feature fields and the legacy geometry layout"
2719
+ );
2720
+ const objectIds = new DataView(
2721
+ await readBytes(resource.files["nodes/1/attributes/f_0/0.bin.gz"])
2722
+ );
2723
+ const count = objectIds.getUint32(0, true);
2724
+ const first = objectIds.getUint32(4, true);
2725
+ if (objectIds.byteLength !== 4 + count * 4 || !count || objectIdRanges.some(([start, end]) => first < end && first + count > start) || Array.from({ length: count }, (_, index2) => objectIds.getUint32(4 + index2 * 4, true)).some(
2726
+ (value, index2) => value !== first + index2
2727
+ ))
2728
+ throw new import_core23.TileConversionError(
2729
+ "I3S_COLLECTION_IDS_INVALID",
2730
+ "Generated object IDs must be contiguous and disjoint across nodes"
2731
+ );
2732
+ const index = leaves.length + 1;
2733
+ const node = (await readJson(resource, "nodepages/0.json.gz")).nodes[1];
2734
+ const legacy = await readJson(
2735
+ resource,
2736
+ "nodes/1/3dNodeIndexDocument.json.gz"
2737
+ );
2738
+ if (node.obb.quaternion.join(",") !== "0,0,0,1" || [...node.obb.center, ...node.obb.halfSize].some((value) => !Number.isFinite(value)) || node.obb.halfSize.some((value) => value < 0))
2739
+ throw new import_core23.TileConversionError(
2740
+ "I3S_COLLECTION_BOUNDS_INVALID",
2741
+ "Expected finite axis-aligned bounds from the I3S mesh writer"
2742
+ );
2743
+ if (!layer) {
2744
+ layer = structuredClone(authored);
2745
+ layer.materialDefinitions = [];
2746
+ layer.geometryDefinitions = [];
2747
+ layer.textureSetDefinitions = [];
2748
+ schemaKey = key;
2749
+ }
2750
+ const material = structuredClone(authored.materialDefinitions[0]);
2751
+ const texture = material.pbrMetallicRoughness?.baseColorTexture;
2752
+ if (texture) {
2753
+ texture.textureSetDefinitionId = layer.textureSetDefinitions.length;
2754
+ layer.textureSetDefinitions.push(authored.textureSetDefinitions[0]);
2755
+ node.mesh.material.resource = index;
2756
+ }
2757
+ node.index = index;
2758
+ node.parentIndex = 0;
2759
+ node.mesh.material.definition = layer.materialDefinitions.length;
2760
+ node.mesh.geometry.definition = layer.geometryDefinitions.length;
2761
+ node.mesh.geometry.resource = index;
2762
+ node.mesh.attribute.resource = index;
2763
+ layer.materialDefinitions.push(material);
2764
+ layer.geometryDefinitions.push(authored.geometryDefinitions[0]);
2765
+ layer.store.resourcePattern = [
2766
+ .../* @__PURE__ */ new Set([...layer.store.resourcePattern, ...authored.store.resourcePattern])
2767
+ ];
2768
+ layer.store.extent = layer.store.extent.map(
2769
+ (value, axis) => axis < 2 ? Math.min(value, authored.store.extent[axis]) : Math.max(value, authored.store.extent[axis])
2770
+ );
2771
+ for (const [name, bytes] of Object.entries(resource.files)) {
2772
+ if (name.startsWith("nodes/1/") && !name.endsWith("3dNodeIndexDocument.json.gz"))
2773
+ await memory.write(
2774
+ {
2775
+ resourceId: name.replace("nodes/1/", `nodes/${index}/`),
2776
+ parts: [bytes],
2777
+ contentType: name.endsWith(".gz") ? "application/gzip" : name.endsWith(".png") ? "image/png" : "image/jpeg"
2778
+ },
2779
+ signal
2780
+ );
2781
+ }
2782
+ leaves.push({ node, legacy: { ...legacy, id: String(index) } });
2783
+ identifiers.add(resource.id);
2784
+ objectIdRanges.push([first, first + count]);
2785
+ state = "open";
2786
+ },
2787
+ /** Rebuilds both indices and layer bounds only after every leaf has completed. */
2788
+ async finalize(report) {
2789
+ if (state !== "open" || !layer || !leaves.length)
2790
+ throw new import_core23.TileConversionError(
2791
+ "I3S_COLLECTION_INCOMPLETE",
2792
+ "Finalization requires successfully written mesh nodes"
2793
+ );
2794
+ state = "writing";
2795
+ const minimum = [Infinity, Infinity, Infinity];
2796
+ const maximum = [-Infinity, -Infinity, -Infinity];
2797
+ for (const { node } of leaves) {
2798
+ const center2 = import_geospatial.Ellipsoid.WGS84.cartographicToCartesian(node.obb.center);
2799
+ for (let axis = 0; axis < 3; axis++) {
2800
+ minimum[axis] = Math.min(minimum[axis], center2[axis] - node.obb.halfSize[axis]);
2801
+ maximum[axis] = Math.max(maximum[axis], center2[axis] + node.obb.halfSize[axis]);
2802
+ }
2803
+ }
2804
+ const center = minimum.map((value, axis) => value / 2 + maximum[axis] / 2);
2805
+ const geographicCenter = Array.from(import_geospatial.Ellipsoid.WGS84.cartesianToCartographic(center));
2806
+ const reconstructedCenter = import_geospatial.Ellipsoid.WGS84.cartographicToCartesian(geographicCenter);
2807
+ const obb = {
2808
+ center: geographicCenter,
2809
+ halfSize: minimum.map(
2810
+ (value, axis) => maximum[axis] / 2 - value / 2 + Math.abs(center[axis] - reconstructedCenter[axis]) + 1e-7
2811
+ ),
2812
+ quaternion: [0, 0, 0, 1]
2813
+ };
2814
+ if ([...obb.center, ...obb.halfSize].some((value) => !Number.isFinite(value)))
2815
+ throw new import_core23.TileConversionError(
2816
+ "I3S_COLLECTION_BOUNDS_INVALID",
2817
+ "Collection bounds cannot be represented"
2818
+ );
2819
+ const mbs = [...obb.center, Math.hypot(...obb.halfSize)];
2820
+ const children = leaves.map(({ node }) => ({
2821
+ id: String(node.index),
2822
+ href: `../${node.index}`,
2823
+ obb: node.obb,
2824
+ mbs: [...node.obb.center, Math.hypot(...node.obb.halfSize)]
2825
+ }));
2826
+ await writeJson("nodes/root/3dNodeIndexDocument.json.gz", {
2827
+ id: "root",
2828
+ level: 0,
2829
+ version: "1",
2830
+ obb,
2831
+ mbs,
2832
+ lodSelection: [{ metricType: "maxScreenThresholdSQ", maxError: 0 }],
2833
+ children
2834
+ });
2835
+ for (const { node, legacy } of leaves)
2836
+ await writeJson(`nodes/${node.index}/3dNodeIndexDocument.json.gz`, {
2837
+ ...legacy,
2838
+ parentNode: { id: "root", href: "../root", obb, mbs }
2839
+ });
2840
+ const nodes = [
2841
+ { index: 0, obb, lodThreshold: 0, children: leaves.map(({ node }) => node.index) },
2842
+ ...leaves.map(({ node }) => node)
2843
+ ];
2844
+ for (let page = 0; page * 64 < nodes.length; page++)
2845
+ await writeJson(`nodepages/${page}.json.gz`, {
2846
+ nodes: nodes.slice(page * 64, (page + 1) * 64)
2847
+ });
2848
+ if (!layer.textureSetDefinitions?.length) delete layer.textureSetDefinitions;
2849
+ await writeJson("3dSceneLayer.json.gz", layer);
2850
+ await writeJson("metadata.json.gz", {
2851
+ folderPattern: "BASIC",
2852
+ archiveCompressionType: "STORE",
2853
+ resourceCompressionType: "GZIP",
2854
+ I3SVersion: "1.7",
2855
+ nodeCount: nodes.length
2856
+ });
2857
+ await memory.finalize(report);
2858
+ state = "closed";
2859
+ completed = true;
2860
+ },
2861
+ /** Discards binary resources and index metadata after any failure. */
2862
+ async abort(error) {
2863
+ completed = false;
2864
+ state = "closed";
2865
+ leaves.length = 0;
2866
+ identifiers.clear();
2867
+ objectIdRanges.length = 0;
2868
+ layer = void 0;
2869
+ await memory.abort(error);
2870
+ }
2871
+ };
2872
+ }
2873
+
2874
+ // src/v5/conversion-api.ts
2875
+ var TileConversionError17 = class extends Error {
2876
+ /** Stable machine-readable failure code. */
2877
+ code;
2878
+ /** Diagnostics associated with this failure. */
2879
+ diagnostics;
2880
+ /** Creates a typed conversion failure. */
2881
+ constructor(code, message, diagnostics = []) {
2882
+ super(message);
2883
+ this.name = "TileConversionError";
2884
+ this.code = code;
2885
+ this.diagnostics = diagnostics;
2886
+ }
2887
+ };
2888
+
2889
+ // src/v5/resource-fetcher.ts
2890
+ function createTileConversionResourceFetcher(options) {
2891
+ let consumed = options.initialInputBytes ?? 0;
2892
+ if (!Number.isSafeInteger(options.maxInputBytes) || options.maxInputBytes < 1 || !Number.isSafeInteger(consumed) || consumed < 0 || consumed > options.maxInputBytes)
2893
+ throw new TileConversionError17(
2894
+ "INVALID_INPUT_MEMORY_LIMIT",
2895
+ "Resource fetch requires a positive aggregate byte budget and valid initial bytes"
2896
+ );
2897
+ const fetcher = options.fetcher ?? fetch;
2898
+ return async (input, requestOptions) => {
2899
+ options.signal?.throwIfAborted();
2900
+ const url = new URL(input instanceof Request ? input.url : String(input));
2901
+ if (!["http:", "https:", "blob:", "data:"].includes(url.protocol))
2902
+ throw new TileConversionError17(
2903
+ "EXTERNAL_RESOURCE_UNSUPPORTED",
2904
+ "Conversion transport requires HTTP(S), Blob or embedded data resources"
2905
+ );
2906
+ const requestSignal = requestOptions?.signal ?? (input instanceof Request ? input.signal : void 0);
2907
+ const signal = options.signal && requestSignal ? AbortSignal.any([options.signal, requestSignal]) : options.signal ?? requestSignal;
2908
+ const response = await fetcher(input, { ...requestOptions, signal });
2909
+ if (!response.ok) {
2910
+ await response.body?.cancel();
2911
+ throw new TileConversionError17(
2912
+ "BROWSER_SOURCE_FETCH_FAILED",
2913
+ `Could not fetch conversion resource (${response.status})`
2914
+ );
2915
+ }
2916
+ const reader = response.body?.getReader();
2917
+ if (!reader)
2918
+ throw new TileConversionError17(
2919
+ "BROWSER_SOURCE_STREAM_UNAVAILABLE",
2920
+ "Conversion resources require readable response streams"
2921
+ );
2922
+ const cancelRead = () => {
2923
+ void reader.cancel(signal?.reason).catch(() => {
2924
+ });
2925
+ };
2926
+ signal?.addEventListener("abort", cancelRead, { once: true });
2927
+ const parts = [];
2928
+ let byteLength = 0;
2929
+ try {
2930
+ while (true) {
2931
+ signal?.throwIfAborted();
2932
+ const { done, value } = await reader.read();
2933
+ signal?.throwIfAborted();
2934
+ if (done) break;
2935
+ if (value.byteLength > options.maxInputBytes - consumed)
2936
+ throw new TileConversionError17(
2937
+ "INPUT_RESOURCE_TOO_LARGE",
2938
+ "Selected contents and external dependencies exceed the aggregate input byte budget"
2939
+ );
2940
+ consumed += value.byteLength;
2941
+ byteLength += value.byteLength;
2942
+ parts.push(value);
2943
+ }
2944
+ } catch (error) {
2945
+ await reader.cancel(error);
2946
+ throw error;
2947
+ } finally {
2948
+ signal?.removeEventListener("abort", cancelRead);
2949
+ reader.releaseLock();
2950
+ }
2951
+ const bytes = new Uint8Array(byteLength);
2952
+ let offset = 0;
2953
+ for (const part of parts) {
2954
+ bytes.set(part, offset);
2955
+ offset += part.byteLength;
2956
+ }
2957
+ return new Response(bytes, {
2958
+ status: response.status,
2959
+ statusText: response.statusText,
2960
+ headers: response.headers
2961
+ });
2962
+ };
2963
+ }
2964
+
2965
+ // src/v5/point-cloud-tileset-sink.ts
2966
+ var import_core24 = require("@loaders.gl/tile-converter/v5/core");
2967
+ function createPointCloudTilesetSink(options) {
2968
+ const { maxTiles, geometricError } = options;
2969
+ if (!Number.isSafeInteger(maxTiles) || maxTiles < 1 || !Number.isFinite(geometricError) || geometricError < 0) {
2970
+ throw new import_core24.TileConversionError(
2971
+ "POINT_CLOUD_TILESET_OPTIONS_INVALID",
2972
+ "Positive maxTiles and finite nonnegative geometricError are required"
2973
+ );
2974
+ }
2975
+ const memory = (0, import_core24.createBoundedMemoryTileConversionSink)(options);
2976
+ const children = [];
2977
+ const identifiers = /* @__PURE__ */ new Set();
2978
+ const minimum = [Infinity, Infinity, Infinity];
2979
+ const maximum = [-Infinity, -Infinity, -Infinity];
2980
+ let state = "open";
2981
+ let completed = false;
2982
+ return {
2983
+ /** Exposes finalized files without exposing a partial package. */
2984
+ getFiles: () => completed ? memory.getFiles() : [],
2985
+ /** Captures one validated placement and encoded PNTS resource. */
2986
+ async write(resource, signal) {
2987
+ signal?.throwIfAborted();
2988
+ if (state !== "open")
2989
+ throw new import_core24.TileConversionError(
2990
+ "POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
2991
+ "Point sink is busy or closed"
2992
+ );
2993
+ if (!resource.id || identifiers.has(resource.id) || children.length >= maxTiles) {
2994
+ throw new import_core24.TileConversionError(
2995
+ "POINT_CLOUD_TILESET_PLACEMENT_INVALID",
2996
+ "Placements require unique nonempty identifiers within maxTiles"
2997
+ );
2998
+ }
2999
+ const [lower, upper] = getPointTileBounds(resource, geometricError);
3000
+ const resourceId = `points/${children.length}.pnts`;
3001
+ state = "writing";
3002
+ await memory.write(
3003
+ { resourceId, parts: [resource.pnts], contentType: "application/octet-stream" },
3004
+ signal
3005
+ );
3006
+ if (state !== "writing")
3007
+ throw new import_core24.TileConversionError(
3008
+ "POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
3009
+ "Point sink was aborted during writing"
3010
+ );
3011
+ children.push({
3012
+ boundingVolume: { box: createPointBoundsBox(lower, upper) },
3013
+ geometricError,
3014
+ content: { uri: resourceId }
3015
+ });
3016
+ identifiers.add(resource.id);
3017
+ for (let axis = 0; axis < 3; axis++) {
3018
+ minimum[axis] = Math.min(minimum[axis], lower[axis]);
3019
+ maximum[axis] = Math.max(maximum[axis], upper[axis]);
3020
+ }
3021
+ state = "open";
3022
+ },
3023
+ /** Serializes the enclosing root after all placements have completed. */
3024
+ async finalize(report) {
3025
+ if (state !== "open" || !children.length)
3026
+ throw new import_core24.TileConversionError(
3027
+ "POINT_CLOUD_TILESET_SINK_INCOMPLETE",
3028
+ "Finalization requires successfully written point tiles"
3029
+ );
3030
+ state = "writing";
3031
+ const rootError = Math.max(
3032
+ geometricError,
3033
+ Math.hypot(...minimum.map((value, axis) => maximum[axis] - value))
3034
+ );
3035
+ if (!Number.isFinite(rootError))
3036
+ throw new import_core24.TileConversionError(
3037
+ "POINT_CLOUD_TILESET_BOUNDS_INVALID",
3038
+ "Enclosing bounds must have a finite diagonal"
3039
+ );
3040
+ const json = JSON.stringify({
3041
+ asset: { version: "1.0" },
3042
+ geometricError: rootError,
3043
+ root: {
3044
+ boundingVolume: { box: createPointBoundsBox(minimum, maximum) },
3045
+ geometricError: rootError,
3046
+ refine: "ADD",
3047
+ children
3048
+ }
3049
+ });
3050
+ await memory.write({
3051
+ resourceId: "tileset.json",
3052
+ parts: [json],
3053
+ contentType: "application/json"
3054
+ });
3055
+ if (state !== "writing")
3056
+ throw new import_core24.TileConversionError(
3057
+ "POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
3058
+ "Point sink was aborted during finalization"
3059
+ );
3060
+ await memory.finalize(report);
3061
+ if (state !== "writing")
3062
+ throw new import_core24.TileConversionError(
3063
+ "POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
3064
+ "Point sink was aborted during finalization"
3065
+ );
3066
+ state = "closed";
3067
+ completed = true;
3068
+ },
3069
+ /** Clears resource and hierarchy storage on failure or cancellation. */
3070
+ async abort(error) {
3071
+ state = "closed";
3072
+ completed = false;
3073
+ children.length = 0;
3074
+ identifiers.clear();
3075
+ await memory.abort(error);
3076
+ }
3077
+ };
3078
+ }
3079
+ function getPointTileBounds(resource, geometricError) {
3080
+ const reference = resource.spatialReference;
3081
+ const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
3082
+ if (!reference || !["native", "transformed"].includes(reference.status) || (reference.targetCrs || reference.sourceCrs) !== "EPSG:4978" || (reference.targetHeightReference === "native" ? reference.heightReference : reference.targetHeightReference) !== "ellipsoidal" || resource.coordinateSystem !== "cartesian" || resource.cartographicOrigin.length !== 3 || resource.cartographicOrigin.some((value) => value !== 0) || resource.modelMatrix && (resource.modelMatrix.length !== 16 || resource.modelMatrix.some((value, index) => value !== identity[index])) || reference.status === "native" && (reference.coordinateFrame !== "geocentric" || reference.axisOrder !== "xyz" || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter"))) {
3083
+ throw new import_core24.TileConversionError(
3084
+ "POINT_CLOUD_TILESET_FRAME_UNSUPPORTED",
3085
+ "Point tiles require declared EPSG:4978 xyz meters, ellipsoidal heights and no additional source placement"
3086
+ );
3087
+ }
3088
+ if (!Number.isFinite(resource.maximumPositionError) || resource.maximumPositionError < 0 || resource.maximumPositionError > geometricError) {
3089
+ throw new import_core24.TileConversionError(
3090
+ "POINT_CLOUD_GEOMETRIC_ERROR_INVALID",
3091
+ "geometricError must include measured position reconstruction error"
3092
+ );
3093
+ }
3094
+ const [minimum, maximum] = resource.localBoundingBox;
3095
+ const center = resource.rtcCenter;
3096
+ if (center.length !== 3 || minimum.length !== 3 || maximum.length !== 3 || [...center, ...minimum, ...maximum].some((value) => !Number.isFinite(value)) || minimum.some((value, axis) => value > maximum[axis])) {
3097
+ throw new import_core24.TileConversionError(
3098
+ "POINT_CLOUD_TILESET_BOUNDS_INVALID",
3099
+ "RTC center and encoded xyz bounds must be finite and ordered"
3100
+ );
3101
+ }
3102
+ const lower = minimum.map((value, axis) => value + center[axis]);
3103
+ const upper = maximum.map((value, axis) => value + center[axis]);
3104
+ if ([...lower, ...upper].some((value) => !Number.isFinite(value)))
3105
+ throw new import_core24.TileConversionError(
3106
+ "POINT_CLOUD_TILESET_BOUNDS_INVALID",
3107
+ "Reconstructed ECEF bounds must be finite"
3108
+ );
3109
+ return [lower, upper];
3110
+ }
3111
+ function createPointBoundsBox(minimum, maximum) {
3112
+ const center = minimum.map((value, axis) => value / 2 + maximum[axis] / 2);
3113
+ const halfSize = minimum.map((value, axis) => maximum[axis] / 2 - value / 2);
3114
+ return [center[0], center[1], center[2], halfSize[0], 0, 0, 0, halfSize[1], 0, 0, 0, halfSize[2]];
3115
+ }
3116
+
3117
+ // src/v5/i3s-mesh-source.ts
3118
+ var import_core27 = require("@loaders.gl/tile-converter/v5/core");
3119
+
3120
+ // src/v5/i3s-mesh-source-material.ts
3121
+ var import_images3 = require("@loaders.gl/images");
3122
+ var import_core25 = require("@loaders.gl/tile-converter/v5/core");
3123
+ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, contentUri, signal) {
3124
+ const material = Object.fromEntries(
3125
+ Object.entries(content.material || {}).filter(([, value]) => value !== void 0)
3126
+ );
3127
+ const metallicRoughness = material.pbrMetallicRoughness ? Object.fromEntries(
3128
+ Object.entries(material.pbrMetallicRoughness).filter(([, value]) => value !== void 0)
3129
+ ) : {};
3130
+ material.pbrMetallicRoughness = metallicRoughness;
3131
+ if (material.cullFace !== void 0 && material.cullFace !== "back") {
3132
+ throw new import_core25.TileConversionError(
3133
+ "I3S_MESH_SOURCE_MATERIAL_UNSUPPORTED",
3134
+ "Only back-face culling is supported"
3135
+ );
3136
+ }
3137
+ delete material.cullFace;
3138
+ let textureInfo = metallicRoughness.baseColorTexture ? { ...metallicRoughness.baseColorTexture } : void 0;
3139
+ const resources = header.textureUrls?.length ? header.textureUrls : header.textureUrl ? [
3140
+ {
3141
+ textureSetDefinitionId: textureInfo?.textureSetDefinitionId ?? 0,
3142
+ textureUrl: header.textureUrl,
3143
+ textureFormat: header.textureFormat || "jpg"
3144
+ }
3145
+ ] : [];
3146
+ if (!textureInfo && !header.materialDefinition && resources.length === 1) {
3147
+ textureInfo = { textureSetDefinitionId: resources[0].textureSetDefinitionId };
3148
+ }
3149
+ const encodedTextures = content.textures || {};
3150
+ if (!textureInfo && (resources.length || content.texture || Object.keys(encodedTextures).length)) {
3151
+ throw new import_core25.TileConversionError(
3152
+ "I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
3153
+ "Texture resources require a base-color mapping"
3154
+ );
3155
+ }
3156
+ if (textureInfo) {
3157
+ const definitionId = textureInfo.textureSetDefinitionId;
3158
+ const wrappingModes = { none: 33071, repeat: 10497, mirror: 33648 };
3159
+ if (typeof definitionId !== "number" || !Number.isSafeInteger(definitionId) || definitionId < 0 || textureInfo.texCoord !== void 0 && textureInfo.texCoord !== 0 || Object.keys(textureInfo).some(
3160
+ (name) => !["textureSetDefinitionId", "texCoord", "wrapS", "wrapT", "texture"].includes(name)
3161
+ ) || ["wrapS", "wrapT"].some(
3162
+ (name) => textureInfo[name] !== void 0 && (typeof textureInfo[name] !== "string" || !Object.hasOwn(wrappingModes, textureInfo[name]))
3163
+ ) || resources.length > 1 || resources.some((resource) => resource.textureSetDefinitionId !== definitionId) || Object.keys(encodedTextures).some((key) => key !== String(definitionId))) {
3164
+ throw new import_core25.TileConversionError(
3165
+ "I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
3166
+ "Only one base-color texture set on TEXCOORD_0 with qualified wrapping is supported"
3167
+ );
3168
+ }
3169
+ let encoded = encodedTextures[String(definitionId)] || content.texture || textureInfo.texture?.source?.image;
3170
+ if (!encoded) {
3171
+ if (!resources.length || !readExternalResource) {
3172
+ throw new import_core25.TileConversionError(
3173
+ "I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
3174
+ "Encoded texture bytes or an explicit bounded resource reader are required"
3175
+ );
3176
+ }
3177
+ signal?.throwIfAborted();
3178
+ encoded = await readExternalResource(resources[0].textureUrl, contentUri, signal);
3179
+ signal?.throwIfAborted();
3180
+ }
3181
+ const data = encoded instanceof ArrayBuffer ? new Uint8Array(encoded) : encoded;
3182
+ let image;
3183
+ if (data instanceof Uint8Array) {
3184
+ try {
3185
+ image = (0, import_images3.getBinaryImageMetadata)(new DataView(data.buffer, data.byteOffset, data.byteLength));
3186
+ } catch {
3187
+ }
3188
+ }
3189
+ const declaredFormat = resources[0]?.textureFormat;
3190
+ if (!image || !["image/png", "image/jpeg"].includes(image.mimeType) || declaredFormat && (declaredFormat === "png" ? image.mimeType !== "image/png" : declaredFormat === "jpg" ? image.mimeType !== "image/jpeg" : true)) {
3191
+ throw new import_core25.TileConversionError(
3192
+ "I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
3193
+ "I3S base-color bytes must have matching PNG/JPEG headers"
3194
+ );
3195
+ }
3196
+ const sampler = Object.fromEntries(
3197
+ ["wrapS", "wrapT"].filter((name) => textureInfo[name] !== void 0).map((name) => [name, wrappingModes[textureInfo[name]]])
3198
+ );
3199
+ metallicRoughness.baseColorTexture = {
3200
+ texCoord: 0,
3201
+ texture: {
3202
+ source: { mimeType: image.mimeType, bufferView: { data } },
3203
+ ...Object.keys(sampler).length ? { sampler } : {}
3204
+ }
3205
+ };
3206
+ }
3207
+ const mapped = await mapMeshSourceMaterial(
3208
+ material,
3209
+ void 0,
3210
+ contentUri,
3211
+ signal
3212
+ );
3213
+ if (mapped?.baseColorTexture && !content.sourceAttributes?.uv0) {
3214
+ throw new import_core25.TileConversionError(
3215
+ "MESH_TEXCOORD_REQUIRED",
3216
+ "Base-color textures require TEXCOORD_0"
3217
+ );
3218
+ }
3219
+ return mapped;
3220
+ }
3221
+
3222
+ // src/v5/i3s-mesh-source-features.ts
3223
+ var import_i3s2 = require("@loaders.gl/i3s");
3224
+ var import_core26 = require("@loaders.gl/tile-converter/v5/core");
3225
+ async function extractI3SMeshFeatures(content, item, metadata, options, readExternalResource, contentUri, signal) {
3226
+ const maximumAttributeBytes = options.maxAttributeBytes;
3227
+ const storage = metadata.tileset.attributeStorageInfo;
3228
+ const attributeUrls = item.tile.header.attributeUrls;
3229
+ if (!options.metadataClass || !options.objectIdProperty || options.rawMetadataField || !Number.isSafeInteger(maximumAttributeBytes) || maximumAttributeBytes <= 0 || !storage?.length || attributeUrls?.length !== storage.length || !readExternalResource || new Set(storage.map((attribute2) => attribute2.name)).size !== storage.length || new Set(storage.map((attribute2) => attribute2.key)).size !== storage.length || !storage.some((attribute2) => attribute2.name === options.objectIdProperty) || (metadata.tileset.fields || []).some(
3230
+ (field) => field.domain || field.type === "esriFieldTypeDate" || !storage.some((attribute2) => attribute2.name === field.name)
3231
+ )) {
3232
+ throw new import_core26.TileConversionError(
3233
+ "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3234
+ "I3S attributes require an explicit scalar schema, object ID column, byte budget and resource reader; domains/dates/raw metadata need a custom mapper"
3235
+ );
3236
+ }
3237
+ const attributeLoader = await import_i3s2.I3SAttributeLoader.preload();
3238
+ const columns = /* @__PURE__ */ Object.create(null);
3239
+ let inputBytes = 0;
3240
+ let count;
3241
+ for (const [index, attribute2] of storage.entries()) {
3242
+ signal?.throwIfAborted();
3243
+ const values = attribute2.objectIds ?? attribute2.attributeValues;
3244
+ const type = values?.valueType;
3245
+ const expectedOrdering = attribute2.objectIds ? ["objectIds"] : type === "String" ? ["attributeByteCounts", "attributeValues"] : ["attributeValues"];
3246
+ const expectedHeader = type === "String" ? [
3247
+ { property: "count", valueType: "UInt32" },
3248
+ { property: "attributeValuesByteCount", valueType: "UInt32" }
3249
+ ] : [{ property: "count", valueType: "UInt32" }];
3250
+ if (!type || values?.valuesPerElement !== 1 || attribute2.objectIds && attribute2.attributeValues || attribute2.header?.length !== expectedHeader.length || expectedHeader.some(
3251
+ (value, headerIndex) => attribute2.header[headerIndex]?.property !== value.property || attribute2.header[headerIndex]?.valueType !== value.valueType
3252
+ ) || attribute2.ordering && JSON.stringify(
3253
+ attribute2.ordering.map((value) => value === "ObjectIds" ? "objectIds" : value)
3254
+ ) !== JSON.stringify(expectedOrdering) || type === "String" && (values.encoding !== "UTF-8" || attribute2.attributeByteCounts?.valueType !== "UInt32" || attribute2.attributeByteCounts.valuesPerElement !== 1) || !attributeUrls[index]) {
3255
+ throw new import_core26.TileConversionError(
3256
+ "I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
3257
+ `Attribute ${attribute2.name} requires a standard scalar/string resource layout`
3258
+ );
3259
+ }
3260
+ const bytes = await readExternalResource(attributeUrls[index], contentUri, signal);
3261
+ signal?.throwIfAborted();
3262
+ inputBytes += bytes instanceof Uint8Array ? bytes.byteLength : Infinity;
3263
+ if (!(bytes instanceof Uint8Array) || inputBytes > maximumAttributeBytes) {
3264
+ throw new import_core26.TileConversionError(
3265
+ "INPUT_RESOURCE_TOO_LARGE",
3266
+ "I3S node attributes exceed maxAttributeBytes"
3267
+ );
3268
+ }
3269
+ let parsed;
3270
+ try {
3271
+ parsed = await attributeLoader.parse(Uint8Array.from(bytes).buffer, {
3272
+ attributeName: attribute2.name,
3273
+ attributeType: type,
3274
+ i3s: { attributeValues: "exact" }
3275
+ });
3276
+ } catch (error) {
3277
+ throw new import_core26.TileConversionError(
3278
+ "I3S_MESH_SOURCE_ATTRIBUTE_INVALID",
3279
+ `Invalid attribute ${attribute2.name}: ${error instanceof Error ? error.message : String(error)}`
3280
+ );
3281
+ }
3282
+ const column = parsed[attribute2.name];
3283
+ if (!column || count !== void 0 && column.length !== count || !column.length) {
3284
+ throw new import_core26.TileConversionError(
3285
+ "I3S_MESH_SOURCE_ATTRIBUTE_COUNT_INVALID",
3286
+ "Every attribute must have the same nonzero row count"
3287
+ );
3288
+ }
3289
+ if (Array.from(column).some(
3290
+ (value) => typeof value === "number" && !Number.isFinite(value)
3291
+ )) {
3292
+ throw new import_core26.TileConversionError(
3293
+ "I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
3294
+ "Numeric noData values require an explicit custom mapper"
3295
+ );
3296
+ }
3297
+ count = column.length;
3298
+ columns[attribute2.name] = column;
3299
+ }
3300
+ const identifiers = columns[options.objectIdProperty];
3301
+ const attributeRows = /* @__PURE__ */ new Map();
3302
+ for (let index = 0; index < count; index++) {
3303
+ const identifier = identifiers[index];
3304
+ if (!(typeof identifier === "bigint" || typeof identifier === "number" && Number.isSafeInteger(identifier)) || attributeRows.has(BigInt(identifier))) {
3305
+ throw new import_core26.TileConversionError(
3306
+ "I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
3307
+ "Object IDs must be unique exact integers"
3308
+ );
3309
+ }
3310
+ attributeRows.set(BigInt(identifier), index);
3311
+ }
3312
+ const vertexCount = content.vertexCount;
3313
+ const geometryIdentifiers = content.featureIds;
3314
+ const attribute = content.sourceAttributes?.id;
3315
+ if (geometryIdentifiers.length !== vertexCount || attribute?.size !== 1 || attribute.normalized || attribute.value.length !== vertexCount || Array.from(geometryIdentifiers).some((value, index) => value !== attribute.value[index])) {
3316
+ throw new import_core26.TileConversionError(
3317
+ "I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
3318
+ "Geometry requires one exact unnormalized object ID per vertex"
3319
+ );
3320
+ }
3321
+ const indices = content.indices;
3322
+ const triangleFeatureIndices = new Uint32Array((indices?.length ?? vertexCount) / 3);
3323
+ if (content.drawRanges?.length) {
3324
+ const covered = new Uint8Array(triangleFeatureIndices.length);
3325
+ for (const range of content.drawRanges) {
3326
+ if (!Number.isSafeInteger(range.firstPrimitive) || range.firstPrimitive < 0 || !Number.isSafeInteger(range.primitiveCount) || range.primitiveCount <= 0 || range.firstPrimitive + range.primitiveCount > covered.length) {
3327
+ throw new import_core26.TileConversionError(
3328
+ "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3329
+ "Feature draw ranges must cover valid triangles"
3330
+ );
3331
+ }
3332
+ for (let triangle = range.firstPrimitive; triangle < range.firstPrimitive + range.primitiveCount; triangle++) {
3333
+ const vertex = indices?.[triangle * 3] ?? triangle * 3;
3334
+ if (covered[triangle] || geometryIdentifiers[vertex] !== range.featureId) {
3335
+ throw new import_core26.TileConversionError(
3336
+ "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3337
+ "Feature draw ranges must agree with decoded ownership without overlapping"
3338
+ );
3339
+ }
3340
+ covered[triangle] = 1;
3341
+ }
3342
+ }
3343
+ if (covered.some((value) => value === 0)) {
3344
+ throw new import_core26.TileConversionError(
3345
+ "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3346
+ "Feature draw ranges must cover every triangle"
3347
+ );
3348
+ }
3349
+ }
3350
+ const usedRows = /* @__PURE__ */ new Set();
3351
+ for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
3352
+ const corners = [0, 1, 2].map(
3353
+ (corner) => indices?.[triangle * 3 + corner] ?? triangle * 3 + corner
3354
+ );
3355
+ const featureIds = corners.map((corner) => geometryIdentifiers[corner]);
3356
+ const row = attributeRows.get(BigInt(featureIds[0]));
3357
+ if (row === void 0 || featureIds.some((identifier) => identifier !== featureIds[0])) {
3358
+ throw new import_core26.TileConversionError(
3359
+ "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3360
+ "Each triangle must reference one declared attribute row"
3361
+ );
3362
+ }
3363
+ triangleFeatureIndices[triangle] = row;
3364
+ usedRows.add(row);
3365
+ }
3366
+ if (usedRows.size !== count) {
3367
+ throw new import_core26.TileConversionError(
3368
+ "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3369
+ "Attribute rows without geometry require a separately qualified mapping"
3370
+ );
3371
+ }
3372
+ const stableProperty = options.sourceFeatureIdProperty ?? options.objectIdProperty;
3373
+ if (!Object.hasOwn(columns, stableProperty)) {
3374
+ throw new import_core26.TileConversionError(
3375
+ "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3376
+ "Stable identifier column is missing"
3377
+ );
3378
+ }
3379
+ const batches = convertFeatureAttributesToArrowBatches(
3380
+ Array.from({ length: count }, (_, index) => ({
3381
+ featureId: columns[stableProperty][index],
3382
+ metadataClass: options.metadataClass,
3383
+ properties: Object.fromEntries(
3384
+ Object.entries(columns).filter(([name]) => name !== options.objectIdProperty && name !== stableProperty).map(([name, values]) => [name, values[index]])
3385
+ )
3386
+ })),
3387
+ options
3388
+ );
3389
+ const stableIds = /* @__PURE__ */ new Set();
3390
+ for (const batch of batches) {
3391
+ const column = batch.data.getChild(options.featureIdField || "feature_id");
3392
+ for (let index = 0; index < column.length; index++) {
3393
+ const identifier = column.get(index);
3394
+ if (stableIds.has(identifier)) {
3395
+ throw new import_core26.TileConversionError(
3396
+ "I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
3397
+ "Stable IDs must be unique"
3398
+ );
3399
+ }
3400
+ stableIds.add(identifier);
3401
+ }
3402
+ }
3403
+ return {
3404
+ batches,
3405
+ triangleFeatureIndices,
3406
+ featureIdField: options.featureIdField || "feature_id",
3407
+ integer64Encoding: options.integer64Encoding
3408
+ };
3409
+ }
3410
+
3411
+ // src/v5/i3s-mesh-source.ts
3412
+ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3413
+ const source = (0, import_core27.createTilesetConversionSource)(tileset, options);
3414
+ return {
3415
+ /** Validates the layer profile and explicit source decode policy before reading. */
3416
+ async inspect(signal) {
3417
+ if (options.features && options.getFeatures) {
3418
+ throw new import_core27.TileConversionError(
3419
+ "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3420
+ "Choose explicit resource features or a custom getFeatures mapper, not both"
3421
+ );
3422
+ }
3423
+ const metadata = await source.inspect(signal);
3424
+ if (metadata.type !== "I3S" || !["3DObject", "IntegratedMesh"].includes(metadata.tileset.layerType) || tileset.options.i3s?.geometryMode !== "source") {
3425
+ throw new import_core27.TileConversionError(
3426
+ "I3S_MESH_SOURCE_PROFILE_UNSUPPORTED",
3427
+ "I3S mesh extraction requires a 3DObject/IntegratedMesh runtime with geometryMode=source"
3428
+ );
3429
+ }
3430
+ if (!metadata.spatialReference?.sourceCrs) {
3431
+ throw new import_core27.TileConversionError(
3432
+ "I3S_MESH_SOURCE_REFERENCE_REQUIRED",
3433
+ "I3S conversion requires an explicit source CRS"
3434
+ );
3435
+ }
3436
+ return metadata;
3437
+ },
3438
+ /** Converts each supported content entry before shared traversal releases its buffers. */
3439
+ async *read(metadata, signal) {
3440
+ for await (const item of source.read(metadata, signal)) {
3441
+ for (const entry of item.contents) {
3442
+ signal?.throwIfAborted();
3443
+ const content = entry.payload;
3444
+ const attributes = content?.sourceAttributes;
3445
+ if (content?.geometryMode !== "source" || content.topology !== "triangle-list" || !attributes || !(attributes.position?.value instanceof Float64Array) || content.spatialReference?.status !== "native" || [
3446
+ "sourceCrs",
3447
+ "verticalCrs",
3448
+ "axisOrder",
3449
+ "units",
3450
+ "verticalUnitScale",
3451
+ "heightReference",
3452
+ "elevationMode",
3453
+ "elevationOffset",
3454
+ "elevationUnitScale",
3455
+ "coordinateEpoch"
3456
+ ].some(
3457
+ (field) => JSON.stringify(content.spatialReference?.[field]) !== JSON.stringify(metadata.spatialReference?.[field])
3458
+ )) {
3459
+ throw new import_core27.TileConversionError(
3460
+ "I3S_MESH_SOURCE_GEOMETRY_UNSUPPORTED",
3461
+ "I3S extraction requires original source-frame Float64 triangle geometry"
3462
+ );
3463
+ }
3464
+ if (!["earth-centered", "vertex-reference-frame"].includes(
3465
+ content.normalReferenceFrame || ""
3466
+ ) || content.normalReferenceFrame !== (metadata.tileset.store?.normalReferenceFrame || "earth-centered")) {
3467
+ throw new import_core27.TileConversionError(
3468
+ "I3S_MESH_SOURCE_NORMAL_FRAME_UNSUPPORTED",
3469
+ "Unknown I3S vector basis"
3470
+ );
3471
+ }
3472
+ if (content.meshSegmentation || Object.entries(attributes).some(
3473
+ ([name, value]) => value && !["position", "normal", "uv0", "id", "faceRange"].includes(name)
3474
+ )) {
3475
+ throw new import_core27.TileConversionError(
3476
+ "I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
3477
+ "Only positions, normals, TEXCOORD_0 and explicitly mapped feature associations are supported"
3478
+ );
3479
+ }
3480
+ const hasFeatures = content.featureIds.length > 0 || metadata.tileset.fields?.length || metadata.tileset.attributeStorageInfo?.length;
3481
+ if (Array.from(content.featureIds).some((value) => !Number.isSafeInteger(value))) {
3482
+ throw new import_core27.TileConversionError(
3483
+ "I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
3484
+ "Decoded numeric feature IDs must be exact safe integers"
3485
+ );
3486
+ }
3487
+ if (hasFeatures && !options.getFeatures && !options.features) {
3488
+ throw new import_core27.TileConversionError(
3489
+ "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3490
+ "I3S feature data requires explicit resource features or a custom Arrow mapper"
3491
+ );
3492
+ }
3493
+ const mesh = validateMeshGeometry(
3494
+ {
3495
+ mode: 4,
3496
+ topology: "triangle-list",
3497
+ attributes: {
3498
+ POSITION: attributes.position,
3499
+ ...attributes.normal ? { NORMAL: attributes.normal } : {},
3500
+ ...attributes.uv0 ? { TEXCOORD_0: attributes.uv0 } : {}
3501
+ },
3502
+ ...content.indices ? { indices: { value: content.indices, size: 1 } } : {}
3503
+ },
3504
+ true
3505
+ );
3506
+ if (mesh.attributes.POSITION.value.length / 3 !== content.vertexCount) {
3507
+ throw new import_core27.TileConversionError(
3508
+ "I3S_MESH_SOURCE_COUNT_INVALID",
3509
+ "I3S vertexCount must match the decoded geometry"
3510
+ );
3511
+ }
3512
+ const features = options.getFeatures ? await options.getFeatures(content, item, metadata, signal) : options.features ? await extractI3SMeshFeatures(
3513
+ content,
3514
+ item,
3515
+ metadata,
3516
+ options.features,
3517
+ options.readExternalResource,
3518
+ entry.uri,
3519
+ signal
3520
+ ) : void 0;
3521
+ signal?.throwIfAborted();
3522
+ if (hasFeatures && !features) {
3523
+ throw new import_core27.TileConversionError(
3524
+ "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3525
+ "The feature mapper must return all declared feature data"
3526
+ );
3527
+ }
3528
+ const mappedMaterial = await mapI3SMeshSourceMaterial(
3529
+ content,
3530
+ item.tile.header,
3531
+ options.readExternalResource,
3532
+ entry.uri,
3533
+ signal
3534
+ );
3535
+ signal?.throwIfAborted();
3536
+ yield {
3537
+ id: `${item.tile.id}/${entry.index}`,
3538
+ origin: [0, 0, 0],
3539
+ mesh,
3540
+ normalReferenceFrame: content.normalReferenceFrame === "vertex-reference-frame" ? "vertex-reference-frame" : "earth-centered",
3541
+ features,
3542
+ material: mappedMaterial
3543
+ };
3544
+ }
3545
+ }
3546
+ }
3547
+ };
3548
+ }