@loaders.gl/tile-converter 5.0.0-alpha.7 → 5.0.0-alpha.9

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 (106) hide show
  1. package/README.md +389 -0
  2. package/dist/converter.min.cjs +194 -175
  3. package/dist/i3s-server/bin/i3s-server.min.cjs +84 -84
  4. package/dist/index.cjs +37747 -0
  5. package/dist/index.js +37718 -0
  6. package/dist/v5/browser-sink.d.ts +37 -0
  7. package/dist/v5/browser-source.d.ts +38 -0
  8. package/dist/v5/browser-tileset-source.d.ts +70 -0
  9. package/dist/v5/browser.cjs +10165 -0
  10. package/dist/v5/browser.d.ts +30 -0
  11. package/dist/v5/browser.js +10140 -0
  12. package/dist/v5/conversion-api.d.ts +122 -0
  13. package/dist/v5/conversion-archive.d.ts +18 -0
  14. package/dist/v5/feature-arrow.d.ts +36 -0
  15. package/dist/v5/index.cjs +11446 -0
  16. package/dist/v5/index.d.ts +28 -0
  17. package/dist/v5/index.js +11410 -0
  18. package/dist/v5/mesh-conversion.d.ts +55 -0
  19. package/dist/v5/mesh-tileset-archive.d.ts +16 -0
  20. package/dist/v5/mesh-tileset-sink.d.ts +31 -0
  21. package/dist/v5/mesh.d.ts +73 -0
  22. package/dist/v5/point-cloud-conversion.d.ts +31 -0
  23. package/dist/v5/point-cloud-source-encoder.d.ts +49 -0
  24. package/dist/v5/point-cloud-source.d.ts +25 -0
  25. package/dist/v5/point-cloud.d.ts +24 -0
  26. package/dist/v5/resource-manifest.d.ts +58 -0
  27. package/dist/v5/spatial-conversion.d.ts +59 -0
  28. package/dist/v5/tileset-source.d.ts +21 -0
  29. package/package.json +41 -26
  30. package/src/common/README.md +5 -0
  31. package/src/index.ts +1 -2
  32. package/src/{3d-tiles-converter → v4/3d-tiles-converter}/3d-tiles-converter.ts +13 -2
  33. package/src/v4/3d-tiles-converter/helpers/validate-i3s-spatial-reference.ts +124 -0
  34. package/src/{deps-installer → v4/deps-installer}/deps-installer.ts +10 -7
  35. package/src/v4/i3s-converter/helpers/encode-string-attribute.ts +23 -0
  36. package/src/{i3s-converter → v4/i3s-converter}/helpers/geometry-converter.ts +15 -42
  37. package/src/{i3s-converter → v4/i3s-converter}/helpers/gltf-attributes.ts +5 -4
  38. package/src/{i3s-converter → v4/i3s-converter}/helpers/load-3d-tiles.ts +5 -9
  39. package/src/{i3s-converter → v4/i3s-converter}/i3s-converter.ts +6 -7
  40. package/src/{i3s-server → v4/i3s-server}/README.md +2 -2
  41. package/src/{i3s-server → v4/i3s-server}/routes/slpk-router.ts +1 -0
  42. package/src/v4/index.ts +2 -0
  43. package/src/v5/browser-sink.ts +130 -0
  44. package/src/v5/browser-source.ts +155 -0
  45. package/src/v5/browser-tileset-source.ts +328 -0
  46. package/src/v5/browser.ts +77 -0
  47. package/src/v5/conversion-api.ts +287 -0
  48. package/src/v5/conversion-archive.ts +51 -0
  49. package/src/v5/feature-arrow.ts +427 -0
  50. package/src/v5/index.ts +68 -0
  51. package/src/v5/mesh-conversion.ts +177 -0
  52. package/src/v5/mesh-tileset-archive.ts +48 -0
  53. package/src/v5/mesh-tileset-sink.ts +178 -0
  54. package/src/v5/mesh.ts +503 -0
  55. package/src/v5/point-cloud-conversion.ts +80 -0
  56. package/src/v5/point-cloud-source-encoder.ts +102 -0
  57. package/src/v5/point-cloud-source.ts +84 -0
  58. package/src/v5/point-cloud.ts +271 -0
  59. package/src/v5/resource-manifest.ts +213 -0
  60. package/src/v5/spatial-conversion.ts +305 -0
  61. package/src/v5/tileset-source.ts +54 -0
  62. /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/3d-tiles-content-converter.ts +0 -0
  63. /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/i3s-obb-to-3d-tiles-obb.ts +0 -0
  64. /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/load-i3s.ts +0 -0
  65. /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/texture-atlas.ts +0 -0
  66. /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/json-templates/tileset.ts +0 -0
  67. /package/src/{constants.ts → v4/constants.ts} +0 -0
  68. /package/src/{converter-cli.ts → v4/converter-cli.ts} +0 -0
  69. /package/src/{i3s-converter → v4/i3s-converter}/helpers/attribute-metadata-info.ts +0 -0
  70. /package/src/{i3s-converter → v4/i3s-converter}/helpers/batch-ids-extensions.ts +0 -0
  71. /package/src/{i3s-converter → v4/i3s-converter}/helpers/coordinate-converter.ts +0 -0
  72. /package/src/{i3s-converter → v4/i3s-converter}/helpers/create-scene-server-path.ts +0 -0
  73. /package/src/{i3s-converter → v4/i3s-converter}/helpers/feature-attributes.ts +0 -0
  74. /package/src/{i3s-converter → v4/i3s-converter}/helpers/geometry-attributes.ts +0 -0
  75. /package/src/{i3s-converter → v4/i3s-converter}/helpers/node-debug.ts +0 -0
  76. /package/src/{i3s-converter → v4/i3s-converter}/helpers/node-index-document.ts +0 -0
  77. /package/src/{i3s-converter → v4/i3s-converter}/helpers/node-pages.ts +0 -0
  78. /package/src/{i3s-converter → v4/i3s-converter}/helpers/preprocess-3d-tiles.ts +0 -0
  79. /package/src/{i3s-converter → v4/i3s-converter}/helpers/progress.ts +0 -0
  80. /package/src/{i3s-converter → v4/i3s-converter}/helpers/tileset-traversal.ts +0 -0
  81. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/geometry-definitions.ts +0 -0
  82. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/layers.ts +0 -0
  83. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/metadata.ts +0 -0
  84. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/node.ts +0 -0
  85. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/scene-server.ts +0 -0
  86. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/shared-resources.ts +0 -0
  87. /package/src/{i3s-converter → v4/i3s-converter}/json-templates/store.ts +0 -0
  88. /package/src/{i3s-converter → v4/i3s-converter}/types.ts +0 -0
  89. /package/src/{i3s-server → v4/i3s-server}/app.ts +0 -0
  90. /package/src/{i3s-server → v4/i3s-server}/bin/www.ts +0 -0
  91. /package/src/{i3s-server → v4/i3s-server}/certs/cert.pem +0 -0
  92. /package/src/{i3s-server → v4/i3s-server}/certs/key.pem +0 -0
  93. /package/src/{i3s-server → v4/i3s-server}/controllers/slpk-controller.ts +0 -0
  94. /package/src/{i3s-server → v4/i3s-server}/utils/create-scene-server.ts +0 -0
  95. /package/src/{i3s-server → v4/i3s-server}/utils/server-utils.ts +0 -0
  96. /package/src/{lib → v4/lib}/json-schemas/conversion-dump-json-schema.ts +0 -0
  97. /package/src/{lib → v4/lib}/utils/cli-utils.ts +0 -0
  98. /package/src/{lib → v4/lib}/utils/compress-util.ts +0 -0
  99. /package/src/{lib → v4/lib}/utils/conversion-dump.ts +0 -0
  100. /package/src/{lib → v4/lib}/utils/file-utils.ts +0 -0
  101. /package/src/{lib → v4/lib}/utils/geometry-utils.ts +0 -0
  102. /package/src/{lib → v4/lib}/utils/lod-conversion-utils.ts +0 -0
  103. /package/src/{lib → v4/lib}/utils/queue.ts +0 -0
  104. /package/src/{lib → v4/lib}/utils/statistic-utils.ts +0 -0
  105. /package/src/{lib → v4/lib}/utils/write-queue.ts +0 -0
  106. /package/src/{pgm-loader.ts → v4/pgm-loader.ts} +0 -0
