@loaders.gl/3d-tiles
Version:
3D Tiles, an open standard for streaming massive heterogeneous 3D geospatial datasets.
316 lines (258 loc) • 9.88 kB
text/typescript
// loaders.gl
// SPDX-License-Identifier: MIT AND Apache-2.0
// Copyright vis.gl contributors
// This file is derived from the Cesium code base under Apache 2 license
// See LICENSE.md and https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md
import {assert} from '@loaders.gl/loader-utils';
import {createTypedArrayFromAccessor} from './helpers/tile-3d-accessor-utils';
import {initializeHierarchy, traverseHierarchy} from './tile-3d-batch-table-hierarchy';
function defined(x) {
return x !== undefined && x !== null;
}
const clone = (x, y) => x;
// These top level fields in the batch table json are not properties
const IGNORED_PROPERTY_FIELDS = {
HIERARCHY: true, // Deprecated HIERARCHY property
extensions: true,
extras: true
};
// The size of this array equals the maximum instance count among all loaded tiles, which has the potential to be large.
export default class Tile3DBatchTableParser {
json;
binary;
featureCount;
_extensions;
// Copy all top-level property fields from the json object, ignoring special fields
_properties;
_binaryProperties;
// TODO: hierarchy support is only partially implemented and not tested
_hierarchy;
constructor(json, binary, featureCount, options = {}) {
assert(featureCount >= 0);
this.json = json || {};
this.binary = binary;
this.featureCount = featureCount;
this._extensions = this.json?.extensions || {};
// Copy all top-level property fields from the json object, ignoring special fields
this._properties = {};
for (const propertyName in this.json) {
if (!IGNORED_PROPERTY_FIELDS[propertyName]) {
this._properties[propertyName] = this.json[propertyName];
}
}
this._binaryProperties = this._initializeBinaryProperties();
// TODO: hierarchy support is only partially implemented and not tested
if (options['3DTILES_batch_table_hierarchy']) {
this._hierarchy = initializeHierarchy(this, this.json, this.binary);
}
}
getExtension(extensionName) {
return this.json && this.json.extensions && this.json.extensions[extensionName];
}
memorySizeInBytes(): number {
return 0;
}
isClass(batchId, className: string): boolean {
this._checkBatchId(batchId);
assert(typeof className === 'string', className);
// extension: 3DTILES_batch_table_hierarchy
if (this._hierarchy) {
// PERFORMANCE_IDEA : cache results in the ancestor classes
// to speed up this check if this area becomes a hotspot
// PERFORMANCE_IDEA : treat class names as integers for faster comparisons
const result = traverseHierarchy(this._hierarchy, batchId, (hierarchy, instanceIndex) => {
const classId = hierarchy.classIds[instanceIndex];
const instanceClass = hierarchy.classes[classId];
return instanceClass.name === className;
});
return defined(result);
}
return false;
}
isExactClass(batchId, className) {
assert(typeof className === 'string', className);
return this.getExactClassName(batchId) === className;
}
getExactClassName(batchId) {
this._checkBatchId(batchId);
// extension: 3DTILES_batch_table_hierarchy
if (this._hierarchy) {
const classId = this._hierarchy.classIds[batchId];
const instanceClass = this._hierarchy.classes[classId];
return instanceClass.name;
}
return undefined;
}
hasProperty(batchId, name) {
this._checkBatchId(batchId);
assert(typeof name === 'string', name);
return defined(this._properties[name]) || this._hasPropertyInHierarchy(batchId, name);
}
getPropertyNames(batchId, results) {
this._checkBatchId(batchId);
results = defined(results) ? results : [];
results.length = 0;
const propertyNames = Object.keys(this._properties);
results.push(...propertyNames);
if (this._hierarchy) {
this._getPropertyNamesInHierarchy(batchId, results);
}
return results;
}
getProperty(batchId, name) {
this._checkBatchId(batchId);
assert(typeof name === 'string', name);
if (this._binaryProperties) {
const binaryProperty = this._binaryProperties[name];
if (defined(binaryProperty)) {
return this._getBinaryProperty(binaryProperty, batchId);
}
}
const propertyValues = this._properties[name];
if (defined(propertyValues)) {
return clone(propertyValues[batchId], true);
}
// EXTENSION: 3DTILES_batch_table_hierarchy
if (this._hierarchy) {
const hierarchyProperty = this._getHierarchyProperty(batchId, name);
if (defined(hierarchyProperty)) {
return hierarchyProperty;
}
}
return undefined;
}
setProperty(batchId, name, value) {
const featureCount = this.featureCount;
this._checkBatchId(batchId);
assert(typeof name === 'string', name);
if (this._binaryProperties) {
const binaryProperty = this._binaryProperties[name];
if (binaryProperty) {
this._setBinaryProperty(binaryProperty, batchId, value);
return;
}
}
// EXTENSION: 3DTILES_batch_table_hierarchy
if (this._hierarchy) {
if (this._setHierarchyProperty(this, batchId, name, value)) {
return;
}
}
let propertyValues = this._properties[name];
if (!defined(propertyValues)) {
// Property does not exist. Create it.
this._properties[name] = new Array(featureCount);
propertyValues = this._properties[name];
}
propertyValues[batchId] = clone(value, true);
}
// PRIVATE METHODS
_checkBatchId(batchId) {
const valid = batchId >= 0 && batchId < this.featureCount;
if (!valid) {
throw new Error('batchId not in range [0, featureCount - 1].');
}
}
_getBinaryProperty(binaryProperty, index) {
return binaryProperty.unpack(binaryProperty.typedArray, index);
}
_setBinaryProperty(binaryProperty, index, value) {
binaryProperty.pack(value, binaryProperty.typedArray, index);
}
_initializeBinaryProperties() {
let binaryProperties: Record<string, any> | null = null;
for (const name in this._properties) {
const property = this._properties[name];
const binaryProperty = this._initializeBinaryProperty(name, property);
// Store any information needed to access the binary data, including the typed array,
// componentCount (e.g. a VEC4 would be 4), and the type used to pack and unpack (e.g. Cartesian4).
if (binaryProperty) {
binaryProperties = binaryProperties || {};
binaryProperties[name] = binaryProperty;
}
}
return binaryProperties;
}
_initializeBinaryProperty(name, property) {
if ('byteOffset' in property) {
// This is a binary property
const tile3DAccessor = property;
assert(this.binary, `Property ${name} requires a batch table binary.`);
assert(tile3DAccessor.type, `Property ${name} requires a type.`);
const accessor = createTypedArrayFromAccessor(
tile3DAccessor,
this.binary.buffer,
this.binary.byteOffset | 0,
this.featureCount
);
// Store any information needed to access the binary data, including the typed array,
// componentCount (e.g. a VEC4 would be 4), and the type used to pack and unpack (e.g. Cartesian4).
return {
typedArray: accessor.values,
componentCount: accessor.size,
unpack: accessor.unpacker,
pack: accessor.packer
};
}
return null;
}
// EXTENSION SUPPORT: 3DTILES_batch_table_hierarchy
_hasPropertyInHierarchy(batchId, name) {
if (!this._hierarchy) {
return false;
}
const result = traverseHierarchy(this._hierarchy, batchId, (hierarchy, instanceIndex) => {
const classId = hierarchy.classIds[instanceIndex];
const instances = hierarchy.classes[classId].instances;
return defined(instances[name]);
});
return defined(result);
}
_getPropertyNamesInHierarchy(batchId, results) {
traverseHierarchy(this._hierarchy, batchId, (hierarchy, instanceIndex) => {
const classId = hierarchy.classIds[instanceIndex];
const instances = hierarchy.classes[classId].instances;
for (const name in instances) {
if (instances.hasOwnProperty(name)) {
if (results.indexOf(name) === -1) {
results.push(name);
}
}
}
});
}
_getHierarchyProperty(batchId, name) {
return traverseHierarchy(this._hierarchy, batchId, (hierarchy, instanceIndex) => {
const classId = hierarchy.classIds[instanceIndex];
const instanceClass = hierarchy.classes[classId];
const indexInClass = hierarchy.classIndexes[instanceIndex];
const propertyValues = instanceClass.instances[name];
if (defined(propertyValues)) {
if (defined(propertyValues.typedArray)) {
return this._getBinaryProperty(propertyValues, indexInClass);
}
return clone(propertyValues[indexInClass], true);
}
return null;
});
}
_setHierarchyProperty(batchTable, batchId, name, value) {
const result = traverseHierarchy(this._hierarchy, batchId, (hierarchy, instanceIndex) => {
const classId = hierarchy.classIds[instanceIndex];
const instanceClass = hierarchy.classes[classId];
const indexInClass = hierarchy.classIndexes[instanceIndex];
const propertyValues = instanceClass.instances[name];
if (defined(propertyValues)) {
assert(instanceIndex === batchId, `Inherited property "${name}" is read-only.`);
if (defined(propertyValues.typedArray)) {
this._setBinaryProperty(propertyValues, indexInClass, value);
} else {
propertyValues[indexInClass] = clone(value, true);
}
return true;
}
return false;
});
return defined(result);
}
}