@loaders.gl/tile-converter 4.0.0-beta.4 → 4.0.0-beta.6

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.
@@ -7,7 +7,11 @@ import type {
7
7
  GLTFMeshPostprocessed,
8
8
  GLTFTexturePostprocessed,
9
9
  GLTF_EXT_feature_metadata_GLTF,
10
- GLTF_EXT_structural_metadata_GLTF
10
+ GLTF_EXT_feature_metadata_FeatureTable,
11
+ GLTF_EXT_feature_metadata_FeatureTexture,
12
+ GLTF_EXT_structural_metadata_GLTF,
13
+ GLTF_EXT_structural_metadata_PropertyTable,
14
+ GLTF_EXT_structural_metadata_PropertyTexture
11
15
  } from '@loaders.gl/gltf';
12
16
 
13
17
  import {Vector3, Matrix4, Vector4} from '@math.gl/core';
@@ -49,12 +53,7 @@ import type {GLTFAttributesData, TextureImageProperties, TypedArrayConstructor}
49
53
  import {generateSyntheticIndices} from '../../lib/utils/geometry-utils';
50
54
  import {BoundingSphere, OrientedBoundingBox} from '@math.gl/culling';
51
55
 
52
- import {
53
- EXT_FEATURE_METADATA,
54
- EXT_STRUCTURAL_METADATA,
55
- getPropertyTableFromExtFeatureMetadata,
56
- getPropertyTableFromExtStructuralMetadata
57
- } from '@loaders.gl/gltf';
56
+ import {EXT_FEATURE_METADATA, EXT_STRUCTURAL_METADATA} from '@loaders.gl/gltf';
58
57
 
59
58
  // Spec - https://github.com/Esri/i3s-spec/blob/master/docs/1.7/pbrMetallicRoughness.cmn.md
60
59
  const DEFAULT_ROUGHNESS_FACTOR = 1;
@@ -1648,6 +1647,99 @@ export function getPropertyTable(
1648
1647
  }
1649
1648
  }
1650
1649
 
