@loaders.gl/tile-converter 4.5.3 → 5.0.0-alpha.10

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