Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Building Scene Layer (BSL) OGC I3S standard #11678

Merged
merged 33 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
808fcaa
- Added support for conversion from sRGB color space profile to linea…
Tamrat-B Dec 5, 2023
0b9869b
Added Support for BSL structure, BSL statistics, generic feature attr…
Tamrat-B Dec 5, 2023
d612e7b
I3S Building Scene Layer Sandcastle app
Tamrat-B Dec 5, 2023
a4da846
I3S BSL app icon
Tamrat-B Dec 5, 2023
f469a21
Added support for conversion from sRGB color space profile to linear …
Tamrat-B Dec 5, 2023
4f92b4a
added additional payload options
Tamrat-B Dec 5, 2023
a2066b0
- Added support for binary attribute data with values stored in objec…
Tamrat-B Dec 5, 2023
562d6e5
- Added support for 3D objects transparency
Tamrat-B Dec 5, 2023
89f6dbe
- Added support for BSL hierarchy of layers
Tamrat-B Dec 5, 2023
394f823
- Added support for the generic feature attribute driven filter
Tamrat-B Dec 5, 2023
1394e4a
Added support for BSL structure per the OGC I3S standard
Tamrat-B Dec 5, 2023
5a34b60
- Added support for symbolization defined in I3S Layer data
Tamrat-B Dec 5, 2023
1054536
Added support for EXT_mesh_features and EXT_structural_metadata glTF …
Tamrat-B Dec 5, 2023
e1d55f7
Added bsl layer tests
Tamrat-B Dec 5, 2023
04f3c51
Added I3S BSL ViewModel tests
Tamrat-B Dec 5, 2023
3e75b94
Added I3S BSL explorer
Tamrat-B Dec 5, 2023
969ac49
Added I3S Explorer tests
Tamrat-B Dec 5, 2023
b4b23a6
Added I3S BSL Explorer layer widget
Tamrat-B Dec 5, 2023
e03286b
Merge branch 'CesiumGS:main' into feature/i3s_bsl_support
Tamrat-B Jan 11, 2024
21b2f4f
updates based on review feedback
Tamrat-B Jan 11, 2024
efc7db5
Added I3S support PR to additions
Tamrat-B Feb 2, 2024
03a9125
removed non necessary options
Tamrat-B Feb 2, 2024
17784df
check before updating visibility
Tamrat-B Feb 2, 2024
c815c03
additional specs changes
Tamrat-B Feb 2, 2024
4bffc64
Merge branch 'main' into feature/i3s_bsl_support
Tamrat-B Feb 2, 2024
9f02cf3
Merge branch 'CesiumGS:main' into feature/i3s_bsl_support
Tamrat-B Feb 28, 2024
9d6dc59
Removed picking result from console output
Tamrat-B Feb 28, 2024
c7570c3
Remove console out if geoid transform is not needed
Tamrat-B Feb 28, 2024
88eae59
Default to model overview if present
Tamrat-B Feb 28, 2024
1aefdc9
update tests to account for model views
Tamrat-B Feb 28, 2024
cf8d1da
Added I3S Building Scene Layer support to March 1 release
Tamrat-B Feb 28, 2024
96aca3e
Handling edge case use cases where there is no Full Model and/or Over…
Tamrat-B Feb 28, 2024
a3ea292
Rename BSL -> BuildingSceneLayer
ggetz Feb 28, 2024
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
- Added support for the generic feature attribute driven filter
- Added support for I3S symbology
  • Loading branch information
Tamrat-B committed Dec 5, 2023
commit 394f82331527238740658455a9e86c8ae76bc966
183 changes: 169 additions & 14 deletions packages/engine/Source/Scene/I3SNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ 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>
Expand Down Expand Up @@ -64,6 +74,7 @@ function I3SNode(parent, ref, isRoot) {
this._globalTransform = undefined;
this._inverseGlobalTransform = undefined;
this._inverseRotationMatrix = undefined;
this._symbologyData = undefined;
}