1650
+ /**
1651
+ * Handles EXT_structural_metadata to get property table.
1652
+ * @param extension - Global level of EXT_STRUCTURAL_METADATA extension.
1653
+ * @param metadataClass - User selected feature metadata class name.
1654
+ * @returns {FeatureTableJson | null} Property table or null if the extension can't be handled properly.
1655
+ */
1656
+ function getPropertyTableFromExtStructuralMetadata(
1657
+ extension: GLTF_EXT_structural_metadata_GLTF,
1658
+ metadataClass?: string
1659
+ ): FeatureTableJson | null {
1660
+ /**
1661
+ * Note, 3dTiles is able to have multiple featureId attributes and multiple feature tables.
1662
+ * In I3S we should decide which featureIds attribute will be passed to geometry data.
1663
+ * So, we take only the feature table / feature texture to generate attributes storage info object.
1664
+ * If the user has selected the metadataClass, the table with the corresponding class will be used,
1665
+ * or just the first one otherwise.
1666
+ */
1667
+ if (extension.propertyTables) {
1668
+ for (const propertyTable of extension.propertyTables) {
1669
+ if (propertyTable.class === metadataClass || !metadataClass) {
1670
+ return getPropertyData(propertyTable);
1671
+ }
1672
+ }
1673
+ }
1674
+
1675
+ if (extension.propertyTextures) {
1676
+ for (const propertyTexture of extension.propertyTextures) {
1677
+ if (propertyTexture.class === metadataClass || !metadataClass) {
1678
+ return getPropertyData(propertyTexture);
1679
+ }
1680
+ }
1681
+ }
1682
+
1683
+ return null;
1684
+ }
1685
+
1686
+ /**
1687
+ * Handles EXT_feature_metadata to get property table.
1688
+ * @param extension - Global level of EXT_FEATURE_METADATA extension.
1689
+ * @param metadataClass - User selected feature metadata class name.
1690
+ * @returns {FeatureTableJson | null} Property table or null if the extension can't be handled properly.
1691
+ */
1692
+ function getPropertyTableFromExtFeatureMetadata(
1693
+ extension: GLTF_EXT_feature_metadata_GLTF,
1694
+ metadataClass?: string
1695
+ ): FeatureTableJson | null {
1696
+ /**
1697
+ * Note, 3dTiles is able to have multiple featureId attributes and multiple feature tables.
1698
+ * In I3S we should decide which featureIds attribute will be passed to geometry data.
1699
+ * So, we take only the feature table / feature texture to generate attributes storage info object.
1700
+ * If the user has selected the metadataClass, the table with the corresponding class will be used,
1701
+ * or just the first one otherwise.
1702
+ */
1703
+ if (extension.featureTables) {
1704
+ for (const featureTableName in extension.featureTables) {
1705
+ const featureTable = extension.featureTables[featureTableName];
1706
+ if (featureTable.class === metadataClass || !metadataClass) {
1707
+ return getPropertyData(featureTable);
1708
+ }
1709
+ }
1710
+ }
1711
+
1712
+ if (extension.featureTextures) {
1713
+ for (const featureTextureName in extension.featureTextures) {
1714
+ const featureTexture = extension.featureTextures[featureTextureName];
1715
+ if (featureTexture.class === metadataClass || !metadataClass) {
1716
+ return getPropertyData(featureTexture);
1717
+ }
1718
+ }
1719
+ }
1720
+
1721
+ return null;
1722
+ }
1723
+
1724
+ /**
1725
+ * Gets data from Property Table or Property Texture
1726
+ * @param featureObject - property table or texture from the extension
1727
+ * @returns Table containing property data
1728
+ */
1729
+ function getPropertyData<
1730
+ Type extends
1731
+ | GLTF_EXT_structural_metadata_PropertyTable
1732
+ | GLTF_EXT_structural_metadata_PropertyTexture
1733
+ | GLTF_EXT_feature_metadata_FeatureTable
1734
+ | GLTF_EXT_feature_metadata_FeatureTexture
1735
+ >(featureObject: Type) {
1736
+ const propertyTableWithData = {};
1737
+ for (const propertyName in featureObject.properties) {
1738
+ propertyTableWithData[propertyName] = featureObject.properties[propertyName].data;
1739
+ }
1740
+ return propertyTableWithData;
1741
+ }
1742
+
1651
1743
  /**
1652
1744
  * Check extensions which can be with property table inside.
1653
1745
  * @param tileContent - 3DTiles tile content
@@ -62,6 +62,7 @@ export const loadTile3DContent = async (
62
62
  const loadOptions = {
63
63
  ...tilesetLoadOptions,
64
64
  [sourceTileset.loader.id]: {
65
+ // @ts-ignore
65
66
  ...(tilesetLoadOptions[sourceTileset.loader.id] || {}),
66
67
  isTileset,
67
68
  assetGltfUpAxis: (sourceTileset.asset && sourceTileset.asset.gltfUpAxis) || 'Y'
@@ -13,7 +13,7 @@ import type {
13
13
  BoundingVolumes,
14
14
  MaxScreenThresholdSQ,
15
15
  NodeInPage,
16
- AttributeStorageInfo
16
+ Attribute
17
17
  } from '@loaders.gl/i3s';
18
18
  import {load, encode, isBrowser} from '@loaders.gl/core';
19
19
  import {CesiumIonLoader, Tiles3DLoader} from '@loaders.gl/3d-tiles';
@@ -61,11 +61,12 @@ import {WorkerFarm} from '@loaders.gl/worker-utils';
61
61
  import WriteQueue from '../lib/utils/write-queue';
62
62
  import {BROWSER_ERROR_MESSAGE} from '../constants';
63
63
  import {
64
+ getAttributeTypesFromPropertyTable,
65
+ getAttributeTypesFromSchema,
64
66
  createdStorageAttribute,
67
+ getFieldAttributeType,
65
68
  createFieldAttribute,
66
- createPopupInfo,
67
- getAttributeType,
68
- getFieldAttributeType
69
+ createPopupInfo
69
70
  } from './helpers/feature-attributes';
70
71
  import {NodeIndexDocument} from './helpers/node-index-document';
71
72
  import {
@@ -663,16 +664,7 @@ export default class I3SConverter {
663
664
  let boundingVolumes = createBoundingVolumes(sourceBoundingVolume, this.geoidHeightModel!);
664
665
 
665
666
  const propertyTable = getPropertyTable(tileContent, this.options.metadataClass);
666
-
667
- if (propertyTable) {
668
- /*
669
- Call the convertion procedure even if the node attributes have been already created.
670
- We will append new attributes only in case the property table is updated.
671
- According to ver 1.9 (see https://github.com/Esri/i3s-spec/blob/master/docs/1.9/attributeStorageInfo.cmn.md):
672
- "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."
673
- */
674
- this._convertPropertyTableToNodeAttributes(propertyTable);
675
- }
667
+ this.createAttributeStorageInfo(tileContent, propertyTable);
676
668
 