package/README.md CHANGED
@@ -6,6 +6,395 @@ This module contains command line scripts and JavaScript APIs for converting bet
6
6
 
7
7
  For documentation please visit the [website](https://loaders.gl).
8
8
 
9
+ ## Source layout
10
+
11
+ The original converter implementation lives in `src/v4`, and the newer conversion APIs live in
12
+ `src/v5`. The package root remains the compatibility entrypoint for the original converter. Shared
13
+ implementation belongs in `src/common` only when it is intentionally used by multiple versions.
14
+
15
+ ## V5 source-backed mesh traversal
16
+
17
+ `createTilesetConversionSource(tileset)` adapts an initializing or initialized `Tileset3D` backed
18
+ by `Tiles3DSource`, `I3SSource`, or a compatible archive source to the v5 conversion core. Inspection
19
+ returns normalized source metadata after initialization. Reading delegates to shared
20
+ camera-independent traversal and yields one placement with all its ordered decoded content entries,
21
+ including empty placements. A codec can inspect each content's payload, feature descriptors and
22
+ bounds together with the tile's placement transform.
23
+
24
+ ```ts
25
+ import {createTilesetConversionSource, convertTileset} from '@loaders.gl/tile-converter/v5';
26
+
27
+ const report = await convertTileset({
28
+ source: createTilesetConversionSource(tileset, {unloadContent: true}),
29
+ codec: meshCodec,
30
+ sink: outputSink,
31
+ measureInputBytes: measureDecodedTileBytes,
32
+ measureOutputBytes: measureEncodedResourceBytes,
33
+ maxInputResourceBytes: 16 * 1024 * 1024,
34
+ signal: abortController.signal
35
+ });
36
+ ```
37
+
38
+ Writes are awaited by the core before traversal advances. The example uses a runtime dedicated to
39
+ conversion and enables `unloadContent` to release newly loaded payloads after their codec and writes
40
+ finish. Cleanup also runs when iteration closes on failure or cancellation, including rejection by
41
+ the input-size gate. Consume payloads before advancing; codecs and sinks must not retain them after
42
+ their work completes. Preloaded payloads are retained. Omit the option to keep all loaded content
43
+ attached, as before. Applications still own tileset destruction and source/archive lifetimes.
44
+ With `Tiles3DSource`, repeated reads reuse installed nested roots and descendants while reloading
45
+ their payloads. Separate content slots and parent placements retain their own nested roots even
46
+ when they reference the same resource URL.
47
+
48
+ This adapter does not bound aggregate input memory, apply CRS transforms, or supply a mesh codec
49
+ or output packaging. Inspection observes
50
+ cancellation before and after initialization; initialization and in-flight content reads retain the
51
+ underlying source's cancellation behavior.
52
+
53
+ `maxInputResourceBytes` limits one decoded input placement before the codec runs. The example
54
+ allows up to 16 MiB as measured by the application's `measureInputBytes` callback, which must return
55
+ a nonnegative safe integer and account for all decoded contents in the placement. Equality is
56
+ allowed; zero permits only empty inputs. Omit the limit or use `Infinity` for no limit. Oversized
57
+ inputs throw `INPUT_RESOURCE_TOO_LARGE`, close source iteration, and abort the destination.
58
+ This check runs after the source read, so decoding allocations and retained source content still
59
+ require source-level controls. `convertPointCloudSource` accepts the same option for decoded tiles.
60
+
61
+ ## V5 single mesh encoding
62
+
63
+ `encodeMeshTile(mesh, {material})` is available from both `/v5` and `/v5/browser`. It encodes one
64
+ triangle-list `MeshGeometry` (`mode: 4`) into a self-contained glTF 2.0 GLB.
65
+
66
+ ```ts
67
+ import {encodeMeshTile} from '@loaders.gl/tile-converter/v5/browser';
68
+
69
+ const glb = encodeMeshTile({
70
+ topology: 'triangle-list',
71
+ mode: 4,
72
+ attributes: {
73
+ POSITION: {value: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), size: 3}
74
+ }
75
+ });
76
+ ```
77
+
78
+ The initial profile accepts finite packed Float32 `POSITION` and optional `NORMAL` xyz
79
+ triples. Normals must have unit length within 0.0001 and match the vertex count. Optional
80
+ indices are packed Uint8, Uint16, or Uint32 scalars forming complete triangles, referencing
81
+ existing vertices and excluding each type's maximum primitive-restart value. Typed array
82
+ subviews are supported; descriptor offsets/strides and encoded transforms
83
+ are rejected. Normalization is supported only for the selected integer colors and UVs below. Other attributes fail with typed diagnostics instead of disappearing.
84
+
85
+ The encoder preserves coordinates and does not mutate input arrays. Callers select a local
86
+ coordinate frame and own CRS conversion, double-precision origins, placement, source
87
+ material mapping, feature mappings, and tileset packaging. This geometry helper does not extract an entire
88
+ source scene or preserve its metadata and appearance automatically.
89
+
90
+ ### Selected appearance
91
+
92
+ Optional `COLOR_0` contains packed Float32 linear RGB (`size: 3`) or RGBA (`size: 4`)
93
+ values in [0, 1], or packed `Uint8Array`/`Uint16Array` values with `normalized: true`, one per
94
+ position. Integer colors represent linear values divided by 255 or 65535, including alpha;
95
+ no sRGB conversion is performed. Component types, normalization, and subview values are preserved.
96
+ Other color sets remain unsupported.
97
+
98
+ One optional `MeshTileMaterial` applies to every triangle:
99
+
100
+ ```ts
101
+ const glb = encodeMeshTile(mesh, {
102
+ material: {
103
+ baseColorFactor: [0.8, 0.5, 0.2, 0.75],
104
+ metallicFactor: 0,
105
+ roughnessFactor: 1,
106
+ alphaMode: 'BLEND',
107
+ doubleSided: true
108
+ }
109
+ });
110
+ ```
111
+
112
+ Base color is a linear RGBA multiplier in [0, 1], multiplied by `COLOR_0` when present.
113
+ Metallic/roughness factors are in [0, 1]. Alpha mode is `OPAQUE`, `MASK`, or `BLEND`;
114
+ finite nonnegative `alphaCutoff` is allowed only with `MASK`. Omitted properties retain
115
+ [glTF defaults](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#materials):
116
+ white base color, metallic/roughness 1, opaque alpha, mask cutoff 0.5, and single-sided rendering.
117
+ No material is added unless explicitly supplied. Unsupported material properties and extensions
118
+ fail with `MESH_MATERIAL_UNSUPPORTED`; invalid values fail with
119
+ `MESH_MATERIAL_INVALID`. Invalid color values/counts/types fail with `MESH_COLOR_INVALID`.
120
+ Inputs are not mutated. Applications explicitly map source colors and materials into this
121
+ subset; other texture maps, multiple materials, and
122
+ feature mappings remain open work.
123
+
124
+ Optional `TEXCOORD_0` contains packed finite Float32 or explicitly normalized Uint8/Uint16
125
+ UV pairs (`size: 2`), one per position. Float32 values outside [0, 1] are allowed for repeating
126
+ textures; integer values represent [0, 1] after division by 255 or 65535. Integer storage requires
127
+ `normalized: true`; signed, Uint32, and normalized floating-point appearance attributes are rejected.
128
+ The glTF writer pads vertex elements to four-byte alignment without changing their values.
129
+ Texture transforms apply to the normalized UV values. A selected `baseColorTexture` requires
130
+ these UVs and embeds an already encoded PNG or JPEG:
131
+
132
+ ```ts
133
+ const glb = encodeMeshTile(meshWithUvs, {
134
+ material: {
135
+ baseColorTexture: {
136
+ data: encodedImageBytes,
137
+ mimeType: 'image/png',
138
+ sampler: {wrapS: 33071, wrapT: 33071, minFilter: 9729, magFilter: 9729},
139
+ transform: {offset: [0, 1], scale: [1, -1]}
140
+ },
141
+ metallicFactor: 0,
142
+ roughnessFactor: 1
143
+ }
144
+ });
145
+ ```
146
+
147
+ `MeshTileTexture.data` is a `Uint8Array`; subviews preserve only their selected bytes. The
148
+ encoder checks header MIME type and positive dimensions, then copies the encoded bytes
149
+ without decoding, re-encoding, flipping UVs, or changing the selected sampling controls. Applications
150
+ supply valid encoded image payloads and map source sampling conventions explicitly. Base-color
151
+ images use glTF's sRGB interpretation and multiply the linear material factor and vertex colors.
152
+ Optional `MeshTileSampler` preserves glTF `wrapS`/`wrapT` (33071 clamp-to-edge,
153
+ 33648 mirrored-repeat, 10497 repeat), `magFilter` (9728 nearest, 9729 linear), and
154
+ `minFilter` (9728/9729 or mipmap filters 9984–9987). Omitted wrapping uses repeat;
155
+ omitted filters are chosen by the renderer. An omitted sampler emits no sampler resource;
156
+ an explicitly selected `{}` emits an empty sampler using the same defaults. Invalid values
157
+ or malformed selected samplers fail with `MESH_SAMPLER_INVALID`; unsupported sampler
158
+ properties fail with `MESH_SAMPLER_UNSUPPORTED`. Inputs are not mutated. Mipmap filter
159
+ selection does not generate mip levels; source adapters own sampling and image conventions.
160
+ Optional `MeshTileTextureTransform` preserves finite `offset`/`scale` UV pairs and a finite
161
+ counterclockwise `rotation` in radians through
162
+ [KHR_texture_transform](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_texture_transform/README.md).
163
+ The reader applies scale, then rotation about the UV origin, then offset. Omitted controls use
164
+ [0, 0], 0, and [1, 1]; zero/negative scales are supported. An omitted transform adds no extension;
165
+ a selected `{}` adds the default transform. The extension is declared required because no baked
166
+ fallback UV set is generated. UV/image bytes are unchanged and inputs are not mutated.
167
+ Malformed/nonfinite controls fail with `MESH_TEXTURE_TRANSFORM_INVALID`; unsupported properties
168
+ (including a `texCoord` override) fail with `MESH_TEXTURE_TRANSFORM_UNSUPPORTED`.
169
+ Source transform extraction/mapping, external image URLs, other UV sets, and other texture
170
+ extensions remain unsupported.
171
+ Invalid selected UVs fail with `MESH_TEXCOORD_INVALID`, missing UVs with
172
+ `MESH_TEXCOORD_REQUIRED`, and invalid/unsupported image descriptors or headers with
173
+ `MESH_TEXTURE_INVALID`. The spatial mesh codec forwards the same selected UVs, images, sampling, and transforms.
174
+
175
+ ## V5 spatial mesh codec
176
+
177
+ `createMeshConversionCodec` connects the mesh encoder to `convertTileset`. Supply
178
+ selected triangle geometries with **absolute source-frame positions**, matching normals, a
179
+ resource ID, an explicit target-frame origin, and an optional selected `material`. Positions
180
+ may be packed Float32 or Float64; the remaining geometry restrictions match `encodeMeshTile`.
181
+ The input byte estimator must include selected image bytes as well as geometry buffers.
182
+
183
+ ```ts
184
+ import {
185
+ convertTileset,
186
+ createMeshConversionCodec,
187
+ createTiles3DConversionSpatialContext
188
+ } from '@loaders.gl/tile-converter/v5/browser';
189
+
190
+ const spatialContext = createTiles3DConversionSpatialContext(sourceSpatialReference, {
191
+ targetCrs: 'EPSG:3857'
192
+ });
193
+ const report = await convertTileset({
194
+ source: selectedMeshSource, // Yields {id, mesh, origin, material?}; origin is in the target frame.
195
+ codec: createMeshConversionCodec({spatialContext, maxPositionError: 0.001}),
196
+ sink: meshSink,
197
+ measureInputBytes: measureDecodedMeshBytes,
198
+ measureOutputBytes: resource => resource.glb.byteLength,
199
+ maxInputResourceBytes: 16 * 1024 * 1024,
200
+ maxOutputResourceBytes: 16 * 1024 * 1024,
201
+ signal: abortController.signal
202
+ });
203
+ ```
204
+
205
+ The codec applies the shared position/normal operations once, subtracts the selected origin
206
+ in double precision, then encodes local Float32 positions. `maxPositionError` is required,
207
+ finite and nonnegative: it limits Euclidean reconstruction error per vertex in **target
208
+ coordinate units** (0.001 meters for EPSG:3857). Equality is allowed; zero requires exact
209
+ reconstruction. Geographic/unknown output frames are rejected. Native coordinates require
210
+ an explicitly declared Cartesian frame in the discovered spatial metadata.
211
+
212
+ Each output contains `{id, glb, origin, boundingBox, localBoundingBox, spatialReference, maximumPositionError}`.
213
+ `localBoundingBox` bounds the actual encoded Float32 positions before adding the origin; use
214
+ it for packaging to avoid precision loss from subtracting large absolute coordinates.
215
+ Accepted rounding is recorded as an informational `MESH_POSITION_ROUNDING` diagnostic in
216
+ the conversion report, with its resource ID and measured error. Bounds describe the
217
+ reconstructed encoded vertices in the target frame, including float32 rounding. Preserve
218
+ this metadata when writing resources: the GLB itself contains local coordinates and does
219
+ not embed its absolute origin or CRS. Source arrays are not mutated. The conversion core
220
+ awaits writes, checks byte limits, reports progress, finalizes on success, and aborts on
221
+ read/codec/write/cancellation failure.
222
+
223
+ Applications still select source geometry, apply source placement and corresponding normal
224
+ transforms before conversion, reconcile format axis conventions, and package hierarchy,
225
+ refinement and geometric error. This increment does not extract complete scenes, convert
226
+ I3S elevation placement, map full source materials/features, or emit a complete tileset.
227
+
228
+ ## V5 single-mesh browser package
229
+
230
+ `createSingleMeshTilesetSink` connects the spatial mesh codec to a bounded **3D Tiles 1.1**
231
+ output package. It accepts exactly one unmodified codec output, with resolved **EPSG:4978**
232
+ ECEF xyz coordinates in meters and ellipsoidal heights. The CRS must use the compact string
233
+ `'EPSG:4978'`; other output frames and identifiers are rejected in this initial profile.
234
+
235
+ ```ts
236
+ import {createSingleMeshTilesetSink} from '@loaders.gl/tile-converter/v5/browser';
237
+
238
+ const meshSink = createSingleMeshTilesetSink({
239
+ maxTotalBytes: 16 * 1024 * 1024,
240
+ geometricError: 0.001 // Meters; include source geometric error and position rounding.
241
+ });
242
+ // Pass meshSink to convertTileset with a mesh codec targeting EPSG:4978.
243
+ // After conversion completes, save both files together in the same directory:
244
+ const files = meshSink.getFiles(); // [{resourceId: 'mesh.glb', blob}, {resourceId: 'tileset.json', blob}]
245
+ ```
246
+
247
+ The package uses fixed relative names, a single root tile with `REPLACE` refinement, and
248
+ explicit geometric error. The tile transform cancels the standard glTF y-up to z-up rotation
249
+ and adds the ECEF origin; its box encloses encoded local positions in the corresponding tile
250
+ frame. The supplied `geometricError` must be finite, nonnegative, and at least the codec's
251
+ measured reconstruction error. It is the application's responsibility to include source
252
+ LOD or simplification error; the sink does not estimate it.
253
+
254
+ `maxTotalBytes` is captured when the sink is created and includes the GLB and UTF-8 JSON Blob sizes. This bounds **retained output**,
255
+ not peak conversion or serialization memory. Conversion report counts/byte totals describe
256
+ codec resources (one GLB), while this sink retains two files. Files are exposed only after
257
+ successful finalization; failed writes, cancellation, zero/multiple meshes, or conversion
258
+ failure abort and clear the package through the core lifecycle. The sink trusts codec GLB
259
+ and local-bound metadata; applications must not alter them before writing. Download UI,
260
+ workers, multi-tile hierarchy, and source-scene extraction remain separate work.
261
+
262
+ ### Download a 3TZ archive
263
+
264
+ `createSingleMeshTilesetArchive` packages the finalized sink's two files into a **3TZ archive**
265
+ with a final `@3dtilesIndex1@` path-hash index. The format writer lives in `@loaders.gl/3d-tiles`,
266
+ uses typed arrays and native Blobs, and needs no filesystem or Node.js Buffer.
267
+
268
+ ```ts
269
+ import {createSingleMeshTilesetArchive} from '@loaders.gl/tile-converter/v5/browser';
270
+
271
+ const archive = await createSingleMeshTilesetArchive(meshSink.getFiles(), {
272
+ maxArchiveBytes: 16 * 1024 * 1024
273
+ });
274
+ const downloadUrl = URL.createObjectURL(archive);
275
+ // Attach downloadUrl to an application-owned link with download="tileset.3tz".
276
+ // When the link is no longer needed:
277
+ URL.revokeObjectURL(downloadUrl);
278
+ ```
279
+
280
+ The required budget includes all ZIP headers and the index (368 bytes of overhead for this
281
+ fixed two-file profile) and is checked **before either Blob is read**. STORE compression,
282
+ fixed timestamps, stable file order, and relative paths produce deterministic bytes. The
283
+ returned Blob uses `application/vnd.maxar.archive.3tz+zip`; save it with `.3tz`. Inputs must be
284
+ unmodified output from a successfully finalized sink. The budget covers archive size, not
285
+ peak memory: the input Blobs and transient ZIP buffers remain additional allocations.
286
+ Packaging is asynchronous but does not support mid-encoding cancellation or streaming.
287
+
288
+ The legacy v4 I3S converter already writes **SLPK** archives. Portable v5 SLPK output requires
289
+ an I3S scene-layer writer and its node/resource layout; a 3D Tiles package cannot be saved as
290
+ SLPK merely by changing its extension.
291
+
292
+ ## V5 spatial conversion
293
+
294
+ The `@loaders.gl/tile-converter/v5` entrypoint can reuse the CRS and elevation operations from
295
+ `@loaders.gl/tiles` when preparing conversion resources. Discover source metadata with
296
+ `get3DTilesSpatialReference` or `getI3SSpatialReference`, then create the matching conversion
297
+ context with an explicit target CRS or height reference when transformation is required. The
298
+ context provides double-precision positions, transformed normals and conservative bounds, plus the
299
+ spatial reference that belongs in output metadata. Required geoid grids and terrain or scene
300
+ providers are passed by the application; the conversion core does not guess a CRS or fetch spatial
301
+ resources implicitly.
302
+
303
+ ```ts
304
+ import {createI3SConversionSpatialContext} from '@loaders.gl/tile-converter/v5';
305
+ import {getI3SSpatialReference} from '@loaders.gl/tiles';
306
+
307
+ const spatial = createI3SConversionSpatialContext(
308
+ getI3SSpatialReference(layerMetadata),
309
+ {targetCrs: 'EPSG:3857'}
310
+ );
311
+ const transformed = spatial.transformPositions(positions, nodeOrigin);
312
+ ```
313
+
314
+ ## V5 feature attributes
315
+
316
+ `convertFeatureAttributesToArrowBatches` writes decoded feature attributes using an explicit Arrow
317
+ schema. The schema owns property types, nested list/struct mappings, enum representations, and the
318
+ feature ID type. IDs may be strings or exact integer values (`bigint` for values outside JavaScript's
319
+ safe integer range). Unknown properties fail instead of disappearing. Supply a binary
320
+ `rawMetadataField` to retain the original encoded metadata alongside interpreted values. Pass each
321
+ source metadata class separately so its name remains attached to the batch schema.
322
+
323
+ ```ts
324
+ import {convertFeatureAttributesToArrowBatches} from '@loaders.gl/tile-converter/v5';
325
+
326
+ const batches = convertFeatureAttributesToArrowBatches(features, {
327
+ schema: {
328
+ fields: [
329
+ {name: 'feature_id', type: 'int64', nullable: false},
330
+ {name: 'name', type: 'utf8', nullable: true}
331
+ ],
332
+ metadata: {}
333
+ },
334
+ batchSize: 65536
335
+ });
336
+ ```
337
+
338
+ ## V5 restartable output sinks
339
+
340
+ `createManifestBackedTileConversionSink` resumes matching resources using caller-provided IDs and
341
+ fingerprints. Applications provide an atomic manifest store and an idempotent resource sink, keeping
342
+ filesystem and archive choices outside the conversion API.
343
+
344
+ ## V5 point-cloud sources
345
+
346
+ `traversePointCloudSource` walks an I3S Point Cloud, COPC, or compatible source independently of a
347
+ render camera. It yields each reachable tile's header and decoded Mesh Arrow content in
348
+ depth-first order, retaining source bounds, coordinate-system metadata, and placement information
349
+ for the output encoder. `encodePointCloudSource` streams non-empty PNTS resources, each paired with
350
+ its original header, coordinate system, origin, and transform. `encodePointCloudSourceTile` encodes
351
+ one yielded tile. Set `maxDepth` to bound traversal; pass an `AbortSignal` to stop between source
352
+ operations. These helpers do not transform coordinates or package a tileset manifest.
353
+
354
+ Pass encoder options directly to `encodePointCloudSourceTile`, or select them for each non-empty
355
+ tile with `encodePointCloudSource`'s `getTileEncodingOptions` callback. A declared `rtcCenter` in the
356
+ point-position coordinate frame is subtracted before float32 encoding and stored as PNTS
357
+ `RTC_CENTER`, retaining small offsets in large double-precision coordinates. `batchTableJson`
358
+ supplies properties for existing `BATCH_ID` values. An explicit `constantRGBA` overrides the source
359
+ constant color; the source origin and placement transform remain attached to the encoded tile.
360
+
361
+ The PNTS encoder accepts `POSITION`, `NORMAL`, `BATCH_ID`, and one color attribute (`COLOR_0` or
362
+ `COLOR`). Other attributes, including classification, intensity, and an additional color column,
363
+ raise `POINT_CLOUD_ATTRIBUTE_UNSUPPORTED` rather than silently disappearing. Applications must
364
+ explicitly map unsupported fields to supported batch-table properties or remove them before
365
+ encoding; this initial encoder does not automatically map point attributes.
366
+
367
+ `convertPointCloudSource` connects the same traversal and encoder to a
368
+ `TileConversionSink<EncodedPointCloudSourceTile>`. Writes are awaited before reading the next tile;
369
+ the sink is finalized on success and aborted on failures or cancellation. Set
370
+ `maxOutputResourceBytes` to limit each PNTS resource and use `onProgress` for the shared v5 progress
371
+ report. `maxDepth`, `signal`, and `getTileEncodingOptions` have the same meanings as above.
372
+ Applications provide `measureInputBytes` for decoded input accounting, returning zero for empty
373
+ tiles when appropriate. Reports count visited tile headers as inputs, including empty tiles, and
374
+ encoded PNTS tiles as outputs. Applications own source cleanup and output naming and packaging.
375
+
376
+ ```ts
377
+ import {convertPointCloudSource} from '@loaders.gl/tile-converter/v5';
378
+
379
+ const report = await convertPointCloudSource(pointCloudSource, {
380
+ sink: outputSink,
381
+ measureInputBytes: measureDecodedTileBytes,
382
+ maxOutputResourceBytes: 8 * 1024 * 1024,
383
+ signal: abortController.signal
384
+ });
385
+ ```
386
+
387
+ ## V5 browser entrypoint
388
+
389
+ Import `@loaders.gl/tile-converter/v5/browser` for the portable conversion API,
390
+ `createBrowserTileConversionSource`, `createBrowserTilesetConversionSource`, and
391
+ `createBoundedMemoryTileConversionSink`. The single-resource source reads one URL or Blob. The
392
+ tileset source traverses explicit 3D Tiles 1.0 and 1.1 documents in depth-first order and reads each
393
+ declared content placement under aggregate byte and resource limits. It rejects implicit tiling and
394
+ nested JSON content. Both sources yield raw bytes to an application-provided codec. The sink retains
395
+ named Blob outputs under a required total-byte limit; applications remain responsible for codecs,
396
+ packaging, and triggering downloads.
397
+
9
398
  ## Installation
10
399
 
11
400
  ```bash