@loaders.gl/tile-converter 3.2.6 → 3.3.0-alpha.2

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 (77) hide show
  1. package/dist/3d-tiles-attributes-worker.js +3 -3
  2. package/dist/3d-tiles-attributes-worker.js.map +1 -1
  3. package/dist/converter-cli.js +30 -7
  4. package/dist/converter.min.js +1 -1
  5. package/dist/dist.min.js +1123 -624
  6. package/dist/es5/3d-tiles-attributes-worker.js +1 -1
  7. package/dist/es5/3d-tiles-attributes-worker.js.map +1 -1
  8. package/dist/es5/converter-cli.js +42 -12
  9. package/dist/es5/converter-cli.js.map +1 -1
  10. package/dist/es5/i3s-attributes-worker.js +1 -1
  11. package/dist/es5/i3s-attributes-worker.js.map +1 -1
  12. package/dist/es5/i3s-converter/helpers/batch-ids-extensions.js +146 -0
  13. package/dist/es5/i3s-converter/helpers/batch-ids-extensions.js.map +1 -0
  14. package/dist/es5/i3s-converter/helpers/feature-attributes.js +60 -0
  15. package/dist/es5/i3s-converter/helpers/feature-attributes.js.map +1 -0
  16. package/dist/es5/i3s-converter/helpers/geometry-attributes.js +39 -7
  17. package/dist/es5/i3s-converter/helpers/geometry-attributes.js.map +1 -1
  18. package/dist/es5/i3s-converter/helpers/geometry-converter.js +177 -59
  19. package/dist/es5/i3s-converter/helpers/geometry-converter.js.map +1 -1
  20. package/dist/es5/i3s-converter/helpers/gltf-attributes.js +15 -1
  21. package/dist/es5/i3s-converter/helpers/gltf-attributes.js.map +1 -1
  22. package/dist/es5/i3s-converter/i3s-converter.js +50 -51
  23. package/dist/es5/i3s-converter/i3s-converter.js.map +1 -1
  24. package/dist/es5/lib/utils/write-queue.js +3 -5
  25. package/dist/es5/lib/utils/write-queue.js.map +1 -1
  26. package/dist/es5/pgm-loader.js +1 -1
  27. package/dist/es5/pgm-loader.js.map +1 -1
  28. package/dist/esm/3d-tiles-attributes-worker.js +1 -1
  29. package/dist/esm/3d-tiles-attributes-worker.js.map +1 -1
  30. package/dist/esm/converter-cli.js +37 -7
  31. package/dist/esm/converter-cli.js.map +1 -1
  32. package/dist/esm/i3s-attributes-worker.js +1 -1
  33. package/dist/esm/i3s-attributes-worker.js.map +1 -1
  34. package/dist/esm/i3s-converter/helpers/batch-ids-extensions.js +128 -0
  35. package/dist/esm/i3s-converter/helpers/batch-ids-extensions.js.map +1 -0
  36. package/dist/esm/i3s-converter/helpers/feature-attributes.js +34 -0
  37. package/dist/esm/i3s-converter/helpers/feature-attributes.js.map +1 -0
  38. package/dist/esm/i3s-converter/helpers/geometry-attributes.js +23 -7
  39. package/dist/esm/i3s-converter/helpers/geometry-attributes.js.map +1 -1
  40. package/dist/esm/i3s-converter/helpers/geometry-converter.js +145 -38
  41. package/dist/esm/i3s-converter/helpers/geometry-converter.js.map +1 -1
  42. package/dist/esm/i3s-converter/helpers/gltf-attributes.js +15 -1
  43. package/dist/esm/i3s-converter/helpers/gltf-attributes.js.map +1 -1
  44. package/dist/esm/i3s-converter/i3s-converter.js +21 -27
  45. package/dist/esm/i3s-converter/i3s-converter.js.map +1 -1
  46. package/dist/esm/lib/utils/write-queue.js +3 -5
  47. package/dist/esm/lib/utils/write-queue.js.map +1 -1
  48. package/dist/esm/pgm-loader.js +1 -1
  49. package/dist/esm/pgm-loader.js.map +1 -1
  50. package/dist/i3s-attributes-worker.js +3 -3
  51. package/dist/i3s-attributes-worker.js.map +3 -3
  52. package/dist/i3s-converter/helpers/batch-ids-extensions.d.ts +12 -0
  53. package/dist/i3s-converter/helpers/batch-ids-extensions.d.ts.map +1 -0
  54. package/dist/i3s-converter/helpers/batch-ids-extensions.js +138 -0
  55. package/dist/i3s-converter/helpers/feature-attributes.d.ts +24 -0
  56. package/dist/i3s-converter/helpers/feature-attributes.d.ts.map +1 -0
  57. package/dist/i3s-converter/helpers/feature-attributes.js +55 -0
  58. package/dist/i3s-converter/helpers/geometry-attributes.js +26 -7
  59. package/dist/i3s-converter/helpers/geometry-converter.d.ts +9 -2
  60. package/dist/i3s-converter/helpers/geometry-converter.d.ts.map +1 -1
  61. package/dist/i3s-converter/helpers/geometry-converter.js +140 -44
  62. package/dist/i3s-converter/helpers/gltf-attributes.d.ts.map +1 -1
  63. package/dist/i3s-converter/helpers/gltf-attributes.js +13 -0
  64. package/dist/i3s-converter/i3s-converter.d.ts +7 -14
  65. package/dist/i3s-converter/i3s-converter.d.ts.map +1 -1
  66. package/dist/i3s-converter/i3s-converter.js +44 -35
  67. package/dist/lib/utils/write-queue.d.ts.map +1 -1
  68. package/dist/lib/utils/write-queue.js +3 -4
  69. package/package.json +15 -15
  70. package/src/converter-cli.ts +33 -7
  71. package/src/i3s-converter/helpers/batch-ids-extensions.ts +199 -0
  72. package/src/i3s-converter/helpers/feature-attributes.ts +65 -0
  73. package/src/i3s-converter/helpers/geometry-attributes.ts +30 -7
  74. package/src/i3s-converter/helpers/geometry-converter.ts +187 -48
  75. package/src/i3s-converter/helpers/gltf-attributes.ts +15 -0
  76. package/src/i3s-converter/i3s-converter.ts +38 -41
  77. package/src/lib/utils/write-queue.ts +7 -5