677
669
  const resourcesData = await this._convertResources(
678
670
  sourceTile,
@@ -1165,42 +1157,83 @@ export default class I3SConverter {
1165
1157
  }
1166
1158
 
1167
1159
  /**
1168
- * Do conversion of 3DTiles property table to I3s node attributes.
1169
- * @param propertyTable - Table with layer meta data.
1160
+ * Creates attribute storage info based on either extension schema or property table.
1161
+ * @param tileContent - content of the source tile
1162
+ * @param propertyTable - feature properties from EXT_FEATURE_METADATA, EXT_STRUCTURAL_METADATA
1170
1163
  */
1171
- private _convertPropertyTableToNodeAttributes(propertyTable: FeatureTableJson): void {
1172
- let attributeIndex = 0;
1173
- const propertyTableWithObjectId = {
1174
- OBJECTID: [0],
1175
- ...propertyTable
1164
+ private createAttributeStorageInfo(
1165
+ tileContent: Tiles3DTileContent | null,
1166
+ propertyTable: FeatureTableJson | null
1167
+ ): void {
1168
+ /*
1169
+ In case the tileset doesn't have either EXT_structural_metadata or EXT_feature_metadata
1170
+ that can be a source of attribute information so metadataClass is not specified
1171
+ we will collect attribute information for node attributes from the property table
1172
+ taken from each tile.
1173
+ */
1174
+ let attributeTypesMap: Record<string, Attribute> | null = null;
1175
+ if (this.options.metadataClass) {
1176
+ if (!this.layers0!.attributeStorageInfo!.length && tileContent?.gltf) {
1177
+ attributeTypesMap = getAttributeTypesFromSchema(
1178
+ tileContent.gltf,
1179
+ this.options.metadataClass
1180
+ );
1181
+ }
1182
+ } else if (propertyTable) {
1183
+ attributeTypesMap = getAttributeTypesFromPropertyTable(propertyTable);
1184
+ }
1185
+
1186
+ if (attributeTypesMap) {
1187
+ this.createStorageAttributes(attributeTypesMap);
1188
+ }
1189
+ }
1190
+
1191
+ /**
1192
+ * Creates Attribute Storage Info objects based on attribute's types
1193
+ * @param attributeTypesMap - set of attribute's types
1194
+ */
1195
+ private createStorageAttributes(attributeTypesMap: Record<string, Attribute>): void {
1196
+ if (!Object.keys(attributeTypesMap).length) {
1197
+ return;
1198
+ }
1199
+ const attributeTypes: Record<string, Attribute> = {
1200
+ OBJECTID: 'OBJECTID',
1201
+ ...attributeTypesMap
1176
1202
  };
1177
1203
 
1178
- for (const key in propertyTableWithObjectId) {
1204
+ let isUpdated = false;
1205
+ let attributeIndex = this.layers0!.attributeStorageInfo!.length;
1206
+ for (const key in attributeTypes) {
1179
1207
  /*
1180
- We will append new attributes only in case the property table is updated.
1181
- According to ver 1.9 (see https://github.com/Esri/i3s-spec/blob/master/docs/1.9/attributeStorageInfo.cmn.md):
1182
- "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."
1208
+ We will append a new attribute only in case it has not been added to the attribute storage info yet.
1183
1209
  */
1184
- const found = this.layers0!.attributeStorageInfo!.find((element) => element.name === key);
1185
- if (!found) {
1186
- const firstAttribute = propertyTableWithObjectId[key][0];
1187
- const attributeType = getAttributeType(key, firstAttribute);
1188
-
1189
- const storageAttribute: AttributeStorageInfo = createdStorageAttribute(
1190
- attributeIndex,
1191
- key,
1192
- attributeType
1193
- );
1210
+ const elementFound = this.layers0!.attributeStorageInfo!.find(
1211
+ (element) => element.name === key
1212
+ );
1213
+ if (!elementFound) {
1214
+ const attributeType = attributeTypes[key];
1215
+
1216
+ const storageAttribute = createdStorageAttribute(attributeIndex, key, attributeType);
1194
1217
  const fieldAttributeType = getFieldAttributeType(attributeType);
1195
1218
  const fieldAttribute = createFieldAttribute(key, fieldAttributeType);
1196
- const popupInfo = createPopupInfo(propertyTableWithObjectId);
1197
1219
 
1198
1220
  this.layers0!.attributeStorageInfo!.push(storageAttribute);
1199
1221
  this.layers0!.fields!.push(fieldAttribute);
1200
- this.layers0!.popupInfo = popupInfo;
1201
- this.layers0!.layerType = _3D_OBJECT_LAYER_TYPE;
1222
+ attributeIndex += 1;
1223
+ isUpdated = true;
1224
+ }
1225
+ }
1226
+ if (isUpdated) {
1227
+ /*
1228
+ The attributeStorageInfo is updated. So, popupInfo should be recreated.
1229
+ Use attributeStorageInfo as a source of attribute names to create the popupInfo.
1230
+ */
1231
+ const attributeNames: string[] = [];
1232
+ for (let info of this.layers0!.attributeStorageInfo!) {
1233
+ attributeNames.push(info.name);
1202
1234
  }
1203
- attributeIndex += 1;
1235
+ this.layers0!.popupInfo = createPopupInfo(attributeNames);
1236
+ this.layers0!.layerType = _3D_OBJECT_LAYER_TYPE;
1204
1237
  }
1205
1238
  }
1206
1239
 
@@ -188,7 +188,7 @@ export type PreprocessData = {
188
188
  /** Mesh topology types used in gltf primitives of the tileset */
189
189
  meshTopologyTypes: Set<GLTFPrimitiveModeString>;
190
190
  /**
191
- * Featrue metadata classes found in glTF extensions
191
+ * Feature metadata classes found in glTF extensions
192
192
  * The tileset might contain multiple metadata classes provided by EXT_feature_metadata and EXT_structural_metadata extensions.
193
193
  * Every class is a set of properties. But I3S can consume only one set of properties.
194
194
  * On the pre-process we collect all classes from the tileset in order to show the prompt to select one class for conversion to I3S.