Object.defineProperties(I3SNode.prototype, {
Expand Down Expand Up @@ -217,6 +228,12 @@ I3SNode.prototype.load = async function () {
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
Expand All @@ -225,23 +242,46 @@ I3SNode.prototype.loadFields = function () {
// Check if we must load fields
const fields = this._layer._data.attributeStorageInfo;

const that = this;
function createAndLoadField(fields, index) {
const newField = new I3SField(that, fields[index]);
that._fields[newField._storageInfo.name] = newField;
return newField.load();
}

const promises = [];
if (defined(fields)) {
for (let i = 0; i < fields.length; i++) {
promises.push(createAndLoadField(fields, 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) {
Tamrat-B marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -608,6 +648,15 @@ I3SNode.prototype._create3DTileDefinition = function () {
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
*/
Expand Down Expand Up @@ -646,14 +695,19 @@ I3SNode.prototype._createContentURL = async function () {
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._featureData[0],
this._symbologyData
);
if (!defined(result)) {
// Postponed
Expand All @@ -666,7 +720,9 @@ I3SNode.prototype._createContentURL = async function () {
result.meshData.meshes,
result.meshData.buffers,
result.meshData.bufferViews,
result.meshData.accessors
result.meshData.accessors,
result.meshData.rootExtensions,
result.meshData.extensionsUsed
);

this._geometryData[0]._customAttributes = result.meshData._customAttributes;
Expand All @@ -679,6 +735,91 @@ I3SNode.prototype._createContentURL = async function () {
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 =
Expand All @@ -700,19 +841,33 @@ Cesium3DTile.prototype.requestContent = function () {

if (!this._isLoading) {
this._isLoading = true;
const that = this;
return this._i3sNode
._createContentURL()
.then((url) => {
if (!defined(url)) {
this._isLoading = false;
that._isLoading = false;
return;
}

this._contentResource = new Resource({ url: url });
return this._hookedRequestContent();
that._contentResource = new Resource({ url: url });
return that._hookedRequestContent();
})
.then((content) => {
this._isLoading = false;
// 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;
});
}
Expand Down
97 changes: 97 additions & 0 deletions packages/engine/Source/Scene/I3SStatistics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import defined from "../Core/defined.js";
import I3SDataProvider from "./I3SDataProvider.js";
import Resource from "../Core/Resource.js";

/**
* This class implements an I3S statistics for Building Scene Layer.
* <p>
* Do not construct this directly, instead access statistics through {@link I3SDataProvider}.
* </p>
* @alias I3SStatistics
* @internalConstructor
*/
function I3SStatistics(dataProvider, uri) {
this._dataProvider = dataProvider;

this._resource = new Resource({ url: uri });
this._resource.setQueryParameters(dataProvider.resource.queryParameters);
this._resource.appendForwardSlash();
}

Object.defineProperties(I3SStatistics.prototype, {
/**
* Gets the resource for the statistics
* @memberof I3SStatistics.prototype
* @type {Resource}
* @readonly
*/
resource: {
get: function () {
return this._resource;
},
},

/**
* Gets the I3S data for this object.
* @memberof I3SStatistics.prototype
* @type {object}
* @readonly
*/
data: {
get: function () {
return this._data;
},
},

/**
* Gets the collection of attribute names.
* @memberof I3SStatistics.prototype
* @type {string[]}
* @readonly
*/
names: {
get: function () {
const names = [];
const summary = this._data.summary;
if (defined(summary)) {
for (let i = 0; i < summary.length; ++i) {
names.push(summary[i].fieldName);
}
}
return names;
},
},
});

/**
* Loads the content.
* @returns {Promise<object>} A promise that is resolved when the data of the I3S statistics is loaded
* @private
*/
I3SStatistics.prototype.load = async function () {
this._data = await I3SDataProvider.loadJson(
this._resource,
this._dataProvider._traceFetches
);
return this._data;
};

/**
* @private
*/
I3SStatistics.prototype._getValues = function (attributeName) {
const summary = this._data.summary;
if (defined(summary)) {
for (let i = 0; i < summary.length; ++i) {
const attribute = summary[i];
if (attribute.fieldName === attributeName) {
if (defined(attribute.mostFrequentValues)) {
return [...attribute.mostFrequentValues];
}
return [];
}
}
}
};

export default I3SStatistics;