@loaders.gl/tile-converter 4.0.0-alpha.21 → 4.0.0-alpha.23

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 (81) hide show
  1. package/dist/constants.d.ts +0 -2
  2. package/dist/constants.d.ts.map +1 -1
  3. package/dist/constants.js +1 -3
  4. package/dist/converter.min.js +114 -114
  5. package/dist/dist.min.js +2409 -1161
  6. package/dist/es5/constants.js +1 -5
  7. package/dist/es5/constants.js.map +1 -1
  8. package/dist/es5/deps-installer/deps-installer.js +1 -1
  9. package/dist/es5/i3s-converter/helpers/batch-ids-extensions.js +26 -11
  10. package/dist/es5/i3s-converter/helpers/batch-ids-extensions.js.map +1 -1
  11. package/dist/es5/i3s-converter/helpers/feature-attributes.js +14 -12
  12. package/dist/es5/i3s-converter/helpers/feature-attributes.js.map +1 -1
  13. package/dist/es5/i3s-converter/helpers/geometry-converter.js +61 -10
  14. package/dist/es5/i3s-converter/helpers/geometry-converter.js.map +1 -1
  15. package/dist/es5/i3s-converter/helpers/load-3d-tiles.js +70 -4
  16. package/dist/es5/i3s-converter/helpers/load-3d-tiles.js.map +1 -1
  17. package/dist/es5/i3s-converter/helpers/node-index-document.js +3 -2
  18. package/dist/es5/i3s-converter/helpers/node-index-document.js.map +1 -1
  19. package/dist/es5/i3s-converter/helpers/preprocess-3d-tiles.js +1 -2
  20. package/dist/es5/i3s-converter/helpers/preprocess-3d-tiles.js.map +1 -1
  21. package/dist/es5/i3s-converter/i3s-converter.js +70 -58
  22. package/dist/es5/i3s-converter/i3s-converter.js.map +1 -1
  23. package/dist/es5/i3s-server/controllers/slpk-controller.js +2 -2
  24. package/dist/es5/i3s-server/controllers/slpk-controller.js.map +1 -1
  25. package/dist/es5/pgm-loader.js +1 -1
  26. package/dist/es5/slpk-extractor/slpk-extractor.js +1 -1
  27. package/dist/es5/slpk-extractor/slpk-extractor.js.map +1 -1
  28. package/dist/esm/constants.js +0 -2
  29. package/dist/esm/constants.js.map +1 -1
  30. package/dist/esm/deps-installer/deps-installer.js +1 -1
  31. package/dist/esm/i3s-converter/helpers/batch-ids-extensions.js +21 -6
  32. package/dist/esm/i3s-converter/helpers/batch-ids-extensions.js.map +1 -1
  33. package/dist/esm/i3s-converter/helpers/feature-attributes.js +6 -4
  34. package/dist/esm/i3s-converter/helpers/feature-attributes.js.map +1 -1
  35. package/dist/esm/i3s-converter/helpers/geometry-converter.js +59 -8
  36. package/dist/esm/i3s-converter/helpers/geometry-converter.js.map +1 -1
  37. package/dist/esm/i3s-converter/helpers/load-3d-tiles.js +33 -4
  38. package/dist/esm/i3s-converter/helpers/load-3d-tiles.js.map +1 -1
  39. package/dist/esm/i3s-converter/helpers/node-index-document.js +2 -1
  40. package/dist/esm/i3s-converter/helpers/node-index-document.js.map +1 -1
  41. package/dist/esm/i3s-converter/helpers/preprocess-3d-tiles.js +1 -1
  42. package/dist/esm/i3s-converter/helpers/preprocess-3d-tiles.js.map +1 -1
  43. package/dist/esm/i3s-converter/i3s-converter.js +27 -22
  44. package/dist/esm/i3s-converter/i3s-converter.js.map +1 -1
  45. package/dist/esm/i3s-server/bin/i3s-server.min.js +71 -71
  46. package/dist/esm/i3s-server/controllers/slpk-controller.js +1 -1
  47. package/dist/esm/i3s-server/controllers/slpk-controller.js.map +1 -1
  48. package/dist/esm/pgm-loader.js +1 -1
  49. package/dist/esm/slpk-extractor/slpk-extractor.js +2 -1
  50. package/dist/esm/slpk-extractor/slpk-extractor.js.map +1 -1
  51. package/dist/i3s-converter/helpers/batch-ids-extensions.d.ts.map +1 -1
  52. package/dist/i3s-converter/helpers/batch-ids-extensions.js +37 -16
  53. package/dist/i3s-converter/helpers/feature-attributes.d.ts.map +1 -1
  54. package/dist/i3s-converter/helpers/feature-attributes.js +6 -4
  55. package/dist/i3s-converter/helpers/geometry-converter.d.ts +2 -2
  56. package/dist/i3s-converter/helpers/geometry-converter.d.ts.map +1 -1
  57. package/dist/i3s-converter/helpers/geometry-converter.js +93 -12
  58. package/dist/i3s-converter/helpers/load-3d-tiles.d.ts +15 -0
  59. package/dist/i3s-converter/helpers/load-3d-tiles.d.ts.map +1 -1
  60. package/dist/i3s-converter/helpers/load-3d-tiles.js +51 -5
  61. package/dist/i3s-converter/helpers/node-index-document.d.ts.map +1 -1
  62. package/dist/i3s-converter/helpers/node-index-document.js +4 -1
  63. package/dist/i3s-converter/helpers/preprocess-3d-tiles.js +2 -2
  64. package/dist/i3s-converter/i3s-converter.d.ts +1 -1
  65. package/dist/i3s-converter/i3s-converter.d.ts.map +1 -1
  66. package/dist/i3s-converter/i3s-converter.js +38 -17
  67. package/dist/i3s-server/controllers/slpk-controller.js +2 -2
  68. package/dist/slpk-extractor/slpk-extractor.d.ts.map +1 -1
  69. package/dist/slpk-extractor/slpk-extractor.js +2 -1
  70. package/dist/slpk-extractor.min.js +38 -38
  71. package/package.json +14 -14
  72. package/src/constants.ts +0 -3
  73. package/src/i3s-converter/helpers/batch-ids-extensions.ts +53 -14
  74. package/src/i3s-converter/helpers/feature-attributes.ts +8 -6
  75. package/src/i3s-converter/helpers/geometry-converter.ts +135 -12
  76. package/src/i3s-converter/helpers/load-3d-tiles.ts +61 -5
  77. package/src/i3s-converter/helpers/node-index-document.ts +5 -1
  78. package/src/i3s-converter/helpers/preprocess-3d-tiles.ts +1 -1
  79. package/src/i3s-converter/i3s-converter.ts +54 -22
  80. package/src/i3s-server/controllers/slpk-controller.ts +1 -1
  81. package/src/slpk-extractor/slpk-extractor.ts +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loaders.gl/tile-converter",
