-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Changes from 1 commit
808fcaa
0b9869b
d612e7b
a4da846
f469a21
4f92b4a
a2066b0
562d6e5
89f6dbe
394f823
1394e4a
5a34b60
1054536
e1d55f7
04f3c51
3e75b94
969ac49
b4b23a6
e03286b
21b2f4f
efc7db5
03a9125
17784df
c815c03
4bffc64
9f02cf3
9d6dc59
c7570c3
88eae59
1aefdc9
cf8d1da
96aca3e
a3ea292
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…ibute driven filter. Added new initialization option: enableFeatureFiltering, adjustMaterialAlphaMode, applySymbology and calculateNormals
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,10 +4,13 @@ | |
* Co-authored-by: Anthony Mirabeau anthony.mirabeau@presagis.com | ||
* Co-authored-by: Elizabeth Rudkin elizabeth.rudkin@presagis.com | ||
* Co-authored-by: Tamrat Belayneh tbelayneh@esri.com | ||
* Co-authored-by: Anton Smirnov anton.smirnov@actionengine.com | ||
* Co-authored-by: Maxim Kuznetsov maxim.kuznetsov@actionengine.com | ||
|
||
* | ||
* The I3S format has been developed by Esri and is shared under an Apache 2.0 license and is maintained @ https://github.com/Esri/i3s-spec. | ||
* This implementation supports loading, displaying, and querying an I3S layer (supported versions include Esri github I3S versions 1.6, 1.7/1.8 - | ||
* whose OGC equivalent are I3S Community Standard Version 1.1 & 1.2) in the CesiumJS viewer. | ||
* whose OGC equivalent are I3S Community Standard Version 1.1, 1.2 & 1.3) in the CesiumJS viewer. | ||
* It enables the user to access an I3S layer via its URL and load it inside of the CesiumJS viewer. | ||
* | ||
* When a scene layer is initialized it accomplishes the following: | ||
|
@@ -57,6 +60,8 @@ import Resource from "../Core/Resource.js"; | |
import RuntimeError from "../Core/RuntimeError.js"; | ||
import WebMercatorProjection from "../Core/WebMercatorProjection.js"; | ||
import I3SLayer from "./I3SLayer.js"; | ||
import I3SStatistics from "./I3SStatistics.js"; | ||
import I3SSublayer from "./I3SSublayer.js"; | ||
import Lerc from "lerc"; | ||
import Rectangle from "../Core/Rectangle.js"; | ||
|
||
|
@@ -70,6 +75,10 @@ import Rectangle from "../Core/Rectangle.js"; | |
* @property {ArcGISTiledElevationTerrainProvider|Promise<ArcGISTiledElevationTerrainProvider>} [geoidTiledTerrainProvider] Tiled elevation provider describing an Earth Gravitational Model. If defined, geometry will be shifted based on the offsets given by this provider. Required to position I3S data sets with gravity-related height at the correct location. | ||
* @property {boolean} [traceFetches=false] Debug option. When true, log a message whenever an I3S tile is fetched. | ||
* @property {Cesium3DTileset.ConstructorOptions} [cesium3dTilesetOptions] Object containing options to pass to an internally created {@link Cesium3DTileset}. See {@link Cesium3DTileset} for list of valid properties. All options can be used with the exception of <code>url</code> and <code>show</code> which are overridden by values from I3SDataProvider. | ||
* @property {boolean} [enableFeatureFiltering=false] Determines if the features will be shown. | ||
* @property {boolean} [adjustMaterialAlphaMode=false] The option to adjust the alpha mode of the material based on the transparency of the vertex color. When <code>true</code>, the alpha mode of the material (if not defined) will be set to BLEND for geometry with any transparency in the color vertex attribute. | ||
Tamrat-B marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* @property {boolean} [applySymbology=false] Determines if the I3S symbology will be parsed and applied for the layers. | ||
Tamrat-B marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* @property {boolean} [calculateNormals=false] Determines if the flat normals will be generated for I3S geometry without normals. | ||
*/ | ||
|
||
/** | ||
|
@@ -123,19 +132,32 @@ function I3SDataProvider(options) { | |
this._show = defaultValue(options.show, true); | ||
this._geoidTiledTerrainProvider = options.geoidTiledTerrainProvider; | ||
this._traceFetches = defaultValue(options.traceFetches, false); | ||
this._enableFeatureFiltering = defaultValue( | ||
options.enableFeatureFiltering, | ||
false | ||
); | ||
this._adjustMaterialAlphaMode = defaultValue( | ||
options.adjustMaterialAlphaMode, | ||
false | ||
); | ||
this._applySymbology = defaultValue(options.applySymbology, false); | ||
this._calculateNormals = defaultValue(options.calculateNormals, false); | ||
|
||
this._cesium3dTilesetOptions = defaultValue( | ||
options.cesium3dTilesetOptions, | ||
defaultValue.EMPTY_OBJECT | ||
); | ||
|
||
this._layers = []; | ||
this._sublayers = []; | ||
this._data = undefined; | ||
this._extent = undefined; | ||
this._geoidDataPromise = undefined; | ||
this._geoidDataList = undefined; | ||
this._decoderTaskProcessor = undefined; | ||
this._taskProcessorReadyPromise = undefined; | ||
this._attributeStatistics = []; | ||
this._layersExtent = []; | ||
} | ||
|
||
Object.defineProperties(I3SDataProvider.prototype, { | ||
|
@@ -167,9 +189,7 @@ Object.defineProperties(I3SDataProvider.prototype, { | |
|
||
this._show = value; | ||
for (let i = 0; i < this._layers.length; i++) { | ||
if (defined(this._layers[i]._tileset)) { | ||
this._layers[i]._tileset.show = this._show; | ||
} | ||
this._layers[i]._updateVisibility(); | ||
} | ||
}, | ||
}, | ||
|
@@ -216,6 +236,18 @@ Object.defineProperties(I3SDataProvider.prototype, { | |
}, | ||
}, | ||
|
||
/** | ||
* Gets the collection of building sublayers. | ||
* @memberof I3SDataProvider.prototype | ||
* @type {I3SSublayer[]} | ||
* @readonly | ||
*/ | ||
sublayers: { | ||
get: function () { | ||
return this._sublayers; | ||
}, | ||
}, | ||
|
||
/** | ||
* Gets the I3S data for this object. | ||
* @memberof I3SDataProvider.prototype | ||
|
@@ -251,6 +283,54 @@ Object.defineProperties(I3SDataProvider.prototype, { | |
return this._resource; | ||
}, | ||
}, | ||
|
||
/** | ||
* Determines if the features will be shown. | ||
* @memberof I3SDataProvider.prototype | ||
* @type {boolean} | ||
* @readonly | ||
*/ | ||
enableFeatureFiltering: { | ||
get: function () { | ||
return this._enableFeatureFiltering; | ||
}, | ||
}, | ||
|
||
/** | ||
* Determines if the alpha mode of the material will be adjusted depending on the color vertex attribute. | ||
* @memberof I3SDataProvider.prototype | ||
* @type {boolean} | ||
* @readonly | ||
*/ | ||
adjustMaterialAlphaMode: { | ||
get: function () { | ||
return this._adjustMaterialAlphaMode; | ||
}, | ||
}, | ||
|
||
/** | ||
* Determines if the I3S symbology will be parsed and applied for the layers. | ||
* @memberof I3SDataProvider.prototype | ||
* @type {boolean} | ||
* @readonly | ||
*/ | ||
applySymbology: { | ||
get: function () { | ||
return this._applySymbology; | ||
}, | ||
}, | ||
|
||
/** | ||
* Determines if the flat normals will be generated for I3S geometry without normals. | ||
* @memberof I3SDataProvider.prototype | ||
* @type {boolean} | ||
* @readonly | ||
*/ | ||
calculateNormals: { | ||
get: function () { | ||
return this._calculateNormals; | ||
}, | ||
}, | ||
}); | ||
|
||
/** | ||
|
@@ -335,9 +415,104 @@ I3SDataProvider.prototype.updateForPass = function (frameState, passState) { | |
} | ||
}; | ||
|
||
function buildLayerUrl(provider, layerId) { | ||
const dataProviderUrl = provider.resource.getUrlComponent(); | ||
|
||
let layerUrl = ""; | ||
if (dataProviderUrl.match(/layers\/\d/)) { | ||
layerUrl = `${dataProviderUrl}`.replace(/\/+$/, ""); | ||
} else { | ||
// Add '/' to url if needed + `$layers/${layerId}/` if tilesetUrl not already in ../layers/[id] format | ||
layerUrl = `${dataProviderUrl}` | ||
.replace(/\/?$/, "/") | ||
.concat(`layers/${layerId}`); | ||
} | ||
return layerUrl; | ||
} | ||
|
||
async function addLayers(provider, data, options) { | ||
if (data.layerType === "Building") { | ||
if (!defined(options.enableFeatureFiltering)) { | ||
// The Building Scene Layer requires features to be shown to support filtering | ||
provider._enableFeatureFiltering = true; | ||
} | ||
if (!defined(options.adjustMaterialAlphaMode)) { | ||
// The Building Scene Layer enables transparency by default | ||
provider._adjustMaterialAlphaMode = true; | ||
} | ||
if (!defined(options.applySymbology)) { | ||
// The Building Scene Layer applies symbology by default | ||
provider._applySymbology = true; | ||
} | ||
if (!defined(options.calculateNormals)) { | ||
// The Building Scene Layer calculates flat normals by default | ||
provider._calculateNormals = true; | ||
} | ||
|
||
const buildingLayerUrl = buildLayerUrl(provider, data.id); | ||
if (defined(data.sublayers)) { | ||
const promises = []; | ||
for (let i = 0; i < data.sublayers.length; i++) { | ||
const promise = I3SSublayer._fromData( | ||
provider, | ||
buildingLayerUrl, | ||
data.sublayers[i], | ||
provider | ||
); | ||
promises.push(promise); | ||
} | ||
const sublayers = await Promise.all(promises); | ||
for (let i = 0; i < sublayers.length; i++) { | ||
const sublayer = sublayers[i]; | ||
provider._sublayers.push(sublayer); | ||
provider._layers.push(...sublayer._i3sLayers); | ||
} | ||
} | ||
|
||
if (defined(data.statisticsHRef)) { | ||
const uri = buildingLayerUrl.concat(`/${data.statisticsHRef}`); | ||
const statistics = new I3SStatistics(provider, uri); | ||
await statistics.load(); | ||
provider._attributeStatistics.push(statistics); | ||
} | ||
|
||
if (defined(data.fullExtent)) { | ||
const extent = Rectangle.fromDegrees( | ||
data.fullExtent.xmin, | ||
data.fullExtent.ymin, | ||
data.fullExtent.xmax, | ||
data.fullExtent.ymax | ||
); | ||
provider._layersExtent.push(extent); | ||
} | ||
} else if ( | ||
data.layerType === "3DObject" || | ||
data.layerType === "IntegratedMesh" | ||
) { | ||
if ( | ||
!defined(options.calculateNormals) && | ||
!defined(data.textureSetDefinitions) | ||
) { | ||
// I3S Layers without textures should calculate flat normals by default | ||
provider._calculateNormals = true; | ||
} | ||
|
||
const newLayer = new I3SLayer(provider, data, provider); | ||
provider._layers.push(newLayer); | ||
if (defined(newLayer._extent)) { | ||
provider._layersExtent.push(newLayer._extent); | ||
} | ||
} else { | ||
// Filter other scene layer types out | ||
console.log( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this case something that will significantly alter the display of a data source? If so, I think this should throw an error rather than failing silently (unless the user looks at the console). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I3S BSL supports a mix of layer types: 3D Object and Point. Point layers are not implemented yet. Throwing an error here will cause entire Building Scene Layer to fail, though the main content should be enough to work with a building model. Support for not implemented (or even new) layer types could be added later, but it should not affect the visualization of the implemented layer types. We think the console is a good place to check by user if the scene view doesn’t meet expectations. |
||
`${data.layerType} layer ${data.name} is skipped as not supported.` | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Creates an I3SDataProvider. Currently supported I3S versions are 1.6 and | ||
* 1.7/1.8 (OGC I3S 1.2). | ||
* 1.7/1.8 (OGC I3S 1.3). | ||
* | ||
* @param {string|Resource} url The url of the I3S dataset, which should return an I3S scene object | ||
* @param {I3SDataProvider.ConstructorOptions} options An object describing initialization options | ||
|
@@ -383,17 +558,14 @@ I3SDataProvider.fromUrl = async function (url, options) { | |
|
||
// Success | ||
if (defined(data.layers)) { | ||
const promises = []; | ||
for (let layerIndex = 0; layerIndex < data.layers.length; layerIndex++) { | ||
const newLayer = new I3SLayer( | ||
provider, | ||
data.layers[layerIndex], | ||
layerIndex | ||
); | ||
provider._layers.push(newLayer); | ||
const promise = addLayers(provider, data.layers[layerIndex], options); | ||
promises.push(promise); | ||
} | ||
await Promise.all(promises); | ||
} else { | ||
const newLayer = new I3SLayer(provider, data, data.id); | ||
provider._layers.push(newLayer); | ||
await addLayers(provider, data, options); | ||
} | ||
|
||
provider._computeExtent(); | ||
|
@@ -631,18 +803,61 @@ I3SDataProvider.prototype._computeExtent = function () { | |
let rectangle; | ||
|
||
// Compute the extent from all layers | ||
for (let layerIndex = 0; layerIndex < this._layers.length; layerIndex++) { | ||
if (defined(this._layers[layerIndex]._extent)) { | ||
const layerExtent = this._layers[layerIndex]._extent; | ||
if (!defined(rectangle)) { | ||
rectangle = Rectangle.clone(layerExtent); | ||
} else { | ||
Rectangle.union(rectangle, layerExtent, rectangle); | ||
} | ||
for ( | ||
let layerIndex = 0; | ||
layerIndex < this._layersExtent.length; | ||
layerIndex++ | ||
) { | ||
const layerExtent = this._layersExtent[layerIndex]; | ||
if (!defined(rectangle)) { | ||
rectangle = Rectangle.clone(layerExtent); | ||
} else { | ||
Rectangle.union(rectangle, layerExtent, rectangle); | ||
} | ||
} | ||
|
||
this._extent = rectangle; | ||
}; | ||
|
||
/** | ||
* Returns the collection of names for all available attributes | ||
* @returns {string[]} The collection of attribute names | ||
*/ | ||
I3SDataProvider.prototype.getAttributeNames = function () { | ||
const attributes = []; | ||
for (let i = 0; i < this._attributeStatistics.length; ++i) { | ||
attributes.push(...this._attributeStatistics[i].names); | ||
} | ||
return attributes; | ||
}; | ||
|
||
/** | ||
* Returns the collection of values for the attribute with the given name | ||
* @param {string} name The attribute name | ||
* @returns {string[]} The collection of attribute values | ||
*/ | ||
I3SDataProvider.prototype.getAttributeValues = function (name) { | ||
Tamrat-B marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for (let i = 0; i < this._attributeStatistics.length; ++i) { | ||
const values = this._attributeStatistics[i]._getValues(name); | ||
if (defined(values)) { | ||
return values; | ||
} | ||
} | ||
return []; | ||
}; | ||
|
||
/** | ||
* Filters the drawn elements of a scene to specific attribute names and values | ||
* @param {I3SNode.AttributeFilter[]} [filters=[]] The collection of attribute filters | ||
* @returns {Promise<void>} A promise that is resolved when the filter is applied | ||
*/ | ||
I3SDataProvider.prototype.filterByAttributes = function (filters) { | ||
const promises = []; | ||
for (let i = 0; i < this._layers.length; i++) { | ||
const promise = this._layers[i].filterByAttributes(filters); | ||
promises.push(promise); | ||
} | ||
return Promise.all(promises); | ||
}; | ||
|
||
export default I3SDataProvider; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be best to get rid of this option entirely. It's atypical of the CesiumJS codebase as a whole