Add existing to tracked
This commit is contained in:
+927
@@ -0,0 +1,927 @@
|
||||
import Cartographic from "../Core/Cartographic.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import Ellipsoid from "../Core/Ellipsoid.js";
|
||||
import HeadingPitchRoll from "../Core/HeadingPitchRoll.js";
|
||||
import Matrix3 from "../Core/Matrix3.js";
|
||||
import Matrix4 from "../Core/Matrix4.js";
|
||||
import Resource from "../Core/Resource.js";
|
||||
import Quaternion from "../Core/Quaternion.js";
|
||||
import Transforms from "../Core/Transforms.js";
|
||||
import Cesium3DTile from "./Cesium3DTile.js";
|
||||
import I3SDataProvider from "./I3SDataProvider.js";
|
||||
import I3SDecoder from "./I3SDecoder.js";
|
||||
import I3SFeature from "./I3SFeature.js";
|
||||
import I3SField from "./I3SField.js";
|
||||
import I3SGeometry from "./I3SGeometry.js";
|
||||
|
||||
/**
|
||||
* @typedef {object} I3SNode.AttributeFilter
|
||||
*
|
||||
* A filter given by an attribute name and values.
|
||||
* The 3D feature object should be hidden if its value for the attribute name is not specified in the collection of values.
|
||||
*
|
||||
* @property {string} name The name of the attribute
|
||||
* @property {string[]|number[]} values The collection of values
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class implements an I3S Node. In CesiumJS each I3SNode creates a Cesium3DTile.
|
||||
* <p>
|
||||
* Do not construct this directly, instead access tiles through {@link I3SLayer}.
|
||||
* </p>
|
||||
* @alias I3SNode
|
||||
* @internalConstructor
|
||||
*/
|
||||
function I3SNode(parent, ref, isRoot) {
|
||||
let level;
|
||||
let layer;
|
||||
let nodeIndex;
|
||||
let resource;
|
||||
|
||||
if (isRoot) {
|
||||
level = 0;
|
||||
layer = parent;
|
||||
} else {
|
||||
level = parent._level + 1;
|
||||
layer = parent._layer;
|
||||
}
|
||||
|
||||
if (typeof ref === "number") {
|
||||
nodeIndex = ref;
|
||||
} else {
|
||||
resource = parent.resource.getDerivedResource({
|
||||
url: `${ref}/`,
|
||||
});
|
||||
}
|
||||
|
||||
this._parent = parent;
|
||||
this._dataProvider = parent._dataProvider;
|
||||
this._isRoot = isRoot;
|
||||
this._level = level;
|
||||
this._layer = layer;
|
||||
this._nodeIndex = nodeIndex;
|
||||
this._resource = resource;
|
||||
this._isLoading = false;
|
||||
|
||||
this._tile = undefined;
|
||||
this._data = undefined;
|
||||
this._geometryData = [];
|
||||
this._featureData = [];
|
||||
this._fields = {};
|
||||
this._children = [];
|
||||
this._childrenReadyPromise = undefined;
|
||||
this._globalTransform = undefined;
|
||||
this._inverseGlobalTransform = undefined;
|
||||
this._inverseRotationMatrix = undefined;
|
||||
this._symbologyData = undefined;
|
||||
}
|
||||
|
||||
Object.defineProperties(I3SNode.prototype, {
|
||||
/**
|
||||
* Gets the resource for the node.
|
||||
* @memberof I3SNode.prototype
|
||||
* @type {Resource}
|
||||
* @readonly
|
||||
*/
|
||||
resource: {
|
||||
get: function () {
|
||||
return this._resource;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Gets the parent layer.
|
||||
* @memberof I3SNode.prototype
|
||||
* @type {I3SLayer}
|
||||
* @readonly
|
||||
*/
|
||||
layer: {
|
||||
get: function () {
|
||||
return this._layer;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Gets the parent node.
|
||||
* @memberof I3SNode.prototype
|
||||
* @type {I3SNode|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
parent: {
|
||||
get: function () {
|
||||
return this._parent;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Gets the children nodes.
|
||||
* @memberof I3SNode.prototype
|
||||
* @type {I3SNode[]}
|
||||
* @readonly
|
||||
*/
|
||||
children: {
|
||||
get: function () {
|
||||
return this._children;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Gets the collection of geometries.
|
||||
* @memberof I3SNode.prototype
|
||||
* @type {I3SGeometry[]}
|
||||
* @readonly
|
||||
*/
|
||||
geometryData: {
|
||||
get: function () {
|
||||
return this._geometryData;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Gets the collection of features.
|
||||
* @memberof I3SNode.prototype
|
||||
* @type {I3SFeature[]}
|
||||
* @readonly
|
||||
*/
|
||||
featureData: {
|
||||
get: function () {
|
||||
return this._featureData;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Gets the collection of fields.
|
||||
* @memberof I3SNode.prototype
|
||||
* @type {I3SField[]}
|
||||
* @readonly
|
||||
*/
|
||||
fields: {
|
||||
get: function () {
|
||||
return this._fields;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Gets the Cesium3DTile for this node.
|
||||
* @memberof I3SNode.prototype
|
||||
* @type {Cesium3DTile}
|
||||
* @readonly
|
||||
*/
|
||||
tile: {
|
||||
get: function () {
|
||||
return this._tile;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Gets the I3S data for this object.
|
||||
* @memberof I3SNode.prototype
|
||||
* @type {object}
|
||||
* @readonly
|
||||
*/
|
||||
data: {
|
||||
get: function () {
|
||||
return this._data;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
I3SNode.prototype.load = async function () {
|
||||
const that = this;
|
||||
|
||||
function processData() {
|
||||
if (!that._isRoot) {
|
||||
// Create a new tile
|
||||
const tileDefinition = that._create3DTileDefinition();
|
||||
|
||||
that._tile = new Cesium3DTile(
|
||||
that._layer._tileset,
|
||||
that._dataProvider.resource,
|
||||
tileDefinition,
|
||||
that._parent._tile,
|
||||
);
|
||||
|
||||
that._tile._i3sNode = that;
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have a nodepage index load from json
|
||||
if (!defined(this._nodeIndex)) {
|
||||
const data = await I3SDataProvider.loadJson(this._resource);
|
||||
that._data = data;
|
||||
processData();
|
||||
return;
|
||||
}
|
||||
|
||||
const node = await this._layer._getNodeInNodePages(this._nodeIndex);
|
||||
that._data = node;
|
||||
let uri;
|
||||
if (that._isRoot) {
|
||||
uri = "nodes/root/";
|
||||
} else if (defined(node.mesh)) {
|
||||
const uriIndex = node.mesh.geometry.resource;
|
||||
uri = `../${uriIndex}/`;
|
||||
}
|
||||
if (defined(uri) && defined(that._parent.resource)) {
|
||||
that._resource = that._parent.resource.getDerivedResource({ url: uri });
|
||||
}
|
||||
|
||||
processData();
|
||||
};
|
||||
|
||||
function createAndLoadField(node, storageInfo) {
|
||||
const newField = new I3SField(node, storageInfo);
|
||||
node._fields[storageInfo.name] = newField;
|
||||
return newField.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the node fields.
|
||||
* @returns {Promise<void>} A promise that is resolved when the I3S Node fields are loaded
|
||||
*/
|
||||
I3SNode.prototype.loadFields = function () {
|
||||
// Check if we must load fields
|
||||
const fields = this._layer._data.attributeStorageInfo;
|
||||
|
||||
const promises = [];
|
||||
if (defined(fields)) {
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const storageInfo = fields[i];
|
||||
const field = this._fields[storageInfo.name];
|
||||
if (defined(field)) {
|
||||
promises.push(field.load());
|
||||
} else {
|
||||
promises.push(createAndLoadField(this, storageInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads the node field.
|
||||
* @param {string} name The field name
|
||||
* @returns {Promise<void>} A promise that is resolved when the I3S Node field is loaded
|
||||
*/
|
||||
I3SNode.prototype.loadField = function (name) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("name", name);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const field = this._fields[name];
|
||||
if (defined(field)) {
|
||||
return field.load();
|
||||
}
|
||||
|
||||
const fields = this._layer._data.attributeStorageInfo;
|
||||
if (defined(fields)) {
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const storageInfo = fields[i];
|
||||
if (storageInfo.name === name) {
|
||||
return createAndLoadField(this, storageInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the fields for a given picked position
|
||||
* @param {Cartesian3} pickedPosition The picked position
|
||||
* @returns {object} Object containing field names and their values
|
||||
*/
|
||||
I3SNode.prototype.getFieldsForPickedPosition = function (pickedPosition) {
|
||||
const geometry = this.geometryData[0];
|
||||
if (!defined(geometry.customAttributes.featureIndex)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const location = geometry.getClosestPointIndexOnTriangle(
|
||||
pickedPosition.x,
|
||||
pickedPosition.y,
|
||||
pickedPosition.z,
|
||||
);
|
||||
|
||||
if (
|
||||
location.index === -1 ||
|
||||
location.index > geometry.customAttributes.featureIndex.length
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const featureIndex = geometry.customAttributes.featureIndex[location.index];
|
||||
return this.getFieldsForFeature(featureIndex);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the fields for a given feature
|
||||
* @param {number} featureIndex Index of the feature whose attributes we want to get
|
||||
* @returns {object} Object containing field names and their values
|
||||
*/
|
||||
I3SNode.prototype.getFieldsForFeature = function (featureIndex) {
|
||||
const featureFields = {};
|
||||
for (const fieldName in this.fields) {
|
||||
if (this.fields.hasOwnProperty(fieldName)) {
|
||||
const field = this.fields[fieldName];
|
||||
if (featureIndex >= 0 && featureIndex < field.values.length) {
|
||||
featureFields[field.name] = field.values[featureIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
return featureFields;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
I3SNode.prototype._loadChildren = function () {
|
||||
const that = this;
|
||||
// If the promise for loading the children was already created, just return it
|
||||
if (defined(this._childrenReadyPromise)) {
|
||||
return this._childrenReadyPromise;
|
||||
}
|
||||
|
||||
const childPromises = [];
|
||||
if (defined(that._data.children)) {
|
||||
for (
|
||||
let childIndex = 0;
|
||||
childIndex < that._data.children.length;
|
||||
childIndex++
|
||||
) {
|
||||
const child = that._data.children[childIndex];
|
||||
const newChild = new I3SNode(that, child.href ?? child, false);
|
||||
that._children.push(newChild);
|
||||
childPromises.push(newChild.load());
|
||||
}
|
||||
}
|
||||
|
||||
this._childrenReadyPromise = Promise.all(childPromises).then(function () {
|
||||
for (let i = 0; i < that._children.length; i++) {
|
||||
that._tile.children.push(that._children[i]._tile);
|
||||
}
|
||||
});
|
||||
|
||||
return this._childrenReadyPromise;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
I3SNode.prototype._loadGeometryData = function () {
|
||||
const geometryPromises = [];
|
||||
|
||||
// To debug decoding for a specific tile, add a condition
|
||||
// that wraps this if/else to match the tile uri
|
||||
if (defined(this._data.geometryData)) {
|
||||
for (
|
||||
let geomIndex = 0;
|
||||
geomIndex < this._data.geometryData.length;
|
||||
geomIndex++
|
||||
) {
|
||||
const curGeometryData = new I3SGeometry(
|
||||
this,
|
||||
this._data.geometryData[geomIndex].href,
|
||||
);
|
||||
this._geometryData.push(curGeometryData);
|
||||
geometryPromises.push(curGeometryData.load());
|
||||
}
|
||||
} else if (defined(this._data.mesh)) {
|
||||
const geometryDefinition = this._layer._findBestGeometryBuffers(
|
||||
this._data.mesh.geometry.definition,
|
||||
["position", "uv0"],
|
||||
);
|
||||
|
||||
const geometryURI = `./geometries/${geometryDefinition.bufferIndex}/`;
|
||||
const newGeometryData = new I3SGeometry(this, geometryURI);
|
||||
newGeometryData._geometryDefinitions = geometryDefinition.definition;
|
||||
newGeometryData._geometryBufferInfo = geometryDefinition.geometryBufferInfo;
|
||||
this._geometryData.push(newGeometryData);
|
||||
geometryPromises.push(newGeometryData.load());
|
||||
}
|
||||
|
||||
return Promise.all(geometryPromises);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
I3SNode.prototype._loadFeatureData = function () {
|
||||
const featurePromises = [];
|
||||
|
||||
// To debug decoding for a specific tile, add a condition
|
||||
// that wraps this if/else to match the tile uri
|
||||
if (defined(this._data.featureData)) {
|
||||
for (
|
||||
let featureIndex = 0;
|
||||
featureIndex < this._data.featureData.length;
|
||||
featureIndex++
|
||||
) {
|
||||
const newFeatureData = new I3SFeature(
|
||||
this,
|
||||
this._data.featureData[featureIndex].href,
|
||||
);
|
||||
this._featureData.push(newFeatureData);
|
||||
featurePromises.push(newFeatureData.load());
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(featurePromises);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
I3SNode.prototype._clearGeometryData = function () {
|
||||
this._geometryData = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
I3SNode.prototype._create3DTileDefinition = function () {
|
||||
const obb = this._data.obb;
|
||||
const mbs = this._data.mbs;
|
||||
|
||||
if (!defined(obb) && !defined(mbs)) {
|
||||
console.error("Failed to load I3S node. Bounding volume is required.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let geoPosition;
|
||||
|
||||
if (defined(obb)) {
|
||||
geoPosition = Cartographic.fromDegrees(
|
||||
obb.center[0],
|
||||
obb.center[1],
|
||||
obb.center[2],
|
||||
);
|
||||
} else {
|
||||
geoPosition = Cartographic.fromDegrees(mbs[0], mbs[1], mbs[2]);
|
||||
}
|
||||
|
||||
// Offset bounding box position if we have a geoid service defined
|
||||
if (defined(this._dataProvider._geoidDataList) && defined(geoPosition)) {
|
||||
for (let i = 0; i < this._dataProvider._geoidDataList.length; i++) {
|
||||
const tile = this._dataProvider._geoidDataList[i];
|
||||
const projectedPos = tile.projection.project(geoPosition);
|
||||
if (
|
||||
projectedPos.x > tile.nativeExtent.west &&
|
||||
projectedPos.x < tile.nativeExtent.east &&
|
||||
projectedPos.y > tile.nativeExtent.south &&
|
||||
projectedPos.y < tile.nativeExtent.north
|
||||
) {
|
||||
geoPosition.height += sampleGeoid(projectedPos.x, projectedPos.y, tile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let boundingVolume;
|
||||
let position;
|
||||
let span;
|
||||
if (defined(obb)) {
|
||||
boundingVolume = {
|
||||
box: [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
obb.halfSize[0],
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
obb.halfSize[1],
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
obb.halfSize[2],
|
||||
],
|
||||
};
|
||||
span = Math.max(
|
||||
Math.max(this._data.obb.halfSize[0], this._data.obb.halfSize[1]),
|
||||
this._data.obb.halfSize[2],
|
||||
);
|
||||
position = Ellipsoid.WGS84.cartographicToCartesian(geoPosition);
|
||||
} else {
|
||||
boundingVolume = {
|
||||
sphere: [0, 0, 0, mbs[3]],
|
||||
};
|
||||
position = Ellipsoid.WGS84.cartographicToCartesian(geoPosition);
|
||||
span = this._data.mbs[3];
|
||||
}
|
||||
span *= 2;
|
||||
// Compute the geometric error
|
||||
let metersPerPixel = Infinity;
|
||||
|
||||
// Get the meters/pixel density required to pop the next LOD
|
||||
if (defined(this._data.lodThreshold)) {
|
||||
if (
|
||||
this._layer._data.nodePages.lodSelectionMetricType ===
|
||||
"maxScreenThresholdSQ"
|
||||
) {
|
||||
const maxScreenThreshold = Math.sqrt(
|
||||
this._data.lodThreshold / (Math.PI * 0.25),
|
||||
);
|
||||
metersPerPixel = span / maxScreenThreshold;
|
||||
} else if (
|
||||
this._layer._data.nodePages.lodSelectionMetricType ===
|
||||
"maxScreenThreshold"
|
||||
) {
|
||||
const maxScreenThreshold = this._data.lodThreshold;
|
||||
metersPerPixel = span / maxScreenThreshold;
|
||||
} else {
|
||||
// Other LOD selection types can only be used for point cloud data
|
||||
console.error("Invalid lodSelectionMetricType in Layer");
|
||||
}
|
||||
} else if (defined(this._data.lodSelection)) {
|
||||
for (
|
||||
let lodIndex = 0;
|
||||
lodIndex < this._data.lodSelection.length;
|
||||
lodIndex++
|
||||
) {
|
||||
if (
|
||||
this._data.lodSelection[lodIndex].metricType === "maxScreenThreshold"
|
||||
) {
|
||||
metersPerPixel = span / this._data.lodSelection[lodIndex].maxError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (metersPerPixel === Infinity) {
|
||||
metersPerPixel = 100000;
|
||||
}
|
||||
|
||||
// Calculate the length of 16 pixels in order to trigger the screen space error
|
||||
const geometricError = metersPerPixel * 16;
|
||||
|
||||
// Transformations
|
||||
const hpr = new HeadingPitchRoll(0, 0, 0);
|
||||
let orientation = Transforms.headingPitchRollQuaternion(position, hpr);
|
||||
|
||||
if (defined(this._data.obb)) {
|
||||
orientation = new Quaternion(
|
||||
this._data.obb.quaternion[0],
|
||||
this._data.obb.quaternion[1],
|
||||
this._data.obb.quaternion[2],
|
||||
this._data.obb.quaternion[3],
|
||||
);
|
||||
}
|
||||
|
||||
const rotationMatrix = Matrix3.fromQuaternion(orientation);
|
||||
const inverseRotationMatrix = Matrix3.inverse(rotationMatrix, new Matrix3());
|
||||
|
||||
const globalTransform = new Matrix4(
|
||||
rotationMatrix[0],
|
||||
rotationMatrix[1],
|
||||
rotationMatrix[2],
|
||||
0,
|
||||
rotationMatrix[3],
|
||||
rotationMatrix[4],
|
||||
rotationMatrix[5],
|
||||
0,
|
||||
rotationMatrix[6],
|
||||
rotationMatrix[7],
|
||||
rotationMatrix[8],
|
||||
0,
|
||||
position.x,
|
||||
position.y,
|
||||
position.z,
|
||||
1,
|
||||
);
|
||||
|
||||
const inverseGlobalTransform = Matrix4.inverse(
|
||||
globalTransform,
|
||||
new Matrix4(),
|
||||
);
|
||||
|
||||
const localTransform = Matrix4.clone(globalTransform);
|
||||
|
||||
if (defined(this._parent._globalTransform)) {
|
||||
Matrix4.multiply(
|
||||
globalTransform,
|
||||
this._parent._inverseGlobalTransform,
|
||||
localTransform,
|
||||
);
|
||||
}
|
||||
|
||||
this._globalTransform = globalTransform;
|
||||
this._inverseGlobalTransform = inverseGlobalTransform;
|
||||
this._inverseRotationMatrix = inverseRotationMatrix;
|
||||
|
||||
// get children definition
|
||||
const childrenDefinition = [];
|
||||
for (let childIndex = 0; childIndex < this._children.length; childIndex++) {
|
||||
childrenDefinition.push(
|
||||
this._children[childIndex]._create3DTileDefinition(),
|
||||
);
|
||||
}
|
||||
|
||||
// Create a tile set
|
||||
const inPlaceTileDefinition = {
|
||||
children: childrenDefinition,
|
||||
refine: "REPLACE",
|
||||
boundingVolume: boundingVolume,
|
||||
transform: [
|
||||
localTransform[0],
|
||||
localTransform[4],
|
||||
localTransform[8],
|
||||
localTransform[12],
|
||||
localTransform[1],
|
||||
localTransform[5],
|
||||
localTransform[9],
|
||||
localTransform[13],
|
||||
localTransform[2],
|
||||
localTransform[6],
|
||||
localTransform[10],
|
||||
localTransform[14],
|
||||
localTransform[3],
|
||||
localTransform[7],
|
||||
localTransform[11],
|
||||
localTransform[15],
|
||||
],
|
||||
content: {
|
||||
uri: defined(this._resource) ? this._resource.url : undefined,
|
||||
},
|
||||
geometricError: geometricError,
|
||||
};
|
||||
|
||||
return inPlaceTileDefinition;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
I3SNode.prototype._loadSymbology = async function () {
|
||||
if (!defined(this._symbologyData) && defined(this._layer._symbology)) {
|
||||
this._symbologyData = await this._layer._symbology._getSymbology(this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
I3SNode.prototype._createContentURL = async function () {
|
||||
let rawGltf = {
|
||||
scene: 0,
|
||||
scenes: [
|
||||
{
|
||||
nodes: [0],
|
||||
},
|
||||
],
|
||||
nodes: [
|
||||
{
|
||||
name: "singleNode",
|
||||
},
|
||||
],
|
||||
meshes: [],
|
||||
buffers: [],
|
||||
bufferViews: [],
|
||||
accessors: [],
|
||||
materials: [],
|
||||
textures: [],
|
||||
images: [],
|
||||
samplers: [],
|
||||
asset: {
|
||||
version: "2.0",
|
||||
},
|
||||
};
|
||||
|
||||
// Load the geometry data
|
||||
const dataPromises = [this._loadGeometryData()];
|
||||
if (this._dataProvider.legacyVersion16) {
|
||||
dataPromises.push(this._loadFeatureData());
|
||||
}
|
||||
|
||||
await Promise.all(dataPromises);
|
||||
// Binary glTF
|
||||
if (defined(this._geometryData) && this._geometryData.length > 0) {
|
||||
if (this._dataProvider._applySymbology) {
|
||||
await this._loadSymbology();
|
||||
}
|
||||
|
||||
const url = this._geometryData[0].resource.url;
|
||||
const geometrySchema = this._layer._data.store.defaultGeometrySchema;
|
||||
const geometryData = this._geometryData[0];
|
||||
const result = await I3SDecoder.decode(
|
||||
url,
|
||||
geometrySchema,
|
||||
geometryData,
|
||||
this._featureData[0],
|
||||
this._symbologyData,
|
||||
);
|
||||
if (!defined(result)) {
|
||||
// Postponed
|
||||
return;
|
||||
}
|
||||
|
||||
rawGltf = geometryData._generateGltf(
|
||||
result.meshData.nodesInScene,
|
||||
result.meshData.nodes,
|
||||
result.meshData.meshes,
|
||||
result.meshData.buffers,
|
||||
result.meshData.bufferViews,
|
||||
result.meshData.accessors,
|
||||
result.meshData.rootExtensions,
|
||||
result.meshData.extensionsUsed,
|
||||
);
|
||||
|
||||
this._geometryData[0]._customAttributes = result.meshData._customAttributes;
|
||||
}
|
||||
|
||||
const binaryGltfData = this._dataProvider._binarizeGltf(rawGltf);
|
||||
const glbDataBlob = new Blob([binaryGltfData], {
|
||||
type: "application/binary",
|
||||
});
|
||||
return URL.createObjectURL(glbDataBlob);
|
||||
};
|
||||
|
||||
async function loadFilters(node) {
|
||||
const filters = node._layer._filters;
|
||||
const promises = [];
|
||||
for (let i = 0; i < filters.length; i++) {
|
||||
const promise = node.loadField(filters[i].name);
|
||||
promises.push(promise);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
return filters;
|
||||
}
|
||||
|
||||
function checkFeatureValue(featureIndex, field, filter) {
|
||||
if (!defined(filter.values) || filter.values.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fieldValues = defined(field) ? field.values : [];
|
||||
let featureValue;
|
||||
if (featureIndex < fieldValues.length) {
|
||||
featureValue = fieldValues[featureIndex];
|
||||
}
|
||||
let matches = false;
|
||||
for (let i = 0; i < filter.values.length; i++) {
|
||||
if (filter.values[i] === featureValue) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
async function filterFeatures(node, contentModel) {
|
||||
const batchTable = node._tile.content.batchTable;
|
||||
if (defined(batchTable) && batchTable.featuresLength > 0) {
|
||||
batchTable.setAllShow(true);
|
||||
|
||||
const filters = await loadFilters(node);
|
||||
if (filters.length > 0) {
|
||||
for (
|
||||
let featureIndex = 0;
|
||||
featureIndex < batchTable.featuresLength;
|
||||
featureIndex++
|
||||
) {
|
||||
for (let filterIndex = 0; filterIndex < filters.length; filterIndex++) {
|
||||
const filter = filters[filterIndex];
|
||||
if (
|
||||
!checkFeatureValue(featureIndex, node._fields[filter.name], filter)
|
||||
) {
|
||||
batchTable.setShow(featureIndex, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
contentModel.show = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
I3SNode.prototype._filterFeatures = function () {
|
||||
const promises = [];
|
||||
// Forced filtering is required for loaded nodes only
|
||||
for (let i = 0; i < this._children.length; i++) {
|
||||
const promise = this._children[i]._filterFeatures();
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
// Filters are applied for nodes with geometry data only
|
||||
const contentModel = this._tile?.content?._model;
|
||||
if (
|
||||
defined(this._geometryData) &&
|
||||
this._geometryData.length > 0 &&
|
||||
defined(contentModel) &&
|
||||
contentModel.ready
|
||||
) {
|
||||
// The model needs to be hidden till the filters are applied
|
||||
contentModel.show = false;
|
||||
const promise = filterFeatures(this, contentModel);
|
||||
promises.push(promise);
|
||||
}
|
||||
return Promise.all(promises);
|
||||
};
|
||||
|
||||
// Reimplement Cesium3DTile.prototype.requestContent so that
|
||||
// We get a chance to load our own gltf from I3S data
|
||||
Cesium3DTile.prototype._hookedRequestContent =
|
||||
Cesium3DTile.prototype.requestContent;
|
||||
|
||||
/**
|
||||
* Requests the tile's content.
|
||||
* <p>
|
||||
* The request may not be made if the Cesium Request Scheduler can't prioritize it.
|
||||
* </p>
|
||||
*
|
||||
* @return {Promise<Cesium3DTileContent>|undefined} A promise that resolves when the request completes, or undefined if there is no request needed, or the request cannot be scheduled.
|
||||
* @private
|
||||
*/
|
||||
Cesium3DTile.prototype.requestContent = function () {
|
||||
if (!this.tileset._isI3STileSet) {
|
||||
return this._hookedRequestContent();
|
||||
}
|
||||
|
||||
if (!this._isLoading) {
|
||||
this._isLoading = true;
|
||||
const that = this;
|
||||
return this._i3sNode
|
||||
._createContentURL()
|
||||
.then((url) => {
|
||||
if (!defined(url)) {
|
||||
that._isLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
that._contentResource = new Resource({ url: url });
|
||||
return that._hookedRequestContent();
|
||||
})
|
||||
.then((content) => {
|
||||
// Filters are applied for nodes with geometry data only
|
||||
const contentModel = content?._model;
|
||||
if (
|
||||
defined(that._i3sNode._geometryData) &&
|
||||
that._i3sNode._geometryData.length > 0 &&
|
||||
defined(contentModel)
|
||||
) {
|
||||
// The model needs to be hidden till the filters are applied
|
||||
contentModel.show = false;
|
||||
contentModel.readyEvent.addEventListener(() => {
|
||||
filterFeatures(that._i3sNode, contentModel);
|
||||
});
|
||||
}
|
||||
that._isLoading = false;
|
||||
return content;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function bilinearInterpolate(tx, ty, h00, h10, h01, h11) {
|
||||
const a = h00 * (1 - tx) + h10 * tx;
|
||||
const b = h01 * (1 - tx) + h11 * tx;
|
||||
return a * (1 - ty) + b * ty;
|
||||
}
|
||||
|
||||
function sampleMap(u, v, width, data) {
|
||||
const address = u + v * width;
|
||||
return data[address];
|
||||
}
|
||||
|
||||
function sampleGeoid(sampleX, sampleY, geoidData) {
|
||||
const extent = geoidData.nativeExtent;
|
||||
let x =
|
||||
((sampleX - extent.west) / (extent.east - extent.west)) *
|
||||
(geoidData.width - 1);
|
||||
let y =
|
||||
((sampleY - extent.south) / (extent.north - extent.south)) *
|
||||
(geoidData.height - 1);
|
||||
const xi = Math.floor(x);
|
||||
let yi = Math.floor(y);
|
||||
|
||||
x -= xi;
|
||||
y -= yi;
|
||||
|
||||
const xNext = xi < geoidData.width ? xi + 1 : xi;
|
||||
let yNext = yi < geoidData.height ? yi + 1 : yi;
|
||||
|
||||
yi = geoidData.height - 1 - yi;
|
||||
yNext = geoidData.height - 1 - yNext;
|
||||
|
||||
const h00 = sampleMap(xi, yi, geoidData.width, geoidData.buffer);
|
||||
const h10 = sampleMap(xNext, yi, geoidData.width, geoidData.buffer);
|
||||
const h01 = sampleMap(xi, yNext, geoidData.width, geoidData.buffer);
|
||||
const h11 = sampleMap(xNext, yNext, geoidData.width, geoidData.buffer);
|
||||
|
||||
let finalHeight = bilinearInterpolate(x, y, h00, h10, h01, h11);
|
||||
finalHeight = finalHeight * geoidData.scale + geoidData.offset;
|
||||
return finalHeight;
|
||||
}
|
||||
|
||||
Object.defineProperties(Cesium3DTile.prototype, {
|
||||
/**
|
||||
* Gets the I3S Node for the tile.
|
||||
* @memberof Cesium3DTile.prototype
|
||||
* @type {string}
|
||||
*/
|
||||
i3sNode: {
|
||||
get: function () {
|
||||
return this._i3sNode;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default I3SNode;
|
||||
Reference in New Issue
Block a user