3
- "version": "4.0.0-alpha.21",
3
+ "version": "4.0.0-alpha.23",
4
4
  "description": "Converter",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -45,18 +45,18 @@
45
45
  "build-i3s-server-bundle": "esbuild src/i3s-server/bin/www.ts --outfile=dist/esm/i3s-server/bin/i3s-server.min.js --platform=node --target=esnext,node14 --external:join-images --minify --bundle --define:__VERSION__=\\\"$npm_package_version\\\""
46
46
  },
47
47
  "dependencies": {
48
- "@loaders.gl/3d-tiles": "4.0.0-alpha.21",
49
- "@loaders.gl/crypto": "4.0.0-alpha.21",
50
- "@loaders.gl/draco": "4.0.0-alpha.21",
51
- "@loaders.gl/gltf": "4.0.0-alpha.21",
52
- "@loaders.gl/i3s": "4.0.0-alpha.21",
53
- "@loaders.gl/images": "4.0.0-alpha.21",
54
- "@loaders.gl/loader-utils": "4.0.0-alpha.21",
55
- "@loaders.gl/polyfills": "4.0.0-alpha.21",
56
- "@loaders.gl/textures": "4.0.0-alpha.21",
57
- "@loaders.gl/tiles": "4.0.0-alpha.21",
58
- "@loaders.gl/worker-utils": "4.0.0-alpha.21",
59
- "@loaders.gl/zip": "4.0.0-alpha.21",
48
+ "@loaders.gl/3d-tiles": "4.0.0-alpha.23",
49
+ "@loaders.gl/crypto": "4.0.0-alpha.23",
50
+ "@loaders.gl/draco": "4.0.0-alpha.23",
51
+ "@loaders.gl/gltf": "4.0.0-alpha.23",
52
+ "@loaders.gl/i3s": "4.0.0-alpha.23",
53
+ "@loaders.gl/images": "4.0.0-alpha.23",
54
+ "@loaders.gl/loader-utils": "4.0.0-alpha.23",
55
+ "@loaders.gl/polyfills": "4.0.0-alpha.23",
56
+ "@loaders.gl/textures": "4.0.0-alpha.23",
57
+ "@loaders.gl/tiles": "4.0.0-alpha.23",
58
+ "@loaders.gl/worker-utils": "4.0.0-alpha.23",
59
+ "@loaders.gl/zip": "4.0.0-alpha.23",
60
60
  "@math.gl/core": "^3.5.1",
61
61
  "@math.gl/culling": "^3.5.1",
62
62
  "@math.gl/geoid": "^3.5.1",
@@ -80,7 +80,7 @@
80
80
  "join-images": "^1.1.3",
81
81
  "sharp": "^0.31.3"
82
82
  },