@@ -47,7 +47,7 @@ function calculateFaceRangesAndFeaturesCount(featureIndices: number[]): {
47
47
  } {
48
48
  let rangeIndex = 1;
49
49
  let featureIndex = 1;
50
- let currentFeatureId = featureIndices[0];
50
+ let currentFeatureId = getFrequentValue(featureIndices.slice(0, VALUES_PER_VERTEX));
51
51
  const faceRangeList: any[] = [];
52
52
  const featureIds: any[] = [];
53
53
  const uniqueFeatureIds = [currentFeatureId];
@@ -55,20 +55,21 @@ function calculateFaceRangesAndFeaturesCount(featureIndices: number[]): {
55
55
  faceRangeList[0] = 0;
56
56
  featureIds[0] = currentFeatureId;
57
57
 
58
- for (let index = 1; index < featureIndices.length; index++) {
59
- if (currentFeatureId !== featureIndices[index]) {
58
+ for (let index = VALUES_PER_VERTEX; index < featureIndices.length; index += VALUES_PER_VERTEX) {
59
+ const newFeatureId = getFrequentValue(featureIndices.slice(index, index + VALUES_PER_VERTEX));
60
+ if (currentFeatureId !== newFeatureId) {
60
61
  faceRangeList[rangeIndex] = index / VALUES_PER_VERTEX - 1;
61
62
  faceRangeList[rangeIndex + 1] = index / VALUES_PER_VERTEX;
62
- featureIds[featureIndex] = featureIndices[index];
63
+ featureIds[featureIndex] = newFeatureId;
63
64
 
64
- if (!uniqueFeatureIds.includes(featureIndices[index])) {
65
- uniqueFeatureIds.push(featureIndices[index]);
65
+ if (!uniqueFeatureIds.includes(newFeatureId)) {
66
+ uniqueFeatureIds.push(newFeatureId);
66
67
  }
67
68
 
68
69
  rangeIndex += 2;
69
70
  featureIndex += 1;
70
71
  }
71
- currentFeatureId = featureIndices[index];
72
+ currentFeatureId = newFeatureId;
72
73
  }
73
74
 
74
75
  faceRangeList[rangeIndex] = featureIndices.length / VALUES_PER_VERTEX - 1;
@@ -79,6 +80,28 @@ function calculateFaceRangesAndFeaturesCount(featureIndices: number[]): {
79
80
  return {faceRange, featureCount, featureIds};
80
81
  }
81
82
 
83
+ /**
84
+ * Find most frequent value to avoid situation where one vertex can be part of multiple features (objects).
85
+ * @param values
86
+ */
87
+ function getFrequentValue(values: number[]): number {
88
+ const map: {[key: number]: number} = {};
89
+
90
+ let mostFrequentValue = values[0];
91
+ let maxCount = 1;
92
+
93
+ for (const value of values) {
94
+ // Save item and it's frequency count to the map.
95
+ map[value] = (map[value] || 0) + 1;
96
+ // Find max count of frequency.
97
+ maxCount = maxCount > map[value] ? maxCount : map[value];
98
+ // Find the most frequent value.
99
+ mostFrequentValue = maxCount > map[value] ? mostFrequentValue : value;
100
+ }
101
+
102
+ return mostFrequentValue;
103
+ }
104
+
82
105
  /**
83
106
  * Generate list of attribute object grouped by feature ids.
84
107
  * @param attributes
@@ -1,8 +1,13 @@
1
+ import type {Image, MeshPrimitive} from 'modules/gltf/src/lib/types/gltf-postprocessed-schema';
2
+ import type {B3DMContent, FeatureTableJson} from '@loaders.gl/3d-tiles';
3
+ import type {GLTF_EXT_feature_metadata} from '@loaders.gl/gltf';
4
+
1
5
  import {Vector3, Matrix4, Vector4} from '@math.gl/core';
2
6
  import {Ellipsoid} from '@math.gl/geospatial';
3
7
 
4
8
  import {DracoWriterWorker} from '@loaders.gl/draco';
5
9
  import {assert, encode} from '@loaders.gl/core';
10
+ import {Tile3D} from '@loaders.gl/tiles';
6
11
  import {concatenateArrayBuffers, concatenateTypedArrays} from '@loaders.gl/loader-utils';
7
12
  import md5 from 'md5';
8
13
  import {generateAttributes} from './geometry-attributes';
@@ -13,7 +18,6 @@ import {
13
18
  I3SMaterialWithTexture,
14
19
  SharedResourcesArrays
15
20
  } from '../types';
16
- import {B3DMContent} from '@loaders.gl/3d-tiles';
17
21
  import {GLTFMaterialPostprocessed, GLTFNodePostprocessed} from '@loaders.gl/gltf';
18
22
  import {
19
23
  AttributeStorageInfo,
@@ -30,6 +34,8 @@ import {
30
34
  } from 'modules/gltf/src/lib/types/gltf-types';
31
35
  import {B3DMAttributesData /*transformI3SAttributesOnWorker */} from '../../i3s-attributes-worker';
32
36
  import {prepareDataForAttributesConversion} from './gltf-attributes';
37
+ import {handleBatchIdsExtensions} from './batch-ids-extensions';
38
+ import {checkPropertiesLength, flattenPropertyTableByFeatureIds} from './feature-attributes';
33
39
 
34
40
  // Spec - https://github.com/Esri/i3s-spec/blob/master/docs/1.7/pbrMetallicRoughness.cmn.md
35
41
  const DEFAULT_ROUGHNESS_FACTOR = 1;
@@ -50,6 +56,9 @@ const OBJECT_ID_TYPE = 'Oid32';
50
56
  */
51
57
  const BATCHED_ID_POSSIBLE_ATTRIBUTE_NAMES = ['CUSTOM_ATTRIBUTE_2', '_BATCHID', 'BATCHID'];
52
58
 
59
+ const EXT_FEATURE_METADATA = 'EXT_feature_metadata';
60
+ const EXT_MESH_FEATURES = 'EXT_mesh_features';
61
+
53
62
  let scratchVector = new Vector3();
54
63
 
55
64
  /**
@@ -67,6 +76,7 @@ let scratchVector = new Vector3();
67
76
  export default async function convertB3dmToI3sGeometry(
68
77
  tileContent: B3DMContent,
69
78
  nodeId: number,
79
+ propertyTable: FeatureTableJson | null,
70
80
  featuresHashArray: string[],
71
81
  attributeStorageInfo: AttributeStorageInfo[] | undefined,
72
82
  draco: boolean,
@@ -128,6 +138,7 @@ export default async function convertB3dmToI3sGeometry(
128
138
  tileContent,
129
139
  nodeId: nodesCounter,
130
140
  featuresHashArray,
141
+ propertyTable,
131
142
  attributeStorageInfo,
132
143
  draco,
133
144
  workerSource
@@ -191,6 +202,7 @@ async function _makeNodeResources({
191
202
  tileContent,
192
203
  nodeId,
193
204
  featuresHashArray,
205
+ propertyTable,
194
206
  attributeStorageInfo,
195
207
  draco,
196
208
  workerSource
@@ -201,6 +213,7 @@ async function _makeNodeResources({
201
213
  tileContent: B3DMContent;
202
214
  nodeId: number;
203
215
  featuresHashArray: string[];
216
+ propertyTable: FeatureTableJson | null;
204
217
  attributeStorageInfo?: AttributeStorageInfo[];
205
218
  draco: boolean;
206
219
  workerSource: {[key: string]: string};
@@ -250,11 +263,15 @@ async function _makeNodeResources({
250
263
  )
251
264
  : null;
252
265
 
253
- const attributes = convertBatchTableToAttributeBuffers(
254
- tileContent.batchTableJson,
255
- featureIds,
256
- attributeStorageInfo
257
- );
266
+ let attributes: ArrayBuffer[] = [];
267
+
268
+ if (attributeStorageInfo && propertyTable) {
269
+ attributes = convertPropertyTableToAttributeBuffers(
270
+ featureIds,
271
+ propertyTable,
272
+ attributeStorageInfo
273
+ );
274
+ }
258
275
 
259
276
  return {
260
277
  geometry: fileBuffer,
@@ -407,9 +424,12 @@ function convertNode(
407
424
  const transformationMatrix = getCompositeTransformationMatrix(node, matrix);
408
425
 
409
426
  const mesh = node.mesh;
427
+ const images = node.images;
428
+
410
429
  if (mesh) {
411
430
  convertMesh(
412
431
  mesh,
432
+ images,
413
433
  cartographicOrigin,
414
434
  cartesianModelMatrix,
415
435
  attributesMap,
@@ -441,6 +461,7 @@ function convertNode(
441
461
  */
442
462
  function convertMesh(
443
463
  mesh: GLTFMeshPostprocessed,
464
+ images: Image[],
444
465
  cartographicOrigin: Vector3,
445
466
  cartesianModelMatrix: Matrix4,
446
467
  attributesMap: Map<string, ConvertedAttributes>,
@@ -498,7 +519,7 @@ function convertMesh(
498
519
 
499
520
  outputAttributes.featureIndicesGroups = outputAttributes.featureIndicesGroups || [];
500
521
  outputAttributes.featureIndicesGroups.push(
501
- flattenBatchIds(getBatchIdsByAttributeName(attributes), primitive.indices?.value)
522
+ flattenBatchIds(getBatchIds(attributes, primitive, images), primitive.indices?.value)
502
523
  );
503
524
  }
504
525
  }
@@ -665,14 +686,23 @@ function flattenBatchIds(batchedIds: number[], indices: Uint8Array): number[] {
665
686
  }
666
687
 
667
688
  /**
668
- * Return batchIds based on possible attribute names for different kind of maps.
669
- * @param attributes - the gltf primitive attributes
670
- * @returns batch ids attribute
689
+ * Get batchIds for featureIds creation
690
+ * @param attributes
691
+ * @param primitive
692
+ * @param textures
671
693
  */
672
- function getBatchIdsByAttributeName(attributes: {
673
- [key: string]: GLTFAccessorPostprocessed;
674
- }): number[] {
675
- let batchIds: number[] = [];
694
+ function getBatchIds(
695
+ attributes: {
696
+ [key: string]: GLTFAccessorPostprocessed;
697
+ },
698
+ primitive: MeshPrimitive,
699
+ images: Image[]
700
+ ): number[] {
701
+ const batchIds: number[] = handleBatchIdsExtensions(attributes, primitive, images);
702
+
703
+ if (batchIds.length) {
704
+ return batchIds;
705
+ }
676
706
 
677
707
  for (let index = 0; index < BATCHED_ID_POSSIBLE_ATTRIBUTE_NAMES.length; index++) {
678
708
  const possibleBatchIdAttributeName = BATCHED_ID_POSSIBLE_ATTRIBUTE_NAMES[index];
@@ -680,12 +710,11 @@ function getBatchIdsByAttributeName(attributes: {
680
710
  attributes[possibleBatchIdAttributeName] &&
681
711
  attributes[possibleBatchIdAttributeName].value
682
712
  ) {
683
- batchIds = attributes[possibleBatchIdAttributeName].value;
684
- break;
713
+ return attributes[possibleBatchIdAttributeName].value;
685
714
  }
686
715
  }
687
716
 
688
- return batchIds;
717
+ return [];
689
718
  }
690
719
 
691
720
  /**
@@ -1036,49 +1065,66 @@ function replaceIndicesByUnique(indicesArray, featureMap) {
1036
1065
  }
1037
1066
 
1038
1067
  /**
1039
- * Convert batch table data to attribute buffers.
1040
- * @param {Object} batchTable - table with metadata for particular feature.
1068
+ * Convert property table data to attribute buffers.
1069
+ * @param {Object} propertyTable - table with metadata for particular feature.
1041
1070
  * @param {Array} featureIds
1042
1071
  * @param {Array} attributeStorageInfo
1043
1072
  * @returns {Array} - Array of file buffers.
1044
1073
  */
1045
- function convertBatchTableToAttributeBuffers(batchTable, featureIds, attributeStorageInfo) {
1074
+ function convertPropertyTableToAttributeBuffers(
1075
+ featureIds: number[],
1076
+ propertyTable: FeatureTableJson,
1077
+ attributeStorageInfo: AttributeStorageInfo[]
1078
+ ) {
1046
1079
  const attributeBuffers: ArrayBuffer[] = [];
1047
1080
 
1048
- if (batchTable) {
1049
- const batchTableWithFeatureIds = {
1050
- OBJECTID: featureIds,
1051
- ...batchTable
1052
- };
1081
+ const needFlattenPropertyTable = checkPropertiesLength(featureIds, propertyTable);
1082
+ const properties = needFlattenPropertyTable
1083
+ ? flattenPropertyTableByFeatureIds(featureIds, propertyTable)
1084
+ : propertyTable;
1053
1085
 
1054
- for (const key in batchTableWithFeatureIds) {
1055
- const type = getAttributeType(key, attributeStorageInfo);
1056
-
1057
- let attributeBuffer: ArrayBuffer | null = null;
1058
-
1059
- switch (type) {
1060
- case OBJECT_ID_TYPE:
1061
- case SHORT_INT_TYPE:
1062
- attributeBuffer = generateShortIntegerAttributeBuffer(batchTableWithFeatureIds[key]);
1063
- break;
1064
- case DOUBLE_TYPE:
1065
- attributeBuffer = generateDoubleAttributeBuffer(batchTableWithFeatureIds[key]);
1066
- break;
1067
- case STRING_TYPE:
1068
- attributeBuffer = generateStringAttributeBuffer(batchTableWithFeatureIds[key]);
1069
- break;
1070
- default:
1071
- attributeBuffer = generateStringAttributeBuffer(batchTableWithFeatureIds[key]);
1072
- }
1086
+ const propertyTableWithObjectIds = {
1087
+ OBJECTID: featureIds,
1088
+ ...properties
1089
+ };
1073
1090
 
1074
- if (attributeBuffer) {
1075
- attributeBuffers.push(attributeBuffer);
1076
- }
1077
- }
1091
+ for (const propertyName in propertyTableWithObjectIds) {
1092
+ const type = getAttributeType(propertyName, attributeStorageInfo);
1093
+ const value = propertyTableWithObjectIds[propertyName];
1094
+ const attributeBuffer = generateAttributeBuffer(type, value);
1095
+
1096
+ attributeBuffers.push(attributeBuffer);
1078
1097
  }
1079
1098
 
1080
1099
  return attributeBuffers;
1081
1100
  }
1101
+
1102
+ /**
1103
+ * Generates attribute buffer based on attribute type
1104
+ * @param type
1105
+ * @param value
1106
+ */
1107
+ function generateAttributeBuffer(type: string, value: any): ArrayBuffer {
1108
+ let attributeBuffer: ArrayBuffer;
1109
+
1110
+ switch (type) {
1111
+ case OBJECT_ID_TYPE:
1112
+ case SHORT_INT_TYPE:
1113
+ attributeBuffer = generateShortIntegerAttributeBuffer(value);
1114
+ break;
1115
+ case DOUBLE_TYPE:
1116
+ attributeBuffer = generateDoubleAttributeBuffer(value);
1117
+ break;
1118
+ case STRING_TYPE:
1119
+ attributeBuffer = generateStringAttributeBuffer(value);
1120
+ break;
1121
+ default:
1122
+ attributeBuffer = generateStringAttributeBuffer(value);
1123
+ }
1124
+
1125
+ return attributeBuffer;
1126
+ }
1127
+
1082
1128
  /**
1083
1129
  * Return attribute type.
1084
1130
  * @param {String} key
@@ -1242,3 +1288,96 @@ function generateFeatureIndexAttribute(featureIndex, faceRange) {
1242
1288
 
1243
1289
  return orderedFeatureIndices;
1244
1290
  }
1291
+
1292
+ /**
1293
+ * Find property table in tile
1294
+ * For example it can be batchTable for b3dm files or property table in gLTF extension.
1295
+ * @param sourceTile
1296
+ */
1297
+ export function getPropertyTable(sourceTile: Tile3D): FeatureTableJson | null {
1298
+ const batchTableJson = sourceTile?.content?.batchTableJson;
1299
+
1300
+ if (batchTableJson) {
1301
+ return batchTableJson;
1302
+ }
1303
+
1304
+ const {extensionName, extension} = getPropertyTableExtension(sourceTile);
1305
+
1306
+ switch (extensionName) {
1307
+ case EXT_MESH_FEATURES: {
1308
+ console.warn('The I3S converter does not yet support the EXT_mesh_features extension');
1309
+ return null;
1310
+ }
1311
+ case EXT_FEATURE_METADATA: {
1312
+ return getPropertyTableFromExtFeatureMetadata(extension);
1313
+ }
1314
+ default:
1315
+ return null;
1316
+ }
1317
+ }
1318
+
1319
+ /**
1320
+ * Check extensions which can be with property table inside.
1321
+ * @param sourceTile
1322
+ */
1323
+ function getPropertyTableExtension(sourceTile: Tile3D) {
1324
+ const extensionsWithPropertyTables = [EXT_FEATURE_METADATA, EXT_MESH_FEATURES];
1325
+ const extensionsUsed = sourceTile?.content?.gltf?.extensionsUsed;
1326
+
1327
+ if (!extensionsUsed) {
1328
+ return {extensionName: null, extension: null};
1329
+ }
1330
+
1331
+ let extensionName: string = '';
1332
+
1333
+ for (const extensionItem of sourceTile?.content?.gltf?.extensionsUsed) {
1334
+ if (extensionsWithPropertyTables.includes(extensionItem)) {
1335
+ extensionName = extensionItem;
1336
+ break;
1337
+ }
1338
+ }
1339
+
1340
+ const extension = sourceTile?.content?.gltf?.extensions?.[extensionName];
1341
+
1342
+ return {extensionName, extension};
1343
+ }
1344
+
1345
+ /**
1346
+ * Handle EXT_feature_metadata to get property table
1347
+ * @param extension
1348
+ * TODO add EXT_feature_metadata feature textures support.
1349
+ */
1350
+ function getPropertyTableFromExtFeatureMetadata(
1351
+ extension: GLTF_EXT_feature_metadata
1352
+ ): FeatureTableJson | null {
1353
+ if (extension?.featureTextures) {
1354
+ console.warn(
1355
+ 'The I3S converter does not yet support the EXT_feature_metadata feature textures'
1356
+ );
1357
+ return null;
1358
+ }
1359
+
1360
+ if (extension?.featureTables) {
1361
+ /**
1362
+ * Take only first feature table to generate attributes storage info object.
1363
+ * TODO: Think about getting data from all feature tables?
1364
+ * It can be tricky just because 3dTiles is able to have multiple featureId attributes and multiple feature tables.
1365
+ * In I3S we should decide which featureIds attribute will be passed to geometry data.
1366
+ */
1367
+ const firstFeatureTableName = Object.keys(extension.featureTables)?.[0];
1368
+
1369
+ if (firstFeatureTableName) {
1370
+ const featureTable = extension?.featureTables[firstFeatureTableName];
1371
+ const propertyTable = {};
1372
+
1373
+ for (const propertyName in featureTable.properties) {
1374
+ propertyTable[propertyName] = featureTable.properties[propertyName].data;
1375
+ }
1376
+
1377
+ return propertyTable;
1378
+ }
1379
+ }
1380
+
1381
+ console.warn("The I3S converter couldn't handle EXT_feature_metadata extension");
1382
+ return null;
1383
+ }
@@ -35,6 +35,20 @@ export function prepareDataForAttributesConversion(tileContent: B3DMContent): B3
35
35
  tileContent.gltf?.nodes ||
36
36
  [];
37
37
 
38
+ const images =
39
+ tileContent.gltf?.images?.map((imageObject) => {
40
+ // Need data only for uncompressed images because we can't get batchIds from compressed textures.
41
+ const data = imageObject?.image?.compressed ? null : imageObject?.image?.data.subarray();
42
+ return {
43
+ data,
44
+ compressed: Boolean(imageObject?.image?.compressed),
45
+ height: imageObject.image.height,
46
+ width: imageObject.image.width,
47
+ components: imageObject.image.components,
48
+ mimeType: imageObject.mimeType
49
+ };
50
+ }) || [];
51
+
38
52
  const prepearedNodes = nodes.map((node) => {
39
53
  if (!node.mesh) {
40
54
  return node;
@@ -42,6 +56,7 @@ export function prepareDataForAttributesConversion(tileContent: B3DMContent): B3
42
56
 
43
57
  return {
44
58
  ...node,
59
+ images,
45
60
  mesh: {
46
61
  ...node.mesh,
47
62
  primitives: node.mesh?.primitives.map((primitive) => ({
@@ -1,5 +1,5 @@
1
1
  import type {Tile3D, Tileset3DProps} from '@loaders.gl/tiles';
2
- import type {BatchTableJson, B3DMContent} from '@loaders.gl/3d-tiles';
2
+ import type {FeatureTableJson} from '@loaders.gl/3d-tiles';
3
3
  import type {WriteQueueItem} from '../lib/utils/write-queue';
4
4
  import type {
5
5
  AttributeStorageInfo,
@@ -34,7 +34,7 @@ import {
34
34
  // addFileToZip
35
35
  } from '../lib/utils/compress-util';
36
36
  import {calculateFilesSize, timeConverter} from '../lib/utils/statistic-utills';
37
- import convertB3dmToI3sGeometry from './helpers/geometry-converter';
37
+ import convertB3dmToI3sGeometry, {getPropertyTable} from './helpers/geometry-converter';
38
38
  import {
39
39
  createBoundingVolumes,
40
40
  convertBoundingVolumeToI3SFullExtent
@@ -572,13 +572,13 @@ export default class I3SConverter {
572
572
 
573
573
  let boundingVolumes = createBoundingVolumes(sourceTile, this.geoidHeightModel!);
574
574
 
575
- const batchTable = sourceTile?.content?.batchTableJson;
575
+ const propertyTable = getPropertyTable(sourceTile);
576
576
 
577
- if (batchTable) {
578
- this._convertAttributeStorageInfo(sourceTile.content);
577
+ if (propertyTable && !this.layers0?.attributeStorageInfo?.length) {
578
+ this._convertPropertyTableToNodeAttributes(propertyTable);
579
579
  }
580
580
 
581
- const resourcesData = await this._convertResources(sourceTile);
581
+ const resourcesData = await this._convertResources(sourceTile, propertyTable);
582
582
 
583
583
  const nodes: Node3DIndexDocument[] = [];
584
584
  const nodesInPage: NodeInPage[] = [];
@@ -648,20 +648,6 @@ export default class I3SConverter {
648
648
  return nodes;
649
649
  }
650
650
 
651
- /**
652
- * Convert attributesStorageInfo https://github.com/Esri/i3s-spec/blob/master/docs/1.7/attributeStorageInfo.cmn.md
653
- * from B3DM batch table
654
- * @param sourceTileContent - tile content of 3DTile
655
- * @return {void}
656
- */
657
- private _convertAttributeStorageInfo(sourceTileContent: B3DMContent): void {
658
- // In legacy b3dm files sometimes sourceTileContent is null.
659
- const batchTable = sourceTileContent && sourceTileContent.batchTableJson;
660
- if (batchTable && !this.layers0?.attributeStorageInfo?.length) {
661
- this._convertBatchTableInfoToNodeAttributes(batchTable);
662
- }
663
- }
664
-
665
651
  /**
666
652
  * Convert tile to one or more I3S nodes
667
653
  * @param sourceTile - source tile (3DTile)
@@ -674,13 +660,17 @@ export default class I3SConverter {
674
660
  * result.attributes - feature attributes
675
661
  * result.featureCount - number of features
676
662
  */
677
- private async _convertResources(sourceTile: TileHeader): Promise<I3SConvertedResources[] | null> {
663
+ private async _convertResources(
664
+ sourceTile: TileHeader,
665
+ propertyTable: FeatureTableJson | null
666
+ ): Promise<I3SConvertedResources[] | null> {
678
667
  if (!this.isContentSupported(sourceTile)) {
679
668
  return null;
680
669
  }
681
670
  const resourcesData = await convertB3dmToI3sGeometry(
682
671
  sourceTile.content,
683
672
  Number(this.nodePages.nodesCounter),
673
+ propertyTable,
684
674
  this.featuresHashArray,
685
675
  this.layers0?.attributeStorageInfo,
686
676
  this.options.draco,
@@ -938,12 +928,19 @@ export default class I3SConverter {
938
928
 
939
929
  if (this.generateTextures) {
940
930
  formats.push({name: '1', format: 'ktx2'});
941
- const ktx2TextureData = encode(texture.image, KTX2BasisWriterWorker, {
942
- ...KTX2BasisWriterWorker.options,
943
- source: this.workerSource.ktx2,
944
- reuseWorkers: true,
945
- _nodeWorkers: true
946
- });
931
+ // For Node.js texture.image.data is type of Buffer
932
+ const copyArrayBuffer = texture.image.data.subarray();
933
+ const arrayToEncode = new Uint8Array(copyArrayBuffer);
934
+ const ktx2TextureData = encode(
935
+ {...texture.image, data: arrayToEncode},
936
+ KTX2BasisWriterWorker,
937
+ {
938
+ ...KTX2BasisWriterWorker.options,
939
+ source: this.workerSource.ktx2,
940
+ reuseWorkers: true,
941
+ _nodeWorkers: true
942
+ }
943
+ );
947
944
 
948
945
  await this.writeTextureFile(ktx2TextureData, '1', 'ktx2', childPath, slpkChildPath);
949
946
  }
@@ -1073,7 +1070,7 @@ export default class I3SConverter {
1073
1070
  /**
1074
1071
  * Generate storage attribute for map segmentation.
1075
1072
  * @param attributeIndex - order index of attribute (f_0, f_1 ...).
1076
- * @param key - attribute key from batch table.\
1073
+ * @param key - attribute key from propertyTable.
1077
1074
  * @param attributeType - attribute type.
1078
1075
  * @return Updated storageAttribute.
1079
1076
  */
@@ -1112,7 +1109,7 @@ export default class I3SConverter {
1112
1109
  /**
1113
1110
  * Get the attribute type for attributeStorageInfo https://github.com/Esri/i3s-spec/blob/master/docs/1.7/attributeStorageInfo.cmn.md
1114
1111
  * @param key - attribute's key
1115
- * @param attribute - attribute's type in batchTable
1112
+ * @param attribute - attribute's type in propertyTable
1116
1113
  */
1117
1114
  private getAttributeType(key: string, attribute: string): string {
1118
1115
  if (key === OBJECT_ID_TYPE) {
@@ -1180,24 +1177,24 @@ export default class I3SConverter {
1180
1177
  }
1181
1178
 
1182
1179
  /**
1183
- * Do conversion of 3DTiles batch table to I3s node attributes.
1184
- * @param batchTable - Table with layer meta data.
1180
+ * Do conversion of 3DTiles property table to I3s node attributes.
1181
+ * @param propertyTable - Table with layer meta data.
1185
1182
  */
1186
- private _convertBatchTableInfoToNodeAttributes(batchTable: BatchTableJson): void {
1183
+ private _convertPropertyTableToNodeAttributes(propertyTable: FeatureTableJson): void {
1187
1184
  let attributeIndex = 0;
1188
- const batchTableWithObjectId = {
1185
+ const propertyTableWithObjectId = {
1189
1186
  OBJECTID: [0],
1190
- ...batchTable
1187
+ ...propertyTable
1191
1188
  };
1192
1189
 
1193
- for (const key in batchTableWithObjectId) {
1194
- const firstAttribute = batchTableWithObjectId[key][0];
1190
+ for (const key in propertyTableWithObjectId) {
1191
+ const firstAttribute = propertyTableWithObjectId[key][0];
1195
1192
  const attributeType = this.getAttributeType(key, firstAttribute);
1196
1193
 
1197
1194
  const storageAttribute = this._createdStorageAttribute(attributeIndex, key, attributeType);
1198
1195
  const fieldAttributeType = this._getFieldAttributeType(attributeType);
1199
1196
  const fieldAttribute = this._createFieldAttribute(key, fieldAttributeType);
1200
- const popupInfo = this._createPopupInfo(batchTableWithObjectId);
1197
+ const popupInfo = this._createPopupInfo(propertyTableWithObjectId);
1201
1198
 
1202
1199
  this.layers0!.attributeStorageInfo!.push(storageAttribute);
1203
1200
  this.layers0!.fields!.push(fieldAttribute);
@@ -1209,7 +1206,7 @@ export default class I3SConverter {
1209
1206
  }
1210
1207
 
1211
1208
  /**
1212
- * Find and return attribute type based on key form Batch table.
1209
+ * Find and return attribute type based on key form propertyTable.
1213
1210
  * @param attributeType
1214
1211
  */
1215
1212
  private _getFieldAttributeType(attributeType: Attribute): ESRIField {
@@ -1229,10 +1226,10 @@ export default class I3SConverter {
1229
1226
 
1230
1227
  /**
1231
1228
  * Generate popup info to show metadata on the map.
1232
- * @param batchTable - Batch table data with OBJECTID.
1229
+ * @param propertyTable - table data with OBJECTID.
1233
1230
  * @return data for correct rendering of popup.
1234
1231
  */
1235
- private _createPopupInfo(batchTable: BatchTableJson): PopupInfo {
1232
+ private _createPopupInfo(propertyTable: FeatureTableJson): PopupInfo {
1236
1233
  const title = '{OBJECTID}';
1237
1234
  const mediaInfos = [];
1238
1235
  const fieldInfos: FieldInfo[] = [];
@@ -1242,7 +1239,7 @@ export default class I3SConverter {
1242
1239
  }[] = [];
1243
1240
  const expressionInfos = [];
1244
1241
 
1245
- for (const key in batchTable) {
1242
+ for (const key in propertyTable) {
1246
1243
  fieldInfos.push({
1247
1244
  fieldName: key,
1248
1245
  visible: true,
@@ -69,19 +69,21 @@ export default class WriteQueue<T extends WriteQueueItem> extends Queue<T> {
69
69
  archiveKeys.push(archiveKey);
70
70
  promises.push(writePromise);
71
71
  }
72
- const writeResults = await Promise.all(promises);
72
+ const writeResults = await Promise.allSettled(promises);
73
73
  this.updateFileMap(archiveKeys, writeResults);
74
74
  }
75
75
  this.writePromise = null;
76
76
  }
77
77
 
78
- private updateFileMap(archiveKeys: (string | undefined)[], writeResults: string[]) {
78
+ private updateFileMap(
79
+ archiveKeys: (string | undefined)[],
80
+ writeResults: PromiseSettledResult<string>[]
81
+ ) {
79
82
  for (let i = 0; i < archiveKeys.length; i++) {
80
83
  const archiveKey = archiveKeys[i];
81
- if (!archiveKey) {
82
- continue;
84
+ if (archiveKey && 'value' in writeResults[i]) {
85
+ this.fileMap[archiveKey] = (writeResults[i] as PromiseFulfilledResult<string>).value;
83
86
  }
84
- this.fileMap[archiveKey] = writeResults[i];
85
87
  }
86
88
  }
87
89
  }