diff --git a/examples/layers/JSONLayers/GeoidMNT.json b/examples/layers/JSONLayers/GeoidMNT.json index d749d57884..1165d0c853 100644 --- a/examples/layers/JSONLayers/GeoidMNT.json +++ b/examples/layers/JSONLayers/GeoidMNT.json @@ -4,7 +4,9 @@ "updateStrategy": { "type": 0 }, - "zmin": -12000, + "clampValues": { + "min": -12000 + }, "source": { "url": "https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/geoid/geoid/bil/%TILEMATRIX/geoid_%COL_%ROW.bil", "format": "image/x-bil;bits=32", diff --git a/examples/source_file_geojson_3d.html b/examples/source_file_geojson_3d.html index 13990d4e60..4bb744f313 100644 --- a/examples/source_file_geojson_3d.html +++ b/examples/source_file_geojson_3d.html @@ -54,7 +54,6 @@ crs: 'EPSG:4326', format: 'application/json', }), - transparent: true, opacity: 0.7, zoom: { min: 10 }, style: { diff --git a/examples/source_stream_wfs_raster.html b/examples/source_stream_wfs_raster.html index 4771d48820..61e3cec014 100644 --- a/examples/source_stream_wfs_raster.html +++ b/examples/source_stream_wfs_raster.html @@ -53,12 +53,6 @@ itowns.Fetcher.json('./layers/JSONLayers/IGN_MNT_HIGHRES.json').then(addElevationLayerFromConfig); itowns.Fetcher.json('./layers/JSONLayers/WORLD_DTM.json').then(addElevationLayerFromConfig); - function isValidData(data) { - if(data.features[0].geometries.length < 1000) { - return data; - } - } - var wfsBuildingSource = new itowns.WFSSource({ url: 'https://data.geopf.fr/wfs/ows?', version: '2.0.0', @@ -102,7 +96,6 @@ width: 2.0, }, }, - isValidData: isValidData, source: wfsBuildingSource, zoom: { max: 20, min: 13 }, }); diff --git a/src/Converter/Feature2Texture.js b/src/Converter/Feature2Texture.js index 4d08acd8d3..b197e7fe2b 100644 --- a/src/Converter/Feature2Texture.js +++ b/src/Converter/Feature2Texture.js @@ -122,7 +122,7 @@ export default { c.width = sizeTexture; c.height = sizeTexture; - const ctx = c.getContext('2d'); + const ctx = c.getContext('2d', { willReadFrequently: true }); if (backgroundColor) { ctx.fillStyle = backgroundColor.getStyle(); ctx.fillRect(0, 0, sizeTexture, sizeTexture); diff --git a/src/Core/Prefab/Globe/Atmosphere.js b/src/Core/Prefab/Globe/Atmosphere.js index ff62cb6bf9..f5df486ec1 100644 --- a/src/Core/Prefab/Globe/Atmosphere.js +++ b/src/Core/Prefab/Globe/Atmosphere.js @@ -29,7 +29,9 @@ const spaceColor = new THREE.Color(0x030508); const limitAlti = 600000; const mfogDistance = ellipsoidSizes.x * 160.0; - +/** + * @extends GeometryLayer + */ class Atmosphere extends GeometryLayer { /** * It's layer to simulate Globe atmosphere. @@ -39,8 +41,6 @@ class Atmosphere extends GeometryLayer { * * [Atmosphere Shader From Space (Atmospheric scattering)](http://stainlessbeer.weebly.com/planets-9-atmospheric-scattering.html) * * [Accurate Atmospheric Scattering (NVIDIA GPU Gems 2)](https://developer.nvidia.com/gpugems/gpugems2/part-ii-shading-lighting-and-shadows/chapter-16-accurate-atmospheric-scattering). * - * @extends GeometryLayer - * * @param {string} id - The id of the layer Atmosphere. * @param {Object} [options] - options layer. * @param {number} [options.Kr] - `Kr` is the rayleigh scattering constant. diff --git a/src/Core/Prefab/Globe/GlobeLayer.js b/src/Core/Prefab/Globe/GlobeLayer.js index 1fed4cd883..a72a29db17 100644 --- a/src/Core/Prefab/Globe/GlobeLayer.js +++ b/src/Core/Prefab/Globe/GlobeLayer.js @@ -17,18 +17,18 @@ const scaledHorizonCullingPoint = new THREE.Vector3(); * @property {boolean} isGlobeLayer - Used to checkout whether this layer is a * GlobeLayer. Default is true. You should not change this, as it is used * internally for optimisation. + * + * @extends TiledGeometryLayer */ class GlobeLayer extends TiledGeometryLayer { /** * A {@link TiledGeometryLayer} to use with a {@link GlobeView}. It has * specific method for updating and subdivising its grid. * - * @extends TiledGeometryLayer - * * @param {string} id - The id of the layer, that should be unique. It is * not mandatory, but an error will be emitted if this layer is added a * {@link View} that already has a layer going by that id. - * @param {THREE.Object3d} [object3d=THREE.Group] - The object3d used to + * @param {THREE.Object3D} [object3d=THREE.Group] - The object3d used to * contain the geometry of the TiledGeometryLayer. It is usually a * `THREE.Group`, but it can be anything inheriting from a `THREE.Object3d`. * @param {Object} [config] - Optional configuration, all elements in it @@ -46,24 +46,34 @@ class GlobeLayer extends TiledGeometryLayer { * @throws {Error} `object3d` must be a valid `THREE.Object3d`. */ constructor(id, object3d, config = {}) { + const { + minSubdivisionLevel = 2, + maxSubdivisionLevel = 19, + ...tiledConfig + } = config; + // Configure tiles const scheme = schemeTiles.get('EPSG:4326'); const schemeTile = globalExtentTMS.get('EPSG:4326').subdivisionByScheme(scheme); // Supported tile matrix set for color/elevation layer - config.tileMatrixSets = [ + const tileMatrixSets = [ 'EPSG:4326', 'EPSG:3857', ]; - const uvCount = config.tileMatrixSets.length; + const uvCount = tileMatrixSets.length; const builder = new BuilderEllipsoidTile({ crs: 'EPSG:4978', uvCount }); - super(id, object3d || new THREE.Group(), schemeTile, builder, config); + super(id, object3d || new THREE.Group(), schemeTile, builder, { + tileMatrixSets, + ...tiledConfig, + }); this.isGlobeLayer = true; this.options.defaultPickingRadius = 5; - this.minSubdivisionLevel = this.minSubdivisionLevel == undefined ? 2 : this.minSubdivisionLevel; - this.maxSubdivisionLevel = this.maxSubdivisionLevel == undefined ? 19 : this.maxSubdivisionLevel; + this.minSubdivisionLevel = minSubdivisionLevel; + this.maxSubdivisionLevel = maxSubdivisionLevel; + this.extent = this.schemeTile[0].clone(); for (let i = 1; i < this.schemeTile.length; i++) { diff --git a/src/Core/Prefab/Planar/PlanarLayer.js b/src/Core/Prefab/Planar/PlanarLayer.js index 0a3bad7079..dbc154074f 100644 --- a/src/Core/Prefab/Planar/PlanarLayer.js +++ b/src/Core/Prefab/Planar/PlanarLayer.js @@ -8,14 +8,13 @@ import PlanarTileBuilder from './PlanarTileBuilder'; * @property {boolean} isPlanarLayer - Used to checkout whether this layer is a * PlanarLayer. Default is true. You should not change this, as it is used * internally for optimisation. + * @extends TiledGeometryLayer */ class PlanarLayer extends TiledGeometryLayer { /** * A {@link TiledGeometryLayer} to use with a {@link PlanarView}. It has * specific method for updating and subdivising its grid. * - * @extends TiledGeometryLayer - * * @param {string} id - The id of the layer, that should be unique. It is * not mandatory, but an error will be emitted if this layer is added a * {@link View} that already has a layer going by that id. @@ -34,17 +33,28 @@ class PlanarLayer extends TiledGeometryLayer { * @throws {Error} `object3d` must be a valid `THREE.Object3d`. */ constructor(id, extent, object3d, config = {}) { + const { + minSubdivisionLevel = 0, + maxSubdivisionLevel = 5, + ...tiledConfig + } = config; + const tileMatrixSets = [extent.crs]; if (!globalExtentTMS.get(extent.crs)) { // Add new global extent for this new crs projection. globalExtentTMS.set(extent.crs, extent); } - config.tileMatrixSets = tileMatrixSets; - super(id, object3d || new THREE.Group(), [extent], new PlanarTileBuilder({ crs: extent.crs }), config); + + const builder = new PlanarTileBuilder({ crs: extent.crs }); + super(id, object3d || new THREE.Group(), [extent], builder, { + tileMatrixSets, + ...tiledConfig, + }); this.isPlanarLayer = true; this.extent = extent; - this.minSubdivisionLevel = this.minSubdivisionLevel == undefined ? 0 : this.minSubdivisionLevel; - this.maxSubdivisionLevel = this.maxSubdivisionLevel == undefined ? 5 : this.maxSubdivisionLevel; + + this.minSubdivisionLevel = minSubdivisionLevel; + this.maxSubdivisionLevel = maxSubdivisionLevel; } } diff --git a/src/Layer/C3DTilesLayer.js b/src/Layer/C3DTilesLayer.js index 63ad49a9d6..437e30a5d6 100644 --- a/src/Layer/C3DTilesLayer.js +++ b/src/Layer/C3DTilesLayer.js @@ -54,11 +54,13 @@ function object3DHasFeature(object3d) { return object3d.geometry && object3d.geometry.attributes._BATCHID; } +/** + * @extends GeometryLayer + */ class C3DTilesLayer extends GeometryLayer { #fillColorMaterialsBuffer; /** * @deprecated Deprecated 3D Tiles layer. Use {@link OGC3DTilesLayer} instead. - * @extends GeometryLayer * * @example * // Create a new 3d-tiles layer from a web server @@ -86,7 +88,7 @@ class C3DTilesLayer extends GeometryLayer { * {@link View} that already has a layer going by that id. * @param {object} config configuration, all elements in it * will be merged as is in the layer. - * @param {C3TilesSource} config.source The source of 3d Tiles. + * @param {C3DTilesSource} config.source The source of 3d Tiles. * * name. * @param {Number} [config.sseThreshold=16] The [Screen Space Error](https://github.com/CesiumGS/3d-tiles/blob/main/specification/README.md#geometric-error) @@ -143,8 +145,8 @@ class C3DTilesLayer extends GeometryLayer { if (!exists) { console.warn("The points cloud size mode doesn't exist. Use 'VALUE' or 'ATTENUATED' instead."); } else { this.pntsSizeMode = config.pntsSizeMode; } } - /** @type {Style} */ - this.style = config.style || null; + /** @type {Style | null} */ + this._style = config.style || null; /** @type {Map} */ this.#fillColorMaterialsBuffer = new Map(); diff --git a/src/Layer/ColorLayer.js b/src/Layer/ColorLayer.js index 6447b40799..082879f4f6 100644 --- a/src/Layer/ColorLayer.js +++ b/src/Layer/ColorLayer.js @@ -44,6 +44,8 @@ import { deprecatedColorLayerOptions } from 'Core/Deprecated/Undeprecator'; * * `1`: used to amplify the transparency effect. * * `2`: unused. * * `3`: could be used by your own glsl code. + * + * @extends RasterLayer */ class ColorLayer extends RasterLayer { /** @@ -51,8 +53,6 @@ class ColorLayer extends RasterLayer { * it can be an aerial view of the ground or a simple transparent layer with the * roads displayed. * - * @extends Layer - * * @param {string} id - The id of the layer, that should be unique. It is * not mandatory, but an error will be emitted if this layer is added a * {@link View} that already has a layer going by that id. @@ -91,16 +91,45 @@ class ColorLayer extends RasterLayer { */ constructor(id, config = {}) { deprecatedColorLayerOptions(config); - super(id, config); + + const { + effect_type = 0, + effect_parameter = 1.0, + transparent, + ...rasterConfig + } = config; + + super(id, rasterConfig); + + /** + * @type {boolean} + * @readonly + */ this.isColorLayer = true; - this.defineLayerProperty('visible', true); - this.defineLayerProperty('opacity', 1.0); - this.defineLayerProperty('sequence', 0); - this.transparent = config.transparent || (this.opacity < 1.0); + + /** + * @type {boolean} + */ + this.visible = true; + this.defineLayerProperty('visible', this.visible); + + /** + * @type {number} + */ + this.opacity = 1.0; + this.defineLayerProperty('opacity', this.opacity); + + /** + * @type {number} + */ + this.sequence = 0; + this.defineLayerProperty('sequence', this.sequence); + + this.transparent = transparent || (this.opacity < 1.0); this.noTextureParentOutsideLimit = config.source ? config.source.isFileSource : false; - this.effect_type = config.effect_type ?? 0; - this.effect_parameter = config.effect_parameter ?? 1.0; + this.effect_type = effect_type; + this.effect_parameter = effect_parameter; // Feature options this.buildExtent = true; diff --git a/src/Layer/CopcLayer.js b/src/Layer/CopcLayer.js index faef93c450..db07495b8b 100644 --- a/src/Layer/CopcLayer.js +++ b/src/Layer/CopcLayer.js @@ -30,6 +30,11 @@ class CopcLayer extends PointCloudLayer { */ constructor(id, config) { super(id, config); + + /** + * @type {boolean} + * @readonly + */ this.isCopcLayer = true; const resolve = () => this; diff --git a/src/Layer/ElevationLayer.js b/src/Layer/ElevationLayer.js index 0c7ad5749f..2821298cab 100644 --- a/src/Layer/ElevationLayer.js +++ b/src/Layer/ElevationLayer.js @@ -22,14 +22,14 @@ import { RasterElevationTile } from 'Renderer/RasterTile'; * ``` * @property {number} colorTextureElevationMinZ - elevation minimum in `useColorTextureElevation` mode. * @property {number} colorTextureElevationMaxZ - elevation maximum in `useColorTextureElevation` mode. + * + * @extends RasterLayer */ class ElevationLayer extends RasterLayer { /** * A simple layer, managing an elevation texture to add some reliefs on the * plane or globe view for example. * - * @extends Layer - * * @param {string} id - The id of the layer, that should be unique. It is * not mandatory, but an error will be emitted if this layer is added a * {@link View} that already has a layer going by that id. @@ -59,14 +59,52 @@ class ElevationLayer extends RasterLayer { * view.addLayer(elevation); */ constructor(id, config = {}) { - super(id, config); + const { + scale = 1.0, + noDataValue, + clampValues, + useRgbaTextureElevation, + useColorTextureElevation, + colorTextureElevationMinZ, + colorTextureElevationMaxZ, + bias, + mode, + ...rasterConfig + } = config; + + super(id, rasterConfig); + + /** + * @type {boolean} + * @readonly + */ + this.isElevationLayer = true; + + this.noDataValue = noDataValue; + if (config.zmin || config.zmax) { console.warn('Config using zmin and zmax are deprecated, use {clampValues: {min, max}} structure.'); } - this.zmin = config.clampValues?.min ?? config.zmin; - this.zmax = config.clampValues?.max ?? config.zmax; - this.isElevationLayer = true; - this.defineLayerProperty('scale', this.scale || 1.0); + + /** + * @type {number | undefined} + */ + this.zmin = clampValues?.min ?? config.zmin; + + /** + * @type {number | undefined} + */ + this.zmax = clampValues?.max ?? config.zmax; + + this.defineLayerProperty('scale', scale); + + this.useRgbaTextureElevation = useRgbaTextureElevation; + this.useColorTextureElevation = useColorTextureElevation; + this.colorTextureElevationMinZ = colorTextureElevationMinZ; + this.colorTextureElevationMaxZ = colorTextureElevationMaxZ; + + this.bias = bias; + this.mode = mode; } /** diff --git a/src/Layer/EntwinePointTileLayer.js b/src/Layer/EntwinePointTileLayer.js index 902ccb414b..0a1add49a4 100644 --- a/src/Layer/EntwinePointTileLayer.js +++ b/src/Layer/EntwinePointTileLayer.js @@ -11,13 +11,13 @@ bboxMesh.geometry.boundingBox = box3; * @property {boolean} isEntwinePointTileLayer - Used to checkout whether this * layer is a EntwinePointTileLayer. Default is `true`. You should not change * this, as it is used internally for optimisation. + * + * @extends PointCloudLayer */ class EntwinePointTileLayer extends PointCloudLayer { /** * Constructs a new instance of Entwine Point Tile layer. * - * @extends PointCloudLayer - * * @example * // Create a new point cloud layer * const points = new EntwinePointTileLayer('EPT', @@ -37,15 +37,22 @@ class EntwinePointTileLayer extends PointCloudLayer { * contains three elements `name, protocol, extent`, these elements will be * available using `layer.name` or something else depending on the property * name. See the list of properties to know which one can be specified. - * @param {string} [config.crs=ESPG:4326] - The CRS of the {@link View} this + * @param {string} [config.crs='ESPG:4326'] - The CRS of the {@link View} this * layer will be attached to. This is used to determine the extent of this * layer. Default to `EPSG:4326`. - * @param {number} [config.skip=1] - Read one point from every `skip` points - * - see {@link LASParser}. */ constructor(id, config) { super(id, config); + + /** + * @type {boolean} + * @readonly + */ this.isEntwinePointTileLayer = true; + + /** + * @type {THREE.Vector3} + */ this.scale = new THREE.Vector3(1, 1, 1); const resolve = this.addInitializationStep(); diff --git a/src/Layer/FeatureGeometryLayer.js b/src/Layer/FeatureGeometryLayer.js index 1a142eb557..4787856bac 100644 --- a/src/Layer/FeatureGeometryLayer.js +++ b/src/Layer/FeatureGeometryLayer.js @@ -13,10 +13,11 @@ import Feature2Mesh from 'Converter/Feature2Mesh'; * @property {boolean} isFeatureGeometryLayer - Used to checkout whether this layer is * a FeatureGeometryLayer. Default is true. You should not change this, as it is used * internally for optimisation. + * + * @extends GeometryLayer */ class FeatureGeometryLayer extends GeometryLayer { /** - * @extends GeometryLayer * * @param {string} id - The id of the layer, that should be unique. It is * not mandatory, but an error will be emitted if this layer is added a @@ -29,6 +30,9 @@ class FeatureGeometryLayer extends GeometryLayer { * @param {THREE.Object3D} [options.object3d=new THREE.Group()] root object3d layer. * @param {function} [options.onMeshCreated] this callback is called when the mesh is created. The callback parameters are the * `mesh` and the `context`. + * @param {function} [options.filter] callback which filters the features of + * this layer. It takes an object with a `properties` property as argument + * and shall return a boolean. * @param {boolean} [options.accurate=TRUE] If `accurate` is `true`, data are re-projected with maximum geographical accuracy. * With `true`, `proj4` is used to transform data source. * @@ -45,15 +49,28 @@ class FeatureGeometryLayer extends GeometryLayer { * **WARNING** If the source is `VectorTilesSource` then `accurate` is always false. */ constructor(id, options = {}) { - options.update = FeatureProcessing.update; - options.convert = Feature2Mesh.convert({ - batchId: options.batchId, + const { + object3d, + batchId, + onMeshCreated, + accurate = true, + filter, + ...geometryOptions + } = options; + + super(id, object3d || new Group(), geometryOptions); + + this.update = FeatureProcessing.update; + this.convert = Feature2Mesh.convert({ + batchId, }); - super(id, options.object3d || new Group(), options); + + this.onMeshCreated = onMeshCreated; this.isFeatureGeometryLayer = true; - this.accurate = options.accurate ?? true; + this.accurate = accurate; this.buildExtent = !this.accurate; + this.filter = filter; } preUpdate(context, sources) { diff --git a/src/Layer/GeometryLayer.js b/src/Layer/GeometryLayer.js index baae64d04d..57485618c3 100644 --- a/src/Layer/GeometryLayer.js +++ b/src/Layer/GeometryLayer.js @@ -23,8 +23,6 @@ class GeometryLayer extends Layer { * A layer usually managing a geometry to display on a view. For example, it * can be a layer of buildings extruded from a a WFS stream. * - * @extends Layer - * * @param {string} id - The id of the layer, that should be unique. It is * not mandatory, but an error will be emitted if this layer is added a * {@link View} that already has a layer going by that id. @@ -36,7 +34,10 @@ class GeometryLayer extends Layer { * contains three elements `name, protocol, extent`, these elements will be * available using `layer.name` or something else depending on the property * name. - * @param {Source} [config.source] - Description and options of the source. + * @param {Source} config.source - Description and options of the source. + * @param {number} [config.cacheLifeTime=Infinity] - set life time value in cache. + * This value is used for [Cache]{@link Cache} expiration mechanism. + * @param {boolean} [config.visible] * * @throws {Error} `object3d` must be a valid `THREE.Object3d`. * @@ -54,13 +55,22 @@ class GeometryLayer extends Layer { * view.addLayer(geometry); */ constructor(id, object3d, config = {}) { - config.cacheLifeTime = config.cacheLifeTime ?? CACHE_POLICIES.GEOMETRY; - - // Remove this part when Object.assign(this, config) will be removed from Layer Constructor - const visible = config.visible; - delete config.visible; - super(id, config); + const { + cacheLifeTime = CACHE_POLICIES.GEOMETRY, + visible = true, + opacity = 1.0, + ...layerConfig + } = config; + + super(id, { + ...layerConfig, + cacheLifeTime, + }); + /** + * @type {boolean} + * @readonly + */ this.isGeometryLayer = true; if (!object3d || !object3d.isObject3D) { @@ -72,17 +82,35 @@ class GeometryLayer extends Layer { object3d.name = id; } + /** + * @type {THREE.Object3D} + * @readonly + */ + this.object3d = object3d; Object.defineProperty(this, 'object3d', { - value: object3d, writable: false, configurable: true, }); - this.opacity = 1.0; + /** + * @type {number} + */ + this.opacity = opacity; + + /** + * @type {boolean} + */ this.wireframe = false; + /** + * @type {Layer[]} + */ this.attachedLayers = []; - this.visible = visible ?? true; + + /** + * @type {boolean} + */ + this.visible = visible; Object.defineProperty(this.zoom, 'max', { value: Infinity, writable: false, diff --git a/src/Layer/LabelLayer.js b/src/Layer/LabelLayer.js index 87d5d7125c..1dcd649b00 100644 --- a/src/Layer/LabelLayer.js +++ b/src/Layer/LabelLayer.js @@ -176,9 +176,14 @@ class LabelLayer extends GeometryLayer { * except for the `Style.text.anchor` parameter which can help place the label. */ constructor(id, config = {}) { - const domElement = config.domElement; - delete config.domElement; - super(id, config.object3d || new THREE.Group(), config); + const { + domElement, + performance = true, + forceClampToTerrain = false, + margin, + ...geometryConfig + } = config; + super(id, config.object3d || new THREE.Group(), geometryConfig); this.isLabelLayer = true; this.domElement = new DomNode(); @@ -186,8 +191,9 @@ class LabelLayer extends GeometryLayer { this.domElement.dom.id = `itowns-label-${this.id}`; this.buildExtent = true; this.crs = config.source.crs; - this.performance = config.performance || true; - this.forceClampToTerrain = config.forceClampToTerrain || false; + this.performance = performance; + this.forceClampToTerrain = forceClampToTerrain; + this.margin = margin; this.toHide = new THREE.Group(); diff --git a/src/Layer/Layer.js b/src/Layer/Layer.js index d3e2ef1802..de5ad93b8a 100644 --- a/src/Layer/Layer.js +++ b/src/Layer/Layer.js @@ -89,63 +89,101 @@ class Layer extends THREE.EventDispatcher { * layerToListen.addEventListener('opacity-property-changed', (event) => console.log(event)); */ constructor(id, config = {}) { - /* istanbul ignore next */ - if (config.projection) { - console.warn('Layer projection parameter is deprecated, use crs instead.'); - config.crs = config.crs || config.projection; - } + const { + source, + name, + style = {}, + subdivisionThreshold = 256, + addLabelLayer = false, + cacheLifeTime, + options = {}, + updateStrategy, + zoom, + mergeFeatures = true, + crs, + } = config; - if (config.source === undefined || config.source === true) { - throw new Error(`Layer ${id} needs Source`); - } super(); - this.isLayer = true; - if (config.style && !(config.style instanceof Style)) { - if (typeof config.style.fill?.pattern === 'string') { - console.warn('Using style.fill.pattern = { source: Img|url } is adviced'); - config.style.fill.pattern = { source: config.style.fill.pattern }; - } - config.style = new Style(config.style); - } - this.style = config.style || new Style(); - this.subdivisionThreshold = config.subdivisionThreshold || 256; - this.sizeDiagonalTexture = (2 * (this.subdivisionThreshold * this.subdivisionThreshold)) ** 0.5; - Object.assign(this, config); + /** + * @type {boolean} + * @readonly + */ + this.isLayer = true; + /** + * @type {string} + * @readonly + */ + this.id = id; Object.defineProperty(this, 'id', { - value: id, writable: false, }); - // Default properties - this.options = config.options || {}; - if (!this.updateStrategy) { - this.updateStrategy = { - type: STRATEGY_MIN_NETWORK_TRAFFIC, - options: {}, - }; + /** + * @type {string} + */ + this.name = name; + + if (source === undefined || source === true) { + throw new Error(`Layer ${id} needs Source`); } + /** + * @type {Source} + */ + this.source = source || new Source({ url: 'none' }); - this.defineLayerProperty('frozen', false); + this.crs = crs; - if (config.zoom) { - this.zoom = { max: config.zoom.max, min: config.zoom.min || 0 }; - if (this.zoom.max == undefined) { - this.zoom.max = Infinity; + if (style && !(style instanceof Style)) { + if (typeof style.fill?.pattern === 'string') { + console.warn('Using style.fill.pattern = { source: Img|url } is adviced'); + style.fill.pattern = { source: style.fill.pattern }; } + this.style = new Style(style); } else { - this.zoom = { max: Infinity, min: 0 }; + this.style = style || new Style(); } - this.info = new InfoLayer(this); + /** + * @type {number} + */ + this.subdivisionThreshold = subdivisionThreshold; + this.sizeDiagonalTexture = (2 * (this.subdivisionThreshold * this.subdivisionThreshold)) ** 0.5; + + this.addLabelLayer = addLabelLayer; + + // Default properties + this.options = options; - this.source = this.source || new Source({ url: 'none' }); + this.updateStrategy = updateStrategy ?? { + type: STRATEGY_MIN_NETWORK_TRAFFIC, + options: {}, + }; + this.defineLayerProperty('frozen', false); + + this.zoom = { + min: zoom?.min ?? 0, + max: zoom?.max ?? Infinity, + }; + + this.info = new InfoLayer(this); + + /** + * @type {boolean} + */ this.ready = false; + /** + * @type {Promise[]} + * @protected + */ this._promises = []; + /** + * @type {Promise} + */ this.whenReady = new Promise((re, rj) => { this._resolve = re; this._reject = rj; @@ -157,12 +195,12 @@ class Layer extends THREE.EventDispatcher { this._promises.push(this.source.whenReady); - this.cache = new Cache(config.cacheLifeTime); - - this.mergeFeatures = this.mergeFeatures === undefined ? true : config.mergeFeatures; + /** + * @type {Cache} + */ + this.cache = new Cache(cacheLifeTime); - // TODO: verify but this.source.filter seems be always undefined. - this.filter = this.filter || this.source.filter; + this.mergeFeatures = mergeFeatures; } addInitializationStep() { @@ -245,15 +283,6 @@ class Layer extends THREE.EventDispatcher { return data; } - /** - * Determines whether the specified feature is valid data. - * - * @param {Feature} feature The feature - * @returns {Feature} the feature is returned if it's valided - */ - // eslint-disable-next-line - isValidData(feature) {} - /** * Remove and dispose all objects from layer. * @param {boolean} [clearCache=false] Whether to clear the layer cache or not diff --git a/src/Layer/OrientedImageLayer.js b/src/Layer/OrientedImageLayer.js index 54eb40816b..39a5229e49 100644 --- a/src/Layer/OrientedImageLayer.js +++ b/src/Layer/OrientedImageLayer.js @@ -100,16 +100,24 @@ class OrientedImageLayer extends GeometryLayer { * a tecture is need for each camera, for each panoramic. */ constructor(id, config = {}) { + const { + backgroundDistance, + background = createBackground(backgroundDistance), + onPanoChanged = () => {}, + getCamerasNameFromFeature = () => {}, + ...geometryOptions + } = config; + /* istanbul ignore next */ if (config.projection) { console.warn('OrientedImageLayer projection parameter is deprecated, use crs instead.'); config.crs = config.crs || config.projection; } - super(id, new THREE.Group(), config); + super(id, new THREE.Group(), geometryOptions); this.isOrientedImageLayer = true; - this.background = config.background || createBackground(config.backgroundDistance); + this.background = background; if (this.background) { // Add layer id to easily identify the objects later on (e.g. to delete the geometries when deleting the layer) @@ -122,10 +130,10 @@ class OrientedImageLayer extends GeometryLayer { this.currentPano = undefined; // store a callback to fire event when current panoramic change - this.onPanoChanged = config.onPanoChanged || (() => {}); + this.onPanoChanged = onPanoChanged; // function to get cameras name from panoramic feature - this.getCamerasNameFromFeature = config.getCamerasNameFromFeature || (() => {}); + this.getCamerasNameFromFeature = getCamerasNameFromFeature; const resolve = this.addInitializationStep(); diff --git a/src/Layer/PointCloudLayer.js b/src/Layer/PointCloudLayer.js index 9d6aabaf8c..65ce078dc5 100644 --- a/src/Layer/PointCloudLayer.js +++ b/src/Layer/PointCloudLayer.js @@ -127,6 +127,8 @@ function changeAngleRange(layer) { * @property {number} [maxIntensityRange=1] - The maximal intensity of the * layer. Changing this value will affect the material, if it has the * corresponding uniform. The value is normalized between 0 and 1. + * + * @extends GeometryLayer */ class PointCloudLayer extends GeometryLayer { /** @@ -134,8 +136,6 @@ class PointCloudLayer extends GeometryLayer { * Constructs a new instance of a Point Cloud Layer. This should not be used * directly, but rather implemented using `extends`. * - * @extends GeometryLayer - * * @param {string} id - The id of the layer, that should be unique. It is * not mandatory, but an error will be emitted if this layer is added a * {@link View} that already has a layer going by that id. @@ -144,43 +144,89 @@ class PointCloudLayer extends GeometryLayer { * contains three elements `name, protocol, extent`, these elements will be * available using `layer.name` or something else depending on the property * name. See the list of properties to know which one can be specified. + * @param {Source} config.source - Description and options of the source. */ constructor(id, config = {}) { - super(id, config.object3d || new THREE.Group(), config); + const { + object3d = new THREE.Group(), + group = new THREE.Group(), + bboxes = new THREE.Group(), + octreeDepthLimit = -1, + pointBudget = 2000000, + pointSize = 2, + sseThreshold = 2, + minIntensityRange = 1, + maxIntensityRange = 65536, + minElevationRange = 0, + maxElevationRange = 1000, + minAngleRange = -90, + maxAngleRange = 90, + material = {}, + mode = PNTS_MODE.COLOR, + ...geometryLayerConfig + } = config; + + super(id, object3d, geometryLayerConfig); + + /** + * @type {boolean} + * @readonly + */ this.isPointCloudLayer = true; this.protocol = 'pointcloud'; - this.group = config.group || new THREE.Group(); + this.group = group; this.object3d.add(this.group); - this.bboxes = config.bboxes || new THREE.Group(); + this.bboxes = bboxes || new THREE.Group(); this.bboxes.visible = false; this.object3d.add(this.bboxes); this.group.updateMatrixWorld(); // default config - this.octreeDepthLimit = config.octreeDepthLimit || -1; - this.pointBudget = config.pointBudget || 2000000; - this.pointSize = config.pointSize === 0 || !isNaN(config.pointSize) ? config.pointSize : 4; - this.sseThreshold = config.sseThreshold || 2; - - this.defineLayerProperty('minIntensityRange', config.minIntensityRange || 1, changeIntensityRange); - this.defineLayerProperty('maxIntensityRange', config.maxIntensityRange || 65536, changeIntensityRange); - this.defineLayerProperty('minElevationRange', config.minElevationRange || 0, changeElevationRange); - this.defineLayerProperty('maxElevationRange', config.maxElevationRange || 1000, changeElevationRange); - this.defineLayerProperty('minAngleRange', config.minAngleRange || -90, changeAngleRange); - this.defineLayerProperty('maxAngleRange', config.maxAngleRange || 90, changeAngleRange); - - this.material = config.material || {}; + /** + * @type {number} + */ + this.octreeDepthLimit = octreeDepthLimit; + + /** + * @type {number} + */ + this.pointBudget = pointBudget; + + /** + * @type {number} + */ + this.pointSize = pointSize; + + /** + * @type {number} + */ + this.sseThreshold = sseThreshold; + + this.defineLayerProperty('minIntensityRange', minIntensityRange, changeIntensityRange); + this.defineLayerProperty('maxIntensityRange', maxIntensityRange, changeIntensityRange); + this.defineLayerProperty('minElevationRange', minElevationRange, changeElevationRange); + this.defineLayerProperty('maxElevationRange', maxElevationRange, changeElevationRange); + this.defineLayerProperty('minAngleRange', minAngleRange, changeAngleRange); + this.defineLayerProperty('maxAngleRange', maxAngleRange, changeAngleRange); + + /** + * @type {THREE.Material} + */ + this.material = material; if (!this.material.isMaterial) { - config.material = config.material || {}; - config.material.intensityRange = new THREE.Vector2(this.minIntensityRange, this.maxIntensityRange); - config.material.elevationRange = new THREE.Vector2(this.minElevationRange, this.maxElevationRange); - config.material.angleRange = new THREE.Vector2(this.minAngleRange, this.maxAngleRange); - this.material = new PointsMaterial(config.material); + this.material.intensityRange = new THREE.Vector2(this.minIntensityRange, this.maxIntensityRange); + this.material.elevationRange = new THREE.Vector2(this.minElevationRange, this.maxElevationRange); + this.material.angleRange = new THREE.Vector2(this.minAngleRange, this.maxAngleRange); + this.material = new PointsMaterial(this.material); } - this.material.defines = this.material.defines || {}; - this.mode = config.mode || PNTS_MODE.COLOR; + this.mode = mode || PNTS_MODE.COLOR; + + /** + * @type {PointCloudNode | undefined} + */ + this.root = undefined; } preUpdate(context, changeSources) { diff --git a/src/Layer/Potree2Layer.js b/src/Layer/Potree2Layer.js index 610db10cba..df8e7501d0 100644 --- a/src/Layer/Potree2Layer.js +++ b/src/Layer/Potree2Layer.js @@ -113,13 +113,13 @@ function parseAttributes(jsonAttributes) { * @property {boolean} isPotreeLayer - Used to checkout whether this layer * is a Potree2Layer. Default is `true`. You should not change this, as it is * used internally for optimisation. + * + * @extends PointCloudLayer */ class Potree2Layer extends PointCloudLayer { /** * Constructs a new instance of Potree2 layer. * - * @extends PointCloudLayer - * * @example * // Create a new point cloud layer * const points = new Potree2Layer('points', @@ -146,6 +146,11 @@ class Potree2Layer extends PointCloudLayer { */ constructor(id, config) { super(id, config); + + /** + * @type {boolean} + * @readonly + */ this.isPotreeLayer = true; const resolve = this.addInitializationStep(); diff --git a/src/Layer/PotreeLayer.js b/src/Layer/PotreeLayer.js index 4dcee04670..2940272602 100644 --- a/src/Layer/PotreeLayer.js +++ b/src/Layer/PotreeLayer.js @@ -11,13 +11,13 @@ bboxMesh.geometry.boundingBox = box3; * @property {boolean} isPotreeLayer - Used to checkout whether this layer * is a PotreeLayer. Default is `true`. You should not change this, as it is * used internally for optimisation. + * + * @extends PointCloudLayer */ class PotreeLayer extends PointCloudLayer { /** * Constructs a new instance of Potree layer. * - * @extends PointCloudLayer - * * @example * // Create a new point cloud layer * const points = new PotreeLayer('points', @@ -38,12 +38,17 @@ class PotreeLayer extends PointCloudLayer { * contains three elements `name, protocol, extent`, these elements will be * available using `layer.name` or something else depending on the property * name. See the list of properties to know which one can be specified. - * @param {string} [config.crs=ESPG:4326] - The CRS of the {@link View} this + * @param {string} [config.crs='ESPG:4326'] - The CRS of the {@link View} this * layer will be attached to. This is used to determine the extent of this * layer. Default to `EPSG:4326`. */ constructor(id, config) { super(id, config); + + /** + * @type {boolean} + * @readonly + */ this.isPotreeLayer = true; const resolve = this.addInitializationStep(); diff --git a/src/Layer/RasterLayer.js b/src/Layer/RasterLayer.js index 5832413adf..7b18eb1665 100644 --- a/src/Layer/RasterLayer.js +++ b/src/Layer/RasterLayer.js @@ -5,8 +5,20 @@ import { CACHE_POLICIES } from 'Core/Scheduler/Cache'; class RasterLayer extends Layer { constructor(id, config) { - config.cacheLifeTime = config.cacheLifeTime ?? CACHE_POLICIES.TEXTURE; - super(id, config); + const { + cacheLifeTime = CACHE_POLICIES.TEXTURE, + minFilter, + magFilter, + ...layerConfig + } = config; + + super(id, { + ...layerConfig, + cacheLifeTime, + }); + + this.minFilter = minFilter; + this.magFilter = magFilter; } convert(data, extentDestination) { diff --git a/src/Layer/TiledGeometryLayer.js b/src/Layer/TiledGeometryLayer.js index 666d9f3a3c..1502e50a7a 100644 --- a/src/Layer/TiledGeometryLayer.js +++ b/src/Layer/TiledGeometryLayer.js @@ -17,6 +17,8 @@ const boundingSphereCenter = new THREE.Vector3(); * as it is used internally for optimisation. * @property {boolean} hideSkirt (default false) - Used to hide the skirt (tile borders). * Useful when the layer opacity < 1 + * + * @extends GeometryLayer */ class TiledGeometryLayer extends GeometryLayer { /** @@ -39,12 +41,10 @@ class TiledGeometryLayer extends GeometryLayer { * It corresponds at meters by pixel. If the projection tile exceeds a certain pixel size (on screen) * then it is subdivided into 4 tiles with a zoom greater than 1. * - * @extends GeometryLayer - * * @param {string} id - The id of the layer, that should be unique. It is * not mandatory, but an error will be emitted if this layer is added a * {@link View} that already has a layer going by that id. - * @param {THREE.Object3d} object3d - The object3d used to contain the + * @param {THREE.Object3D} object3d - The object3d used to contain the * geometry of the TiledGeometryLayer. It is usually a `THREE.Group`, but it * can be anything inheriting from a `THREE.Object3d`. * @param {Array} schemeTile - extents Array of root tiles @@ -59,20 +59,67 @@ class TiledGeometryLayer extends GeometryLayer { * @throws {Error} `object3d` must be a valid `THREE.Object3d`. */ constructor(id, object3d, schemeTile, builder, config) { - // cacheLifeTime = CACHE_POLICIES.INFINITE because the cache is handled by the builder - config.cacheLifeTime = CACHE_POLICIES.INFINITE; - config.source = false; - super(id, object3d, config); - + const { + sseSubdivisionThreshold = 1.0, + minSubdivisionLevel, + maxSubdivisionLevel, + maxDeltaElevationLevel, + tileMatrixSets, + diffuse, + showOutline = false, + segments, + disableSkirt = false, + materialOptions, + ...configGeometryLayer + } = config; + + super(id, object3d, { + ...configGeometryLayer, + // cacheLifeTime = CACHE_POLICIES.INFINITE because the cache is handled by the builder + cacheLifeTime: CACHE_POLICIES.INFINITE, + source: false, + }); + + /** + * @type {boolean} + * @readonly + */ this.isTiledGeometryLayer = true; + + this.protocol = 'tile'; + // TODO : this should be add in a preprocess method specific to GeoidLayer. this.object3d.geoidHeight = 0; - this.protocol = 'tile'; + /** + * @type {boolean} + */ + this.disableSkirt = disableSkirt; + this._hideSkirt = !!config.hideSkirt; - this.sseSubdivisionThreshold = this.sseSubdivisionThreshold || 1.0; + /** + * @type {number} + */ + this.sseSubdivisionThreshold = sseSubdivisionThreshold; + + /** + * @type {number} + */ + this.minSubdivisionLevel = minSubdivisionLevel; + + /** + * @type {number} + */ + this.maxSubdivisionLevel = maxSubdivisionLevel; + + /** + * @type {number} + * @deprecated + */ + this.maxDeltaElevationLevel = maxDeltaElevationLevel; + this.segments = segments; this.schemeTile = schemeTile; this.builder = builder; this.info = new InfoTiledGeometryLayer(this); @@ -85,9 +132,21 @@ class TiledGeometryLayer extends GeometryLayer { throw new Error(`Cannot init tiled layer without builder for layer ${this.id}`); } - if (config.maxDeltaElevationLevel) { - console.warn('Config using maxDeltaElevationLevel is deprecated. The parameter maxDeltaElevationLevel is not longer used'); - } + this.maxScreenSizeNode = this.sseSubdivisionThreshold * (this.sizeDiagonalTexture * 2); + + this.tileMatrixSets = tileMatrixSets; + + this.materialOptions = materialOptions; + + /* + * @type {boolean} + */ + this.showOutline = showOutline; + + /** + * @type {THREE.Vector3 | undefined} + */ + this.diffuse = diffuse; this.level0Nodes = []; const promises = []; @@ -101,13 +160,12 @@ class TiledGeometryLayer extends GeometryLayer { this.object3d.add(...level0s); this.object3d.updateMatrixWorld(); })); - - this.maxScreenSizeNode = this.sseSubdivisionThreshold * (this.sizeDiagonalTexture * 2); } get hideSkirt() { return this._hideSkirt; } + set hideSkirt(value) { if (!this.level0Nodes) { return; diff --git a/test/unit/dataSourceProvider.js b/test/unit/dataSourceProvider.js index 9ba251069d..6bbbbb52fa 100644 --- a/test/unit/dataSourceProvider.js +++ b/test/unit/dataSourceProvider.js @@ -83,8 +83,6 @@ describe('Provide in Sources', function () { nodeLayerElevation = material.getLayer(elevationlayer.id); featureLayer = new GeometryLayer('geom', new THREE.Group(), { - update: FeatureProcessing.update, - convert: Feature2Mesh.convert(), crs: 'EPSG:4978', mergeFeatures: false, zoom: { min: 10 }, @@ -96,6 +94,8 @@ describe('Provide in Sources', function () { }, }), }); + featureLayer.update = FeatureProcessing.update; + featureLayer.convert = Feature2Mesh.convert(); featureLayer.source = new WFSSource({ url: 'http://domain.com', diff --git a/test/unit/layeredmaterialnodeprocessing.js b/test/unit/layeredmaterialnodeprocessing.js index 0f7e92af02..38c7daf92d 100644 --- a/test/unit/layeredmaterialnodeprocessing.js +++ b/test/unit/layeredmaterialnodeprocessing.js @@ -40,16 +40,17 @@ describe('updateLayeredMaterialNodeImagery', function () { source, crs: 'EPSG:4326', info: { update: () => {} }, + }); + layer.tileMatrixSets = [ + 'EPSG:4326', + 'EPSG:3857', + ]; + layer.parent = { tileMatrixSets: [ 'EPSG:4326', 'EPSG:3857', ], - parent: { tileMatrixSets: [ - 'EPSG:4326', - 'EPSG:3857', - ], - }, - }); + }; const nodeLayer = new RasterColorTile(material, layer); material.getLayer = () => nodeLayer; diff --git a/test/unit/layerupdatestrategy.js b/test/unit/layerupdatestrategy.js index 89b7abceda..dc57ef9b99 100644 --- a/test/unit/layerupdatestrategy.js +++ b/test/unit/layerupdatestrategy.js @@ -43,16 +43,17 @@ describe('Handling no data source error', function () { source, crs: 'EPSG:4326', info: { update: () => {} }, + }); + layer.tileMatrixSets = [ + 'EPSG:4326', + 'EPSG:3857', + ]; + layer.parent = { tileMatrixSets: [ 'EPSG:4326', 'EPSG:3857', ], - parent: { tileMatrixSets: [ - 'EPSG:4326', - 'EPSG:3857', - ], - }, - }); + }; const nodeLayer = new RasterColorTile(material, layer); nodeLayer.level = 10; diff --git a/utils/debug/3dTilesDebug.js b/utils/debug/3dTilesDebug.js index a6a0e79f8e..3081c193da 100644 --- a/utils/debug/3dTilesDebug.js +++ b/utils/debug/3dTilesDebug.js @@ -63,11 +63,11 @@ export default function create3dTilesDebugUI(datDebugTool, view, _3dTileslayer) } const boundingVolumeLayer = new GeometryLayer(boundingVolumeID, new THREE.Object3D(), { - update: debugIdUpdate, visible: false, cacheLifeTime: Infinity, source: false, }); + boundingVolumeLayer.update = debugIdUpdate; View.prototype.addLayer.call(view, boundingVolumeLayer, _3dTileslayer).then((l) => { gui.add(l, 'visible').name('Bounding boxes').onChange(() => { diff --git a/utils/debug/TileDebug.js b/utils/debug/TileDebug.js index 3adb106572..1866698264 100644 --- a/utils/debug/TileDebug.js +++ b/utils/debug/TileDebug.js @@ -156,8 +156,8 @@ export default function createTileDebugUI(datDebugTool, view, layer, debugInstan class DebugLayer extends GeometryLayer { constructor(id, options = {}) { - options.update = debugIdUpdate; super(id, options.object3d || new THREE.Group(), options); + this.update = debugIdUpdate; this.isDebugLayer = true; }