83
- "gitHead": "df5d670b136192b26941396e944f9c46be788e83",
83
+ "gitHead": "e212f2a0c0e342f7cb65ce84fa2ff39f64b7d94b",
84
84
  "devDependencies": {
85
85
  "@types/express": "^4.17.17",
86
86
  "@types/node": "^20.4.2"
package/src/constants.ts CHANGED
@@ -1,5 +1,2 @@
1
1
  export const BROWSER_ERROR_MESSAGE =
2
2
  'Tile converter does not work in browser, only in node js environment';
3
-
4
- export const EXT_MESH_FEATURES = 'EXT_mesh_features';
5
- export const EXT_FEATURE_METADATA = 'EXT_feature_metadata';
@@ -3,11 +3,16 @@ import type {NumericArray} from '@loaders.gl/loader-utils';
3
3
  import type {
4
4
  GLTF_EXT_feature_metadata_FeatureIdTexture,
5
5
  GLTF_EXT_feature_metadata_GLTF,
6
- GLTF_EXT_feature_metadata_Primitive
6
+ GLTF_EXT_feature_metadata_Primitive,
7
+ GLTF_EXT_structural_metadata
7
8
  } from '@loaders.gl/gltf';
9
+
10
+ import type {GLTF_EXT_mesh_features} from '@loaders.gl/gltf';
11
+
8
12
  import {TypedArray} from '@math.gl/core';
9
13
  import {TextureImageProperties} from '../types';
10
- import {EXT_FEATURE_METADATA, EXT_MESH_FEATURES} from '../../constants';
14
+ import {emod} from '@loaders.gl/math';
15
+ import {EXT_STRUCTURAL_METADATA, EXT_MESH_FEATURES, EXT_FEATURE_METADATA} from '@loaders.gl/gltf';
11
16
  import {Tiles3DTileContent} from '@loaders.gl/3d-tiles';
12
17
 
13
18
  /**
@@ -52,7 +57,6 @@ export function handleBatchIdsExtensions(
52
57
  featureTexture: string | null
53
58
  ): NumericArray {
54
59
  const extensions = primitive?.extensions;
55
-
56
60
  if (!extensions) {
57
61
  return [];
58
62
  }
@@ -67,8 +71,13 @@ export function handleBatchIdsExtensions(
67
71
  featureTexture
68
72
  );
69
73
  case EXT_MESH_FEATURES:
70
- console.warn('EXT_mesh_features extension is not supported yet');
71
- return [];
74
+ return handleExtMeshFeaturesExtension(attributes, extensionData as GLTF_EXT_mesh_features);
75
+ case EXT_STRUCTURAL_METADATA:
76
+ return handleExtStructuralMetadataExtension(
77
+ attributes,
78
+ extensionData as GLTF_EXT_structural_metadata
79
+ );
80
+
72
81
  default:
73
82
  return [];
74
83
  }
@@ -77,6 +86,45 @@ export function handleBatchIdsExtensions(
77
86
  return [];
78
87
  }
79
88
 
89
+ function handleExtStructuralMetadataExtension(
90
+ attributes: {
91
+ [key: string]: GLTFAccessorPostprocessed;
92
+ },
93
+ extStructuralMetadata: GLTF_EXT_structural_metadata
94
+ ): NumericArray {
95
+ // Take only first extension object to get batchIds attribute name.
96
+ const dataAttributeNames = extStructuralMetadata?.dataAttributeNames;
97
+ if (dataAttributeNames?.length) {
98
+ // Let's use the first element of the array
99
+ // TODO: What to do with others if any?
100
+ const batchIdsAttribute = attributes[dataAttributeNames[0]];
101
+ return batchIdsAttribute.value;
102
+ }
103
+ return [];
104
+ }
105
+
106
+ /**
107
+ * Getting batchIds from EXT_mesh_features extensions.
108
+ * @param attributes - gltf accessors
109
+ * @param extMeshFeatures - EXT_mesh_features extension
110
+ * @returns an array of attribute values
111
+ */
112
+ function handleExtMeshFeaturesExtension(
113
+ attributes: {
114
+ [key: string]: GLTFAccessorPostprocessed;
115
+ },
116
+ extMeshFeatures: GLTF_EXT_mesh_features
117
+ ): NumericArray {
118
+ const dataAttributeNames = extMeshFeatures?.dataAttributeNames;
119
+ if (dataAttributeNames?.length) {
120
+ // Let's use the first element of the array
121
+ // TODO: What to do with others if any?
122
+ const batchIdsAttribute = attributes[dataAttributeNames[0]];
123
+ return batchIdsAttribute.value;
124
+ }
125
+ return [];
126
+ }
127
+
80
128
  /**
81
129
  * Get batchIds from EXT_feature_metadata extension.
82
130
  * Docs - https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_feature_metadata
@@ -218,12 +266,3 @@ function generateBatchIdsFromTexture(
218
266
 
219
267
  return batchIds;
220
268
  }
221
-
222
- /**
223
- * Handle UVs if they are out of range [0,1].
224
- * @param n
225
- * @param m
226
- */
227
- function emod(n: number): number {
228
- return ((n % 1) + 1) % 1;
229
- }
@@ -39,12 +39,14 @@ export function flattenPropertyTableByFeatureIds(
39
39
  * @param properties
40
40
  * @param featureIds
41
41
  */
42
- function getPropertiesByFeatureIds(properties: any[], featureIds: number[]): any[] {
43
- const resultProperties: any = [];
42
+ function getPropertiesByFeatureIds(properties: unknown[], featureIds: number[]): unknown[] {
43
+ const resultProperties: unknown[] = [];
44
44
 
45
- for (const featureId of featureIds) {
46
- const property = properties[featureId] || null;
47
- resultProperties.push(property);
45
+ if (properties) {
46
+ for (const featureId of featureIds) {
47
+ const property = properties[featureId] || null;
48
+ resultProperties.push(property);
49
+ }
48
50
  }
49
51
 
50
52
  return resultProperties;
@@ -64,7 +66,7 @@ export function checkPropertiesLength(
64
66
  let needFlatten = false;
65
67
 
66
68
  for (const attribute of Object.values(propertyTable)) {
67
- if (featureIds.length !== attribute.length) {
69
+ if (!featureIds || !attribute || featureIds.length !== attribute.length) {
68
70
  needFlatten = true;
69
71
  }
70
72
  }
@@ -1,5 +1,7 @@
1
1
  import type {FeatureTableJson, Tiles3DTileContent} from '@loaders.gl/3d-tiles';
2
2
  import type {
3
+ GLTF_EXT_mesh_features,
4
+ GLTF_EXT_structural_metadata,
3
5
  GLTFAccessorPostprocessed,
4
6
  GLTFMaterialPostprocessed,
5
7
  GLTFNodePostprocessed,
@@ -47,7 +49,8 @@ import {GL} from '@loaders.gl/math';
47
49
  import type {GLTFAttributesData, TextureImageProperties, TypedArrayConstructor} from '../types';
48
50
  import {generateSyntheticIndices} from '../../lib/utils/geometry-utils';
49
51
  import {BoundingSphere, OrientedBoundingBox} from '@math.gl/culling';
50
- import {EXT_FEATURE_METADATA, EXT_MESH_FEATURES} from '../../constants';
52
+
53
+ import {EXT_MESH_FEATURES, EXT_FEATURE_METADATA, EXT_STRUCTURAL_METADATA} from '@loaders.gl/gltf';
51
54
 
52
55
  // Spec - https://github.com/Esri/i3s-spec/blob/master/docs/1.7/pbrMetallicRoughness.cmn.md
53
56
  const DEFAULT_ROUGHNESS_FACTOR = 1;
@@ -1358,10 +1361,12 @@ function convertPropertyTableToAttributeBuffers(
1358
1361
 
1359
1362
  for (const propertyName in propertyTableWithObjectIds) {
1360
1363
  const type = getAttributeType(propertyName, attributeStorageInfo);
1361
- const value = propertyTableWithObjectIds[propertyName];
1362
- const attributeBuffer = generateAttributeBuffer(type, value);
1364
+ if (type) {
1365
+ const value = propertyTableWithObjectIds[propertyName];
1366
+ const attributeBuffer = generateAttributeBuffer(type, value);
1363
1367
 
1364
- attributeBuffers.push(attributeBuffer);
1368
+ attributeBuffers.push(attributeBuffer);
1369
+ }
1365
1370
  }
1366
1371
 
1367
1372
  return attributeBuffers;
@@ -1401,6 +1406,20 @@ function generateAttributeBuffer(type: string, value: any): ArrayBuffer {
1401
1406
  */
1402
1407
  function getAttributeType(key: string, attributeStorageInfo: any[]): string {
1403
1408
  const attribute = attributeStorageInfo.find((attr) => attr.name === key);
1409
+ if (!attribute) {
1410
+ console.error(
1411
+ `attribute is null, key=${key}, attributeStorageInfo=${JSON.stringify(
1412
+ attributeStorageInfo,
1413
+ null,
1414
+ 2
1415
+ )}`
1416
+ );
1417
+ return '';
1418
+ }
1419
+ if (!attribute.attributeValues) {
1420
+ console.error(`attributeValues is null, attribute=${attribute}`);
1421
+ return '';
1422
+ }
1404
1423
  return attribute.attributeValues.valueType;
1405
1424
  }
1406
1425
 
@@ -1577,8 +1596,8 @@ function generateFeatureIndexAttribute(
1577
1596
  * Find property table in tile
1578
1597
  * For example it can be batchTable for b3dm files or property table in gLTF extension.
1579
1598
  * @param tileContent - 3DTiles tile content
1580
- * @param metadataClass - - user selected feature metadata class name
1581
- * @return batch table from b3dm / feature properties from EXT_FEATURE_METADATA
1599
+ * @param metadataClass - user selected feature metadata class name
1600
+ * @return batch table from b3dm / feature properties from EXT_FEATURE_METADATA, EXT_MESH_FEATURES or EXT_STRUCTURAL_METADATA
1582
1601
  */
1583
1602
  export function getPropertyTable(
1584
1603
  tileContent: Tiles3DTileContent | null,
@@ -1587,7 +1606,7 @@ export function getPropertyTable(
1587
1606
  if (!tileContent) {
1588
1607
  return null;
1589
1608
  }
1590
-
1609
+ let propertyTable: FeatureTableJson | null;
1591
1610
  const batchTableJson = tileContent?.batchTableJson;
1592
1611
 
1593
1612
  if (batchTableJson) {
@@ -1598,14 +1617,25 @@ export function getPropertyTable(
1598
1617
 
1599
1618
  switch (extensionName) {
1600
1619
  case EXT_MESH_FEATURES: {
1601
- console.warn('The I3S converter does not yet support the EXT_mesh_features extension');
1602
- return null;
1620
+ propertyTable = getPropertyTableFromExtMeshFeatures(
1621
+ extension as GLTF_EXT_mesh_features,
1622
+ metadataClass
1623
+ );
1624
+ return propertyTable;
1625
+ }
1626
+ case EXT_STRUCTURAL_METADATA: {
1627
+ propertyTable = getPropertyTableFromExtStructuralMetadata(
1628
+ extension as GLTF_EXT_structural_metadata,
1629
+ metadataClass
1630
+ );
1631
+ return propertyTable;
1603
1632
  }
1604
1633
  case EXT_FEATURE_METADATA: {
1605
- return getPropertyTableFromExtFeatureMetadata(
1634
+ propertyTable = getPropertyTableFromExtFeatureMetadata(
1606
1635
  extension as GLTF_EXT_feature_metadata_GLTF,
1607
1636
  metadataClass
1608
1637
  );
1638
+ return propertyTable;
1609
1639
  }
1610
1640
  default:
1611
1641
  return null;
@@ -1618,9 +1648,18 @@ export function getPropertyTable(
1618
1648
  */
1619
1649
  function getPropertyTableExtension(tileContent: Tiles3DTileContent): {
1620
1650
  extensionName: null | string;
1621
- extension: string | GLTF_EXT_feature_metadata_GLTF | null;
1651
+ extension:
1652
+ | string
1653
+ | GLTF_EXT_feature_metadata_GLTF
1654
+ | GLTF_EXT_structural_metadata
1655
+ | GLTF_EXT_mesh_features
1656
+ | null;
1622
1657
  } {
1623
- const extensionsWithPropertyTables = [EXT_FEATURE_METADATA, EXT_MESH_FEATURES];
1658
+ const extensionsWithPropertyTables = [
1659
+ EXT_FEATURE_METADATA,
1660
+ EXT_STRUCTURAL_METADATA,
1661
+ EXT_MESH_FEATURES
1662
+ ];
1624
1663
  const extensionsUsed = tileContent?.gltf?.extensionsUsed;
1625
1664
 
1626
1665
  if (!extensionsUsed) {
@@ -1649,6 +1688,8 @@ function getPropertyTableExtension(tileContent: Tiles3DTileContent): {
1649
1688
  /**
1650
1689
  * Handle EXT_feature_metadata to get property table
1651
1690
  * @param extension - global level of EXT_FEATURE_METADATA extension
1691
+ * @param metadataClass - user selected feature metadata class name
1692
+ * @returns {FeatureTableJson | null} Property table or null if the extension can't be handled properly.
1652
1693
  */
1653
1694
  function getPropertyTableFromExtFeatureMetadata(
1654
1695
  extension: GLTF_EXT_feature_metadata_GLTF,
@@ -1701,3 +1742,85 @@ function getPropertyTableFromExtFeatureMetadata(
1701
1742
  );
1702
1743
  return null;
1703
1744
  }
1745
+
1746
+ /**
1747
+ * Handle EXT_structural_metadata to get property table
1748
+ * @param extension - global level of EXT_STRUCTURAL_METADATA extension
1749
+ * @param metadataClass - user selected feature metadata class name
1750
+ * @returns {FeatureTableJson | null} Property table or null if the extension can't be handled properly.
1751
+ */
1752
+ function getPropertyTableFromExtStructuralMetadata(
1753
+ extension: GLTF_EXT_structural_metadata,
1754
+ metadataClass?: string
1755
+ ): FeatureTableJson | null {
1756
+ if (extension?.propertyTables) {
1757
+ /**
1758
+ * Take only first feature table to generate attributes storage info object.
1759
+ * TODO: Think about getting data from all feature tables?
1760
+ * It can be tricky just because 3dTiles is able to have multiple featureId attributes and multiple feature tables.
1761
+ * In I3S we should decide which featureIds attribute will be passed to geometry data.
1762
+ */
1763
+ const firstPropertyTable = extension?.propertyTables[0];
1764
+ const propertyTableWithData = {};
1765
+
1766
+ for (const propertyName in firstPropertyTable.properties) {
1767
+ propertyTableWithData[propertyName] = firstPropertyTable.properties[propertyName].data;
1768
+ }
1769
+
1770
+ return propertyTableWithData;
1771
+ }
1772
+
1773
+ if (extension?.propertyTextures) {
1774
+ /**
1775
+ * Take only first feature table to generate attributes storage info object.
1776
+ * TODO: Think about getting data from all feature tables?
1777
+ * It can be tricky just because 3dTiles is able to have multiple featureId attributes and multiple feature tables.
1778
+ * In I3S we should decide which featureIds attribute will be passed to geometry data.
1779
+ */
1780
+ if (extension?.propertyTextures) {
1781
+ const firstPropertyTexture = extension?.propertyTextures[0];
1782
+ const propertyTableWithData = {};
1783
+
1784
+ for (const propertyName in firstPropertyTexture.properties) {
1785
+ propertyTableWithData[propertyName] = firstPropertyTexture.properties[propertyName].data;
1786
+ }
1787
+
1788
+ return propertyTableWithData;
1789
+ }
1790
+ }
1791
+
1792
+ console.warn(
1793
+ "The I3S converter couldn't handle EXT_structural_metadata extension: There is neither propertyTables, no propertyTextures in the extension."
1794
+ );
1795
+ return null;
1796
+ }
1797
+
1798
+ /**
1799
+ * Handle EXT_mesh_features to get property table
1800
+ * @param extension - global level of EXT_MESH_FEATURES extension
1801
+ * @param metadataClass - user selected feature metadata class name
1802
+ * @returns {FeatureTableJson | null} Property table or null if the extension can't be handled properly.
1803
+ */
1804
+ function getPropertyTableFromExtMeshFeatures(
1805
+ extension: GLTF_EXT_mesh_features,
1806
+ metadataClass?: string
1807
+ ): FeatureTableJson | null {
1808
+ if (extension?.featureIds) {
1809
+ const firstFeatureId = extension?.featureIds[0];
1810
+ const propertyTableWithData = {};
1811
+
1812
+ // When firstFeatureId.propertyTable is defined, the property data will be taken from EXT_structural_metadata extension
1813
+ if (!firstFeatureId.propertyTable) {
1814
+ console.warn(
1815
+ 'Should be implemented as we have the tileset with Ext_mesh_features not linked with EXT_structural_metadata extension'
1816
+ );
1817
+ }
1818
+
1819
+ return propertyTableWithData;
1820
+ }
1821
+
1822
+ console.warn(
1823
+ "The I3S converter couldn't handle EXT_mesh_features extension: There is no featureIds in the extension."
1824
+ );
1825
+ return null;
1826
+ }
@@ -4,7 +4,8 @@ import type {
4
4
  Tiles3DTileJSONPostprocessed,
5
5
  Tiles3DTilesetJSONPostprocessed
6
6
  } from '@loaders.gl/3d-tiles';
7
- import {load} from '@loaders.gl/core';
7
+ import {Tiles3DArchiveFileSystem} from '@loaders.gl/3d-tiles';
8
+ import {LoaderWithParser, load} from '@loaders.gl/core';
8
9
 
9
10
  /**
10
11
  * Load nested 3DTiles tileset. If the sourceTile is not nested tileset - do nothing
@@ -18,7 +19,7 @@ export const loadNestedTileset = async (
18
19
  sourceTile: Tiles3DTileJSONPostprocessed,
19
20
  tilesetLoadOptions: Tiles3DLoaderOptions
20
21
  ): Promise<void> => {
21
- const isTileset = sourceTile.type === 'json';
22
+ const isTileset = isNestedTileset(sourceTile);
22
23
  if (!sourceTileset || !sourceTile.contentUrl || !isTileset) {
23
24
  return;
24
25
  }
@@ -30,7 +31,11 @@ export const loadNestedTileset = async (
30
31
  assetGltfUpAxis: (sourceTileset.asset && sourceTileset.asset.gltfUpAxis) || 'Y'
31
32
  }
32
33
  };
33
- const tileContent = await load(sourceTile.contentUrl, sourceTileset.loader, loadOptions);
34
+ const tileContent = await loadFromArchive(
35
+ sourceTile.contentUrl,
36
+ sourceTileset.loader,
37
+ loadOptions
38
+ );
34
39
 
35
40
  if (tileContent.root) {
36
41
  sourceTile.children = [tileContent.root];
@@ -49,7 +54,7 @@ export const loadTile3DContent = async (
49
54
  sourceTile: Tiles3DTileJSONPostprocessed,
50
55
  tilesetLoadOptions: Tiles3DLoaderOptions
51
56
  ): Promise<Tiles3DTileContent | null> => {
52
- const isTileset = sourceTile.type === 'json';
57
+ const isTileset = isNestedTileset(sourceTile);
53
58
  if (!sourceTileset || !sourceTile.contentUrl || isTileset) {
54
59
  return null;
55
60
  }
@@ -62,7 +67,58 @@ export const loadTile3DContent = async (
62
67
  assetGltfUpAxis: (sourceTileset.asset && sourceTileset.asset.gltfUpAxis) || 'Y'
63
68
  }
64
69
  };
65
- const tileContent = await load(sourceTile.contentUrl, sourceTileset.loader, loadOptions);
70
+ const tileContent = await loadFromArchive(
71
+ sourceTile.contentUrl,
72
+ sourceTileset.loader,
73
+ loadOptions
74
+ );
66
75
 
67
76
  return tileContent;
68
77
  };
78
+
79
+ /**
80
+ * Load a resource with load options and .3tz format support
81
+ * @param url - resource URL
82
+ * @param loader - loader to parse data (Tiles3DLoader / CesiumIonLoader)
83
+ * @param loadOptions - 3d-tiles loader options
84
+ * @returns 3d-tiles resource
85
+ */
86
+ export async function loadFromArchive(
87
+ url: string,
88
+ loader: LoaderWithParser,
89
+ loadOptions: Tiles3DLoaderOptions
90
+ ) {
91
+ const tz3UrlParts = url.split('.3tz');
92
+ let filename: string | null;
93
+ // No '.3tz'. The file will be loaded with global fetch function
94
+ if (tz3UrlParts.length === 1) {
95
+ filename = null;
96
+ } else if (tz3UrlParts.length === 2) {
97
+ filename = tz3UrlParts[1].slice(1);
98
+ if (filename === '') {
99
+ filename = 'tileset.json';
100
+ }
101
+ } else {
102
+ throw new Error('Unexpected URL format');
103
+ }
104
+ if (filename) {
105
+ const tz3Path = `${tz3UrlParts[0]}.3tz`;
106
+ const fileSystem = new Tiles3DArchiveFileSystem(tz3Path);
107
+ const content = await load(filename, loader, {
108
+ ...loadOptions,
109
+ fetch: fileSystem.fetch.bind(fileSystem)
110
+ });
111
+ await fileSystem.destroy();
112
+ return content;
113
+ }
114
+ return await load(url, loader, loadOptions);
115
+ }
116
+
117
+ /**
118
+ * Check if tile is nested tileset
119
+ * @param tile - 3DTiles header data
120
+ * @returns true if tile is nested tileset
121
+ */
122
+ export function isNestedTileset(tile: Tiles3DTileJSONPostprocessed) {
123
+ return tile?.type === 'json' || tile?.type === '3tz';
124
+ }
@@ -323,7 +323,11 @@ export class NodeIndexDocument {
323
323
  parentNode.converter.layers0?.attributeStorageInfo?.length
324
324
  ) {
325
325
  node.attributeData = [];
326
- for (let index = 0; index < attributes.length; index++) {
326
+ const minimumLength =
327
+ attributes.length < parentNode.converter.layers0.attributeStorageInfo.length
328
+ ? attributes.length
329
+ : parentNode.converter.layers0.attributeStorageInfo.length;
330
+ for (let index = 0; index < minimumLength; index++) {
327
331
  const folderName = parentNode.converter.layers0.attributeStorageInfo[index].key;
328
332
  node.attributeData.push({href: `./attributes/${folderName}/0`});
329
333
  }
@@ -2,7 +2,7 @@ import {Tiles3DTileContent} from '@loaders.gl/3d-tiles';
2
2
  import {GLTFPrimitiveModeString, PreprocessData} from '../types';
3
3
  import {GLTF, GLTFLoader, GLTF_EXT_feature_metadata_GLTF} from '@loaders.gl/gltf';
4
4
  import {parse} from '@loaders.gl/core';
5
- import {EXT_FEATURE_METADATA} from '../../constants';
5
+ import {EXT_FEATURE_METADATA} from '@loaders.gl/gltf';
6
6
 
7
7
  /**
8
8
  * glTF primitive modes
@@ -12,7 +12,8 @@ import type {
12
12
  SceneLayer3D,
13
13
  BoundingVolumes,
14
14
  MaxScreenThresholdSQ,
15
- NodeInPage
15
+ NodeInPage,
16
+ AttributeStorageInfo
16
17
  } from '@loaders.gl/i3s';
17
18
  import {load, encode, isBrowser} from '@loaders.gl/core';
18
19
  import {CesiumIonLoader, Tiles3DLoader} from '@loaders.gl/3d-tiles';
@@ -67,7 +68,12 @@ import {
67
68
  getFieldAttributeType
68
69
  } from './helpers/feature-attributes';
69
70
  import {NodeIndexDocument} from './helpers/node-index-document';
70
- import {loadNestedTileset, loadTile3DContent} from './helpers/load-3d-tiles';
71
+ import {
72
+ isNestedTileset,
73
+ loadNestedTileset,
74
+ loadTile3DContent,
75
+ loadFromArchive
76
+ } from './helpers/load-3d-tiles';
71
77
  import {Matrix4} from '@math.gl/core';
72
78
  import {BoundingSphere, OrientedBoundingBox} from '@math.gl/culling';
73
79
  import {createBoundingVolume} from '@loaders.gl/tiles';
@@ -251,7 +257,7 @@ export default class I3SConverter {
251
257
  if (preloadOptions.headers) {
252
258
  this.loadOptions.fetch = {headers: preloadOptions.headers};
253
259
  }
254
- this.sourceTileset = await load(tilesetUrl, this.Loader, this.loadOptions);
260
+ this.sourceTileset = await loadFromArchive(tilesetUrl, this.Loader, this.loadOptions);
255
261
 
256
262
  const preprocessResult = await this.preprocessConversion();
257
263
 
@@ -349,7 +355,8 @@ export default class I3SConverter {
349
355
  sourceTile: Tiles3DTileJSONPostprocessed,
350
356
  traversalProps: null
351
357
  ): Promise<null> {
352
- if (sourceTile.type === 'json') {
358
+ const isTileset = isNestedTileset(sourceTile);
359
+ if (isTileset) {
353
360
  await loadNestedTileset(this.sourceTileset, sourceTile, this.loadOptions);
354
361
  return null;
355
362
  }
@@ -562,8 +569,9 @@ export default class I3SConverter {
562
569
  sourceTile: Tiles3DTileJSONPostprocessed,
563
570
  traversalProps: TraversalConversionProps
564
571
  ): Promise<TraversalConversionProps> {
565
- if (sourceTile.type === 'json' || sourceTile.type === 'empty') {
566
- if (sourceTile.type === 'json') {
572
+ const isTileset = isNestedTileset(sourceTile);
573
+ if (isTileset || sourceTile.type === 'empty') {
574
+ if (isTileset) {
567
575
  if (sourceTile.id) {
568
576
  console.log(`[load]: ${sourceTile.id}`); // eslint-disable-line
569
577
  }
@@ -642,7 +650,14 @@ export default class I3SConverter {
642
650
 
643
651
  const propertyTable = getPropertyTable(tileContent, this.options.metadataClass);
644
652
 
645
- if (propertyTable && !this.layers0?.attributeStorageInfo?.length) {
653
+ if (propertyTable) {
654
+ /*
655
+ Call the convertion procedure even if the node attributes have been already created.
656
+ We will append new attributes only in case the property table is updated.
657
+ According to ver 1.9 (see https://github.com/Esri/i3s-spec/blob/master/docs/1.9/attributeStorageInfo.cmn.md):
658
+ "The attributeStorageInfo object describes the structure of the binary attribute data resource of a layer, which is the same for every node in the layer."
659
+ But the specification of ver 2.1 doesn't have such a requirement ("...the same for every node...")
660
+ */
646
661
  this._convertPropertyTableToNodeAttributes(propertyTable);
647
662
  }
648
663
 
@@ -728,7 +743,7 @@ export default class I3SConverter {
728
743
  * @param boundingVolume - initialized bounding volume of the source tile
729
744
  * @param tileContent - content of the source tile
730
745
  * @param parentId - id of parent node in node pages
731
- * @param propertyTable - batch table from b3dm / feature properties from EXT_FEATURE_METADATA
746
+ * @param propertyTable - batch table from b3dm / feature properties from EXT_FEATURE_METADATA, EXT_MESH_FEATURES or EXT_STRUCTURAL_METADATA
732
747
  * @returns - converted node resources
733
748
  */
734
749
  private async _convertResources(
@@ -1061,7 +1076,12 @@ export default class I3SConverter {
1061
1076
  slpkChildPath: string
1062
1077
  ): Promise<void> {
1063
1078
  if (attributes?.length && this.layers0?.attributeStorageInfo?.length) {
1064
- for (let index = 0; index < attributes.length; index++) {
1079
+ const minimumLength =
1080
+ attributes.length < this.layers0.attributeStorageInfo.length
1081
+ ? attributes.length
1082
+ : this.layers0.attributeStorageInfo.length;
1083
+
1084
+ for (let index = 0; index < minimumLength; index++) {
1065
1085
  const folderName = this.layers0.attributeStorageInfo[index].key;
1066
1086
  const fileBuffer = new Uint8Array(attributes[index]);
1067
1087
 
@@ -1143,19 +1163,31 @@ export default class I3SConverter {
1143
1163
  };
1144
1164
 
1145
1165
  for (const key in propertyTableWithObjectId) {
1146
- const firstAttribute = propertyTableWithObjectId[key][0];
1147
- const attributeType = getAttributeType(key, firstAttribute);
1148
-
1149
- const storageAttribute = createdStorageAttribute(attributeIndex, key, attributeType);
1150
- const fieldAttributeType = getFieldAttributeType(attributeType);
1151
- const fieldAttribute = createFieldAttribute(key, fieldAttributeType);
1152
- const popupInfo = createPopupInfo(propertyTableWithObjectId);
1153
-
1154
- this.layers0!.attributeStorageInfo!.push(storageAttribute);
1155
- this.layers0!.fields!.push(fieldAttribute);
1156
- this.layers0!.popupInfo = popupInfo;
1157
- this.layers0!.layerType = _3D_OBJECT_LAYER_TYPE;
1158
-
1166
+ /*
1167
+ We will append new attributes only in case the property table is updated.
1168
+ According to ver 1.9 (see https://github.com/Esri/i3s-spec/blob/master/docs/1.9/attributeStorageInfo.cmn.md):
1169
+ "The attributeStorageInfo object describes the structure of the binary attribute data resource of a layer, which is the same for every node in the layer."
1170
+ But the specification of ver 2.1 doesn't have such a requirement ("...the same for every node...")
1171
+ */
1172
+ const found = this.layers0!.attributeStorageInfo!.find((element) => element.name === key);
1173
+ if (!found) {
1174
+ const firstAttribute = propertyTableWithObjectId[key][0];
1175
+ const attributeType = getAttributeType(key, firstAttribute);
1176
+
1177
+ const storageAttribute: AttributeStorageInfo = createdStorageAttribute(
1178
+ attributeIndex,
1179
+ key,
1180
+ attributeType
1181
+ );
1182
+ const fieldAttributeType = getFieldAttributeType(attributeType);
1183
+ const fieldAttribute = createFieldAttribute(key, fieldAttributeType);
1184
+ const popupInfo = createPopupInfo(propertyTableWithObjectId);
1185
+
1186
+ this.layers0!.attributeStorageInfo!.push(storageAttribute);
1187
+ this.layers0!.fields!.push(fieldAttribute);
1188
+ this.layers0!.popupInfo = popupInfo;
1189
+ this.layers0!.layerType = _3D_OBJECT_LAYER_TYPE;
1190
+ }
1159
1191
  attributeIndex += 1;
1160
1192
  }
1161
1193
  }
@@ -1,6 +1,6 @@
1
1
  import '@loaders.gl/polyfills';
2
2
  import {parseSLPK} from '@loaders.gl/i3s';
3
- import {FileHandleFile} from '@loaders.gl/zip';
3
+ import {FileHandleFile} from '@loaders.gl/loader-utils';
4
4
 
5
5
  let slpkArchive;
6
6