From cb96727f71f58aed6bc961d3b57cfe9d5ccb5713 Mon Sep 17 00:00:00 2001 From: Vincent JAILLOT Date: Wed, 2 Oct 2024 10:21:12 +0200 Subject: [PATCH 01/33] fix(Terrain): fix terrain subdivision when a terrain tile only has values that should be clamped --- src/Parser/XbilParser.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Parser/XbilParser.js b/src/Parser/XbilParser.js index 8ce7c7d588..b8dccd16fc 100644 --- a/src/Parser/XbilParser.js +++ b/src/Parser/XbilParser.js @@ -64,8 +64,16 @@ export function computeMinMaxElevation(texture, pitch, options) { } } } - if (options.zmin > min) { min = options.zmin; } - if (options.zmax < max) { max = options.zmax; } + // Clamp values to zmin and zmax values configured in ElevationLayer + if (options.zmin != null) { + if (min < options.zmin) { min = options.zmin; } + if (max < options.zmin) { max = options.zmin; } + } + + if (options.zmax != null) { + if (min > options.zmax) { min = options.zmax; } + if (max > options.zmax) { max = options.zmax; } + } } if (max === -Infinity || min === Infinity) { From a4f0d2284fabbe4a5ecf0cb512c9c35d6b684ee3 Mon Sep 17 00:00:00 2001 From: Lamache Date: Mon, 7 Oct 2024 11:40:42 +0200 Subject: [PATCH 02/33] fix(C3DTilesLayer): updateStyle works with new style API --- src/Layer/C3DTilesLayer.js | 29 +++++++++++++++-------------- src/Utils/gui/C3DTilesStyle.js | 5 ++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Layer/C3DTilesLayer.js b/src/Layer/C3DTilesLayer.js index d8fd1d43f8..83ab6d2f33 100644 --- a/src/Layer/C3DTilesLayer.js +++ b/src/Layer/C3DTilesLayer.js @@ -143,7 +143,7 @@ class C3DTilesLayer extends GeometryLayer { } /** @type {Style} */ - this._style = config.style || null; + this.style = config.style || null; /** @type {Map} */ this.#fillColorMaterialsBuffer = new Map(); @@ -386,21 +386,16 @@ class C3DTilesLayer extends GeometryLayer { if (c3DTileFeature.object3d != object3d) { continue;// this feature do not belong to object3d } + + this._style.context.setGeometry({ + properties: c3DTileFeature, + }); + /** @type {THREE.Color} */ - let color = null; - if (typeof this._style.fill.color === 'function') { - color = new THREE.Color(this._style.fill.color(c3DTileFeature)); - } else { - color = new THREE.Color(this._style.fill.color); - } + const color = new THREE.Color(this._style.fill.color); /** @type {number} */ - let opacity = null; - if (typeof this._style.fill.opacity === 'function') { - opacity = this._style.fill.opacity(c3DTileFeature); - } else { - opacity = this._style.fill.opacity; - } + const opacity = this._style.fill.opacity; const materialId = color.getHexString() + opacity; @@ -464,7 +459,13 @@ class C3DTilesLayer extends GeometryLayer { } set style(value) { - this._style = value; + if (value instanceof Style) { + this._style = value; + } else if (!value) { + this._style = null; + } else { + this._style = new Style(value); + } this.updateStyle(); } diff --git a/src/Utils/gui/C3DTilesStyle.js b/src/Utils/gui/C3DTilesStyle.js index a0c077eb29..6fabf4f72a 100644 --- a/src/Utils/gui/C3DTilesStyle.js +++ b/src/Utils/gui/C3DTilesStyle.js @@ -1,4 +1,3 @@ -import Style from 'Core/Style'; import { C3DTILES_LAYER_EVENTS } from 'Layer/C3DTilesLayer'; import Widget from './Widget'; @@ -221,12 +220,12 @@ class C3DTilesStyle extends Widget { } // set style - c3DTilesLayer.style = new Style({ + c3DTilesLayer.style = { fill: { color: fillColorFunction, opacity: fillOpacityFunction, }, - }); + }; }); }); From 3eb7a23b810db2a967c07a7374ef65c219ce17fe Mon Sep 17 00:00:00 2001 From: Vincent JAILLOT Date: Wed, 25 Sep 2024 12:04:04 +0200 Subject: [PATCH 03/33] example(3DTiles): create an only 3D tiles example that can load any 3D tiles --- examples/3dtiles_25d.html | 120 -------------------- examples/3dtiles_basic.html | 94 ---------------- examples/3dtiles_batch_table.html | 86 --------------- examples/3dtiles_ion.html | 126 --------------------- examples/3dtiles_loader.html | 147 +++++++++++++++++-------- examples/3dtiles_pointcloud.html | 95 ---------------- examples/config.json | 12 +- src/Layer/OGC3DTilesLayer.js | 8 +- src/Renderer/PointsMaterial.js | 2 +- test/functional/3dtiles_25d.js | 27 ----- test/functional/3dtiles_basic.js | 48 -------- test/functional/3dtiles_batch_table.js | 77 ------------- test/functional/3dtiles_ion.js | 40 ------- test/functional/3dtiles_pointcloud.js | 49 --------- utils/debug/OGC3DTilesDebug.js | 1 - 15 files changed, 111 insertions(+), 821 deletions(-) delete mode 100644 examples/3dtiles_25d.html delete mode 100644 examples/3dtiles_basic.html delete mode 100644 examples/3dtiles_batch_table.html delete mode 100644 examples/3dtiles_ion.html delete mode 100644 examples/3dtiles_pointcloud.html delete mode 100644 test/functional/3dtiles_25d.js delete mode 100644 test/functional/3dtiles_basic.js delete mode 100644 test/functional/3dtiles_batch_table.js delete mode 100644 test/functional/3dtiles_ion.js delete mode 100644 test/functional/3dtiles_pointcloud.js diff --git a/examples/3dtiles_25d.html b/examples/3dtiles_25d.html deleted file mode 100644 index 41cc65ad15..0000000000 --- a/examples/3dtiles_25d.html +++ /dev/null @@ -1,120 +0,0 @@ - - - Itowns - 3D Tiles on 2.5D map example - - - - - - - - -
- - - - - - - - diff --git a/examples/3dtiles_basic.html b/examples/3dtiles_basic.html deleted file mode 100644 index 9db050b8a3..0000000000 --- a/examples/3dtiles_basic.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - Itowns - 3d-tiles example - - - - - - - - -
-
-

Feature Information:

-
-
- - - - - - - - diff --git a/examples/3dtiles_batch_table.html b/examples/3dtiles_batch_table.html deleted file mode 100644 index 4471f98eb4..0000000000 --- a/examples/3dtiles_batch_table.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - Itowns - 3d-tiles hierarchy example - - - - - - - - -
-
-

Feature Information:

-
-
- - - - - - - - diff --git a/examples/3dtiles_ion.html b/examples/3dtiles_ion.html deleted file mode 100644 index 3ef10b4227..0000000000 --- a/examples/3dtiles_ion.html +++ /dev/null @@ -1,126 +0,0 @@ - - - - Itowns - 3d-tiles from Cesium ion example - - - - - - - - - - - - - -
-

This example displays a dataset representing extruded OSM buildings from Cesium ion - with Cesium default access token. Zoom to any place in the world to see the buildings.
- Buildings may appear to "fly" above the ground in some places, this is due to the combination - of precision errors of this dataset and of the 3D terrain we use in this example.
-

-
-
-
- - - - - - - - - diff --git a/examples/3dtiles_loader.html b/examples/3dtiles_loader.html index 3d701f7f60..66ac75c62f 100644 --- a/examples/3dtiles_loader.html +++ b/examples/3dtiles_loader.html @@ -11,7 +11,15 @@ @@ -21,12 +29,17 @@
Specify the URL of a tileset to load: - + +
+ + + +
+

+

Display feature information by clicking on it

Feature Information:

-
+
@@ -52,28 +65,17 @@ const { TMSSource, WMTSSource, OGC3DTilesSource, - ColorLayer, ElevationLayer, OGC3DTilesLayer, + ColorLayer, ElevationLayer, OGC3DTilesLayer, PNTS_SIZE_MODE, GlobeView, Coordinates, Fetcher, } = itowns; - const uri = new URL(location); - const state = { - // URL to tileset JSON - tileset: uri.searchParams.get('tileset'), - // Cesium ION / - assetId: uri.searchParams.get('assetId'), - }; - - function setURL(url) { - if (!url) return; + window.layer = null; // 3D Tiles layer - uri.searchParams.set('tileset', url); - history.pushState(null, '', `?${uri.searchParams.toString()}`); + const uri = new URL(location); - location.reload(); - } + window.gui = new dat.GUI(); - // ---- CREATE A GlobeView FOR SUPPORTING DATA VISUALIZATION ---- + // ---- Create a GlobeView ---- // Define camera initial position const placement = { @@ -85,7 +87,17 @@ const viewerDiv = document.getElementById('viewerDiv'); // Create a GlobeView - const view = new GlobeView(viewerDiv, placement, {}); + const view = new GlobeView(viewerDiv, placement, { + controls: { + minDistance: 100, + } + }); + + // Enable various compression support for 3D Tiles tileset: + // - `KHR_draco_mesh_compression` mesh compression extension + // - `KHR_texture_basisu` texture compresion extension + itowns.enableDracoLoader('./libs/draco/'); + itowns.enableKtx2Loader('./lib/basis/', view.renderer); // Add ambient light to globally illuminates all objects const light = new AmbientLight(0x404040, 15); @@ -94,36 +106,68 @@ // Setup loading screen setupLoadingScreen(viewerDiv, view); - // Setup debug menu - const menuGlobe = new GuiTools('menuDiv', view, 300); - debug.createTileDebugUI(menuGlobe.gui, view, view.tileLayer); - - - // ---- ADD A BASEMAP ---- + // ---- Add a basemap ---- // Add one imagery layer to the scene. This layer's properties are // defined in a json file, but it cou ld be defined as a plain js // object. See `Layer` documentation for more info. Fetcher.json('./layers/JSONLayers/OPENSM.json').then((config) => { - const layer = new ColorLayer('Ortho', { + const colorLayer = new ColorLayer('Ortho', { ...config, source: new TMSSource(config.source), }); - view.addLayer(layer).then(menuGlobe.addLayerGUI.bind(menuGlobe)); + view.addLayer(colorLayer); }); - // ---- ADD 3D TILES TILESET ---- + // ---- Add 3D terrain ---- - // Enable various compression support for 3D Tiles tileset: - // - `KHR_draco_mesh_compression` mesh compression extension - // - `KHR_texture_basisu` texture compresion extension - itowns.enableDracoLoader('./libs/draco/'); - itowns.enableKtx2Loader('./lib/basis/', view.renderer); + // Add two elevation layers: world terrain and a more precise terrain for france + // These will deform iTowns globe geometry to represent terrain elevation. + function addElevationLayerFromConfig(config) { + config.source = new itowns.WMTSSource(config.source); + var elevationLayer = new itowns.ElevationLayer(config.id, config); + view.addLayer(elevationLayer); + } + itowns.Fetcher.json('./layers/JSONLayers/IGN_MNT_HIGHRES.json').then(addElevationLayerFromConfig); + itowns.Fetcher.json('./layers/JSONLayers/WORLD_DTM.json').then(addElevationLayerFromConfig); + + // ---------- 3D Tiles loading + + function readURL() { + const url = document.getElementById('url').value; + + if (url) { + setUrl(url); + } + } + + function setUrl(url) { + if (!url) return; + + const input_url = document.getElementById('url'); + if (!input_url) return; + + uri.searchParams.set('copc', url); + history.replaceState(null, null, `?${uri.searchParams.toString()}`); + + input_url.value = url; + load(url); + } + + + function load(url) { + const source = new OGC3DTilesSource({ url }); - if (state.tileset) { - const source = new OGC3DTilesSource({ url: state.tileset }); - const layer = new OGC3DTilesLayer('3DTiles', { + if (window.layer) { + gui.removeFolder('Layer 3DTiles'); + view.removeLayer('3DTiles'); + view.notifyChange(); + window.layer.delete(); + } + + window.layer = new OGC3DTilesLayer('3DTiles', { source, + pntsSizeMode: PNTS_SIZE_MODE.ATTENUATED, }); // Add an event for picking the 3D Tiles layer and displaying @@ -131,20 +175,37 @@ const pickingArgs = { htmlDiv: document.getElementById('featureInfo'), view, - layer, + layer: window.layer, }; // Add the layer to our view - view.addLayer(layer).then((layer) => { + view.addLayer(window.layer).then((layer) => { zoomToLayer(view, layer); window.addEventListener('click', (event) => fillHTMLWithPickingInfo(event, pickingArgs), false); }); - debug.createOGC3DTilesDebugUI(menuGlobe.gui, view, layer); + window.layer.whenReady + .then(() => debug.createOGC3DTilesDebugUI(gui, view, window.layer)); + } + + function loadLyon() { + setUrl("https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/refs/heads/master/3DTiles/lyon1-4978/tileset.json"); + } + + function loadSete() { + setUrl("https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/pointclouds/pnts-sete-2021-0756_6256/tileset.json"); } - window.setURL = setURL; + function loadLille() { + setUrl("https://webimaging.lillemetropole.fr/externe/maillage/2020_mel_5cm/tileset.json"); + } + + window.readURL = readURL; + window.loadLyon = loadLyon; + window.loadSete = loadSete; + window.loadLille = loadLille; + diff --git a/examples/3dtiles_pointcloud.html b/examples/3dtiles_pointcloud.html deleted file mode 100644 index 25300f0727..0000000000 --- a/examples/3dtiles_pointcloud.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - Itowns - Pointcloud classification - - - - - - - - -
- Pointcloud classification -
    -
  • Display your pointcloud
  • -
  • Use classification to assign colour to each points
  • -
  • Switch between mode on the left panel
  • -
-
-
- - - - - - - - diff --git a/examples/config.json b/examples/config.json index 2fdeaa2c60..318b70e726 100644 --- a/examples/config.json +++ b/examples/config.json @@ -14,16 +14,8 @@ "geoid_geoidLayer": "Display geoid heights" }, - "3d Tiles": { - "3dtiles_basic": "on 3D map", - "3dtiles_25d": "On 2.5D map", - "3dtiles_batch_table": "Batch table Hierarchy extension", - "3dtiles_ion": "From Cesium ion", - "3dtiles_pointcloud": "Pointcloud classification" - }, - - "3D Tiles (new)": { - "3dtiles_loader": "3D Tiles tileset loader" + "3D Tiles": { + "3dtiles_loader": "3D Tiles loader" }, "Pointcloud": { diff --git a/src/Layer/OGC3DTilesLayer.js b/src/Layer/OGC3DTilesLayer.js index 1a75f9514a..c284bcee5b 100644 --- a/src/Layer/OGC3DTilesLayer.js +++ b/src/Layer/OGC3DTilesLayer.js @@ -126,8 +126,8 @@ class OGC3DTilesLayer extends GeometryLayer { * @param {String} [config.pntsSizeMode= PNTS_SIZE_MODE.VALUE] {@link PointsMaterial} Point cloud size mode (passed to {@link PointsMaterial}). * Only 'VALUE' or 'ATTENUATED' are possible. VALUE use constant size, ATTENUATED compute size depending on distance * from point to camera. - * @param {Number} [config.pntsMinAttenuatedSize=1] Minimum scale used by 'ATTENUATED' size mode. - * @param {Number} [config.pntsMaxAttenuatedSize=7] Maximum scale used by 'ATTENUATED' size mode. + * @param {Number} [config.pntsMinAttenuatedSize=3] Minimum scale used by 'ATTENUATED' size mode. + * @param {Number} [config.pntsMaxAttenuatedSize=10] Maximum scale used by 'ATTENUATED' size mode. */ constructor(id, config) { super(id, new THREE.Group(), { source: config.source }); @@ -186,8 +186,8 @@ class OGC3DTilesLayer extends GeometryLayer { this.pntsShape = config.pntsShape ?? PNTS_SHAPE.CIRCLE; this.classification = config.classification ?? ClassificationScheme.DEFAULT; this.pntsSizeMode = config.pntsSizeMode ?? PNTS_SIZE_MODE.VALUE; - this.pntsMinAttenuatedSize = config.pntsMinAttenuatedSize || 1; - this.pntsMaxAttenuatedSize = config.pntsMaxAttenuatedSize || 7; + this.pntsMinAttenuatedSize = config.pntsMinAttenuatedSize || 3; + this.pntsMaxAttenuatedSize = config.pntsMaxAttenuatedSize || 10; } /** diff --git a/src/Renderer/PointsMaterial.js b/src/Renderer/PointsMaterial.js index 1a478b14ea..e6bc3fc194 100644 --- a/src/Renderer/PointsMaterial.js +++ b/src/Renderer/PointsMaterial.js @@ -156,7 +156,7 @@ class PointsMaterial extends THREE.ShaderMaterial { /** * @class PointsMaterial * @param {object} [options={}] The options - * @param {number} [options.size=0] size point + * @param {number} [options.size=1] point size * @param {number} [options.mode=PNTS_MODE.COLOR] display mode. * @param {number} [options.shape=PNTS_SHAPE.CIRCLE] rendered points shape. * @param {THREE.Vector4} [options.overlayColor=new THREE.Vector4(0, 0, 0, 0)] overlay color. diff --git a/test/functional/3dtiles_25d.js b/test/functional/3dtiles_25d.js deleted file mode 100644 index 533b3c6ca3..0000000000 --- a/test/functional/3dtiles_25d.js +++ /dev/null @@ -1,27 +0,0 @@ -import assert from 'assert'; - -describe('3dtiles_25d', function _() { - let result; - before(async () => { - result = await loadExample( - 'examples/3dtiles_25d.html', - this.fullTitle(), - ); - }); - - it('should run', async () => { - assert.ok(result); - }); - - it('should pick the planar layer', async () => { - const layers = await page.evaluate( - () => view.pickObjectsAt({ - x: 194, - y: 100, - }) - .map(p => p.layer.id), - ); - - assert.ok(layers.indexOf('planar') >= 0); - }); -}); diff --git a/test/functional/3dtiles_basic.js b/test/functional/3dtiles_basic.js deleted file mode 100644 index 6ffa74bc58..0000000000 --- a/test/functional/3dtiles_basic.js +++ /dev/null @@ -1,48 +0,0 @@ -import assert from 'assert'; - -describe('3dtiles_basic', function _() { - let result; - before(async () => { - result = await loadExample('examples/3dtiles_basic.html', this.fullTitle()); - }); - - it('should run', async () => { - assert.ok(result); - }); - - it('should return the dragon and the globe', async () => { - const layers = await page.evaluate( - () => view.pickObjectsAt({ x: 195, y: 146 }).map(p => p.layer.id), - ); - - assert.ok(layers.indexOf('globe') >= 0); - assert.ok(layers.indexOf('3d-tiles-discrete-lod') >= 0); - assert.equal(layers.indexOf('3d-tiles-request-volume'), -1); - }); - - it('should return points', async function __() { - // click on the 'goto pointcloud' button - await page.evaluate(() => d.zoom()); - - await waitUntilItownsIsIdle(this.test.fullTitle()); - - const pickingCount = await page.evaluate(() => view.pickObjectsAt( - { x: 200, y: 150 }, - 1, - '3d-tiles-request-volume', - ).length); - assert.ok(pickingCount > 0); - }); - - it('should remove GeometryLayer', async () => { - const countGeometryLayerStart = await page.evaluate(() => view.getLayers(l => l.isGeometryLayer).length); - const attachedGeometricLayerCount = await page.evaluate(() => { - const attachedLayers = view.getLayerById('3d-tiles-discrete-lod').attachedLayers.filter(l => l.isGeometryLayer); - return attachedLayers.length; - }); - await page.evaluate(() => view.removeLayer('3d-tiles-discrete-lod')); - const countGeometryLayerEnd = await page.evaluate(() => view.getLayers(l => l.isGeometryLayer).length); - // the attached GeometryLayers are, also, removed. - assert.equal(countGeometryLayerStart - countGeometryLayerEnd, 1 + attachedGeometricLayerCount); - }); -}); diff --git a/test/functional/3dtiles_batch_table.js b/test/functional/3dtiles_batch_table.js deleted file mode 100644 index 662a082de4..0000000000 --- a/test/functional/3dtiles_batch_table.js +++ /dev/null @@ -1,77 +0,0 @@ -import assert from 'assert'; - -describe('3dtiles_batch_table', function _() { - let result; - before(async () => { - result = await loadExample( - 'examples/3dtiles_batch_table.html', - this.fullTitle(), - ); - }); - - it('should run', async () => { - assert.ok(result); - }); - - it('should return the globe and 3d tiles layer', async () => { - const layers = await page.evaluate( - () => view.pickObjectsAt({ - x: 218, - y: 90, - }) - .map(p => p.layer.id), - ); - - assert.ok(layers.indexOf('globe') >= 0); - assert.ok(layers.indexOf('3d-tiles-bt-hierarchy') >= 0); - }); - - // Verifies that the batch id, batch table and batch table hierarchy - // extension picked information are correct for object at { x: 218, y: 90 } - it('should return the batch table and batch hierarchy picked information', - async function _it() { - // Picks the object at (218,90) and gets its closest c3DTileFeature - const pickResult = await page.evaluate( - () => { - const intersects = view.pickObjectsAt({ - x: 218, - y: 90, - }); - const layer = view.getLayerById('3d-tiles-bt-hierarchy'); - const c3DTileFeaturePicked = layer.getC3DTileFeatureFromIntersectsArray(intersects); - return { - info: c3DTileFeaturePicked.getInfo(), - batchId: c3DTileFeaturePicked.batchId, - }; - }, - ); - - // Create the expected object - const expectedPickingInfo = { - batchTable: { - height: 10, - area: 20, - }, - extensions: { - '3DTILES_batch_table_hierarchy': { - wall: { - wall_name: 'wall2', - wall_paint: 'blue', - wall_windows: 4, - }, - building: { - building_name: 'building2', - building_area: 39.3, - }, - zone: { - zone_name: 'zone0', - zone_buildings: 3, - }, - }, - }, - }; - - assert.equal(pickResult.batchId, 29); - assert.deepStrictEqual(pickResult.info, expectedPickingInfo); - }); -}); diff --git a/test/functional/3dtiles_ion.js b/test/functional/3dtiles_ion.js deleted file mode 100644 index d402d22442..0000000000 --- a/test/functional/3dtiles_ion.js +++ /dev/null @@ -1,40 +0,0 @@ -import assert from 'assert'; - -describe('3dtiles_ion', function _() { - let result; - before(async () => { - result = await loadExample( - 'examples/3dtiles_ion.html', - this.fullTitle(), - ); - }); - - it('should run', async () => { - assert.ok(result); - }); - - it('should pick the globe', async () => { - const layers = await page.evaluate( - () => view.pickObjectsAt({ x: 195, y: 146 }).map(p => p.layer.id), - ); - - assert.ok(layers.indexOf('globe') >= 0); - }); - - it('should pick a building from 3D', async () => { - await page.evaluate(() => { - const lyonExtent = new itowns.Extent('EPSG:4326', 4.85, 4.9, 45.75, 45.77); - itowns.CameraUtils.transformCameraToLookAtTarget(view, view.camera3D, lyonExtent); - }); - await waitUntilItownsIsIdle(this.fullTitle()); - const layers = await page.evaluate( - () => view.pickObjectsAt({ - x: 166, - y: 65, - }) - .map(p => p.layer.id), - ); - - assert.ok(layers.indexOf('3d-tiles-cesium-ion') >= 0); - }); -}); diff --git a/test/functional/3dtiles_pointcloud.js b/test/functional/3dtiles_pointcloud.js deleted file mode 100644 index 4c88639e8d..0000000000 --- a/test/functional/3dtiles_pointcloud.js +++ /dev/null @@ -1,49 +0,0 @@ -import assert from 'assert'; - -describe('3dtiles_pointcloud', function _() { - let result; - before(async () => { - result = await loadExample( - 'examples/3dtiles_pointcloud.html', - this.fullTitle(), - ); - }); - - it('should run', async () => { - assert.ok(result); - }); - - it('should load 3d tile layer', async () => { - const exist = await page.evaluate( - () => view.getLayerById('3d-tiles-sete') != null, - ); - - assert.ok(exist); - }); - - it('should load points', async () => { - const objects = await page.evaluate( - () => { - const res = []; - if (view.scene) { - const objects3d = view.scene; - objects3d.traverse((obj) => { - if (obj.isPoints) { - if (obj.layer) { - res.push(obj.layer.id); - } - } - }); - } - return res; - }); - assert.ok(objects.indexOf('3d-tiles-sete') >= 0); - }); - - it('Add 3dtiles layer with batch table', async () => { - const batchLength = await page.evaluate( - () => view.getLayerById('3d-tiles-sete').root.batchTable.batchLength, - ); - assert.equal(batchLength, 1858); - }); -}); diff --git a/utils/debug/OGC3DTilesDebug.js b/utils/debug/OGC3DTilesDebug.js index 8a9e8c086e..7963f4314c 100644 --- a/utils/debug/OGC3DTilesDebug.js +++ b/utils/debug/OGC3DTilesDebug.js @@ -59,7 +59,6 @@ export default function createOGC3DTilesDebugUI(datDebugTool, view, _3dTileslaye gui.add(_3dTileslayer, 'pntsSizeMode', PNTS_SIZE_MODE).name('Pnts size mode').onChange(() => { view.notifyChange(view.camera.camera3D); }); - gui.add(_3dTileslayer, 'pntsMinAttenuatedSize', 0, 15).name('Min attenuated size').onChange(() => { view.notifyChange(view.camera.camera3D); }); From cbfd1bb6aa28cad66029e019ac4db5bf0e5aa460 Mon Sep 17 00:00:00 2001 From: Vincent JAILLOT Date: Thu, 26 Sep 2024 11:26:53 +0200 Subject: [PATCH 04/33] feat(3dtiles): add deprecation warning to C3DTilesLayer. Use OGC3DTilesLayer instead --- src/Layer/C3DTilesLayer.js | 1 + src/Layer/OGC3DTilesLayer.js | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/Layer/C3DTilesLayer.js b/src/Layer/C3DTilesLayer.js index 83ab6d2f33..63ad49a9d6 100644 --- a/src/Layer/C3DTilesLayer.js +++ b/src/Layer/C3DTilesLayer.js @@ -105,6 +105,7 @@ class C3DTilesLayer extends GeometryLayer { * @param {View} view The view */ constructor(id, config, view) { + console.warn('C3DTilesLayer is deprecated and will be removed in iTowns 3.0 version. Use OGC3DTilesLayer instead.'); super(id, new THREE.Group(), { source: config.source }); this.isC3DTilesLayer = true; this.sseThreshold = config.sseThreshold || 16; diff --git a/src/Layer/OGC3DTilesLayer.js b/src/Layer/OGC3DTilesLayer.js index c284bcee5b..830c5894ed 100644 --- a/src/Layer/OGC3DTilesLayer.js +++ b/src/Layer/OGC3DTilesLayer.js @@ -112,6 +112,13 @@ export function enableKtx2Loader(path, renderer) { class OGC3DTilesLayer extends GeometryLayer { /** * Layer for [3D Tiles](https://www.ogc.org/standard/3dtiles/) datasets. + * + * Advanced configuration note: 3D Tiles rendering is delegated to 3DTilesRendererJS that exposes several + * configuration options accessible through the tilesRenderer property of this class. see the + * [3DTilesRendererJS doc](https://github.com/NASA-AMMOS/3DTilesRendererJS/blob/master/README.md). Also note that + * the cache is shared amongst 3D tiles layers and can be configured through tilesRenderer.lruCache (see the + * [following documentation](https://github.com/NASA-AMMOS/3DTilesRendererJS/blob/master/README.md#lrucache-1). + * * @extends Layer * * @param {String} id - unique layer id. From 25467e5257754b388d0eda677db56463d2ac9085 Mon Sep 17 00:00:00 2001 From: Vincent JAILLOT Date: Wed, 9 Oct 2024 09:53:33 +0200 Subject: [PATCH 05/33] fix(3dtiles): add layer to object returned by OGC3DTilesLayer.pickObjectsAt --- src/Layer/OGC3DTilesLayer.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Layer/OGC3DTilesLayer.js b/src/Layer/OGC3DTilesLayer.js index 830c5894ed..efb1e8fa96 100644 --- a/src/Layer/OGC3DTilesLayer.js +++ b/src/Layer/OGC3DTilesLayer.js @@ -377,7 +377,11 @@ class OGC3DTilesLayer extends GeometryLayer { _raycaster.far = camera.far; _raycaster.firstHitOnly = true; - _raycaster.intersectObject(this.tilesRenderer.group, true, target); + const picked = _raycaster.intersectObject(this.tilesRenderer.group, true); + // Store the layer of the picked object to conform to the interface of what's returned by Picking.js (used for + // other GeometryLayers + picked.forEach((p) => { p.layer = this; }); + target.push(...picked); return target; } From 3d89169de86b394cd05171e9f1679931ea45e0cd Mon Sep 17 00:00:00 2001 From: Vincent JAILLOT Date: Fri, 11 Oct 2024 11:01:47 +0200 Subject: [PATCH 06/33] feat(3dtiles): add tiles-load-start and tiles-load-end events --- src/Layer/OGC3DTilesLayer.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Layer/OGC3DTilesLayer.js b/src/Layer/OGC3DTilesLayer.js index efb1e8fa96..1112cf8675 100644 --- a/src/Layer/OGC3DTilesLayer.js +++ b/src/Layer/OGC3DTilesLayer.js @@ -69,6 +69,18 @@ export const OGC3DTILES_LAYER_EVENTS = { * @property {boolean} visible - the tile visible state */ TILE_VISIBILITY_CHANGE: 'tile-visibility-change', + /** + * Fired when a new batch of tiles start loading (can be fired multiple times, e.g. when the camera moves and new tiles + * start loading) + * @event OGC3DTilesLayer#tiles-load-start + */ + TILES_LOAD_START: 'tiles-load-start', + /** + * Fired when all visible tiles are loaded (can be fired multiple times, e.g. when the camera moves and new tiles + * are loaded) + * @event OGC3DTilesLayer#tiles-load-end + */ + TILES_LOAD_END: 'tiles-load-end', }; /** From 85ce17807651ba5ba3a91215ffd73b1f116f54b1 Mon Sep 17 00:00:00 2001 From: Anthony GULLIENT Date: Fri, 10 Mar 2023 10:55:37 +0100 Subject: [PATCH 07/33] feat(globeControls): zoom on mouse position while using wheel --- src/Controls/GlobeControls.js | 71 +++++++++++++++++++++-------------- src/Controls/StateControl.js | 3 +- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/Controls/GlobeControls.js b/src/Controls/GlobeControls.js index eda23a25ef..12f021b49f 100644 --- a/src/Controls/GlobeControls.js +++ b/src/Controls/GlobeControls.js @@ -159,7 +159,7 @@ let previous; * @param {object} [options] An object with one or more configuration properties. Any property of GlobeControls * can be passed in this object. * @property {number} zoomFactor The factor the scale is multiplied by when dollying (zooming) in or - * divided by when dollying out. Default is 2. + * divided by when dollying out. Default is 1.1. * @property {number} rotateSpeed Speed camera rotation in orbit and panoramic mode. Default is 0.25. * @property {number} minDistance Minimum distance between ground and camera in meters (Perspective Camera only). * Default is 250. @@ -211,7 +211,7 @@ class GlobeControls extends THREE.EventDispatcher { console.warn('Controls zoomSpeed parameter is deprecated. Use zoomFactor instead.'); options.zoomFactor = options.zoomFactor || options.zoomSpeed; } - this.zoomFactor = options.zoomFactor || 1.25; + this.zoomFactor = options.zoomFactor || 1.1; // Limits to how far you can dolly in and out ( PerspectiveCamera only ) this.minDistance = options.minDistance || 250; @@ -268,7 +268,7 @@ class GlobeControls extends THREE.EventDispatcher { this.updateHelper = enableTargetHelper ? (position, helper) => { positionObject(position, helper); view.notifyChange(this.camera); - } : function empty() {}; + } : function empty() { }; this._onEndingMove = null; this._onTravel = this.travel.bind(this); @@ -323,10 +323,10 @@ class GlobeControls extends THREE.EventDispatcher { coordCameraTarget.crs = this.view.referenceCrs; } - get dollyInScale() { + get zoomInScale() { return this.zoomFactor; } - get dollyOutScale() { + get zoomOutScale() { return 1 / this.zoomFactor; } @@ -389,9 +389,10 @@ class GlobeControls extends THREE.EventDispatcher { } } + // For Mobile dolly(delta) { if (delta === 0) { return; } - dollyScale = delta > 0 ? this.dollyInScale : this.dollyOutScale; + dollyScale = delta > 0 ? this.zoomInScale : this.zoomOutScale; if (this.camera.isPerspectiveCamera) { orbitScale /= dollyScale; @@ -454,7 +455,8 @@ class GlobeControls extends THREE.EventDispatcher { quaterPano.setFromAxisAngle(normal, sphericalDelta.theta).multiply(quaterAxis.setFromAxisAngle(axisX, sphericalDelta.phi)); cameraTarget.position.applyQuaternion(quaterPano); this.camera.localToWorld(cameraTarget.position); - break; } + break; + } // ZOOM/ORBIT Move Camera around the target camera default: { // get camera position in local space of target @@ -594,6 +596,7 @@ class GlobeControls extends THREE.EventDispatcher { // Initialize dolly movement. dollyStart.copy(event.viewCoords); + this.view.getPickingPositionFromDepth(event.viewCoords, pickedPosition); // mouse position // Initialize pan movement. panStart.copy(event.viewCoords); @@ -629,11 +632,9 @@ class GlobeControls extends THREE.EventDispatcher { handleDolly(event) { dollyEnd.copy(event.viewCoords); dollyDelta.subVectors(dollyEnd, dollyStart); - - this.dolly(-dollyDelta.y); dollyStart.copy(dollyEnd); - - this.update(); + event.delta = dollyDelta.y; + if (event.delta != 0) { this.handleZoom(event); } } handlePan(event) { @@ -764,23 +765,31 @@ class GlobeControls extends THREE.EventDispatcher { handleZoom(event) { this.player.stop(); CameraUtils.stop(this.view, this.camera); + const zoomScale = event.delta > 0 ? this.zoomInScale : this.zoomOutScale; + let point = event.type === 'dolly' ? pickedPosition : this.view.getPickingPositionFromDepth(event.viewCoords); // get cursor position + let range = this.getRange(); + range *= zoomScale; - this.updateTarget(); - const delta = -event.delta; - this.dolly(delta); + if (point && (range > this.minDistance && range < this.maxDistance)) { // check if the zoom is in the allowed interval + const camPos = xyz.setFromVector3(cameraTarget.position).as('EPSG:4326', c).toVector3(); + point = xyz.setFromVector3(point).as('EPSG:4326', c).toVector3(); - const previousRange = this.getRange(pickedPosition); - this.update(); - const newRange = this.getRange(pickedPosition); - if (Math.abs(newRange - previousRange) / previousRange > 0.001) { - this.dispatchEvent({ - type: CONTROL_EVENTS.RANGE_CHANGED, - previous: previousRange, - new: newRange, - }); + if (camPos.x * point.x < 0) { // Correct rotation at 180th meridian by using 0 <= longitude <=360 for interpolation purpose + if (camPos.x - point.x > 180) { point.x += 360; } else if (point.x - camPos.x > 180) { camPos.x += 360; } + } + point.lerp( // point interpol between mouse cursor and cam pos + camPos, + zoomScale, // interpol factor + ); + + point = c.setFromVector3(point).as('EPSG:4978', xyz); + + return this.lookAtCoordinate({ // update view to the interpolate point + coord: point, + range, + }, + false); } - this.dispatchEvent(this.startEvent); - this.dispatchEvent(this.endEvent); } onTouchStart(event) { @@ -804,7 +813,8 @@ class GlobeControls extends THREE.EventDispatcher { } else { this.state = this.states.NONE; } - break; } + break; + } case this.states.ORBIT: case this.states.DOLLY: { const x = event.touches[0].pageX; @@ -814,7 +824,8 @@ class GlobeControls extends THREE.EventDispatcher { const distance = Math.sqrt(dx * dx + dy * dy); dollyStart.set(0, distance); rotateStart.set(x, y); - break; } + break; + } case this.states.PAN: panStart.set(event.touches[0].pageX, event.touches[0].pageY); break; @@ -851,7 +862,8 @@ class GlobeControls extends THREE.EventDispatcher { } else { this.onTouchEnd(); } - break; } + break; + } case this.states.ORBIT.finger: case this.states.DOLLY.finger: { const gfx = this.view.mainLoop.gfxEngine; @@ -875,7 +887,8 @@ class GlobeControls extends THREE.EventDispatcher { dollyStart.copy(dollyEnd); - break; } + break; + } case this.states.PAN.finger: panEnd.set(event.touches[0].pageX, event.touches[0].pageY); panDelta.subVectors(panEnd, panStart); diff --git a/src/Controls/StateControl.js b/src/Controls/StateControl.js index 8572224c06..bc7c8ebc2f 100644 --- a/src/Controls/StateControl.js +++ b/src/Controls/StateControl.js @@ -395,7 +395,8 @@ class StateControl extends THREE.EventDispatcher { event.preventDefault(); if (this.enabled && this.ZOOM.enable) { - this.dispatchEvent({ type: this.ZOOM._event, delta: event.deltaY }); + viewCoords.copy(this._view.eventToViewCoords(event)); + this.dispatchEvent({ type: this.ZOOM._event, delta: event.deltaY, viewCoords }); } } From 89bbbd8c03ec88ae9f5219e584c29d10451b2a59 Mon Sep 17 00:00:00 2001 From: Anthony GULLIENT Date: Fri, 4 Oct 2024 10:24:28 +0200 Subject: [PATCH 08/33] feat(controls): disabled multi actions when zooming BREAKING CHANGE: disabled multi actions when zooming --- src/Controls/StateControl.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Controls/StateControl.js b/src/Controls/StateControl.js index bc7c8ebc2f..19cb098a5f 100644 --- a/src/Controls/StateControl.js +++ b/src/Controls/StateControl.js @@ -396,6 +396,7 @@ class StateControl extends THREE.EventDispatcher { if (this.enabled && this.ZOOM.enable) { viewCoords.copy(this._view.eventToViewCoords(event)); + this.currentState = this.NONE; this.dispatchEvent({ type: this.ZOOM._event, delta: event.deltaY, viewCoords }); } } From cfb9d0f5184f6d569a3f81101778dd767df9a875 Mon Sep 17 00:00:00 2001 From: alavenant Date: Fri, 11 Oct 2024 14:18:54 +0200 Subject: [PATCH 09/33] feat: add publiccode (#2417) * feat(publiccode): add first version of publiccode * feat(publiccode): add logo in svg --- examples/images/itowns_logo.svg | 123 ++++++++++++++++++++++++++++++++ publiccode.yml | 88 +++++++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 examples/images/itowns_logo.svg create mode 100644 publiccode.yml diff --git a/examples/images/itowns_logo.svg b/examples/images/itowns_logo.svg new file mode 100644 index 0000000000..14d5159d31 --- /dev/null +++ b/examples/images/itowns_logo.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/publiccode.yml b/publiccode.yml new file mode 100644 index 0000000000..32fd38df04 --- /dev/null +++ b/publiccode.yml @@ -0,0 +1,88 @@ +# This repository adheres to the publiccode.yml standard by including this +# metadata file that makes public software easily discoverable. +# More info at https://github.com/italia/publiccode.yml + +publiccodeYmlVersion: '0.2' +name: itowns +url: 'https://github.com/iTowns/itowns.git' +landingURL: 'https://github.com/iTowns/itowns' +softwareVersion: 2.44.2 +releaseDate: '2024-09-17' +softwareType: library +logo: 'https://github.com/iTowns/itowns/blob/master/examples/images/itowns_logo.png' +platforms: + - web +categories: + - data-visualization + - it-development + - geographic-information-systems +usedBy: + - Institut national de l'information géographique et forestière (IGN) +roadmap: 'https://www.itowns-project.org/' +developmentStatus: stable +intendedAudience: + scope: + - science-and-technology + - research + - environment + - government + - defence +description: + en: + documentation: 'https://www.itowns-project.org/' + apiDocumentation: 'https://www.itowns-project.org/itowns/docs/#tutorials/Fundamentals' + shortDescription: Open-source framework designed for web-based visualisation, navigation and interaction with 2D and 3D geospatial data. + longDescription: | + iTowns is a powerful, open-source framework designed for the web-based visualization, navigation, and interaction with both 2D and 3D geospatial data on globes and maps. Built on Open Geospatial Consortium (OGC) standards, it prioritizes interoperability, seamlessly integrating with various geospatial services. The framework supports standard raster and vector data, including aerial imagery, terrain models, and large, heterogeneous 3D datasets like OGC's 3D Tiles. This makes iTowns particularly well-suited for applications in urban planning, environmental monitoring, geospatial asset management and beyond. + + iTowns, based on famous ThreeJS 3D visualization library, it's very extensible and adaptable. Developers can easily extend the framework to support additional open formats, making it a highly customizable solution for a wide range of geospatial projects. Whether you're building GIS tools, visualization platforms, or educational applications, iTowns offers a modular and versatile base. + + iTowns is also a collaborative geographic commons, developed by a diverse community that includes independent developers, public organizations, research institutions, and private companies. This collective effort ensures a constantly evolving platform, open to innovation and adaptable to the unique needs of various geospatial use cases. + features: + - 2D and 3D Geospatial Visualization + - OGC Standards Compliance + - Support for Large Datasets + - Extensibility + - Collaborative Development + screenshots: + - 'https://raw.githubusercontent.com/iTowns/itowns.github.io/master/images/itownsReleaseXS.jpg' + - 'https://camo.githubusercontent.com/337a7f2d1c254e9099b30dc336982518fd6e8c8733c1f29fe669290b05f33905/687474703a2f2f7777772e69746f776e732d70726f6a6563742e6f72672f696d616765732f6d6f6e746167652e6a7067' + - 'https://raw.githubusercontent.com/iTowns/itowns/refs/heads/master/docs/tutorials/images/3DTiles-mesh-b3dm-2.png' + genericName: iTowns +legal: + license: MIT OR CECILL-B + mainCopyrightOwner: IGN and Ciril GROUP + repoOwner: IGN and Ciril GROUP + authorsFile: 'https://github.com/iTowns/itowns/blob/master/CONTRIBUTORS.md' +maintenance: + type: community + contractors: + - name: Institut national de l'information géographique et forestière (IGN) + website: 'https://www.ign.fr/' + until: '2015-09-20' + - name: Ciril GROUP + website: 'https://www.cirilgroup.com' + until: '2022-01-01' + contacts: + - name: Bouillaget Quentin + email: quentin.bouillaget@ign.fr + affiliation: Institut national de l'information géographique et forestière (IGN) + - name: Jaillot Vincent + email: vjaillot@cirilgroup.com + affiliation: Ciril GROUP + - name: Picavet Loïc + email: lpicavet@cirilgroup.com + affiliation: Ciril GROUP + - name: Lavenant Antoine + email: antoine.lavenant@ign.fr + affiliation: Institut national de l'information géographique et forestière (IGN) +localisation: + localisationReady: false + availableLanguages: + - en +dependsOn: + open: + - name: Three.JS + optional: 'no' + - name: proj4js + optional: 'no' From b65d8ae168e5855f1ef0d38b50c6afde69b99a4d Mon Sep 17 00:00:00 2001 From: tebben Date: Thu, 10 Oct 2024 11:26:08 +0200 Subject: [PATCH 10/33] doc(contributors): add Tim Ebben --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 26fd14e0d3..0f352716b1 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -49,6 +49,7 @@ The following people have contributed to iTowns. * [Sogelink](https://www.sogelink.com/) * [Kévin ETOURNEAU](https://github.com/ketourneau) * [Alexis DELFORGES](https://github.com/pourfex) + * [Tim Ebben](https://github.com/tebben) The following organizations are the current maintainers of iTowns: * IGN (http://www.ign.fr) From 4f530259bcdb339ea5fbfe71470330f1c4ffa1bc Mon Sep 17 00:00:00 2001 From: tebben Date: Thu, 10 Oct 2024 11:29:21 +0200 Subject: [PATCH 11/33] fix(source): support urls already containing query parameters for wms, wmts, and wfs --- src/Source/WFSSource.js | 25 ++++++++++++------------- src/Source/WMSSource.js | 29 +++++++++++++++-------------- src/Source/WMTSSource.js | 28 ++++++++++++++-------------- test/unit/dataSourceProvider.js | 10 +++++----- test/unit/source.js | 6 +++--- 5 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/Source/WFSSource.js b/src/Source/WFSSource.js index 60d346c657..73471dbb35 100644 --- a/src/Source/WFSSource.js +++ b/src/Source/WFSSource.js @@ -125,26 +125,25 @@ class WFSSource extends Source { this.typeName = source.typeName; this.version = source.version || '2.0.2'; this.bboxDigits = source.bboxDigits; - - // Add ? at the end of the url if it is not already in the given URL - if (!this.url.endsWith('?')) { - this.url = `${this.url}?`; - } - this.url = `${source.url - }SERVICE=WFS&REQUEST=GetFeature&typeName=${this.typeName - }&VERSION=${this.version - }&SRSNAME=${this.crs - }&outputFormat=${this.format - }&BBOX=%bbox,${this.crs}`; - this.zoom = { min: 0, max: Infinity }; + const urlObj = new URL(source.url); + urlObj.searchParams.set('SERVICE', 'WFS'); + urlObj.searchParams.set('REQUEST', 'GetFeature'); + urlObj.searchParams.set('typeName', this.typeName); + urlObj.searchParams.set('VERSION', this.version); + urlObj.searchParams.set('SRSNAME', this.crs); + urlObj.searchParams.set('outputFormat', this.format); + urlObj.searchParams.set('BBOX', `%bbox,${this.crs}`); + this.vendorSpecific = source.vendorSpecific; for (const name in this.vendorSpecific) { if (Object.prototype.hasOwnProperty.call(this.vendorSpecific, name)) { - this.url = `${this.url}&${name}=${this.vendorSpecific[name]}`; + urlObj.searchParams.set(name, this.vendorSpecific[name]); } } + + this.url = decodeURIComponent(urlObj.toString()); } handlingError(err) { diff --git a/src/Source/WMSSource.js b/src/Source/WMSSource.js index 0f5d9c8645..16050a87bc 100644 --- a/src/Source/WMSSource.js +++ b/src/Source/WMSSource.js @@ -116,26 +116,27 @@ class WMSSource extends Source { const crsPropName = (this.version === '1.3.0') ? 'CRS' : 'SRS'; - // Add ? at the end of the url if it is not already in the given URL - if (!this.url.endsWith('?')) { - this.url = `${this.url}?`; - } - this.url = `${this.url}SERVICE=WMS&REQUEST=GetMap&LAYERS=${ - this.name}&VERSION=${ - this.version}&STYLES=${ - this.style}&FORMAT=${ - this.format}&TRANSPARENT=${ - this.transparent}&BBOX=%bbox&${ - crsPropName}=${ - this.crs}&WIDTH=${this.width}&HEIGHT=${this.height}`; - + const urlObj = new URL(this.url); + urlObj.searchParams.set('SERVICE', 'WMS'); + urlObj.searchParams.set('REQUEST', 'GetMap'); + urlObj.searchParams.set('LAYERS', this.name); + urlObj.searchParams.set('VERSION', this.version); + urlObj.searchParams.set('STYLES', this.style); + urlObj.searchParams.set('FORMAT', this.format); + urlObj.searchParams.set('TRANSPARENT', this.transparent); + urlObj.searchParams.set('BBOX', '%bbox'); + urlObj.searchParams.set(crsPropName, this.crs); + urlObj.searchParams.set('WIDTH', this.width); + urlObj.searchParams.set('HEIGHT', this.height); this.vendorSpecific = source.vendorSpecific; for (const name in this.vendorSpecific) { if (Object.prototype.hasOwnProperty.call(this.vendorSpecific, name)) { - this.url = `${this.url}&${name}=${this.vendorSpecific[name]}`; + urlObj.searchParams.set(name, this.vendorSpecific[name]); } } + + this.url = decodeURIComponent(urlObj.toString()); } urlFromExtent(extentOrTile) { diff --git a/src/Source/WMTSSource.js b/src/Source/WMTSSource.js index 40d0c17685..62ee8ea245 100644 --- a/src/Source/WMTSSource.js +++ b/src/Source/WMTSSource.js @@ -72,26 +72,26 @@ class WMTSSource extends TMSSource { this.isWMTSSource = true; - // Add ? at the end of the url if it is not already in the given URL - if (!this.url.endsWith('?')) { - this.url = `${this.url}?`; - } - this.url = `${this.url}` + - `LAYER=${source.name}` + - `&FORMAT=${this.format}` + - '&SERVICE=WMTS' + - `&VERSION=${source.version || '1.0.0'}` + - '&REQUEST=GetTile' + - `&STYLE=${source.style || 'normal'}` + - `&TILEMATRIXSET=${source.tileMatrixSet}` + - '&TILEMATRIX=%TILEMATRIX&TILEROW=%ROW&TILECOL=%COL'; + const urlObj = new URL(this.url); + urlObj.searchParams.set('LAYER', source.name); + urlObj.searchParams.set('FORMAT', this.format); + urlObj.searchParams.set('SERVICE', 'WMTS'); + urlObj.searchParams.set('VERSION', source.version || '1.0.0'); + urlObj.searchParams.set('REQUEST', 'GetTile'); + urlObj.searchParams.set('STYLE', source.style || 'normal'); + urlObj.searchParams.set('TILEMATRIXSET', source.tileMatrixSet); + urlObj.searchParams.set('TILEMATRIX', '%TILEMATRIX'); + urlObj.searchParams.set('TILEROW', '%ROW'); + urlObj.searchParams.set('TILECOL', '%COL'); this.vendorSpecific = source.vendorSpecific; for (const name in this.vendorSpecific) { if (Object.prototype.hasOwnProperty.call(this.vendorSpecific, name)) { - this.url = `${this.url}&${name}=${this.vendorSpecific[name]}`; + urlObj.searchParams.set(name, this.vendorSpecific[name]); } } + + this.url = decodeURIComponent(urlObj.toString()); } } diff --git a/test/unit/dataSourceProvider.js b/test/unit/dataSourceProvider.js index 93910d6f7a..9ba251069d 100644 --- a/test/unit/dataSourceProvider.js +++ b/test/unit/dataSourceProvider.js @@ -98,7 +98,7 @@ describe('Provide in Sources', function () { }); featureLayer.source = new WFSSource({ - url: 'http://', + url: 'http://domain.com', typeName: 'name', format: 'application/json', extent: globalExtent, @@ -128,7 +128,7 @@ describe('Provide in Sources', function () { it('should get wmts texture with DataSourceProvider', (done) => { colorlayer.source = new WMTSSource({ - url: 'http://', + url: 'http://domain.com', name: 'name', format: 'image/png', tileMatrixSet: 'PM', @@ -159,7 +159,7 @@ describe('Provide in Sources', function () { it('should get wmts texture elevation with DataSourceProvider', (done) => { elevationlayer.source = new WMTSSource({ - url: 'http://', + url: 'http://domain.com', name: 'name', format: 'image/png', tileMatrixSet: 'PM', @@ -188,7 +188,7 @@ describe('Provide in Sources', function () { it('should get wms texture with DataSourceProvider', (done) => { colorlayer.source = new WMSSource({ - url: 'http://', + url: 'http://domain.com', name: 'name', format: 'image/png', extent: globalExtent, @@ -320,7 +320,7 @@ describe('Provide in Sources', function () { it('should get updated RasterLayer', (done) => { colorlayer.source = new WMTSSource({ - url: 'http://', + url: 'http://domain.com', name: 'name', format: 'image/png', tileMatrixSet: 'PM', diff --git a/test/unit/source.js b/test/unit/source.js index 6014e877a3..2a3a404d06 100644 --- a/test/unit/source.js +++ b/test/unit/source.js @@ -44,7 +44,7 @@ describe('Sources', function () { describe('WFSSource', function () { const paramsWFS = { - url: 'http://', + url: 'http://domain.com', typeName: 'test', crs: 'EPSG:4326', }; @@ -93,7 +93,7 @@ describe('Sources', function () { describe('WMTSSource', function () { const paramsWMTS = { - url: 'http://', + url: 'http://domain.com', name: 'name', crs: 'EPSG:4326', tileMatrixSet: 'PM', @@ -141,7 +141,7 @@ describe('Sources', function () { describe('WMSSource', function () { const paramsWMS = { - url: 'http://', + url: 'http://domain.com', name: 'name', extent: [-90, 90, -45, 45], crs: 'EPSG:4326', From 426fe294481ab4ec3e780b72396f5864fcb3b8b0 Mon Sep 17 00:00:00 2001 From: Anthony GULLIENT Date: Tue, 15 Oct 2024 16:06:22 +0200 Subject: [PATCH 12/33] fix(Zoom): use zoom state --- src/Controls/StateControl.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Controls/StateControl.js b/src/Controls/StateControl.js index 19cb098a5f..3d7a15c4a2 100644 --- a/src/Controls/StateControl.js +++ b/src/Controls/StateControl.js @@ -265,7 +265,7 @@ class StateControl extends THREE.EventDispatcher { touchToState(finger) { for (const key of Object.keys(DEFAULT_STATES)) { const state = this[key]; - if (state.enable && finger == state.finger) { + if (state.enable && finger === state.finger) { return state; } } @@ -396,7 +396,7 @@ class StateControl extends THREE.EventDispatcher { if (this.enabled && this.ZOOM.enable) { viewCoords.copy(this._view.eventToViewCoords(event)); - this.currentState = this.NONE; + this.currentState = this.ZOOM; this.dispatchEvent({ type: this.ZOOM._event, delta: event.deltaY, viewCoords }); } } From f8021b4beca2080b77bff1d9ba4d1b5b85013e43 Mon Sep 17 00:00:00 2001 From: Bouillaguet Quentin Date: Fri, 11 Oct 2024 23:16:35 +0200 Subject: [PATCH 13/33] chore(eslint): add no-use-before-define and change max-len rules --- .eslintrc.cjs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index f46e73a90a..095d1ee9c0 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -136,7 +136,14 @@ module.exports = { 'plugin:@typescript-eslint/recommended', ], rules: { - '@stylistic/max-len': ['warn', 80], + '@stylistic/max-len': ['warn', { + code: 100, + comments: 80, + ignoreUrls: true, + }], + // see https://typescript-eslint.io/rules/no-use-before-define/ + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': 'error', 'valid-jsdoc': 'off', 'tsdoc/syntax': 'warn', }, From b991878fb7b8ccd409c6ad53adbfbb398003aca0 Mon Sep 17 00:00:00 2001 From: AnthonyGlt <126568810+AnthonyGlt@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:16:11 +0200 Subject: [PATCH 14/33] fix(OGC3DTilesLayer): handle multiple views (#2435) fix(OGC3DTilesLayer): handle multiple views cache --- src/Core/View.js | 16 +++++++---- src/Layer/OGC3DTilesLayer.js | 53 ++++++++++++++++++++---------------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/Core/View.js b/src/Core/View.js index 044bf6b205..3465ee34ff 100644 --- a/src/Core/View.js +++ b/src/Core/View.js @@ -32,6 +32,7 @@ export const VIEW_EVENTS = { INITIALIZED: 'initialized', COLOR_LAYERS_ORDER_CHANGED, CAMERA_MOVED: 'camera-moved', + DISPOSED: 'disposed', }; /** @@ -119,8 +120,11 @@ const viewers = []; // Size of the camera frustrum, in meters let screenMeters; +let id = 0; + /** - * @property {HTMLElement} domElement - Thhe domElement holding the canvas where the view is displayed + * @property {number} id - The id of the view. It's incremented at each new view instance, starting at 0. + * @property {HTMLElement} domElement - The domElement holding the canvas where the view is displayed * @property {String} referenceCrs - The coordinate reference system of the view * @property {MainLoop} mainLoop - itowns mainloop scheduling the operations * @property {THREE.Scene} scene - threejs scene of the view @@ -145,10 +149,10 @@ class View extends THREE.EventDispatcher { * var view = itowns.View('EPSG:4326', viewerDiv, { camera: { type: itowns.CAMERA_TYPE.ORTHOGRAPHIC } }); * var customControls = itowns.THREE.OrbitControls(view.camera3D, viewerDiv); * - * @param {string} crs - The default CRS of Three.js coordinates. Should be a cartesian CRS. + * @param {String} crs - The default CRS of Three.js coordinates. Should be a cartesian CRS. * @param {HTMLElement} viewerDiv - Where to instanciate the Three.js scene in the DOM * @param {Object} [options] - Optional properties. - * @param {object} [options.camera] - Options for the camera associated to the view. See {@link Camera} options. + * @param {Object} [options.camera] - Options for the camera associated to the view. See {@link Camera} options. * @param {MainLoop} [options.mainLoop] - {@link MainLoop} instance to use, otherwise a default one will be constructed * @param {WebGLRenderer|Object} [options.renderer] - {@link WebGLRenderer} instance to use, otherwise * a default one will be constructed. In this case, if options.renderer is an object, it will be used to @@ -169,6 +173,7 @@ class View extends THREE.EventDispatcher { super(); this.domElement = viewerDiv; + this.id = id++; this.referenceCrs = crs; @@ -303,8 +308,6 @@ class View extends THREE.EventDispatcher { } // remove alls frameRequester this.removeAllFrameRequesters(); - // remove alls events - this.removeAllEvents(); // remove all layers const layers = this.getLayers(l => !l.isTiledGeometryLayer && !l.isAtmosphere); for (const layer of layers) { @@ -321,6 +324,9 @@ class View extends THREE.EventDispatcher { viewers.splice(id, 1); // Remove remaining objects in the scene (e.g. helpers, debug, etc.) this.scene.traverse(ObjectRemovalHelper.cleanup); + this.dispatchEvent({ type: VIEW_EVENTS.DISPOSED }); + // remove alls events + this.removeAllEvents(); } /** diff --git a/src/Layer/OGC3DTilesLayer.js b/src/Layer/OGC3DTilesLayer.js index 1112cf8675..c43be234a3 100644 --- a/src/Layer/OGC3DTilesLayer.js +++ b/src/Layer/OGC3DTilesLayer.js @@ -20,9 +20,15 @@ import PointsMaterial, { PNTS_SIZE_MODE, ClassificationScheme, } from 'Renderer/PointsMaterial'; +import { VIEW_EVENTS } from 'Core/View'; const _raycaster = new THREE.Raycaster(); +// Stores lruCache, downloadQueue and parseQueue for each id of view {@link View} +// every time a tileset has been added +// https://github.com/iTowns/itowns/issues/2426 +const viewers = {}; + // Internal instance of GLTFLoader, passed to 3d-tiles-renderer-js to support GLTF 1.0 and 2.0 // Temporary exported to be used in deprecated B3dmParser export const itownsGLTFLoader = new iGLTFLoader(); @@ -30,11 +36,6 @@ itownsGLTFLoader.register(() => new GLTFMeshFeaturesExtension()); itownsGLTFLoader.register(() => new GLTFStructuralMetadataExtension()); itownsGLTFLoader.register(() => new GLTFCesiumRTCExtension()); -// Instantiated by the first tileset. Used to share cache and download and parse queues between tilesets -let lruCache = null; -let downloadQueue = null; -let parseQueue = null; - export const OGC3DTILES_LAYER_EVENTS = { /** * Fired when a new root or child tile set is loaded @@ -171,9 +172,6 @@ class OGC3DTilesLayer extends GeometryLayer { this.tilesRenderer.manager.addHandler(/\.gltf$/, itownsGLTFLoader); - this._setupCacheAndQueues(); - this._setupEvents(); - this.object3d.add(this.tilesRenderer.group); // Add an initialization step that is resolved when the root tileset is loaded (see this._setup below), meaning @@ -209,25 +207,28 @@ class OGC3DTilesLayer extends GeometryLayer { this.pntsMaxAttenuatedSize = config.pntsMaxAttenuatedSize || 10; } + /** - * Sets the lruCache and download and parse queues so they are shared amongst all tilesets. + * Sets the lruCache and download and parse queues so they are shared amongst + * all tilesets from a same {@link View} view. + * @param {View} view - view associated to this layer. * @private */ - _setupCacheAndQueues() { - if (lruCache === null) { - lruCache = this.tilesRenderer.lruCache; - } else { - this.tilesRenderer.lruCache = lruCache; - } - if (downloadQueue === null) { - downloadQueue = this.tilesRenderer.downloadQueue; + _setupCacheAndQueues(view) { + const id = view.id; + if (viewers[id]) { + this.tilesRenderer.lruCache = viewers[id].lruCache; + this.tilesRenderer.downloadQueue = viewers[id].downloadQueue; + this.tilesRenderer.parseQueue = viewers[id].parseQueue; } else { - this.tilesRenderer.downloadQueue = downloadQueue; - } - if (parseQueue === null) { - parseQueue = this.tilesRenderer.parseQueue; - } else { - this.tilesRenderer.parseQueue = parseQueue; + viewers[id] = { + lruCache: this.tilesRenderer.lruCache, + downloadQueue: this.tilesRenderer.downloadQueue, + parseQueue: this.tilesRenderer.parseQueue, + }; + view.addEventListener(VIEW_EVENTS.DISPOSED, (evt) => { + delete viewers[evt.target.id]; + }); } } @@ -268,6 +269,12 @@ class OGC3DTilesLayer extends GeometryLayer { }); view.notifyChange(this); }); + + + this._setupCacheAndQueues(view); + this._setupEvents(); + + // Start loading tileset and tiles this.tilesRenderer.update(); } From 565ba36b7c969c81040a8e99e1bff2b59fb17d50 Mon Sep 17 00:00:00 2001 From: Vincent JAILLOT Date: Wed, 23 Oct 2024 12:35:50 +0200 Subject: [PATCH 15/33] feat(3dtiles): update 3d-tiles-renderer to 0.3.39 --- package-lock.json | 1265 ++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 1195 insertions(+), 72 deletions(-) diff --git a/package-lock.json b/package-lock.json index a3d630a77d..6cc10e0de1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@mapbox/vector-tile": "^2.0.3", "@tmcw/togeojson": "^5.8.1", "@tweenjs/tween.js": "^25.0.0", - "3d-tiles-renderer": "^0.3.38", + "3d-tiles-renderer": "^0.3.39", "brotli-compress": "^1.3.3", "copc": "^0.0.6", "earcut": "^3.0.0", @@ -83,7 +83,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -127,7 +127,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/highlight": "^7.24.7", @@ -141,7 +141,7 @@ "version": "7.25.4", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -151,7 +151,7 @@ "version": "7.25.2", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -182,7 +182,7 @@ "version": "7.25.6", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/types": "^7.25.6", @@ -225,7 +225,7 @@ "version": "7.25.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.25.2", @@ -313,7 +313,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.24.7", @@ -327,7 +327,7 @@ "version": "7.25.2", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.24.7", @@ -356,11 +356,10 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", - "dev": true, - "license": "MIT", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "devOptional": true, "engines": { "node": ">=6.9.0" } @@ -405,7 +404,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.24.7", @@ -433,7 +432,7 @@ "version": "7.24.8", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -443,7 +442,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -453,7 +452,7 @@ "version": "7.24.8", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -478,7 +477,7 @@ "version": "7.25.6", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/template": "^7.25.0", @@ -492,7 +491,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.24.7", @@ -508,7 +507,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^1.9.0" @@ -521,7 +520,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", @@ -536,7 +535,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "1.1.3" @@ -546,14 +545,14 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8.0" @@ -563,7 +562,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=4" @@ -573,7 +572,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^3.0.0" @@ -586,7 +585,7 @@ "version": "7.25.6", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/types": "^7.25.6" @@ -1633,6 +1632,36 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "optional": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "optional": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-regenerator": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", @@ -2019,7 +2048,7 @@ "version": "7.25.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -2032,7 +2061,7 @@ "version": "7.25.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.24.7", @@ -2047,7 +2076,7 @@ "version": "7.25.6", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.24.7", @@ -2066,7 +2095,7 @@ "version": "7.25.6", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.24.8", @@ -2094,6 +2123,374 @@ "node": ">=10.0.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2396,7 +2793,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -2411,7 +2808,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -2421,7 +2818,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -2431,7 +2828,7 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -2442,14 +2839,14 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2732,6 +3129,288 @@ "node": ">=10" } }, + "node_modules/@react-three/fiber": { + "version": "8.17.10", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.17.10.tgz", + "integrity": "sha512-S6bqa4DqUooEkInYv/W+Jklv2zjSYCXAhm6qKpAQyOXhTEt5gBXnA7W6aoJ0bjmp9pAeaSj/AZUoz1HCSof/uA==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.17.8", + "@types/debounce": "^1.2.1", + "@types/react-reconciler": "^0.26.7", + "@types/webxr": "*", + "base64-js": "^1.5.1", + "buffer": "^6.0.3", + "debounce": "^1.2.1", + "its-fine": "^1.0.6", + "react-reconciler": "^0.27.0", + "scheduler": "^0.21.0", + "suspend-react": "^0.1.3", + "zustand": "^3.7.1" + }, + "peerDependencies": { + "expo": ">=43.0", + "expo-asset": ">=8.4", + "expo-file-system": ">=11.0", + "expo-gl": ">=11.0", + "react": ">=18.0", + "react-dom": ">=18.0", + "react-native": ">=0.64", + "three": ">=0.133" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-asset": { + "optional": true + }, + "expo-file-system": { + "optional": true + }, + "expo-gl": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -2883,6 +3562,47 @@ "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==" }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "optional": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "optional": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "optional": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "optional": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -2925,6 +3645,12 @@ "@types/node": "*" } }, + "node_modules/@types/debounce": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.4.tgz", + "integrity": "sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==", + "optional": true + }, "node_modules/@types/eslint": { "version": "8.56.12", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", @@ -2940,7 +3666,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/express": { @@ -3095,6 +3821,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "optional": true + }, "node_modules/@types/qs": { "version": "6.9.16", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", @@ -3109,6 +3841,25 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "optional": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-reconciler": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", + "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", + "optional": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", @@ -3191,7 +3942,7 @@ "version": "0.5.20", "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.20.tgz", "integrity": "sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/ws": { @@ -3707,14 +4458,108 @@ "license": "Apache-2.0" }, "node_modules/3d-tiles-renderer": { - "version": "0.3.38", - "resolved": "https://registry.npmjs.org/3d-tiles-renderer/-/3d-tiles-renderer-0.3.38.tgz", - "integrity": "sha512-gpfigbT1OeFDmXl8j5ZKdTR9x4wQ/9ZjYGoixEiSrsfW7+w7dwkzGfWORxMsCw+Y8Q9c2HURiz5uyMfpUy43vg==", - "license": "Apache-2.0", + "version": "0.3.39", + "resolved": "https://registry.npmjs.org/3d-tiles-renderer/-/3d-tiles-renderer-0.3.39.tgz", + "integrity": "sha512-Pwxl/dgguyHEtRDsCm+m+jj45HX9zaZDbt+Ajr7qs8R2kEwDae4wvpVDJwMJYObx6iZS68w1RMl3AqcK4okwYA==", + "optionalDependencies": { + "@react-three/fiber": "^8.17.9", + "@vitejs/plugin-react": "^4.3.2", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, "peerDependencies": { "three": ">=0.123.0" } }, + "node_modules/3d-tiles-renderer/node_modules/@types/node": { + "version": "22.7.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz", + "integrity": "sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==", + "optional": true, + "peer": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/3d-tiles-renderer/node_modules/@vitejs/plugin-react": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", + "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", + "optional": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/3d-tiles-renderer/node_modules/vite": { + "version": "5.4.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", + "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", + "optional": true, + "peer": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -3740,7 +4585,7 @@ "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -4501,7 +5346,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -4682,7 +5527,7 @@ "version": "4.23.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "opencollective", @@ -4750,7 +5595,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/bundle-name": { @@ -4865,7 +5710,7 @@ "version": "1.0.30001660", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==", - "dev": true, + "devOptional": true, "funding": [ { "type": "opencollective", @@ -5432,7 +6277,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/cookie": { @@ -5632,6 +6477,12 @@ "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==", "license": "MIT" }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "optional": true + }, "node_modules/dargs": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", @@ -5709,6 +6560,12 @@ "node": "*" } }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "optional": true + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -5958,7 +6815,7 @@ "version": "1.5.25", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.25.tgz", "integrity": "sha512-kMb204zvK3PsSlgvvwzI3wBIcAw15tRkYk+NQdsjdDtcQWTp2RABbMQ9rUBy8KNEOM+/E6ep+XC3AykiWZld4g==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/emoji-regex": { @@ -6195,11 +7052,50 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "hasInstallScript": true, + "optional": true, + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -7215,7 +8111,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -7269,7 +8164,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -7520,7 +8415,7 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=4" @@ -8172,7 +9067,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -8971,6 +9866,27 @@ "node": ">=8" } }, + "node_modules/its-fine": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.5.tgz", + "integrity": "sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==", + "optional": true, + "dependencies": { + "@types/react-reconciler": "^0.28.0" + }, + "peerDependencies": { + "react": ">=18.0" + } + }, + "node_modules/its-fine/node_modules/@types/react-reconciler": { + "version": "0.28.8", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.8.tgz", + "integrity": "sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==", + "optional": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -9070,7 +9986,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -9147,7 +10063,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -9201,7 +10117,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -9459,11 +10375,23 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "optional": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -9924,6 +10852,25 @@ "multicast-dns": "cli.js" } }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "optional": true, + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10005,7 +10952,7 @@ "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/noms": { @@ -10645,7 +11592,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/picomatch": { @@ -10849,6 +11796,35 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "optional": true, + "peer": true, + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11139,6 +12115,65 @@ "node": ">=0.10.0" } }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "optional": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "optional": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-dom/node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "optional": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz", + "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==", + "optional": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.21.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/read-pkg": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", @@ -11380,7 +12415,7 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/regenerator-transform": { @@ -11693,6 +12728,42 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "optional": true, + "peer": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", + "fsevents": "~2.3.2" + } + }, "node_modules/run-applescript": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", @@ -11808,6 +12879,15 @@ "dev": true, "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "optional": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/schema-utils": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", @@ -11890,7 +12970,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -12314,17 +13394,27 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -12632,6 +13722,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "optional": true, + "peerDependencies": { + "react": ">=17.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -12699,7 +13798,7 @@ "version": "5.33.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.33.0.tgz", "integrity": "sha512-JuPVaB7s1gdFKPKTelwUyRq5Sid2A3Gko2S0PncwdBq7kN9Ti9HPWDQ06MPsEDGsZeVESjKEnyGy68quBk1w6g==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -12803,7 +13902,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/test-exclude": { @@ -13024,7 +14123,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=4" @@ -13349,6 +14448,13 @@ "node": "*" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "optional": true, + "peer": true + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", @@ -13427,7 +14533,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "opencollective", @@ -14098,7 +15204,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/yargs": { @@ -14189,6 +15295,23 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "optional": true, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 94c3de89f1..b1ef73b80a 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@mapbox/vector-tile": "^2.0.3", "@tmcw/togeojson": "^5.8.1", "@tweenjs/tween.js": "^25.0.0", - "3d-tiles-renderer": "^0.3.38", + "3d-tiles-renderer": "^0.3.39", "brotli-compress": "^1.3.3", "copc": "^0.0.6", "earcut": "^3.0.0", From 0a098af9b52b3ec8f08ee4675c33f7bc77a87375 Mon Sep 17 00:00:00 2001 From: Vincent JAILLOT Date: Tue, 22 Oct 2024 10:43:17 +0200 Subject: [PATCH 16/33] fix(GlobeView): remove default directional light --- examples/3dtiles_loader.html | 2 +- src/Core/Prefab/GlobeView.js | 5 ----- test/functional/source_file_gpx_3d.js | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/3dtiles_loader.html b/examples/3dtiles_loader.html index 66ac75c62f..176cea5c1b 100644 --- a/examples/3dtiles_loader.html +++ b/examples/3dtiles_loader.html @@ -100,7 +100,7 @@ itowns.enableKtx2Loader('./lib/basis/', view.renderer); // Add ambient light to globally illuminates all objects - const light = new AmbientLight(0x404040, 15); + const light = new AmbientLight(0x404040, 40); view.scene.add(light); // Setup loading screen diff --git a/src/Core/Prefab/GlobeView.js b/src/Core/Prefab/GlobeView.js index fff4033d02..fbe2fd686f 100644 --- a/src/Core/Prefab/GlobeView.js +++ b/src/Core/Prefab/GlobeView.js @@ -96,11 +96,6 @@ class GlobeView extends View { const tileLayer = new GlobeLayer('globe', options.object3d, options); this.mainLoop.gfxEngine.label2dRenderer.infoTileLayer = tileLayer.info; - const sun = new THREE.DirectionalLight(); - sun.position.set(-0.5, 0, 1); - sun.updateMatrixWorld(true); - this.scene.add(sun); - this.addLayer(tileLayer); this.tileLayer = tileLayer; diff --git a/test/functional/source_file_gpx_3d.js b/test/functional/source_file_gpx_3d.js index 6a5fb97ec8..83e70ad19c 100644 --- a/test/functional/source_file_gpx_3d.js +++ b/test/functional/source_file_gpx_3d.js @@ -11,6 +11,6 @@ describe('source_file_gpx_3d', function _() { }); it('should wait for the mesh to be added to the scene', async function _it() { - await page.waitForFunction(() => view.scene.children.length === 5, { timeout: 10000 }); + await page.waitForFunction(() => view.scene.children.length === 4, { timeout: 10000 }); }); }); From 4e7bcd258a30ad1be4cb0c983ba536f3e40b89e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= <45125768+HoloTheDrunk@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:28:40 +0200 Subject: [PATCH 17/33] feat(eslint): remove preference for default export (#2447) Default exports mess with tree shaking and are a source of naming inconsistencies. --- .eslintrc.cjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 095d1ee9c0..0006ab9edd 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -60,6 +60,7 @@ module.exports = { devDependencies: ['test/**', 'tests/**', 'examples/**'], }], */ 'import/no-extraneous-dependencies': 'off', + 'import/prefer-default-export': 'off', // TODO reactivate all the following rules @@ -95,9 +96,9 @@ module.exports = { 'error', 'ignorePackages', { - 'js': 'never', - 'ts': 'never', - 'tsx': 'never', + js: 'never', + ts: 'never', + tsx: 'never', }, ], camelcase: 'off', From f602ac739412df51df50c8dea7c0d23ad69618be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:36:47 +0000 Subject: [PATCH 18/33] chore(deps): bump cookie and express Bumps [cookie](https://github.com/jshttp/cookie) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together. Updates `cookie` from 0.6.0 to 0.7.1 - [Release notes](https://github.com/jshttp/cookie/releases) - [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1) Updates `express` from 4.21.0 to 4.21.1 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.0...4.21.1) --- updated-dependencies: - dependency-name: cookie dependency-type: indirect - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6cc10e0de1..af2abcdce7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6281,11 +6281,10 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7612,18 +7611,17 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, - "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", From 8df42d255828b198f40166b76622e27a32165785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= <45125768+HoloTheDrunk@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:18:10 +0100 Subject: [PATCH 19/33] feat(ci): bump node to next LTS (v22) (#2452) * feat(ci): update node to next LTS (v22) * feat(ci): update to v4 * fix(ci): bump setup-nodes' node version --------- Co-authored-by: ftoromanoff --- .github/workflows/integration.yml | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index c8d65507f8..bcb91e0cb5 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -19,11 +19,11 @@ jobs: steps: # Use specific Node.js version - - uses: actions/checkout@v3 - - name: Use Node.js 18.x - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - name: Use Node.js 22.x + uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x cache: 'npm' # Install packages @@ -84,11 +84,11 @@ jobs: steps: # Use specific Node.js version - - uses: actions/checkout@v3 - - name: Use Node.js 18.x - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - name: Use Node.js 22.x + uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x cache: 'npm' # Install packages @@ -114,11 +114,11 @@ jobs: steps: # Use specific Node.js version - - uses: actions/checkout@v3 - - name: Use Node.js 18.x - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - name: Use Node.js 22.x + uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x cache: 'npm' # Install packages @@ -149,7 +149,7 @@ jobs: contents: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # fetch all branches fetch-depth: 0 @@ -157,9 +157,9 @@ jobs: # Configure git user for later command induced commits - uses: fregante/setup-git-user@v1 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 22.x registry-url: https://registry.npmjs.org/ - run: npm ci @@ -261,12 +261,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Use Node.js 18.x - uses: actions/setup-node@v3 + - name: Use Node.js 22.x + uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x - name: Message commit run: echo "is RELEASE => ${{ github.event.head_commit.message }} !!" From 0499f9540175d8f75bb1476364cd95d70c39be83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ir=C3=A9n=C3=A9e=20Dubourg?= Date: Fri, 8 Nov 2024 17:08:36 +0100 Subject: [PATCH 20/33] fix(wms): take wms 1.1.1 version into account for axis order --- src/Source/WMSSource.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Source/WMSSource.js b/src/Source/WMSSource.js index 16050a87bc..c6217165da 100644 --- a/src/Source/WMSSource.js +++ b/src/Source/WMSSource.js @@ -106,8 +106,8 @@ class WMSSource extends Source { // 4326 (lat/long) axis order depends on the WMS version used if (this.crs == 'EPSG:4326') { // EPSG 4326 x = lat, long = y - // version 1.1.0 long/lat while version 1.3.0 mandates xy (so lat,long) - this.axisOrder = (this.version === '1.1.0' ? 'wsen' : 'swne'); + // version 1.X.X long/lat while version 1.3.0 mandates xy (so lat,long) + this.axisOrder = (this.version === '1.3.0' ? 'swne' : 'wsen'); } else { // xy,xy order this.axisOrder = 'wsen'; From 745ab2c428df74f8ecbce3ae7d0ab8acd2b6035e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ir=C3=A9n=C3=A9e=20Dubourg?= Date: Thu, 7 Nov 2024 17:52:20 +0100 Subject: [PATCH 21/33] fix(xbilparser): apply zmin / zmax for any texture subsampling size --- src/Parser/XbilParser.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Parser/XbilParser.js b/src/Parser/XbilParser.js index b8dccd16fc..5f31cf2edd 100644 --- a/src/Parser/XbilParser.js +++ b/src/Parser/XbilParser.js @@ -64,16 +64,17 @@ export function computeMinMaxElevation(texture, pitch, options) { } } } - // Clamp values to zmin and zmax values configured in ElevationLayer - if (options.zmin != null) { - if (min < options.zmin) { min = options.zmin; } - if (max < options.zmin) { max = options.zmin; } - } + } - if (options.zmax != null) { - if (min > options.zmax) { min = options.zmax; } - if (max > options.zmax) { max = options.zmax; } - } + // Clamp values to zmin and zmax values configured in ElevationLayer + if (options.zmin != null) { + if (min < options.zmin) { min = options.zmin; } + if (max < options.zmin) { max = options.zmin; } + } + + if (options.zmax != null) { + if (min > options.zmax) { min = options.zmax; } + if (max > options.zmax) { max = options.zmax; } } if (max === -Infinity || min === Infinity) { From 452ca7eaf31a4da541252bffea063955d29b8885 Mon Sep 17 00:00:00 2001 From: Kevin ETOURNEAU Date: Tue, 29 Oct 2024 16:44:34 +0100 Subject: [PATCH 22/33] fix(COG): Fix extent in COG parser --- examples/js/plugins/COGParser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/js/plugins/COGParser.js b/examples/js/plugins/COGParser.js index 612598f549..f8d1294c9e 100644 --- a/examples/js/plugins/COGParser.js +++ b/examples/js/plugins/COGParser.js @@ -235,7 +235,7 @@ const COGParser = (function _() { */ parse: async function _(data, options) { const source = options.in; - const tileExtent = options.extent.as(source.crs); + const tileExtent = options.extent.isExtent ? options.extent.as(source.crs) : options.extent.toExtent(source.crs); const level = selectLevel(source, tileExtent, source.tileWidth, source.tileHeight); const viewport = makeWindowFromExtent(source, tileExtent, level.resolution); From 822c63b2ad76ddf23daf20818d76c8b5bd0e6ea2 Mon Sep 17 00:00:00 2001 From: Bastien Guerry Date: Wed, 13 Nov 2024 14:55:52 +0100 Subject: [PATCH 23/33] fix(publiccode.yml): fix the logo URL --- publiccode.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/publiccode.yml b/publiccode.yml index 32fd38df04..06f3dc56e9 100644 --- a/publiccode.yml +++ b/publiccode.yml @@ -9,7 +9,7 @@ landingURL: 'https://github.com/iTowns/itowns' softwareVersion: 2.44.2 releaseDate: '2024-09-17' softwareType: library -logo: 'https://github.com/iTowns/itowns/blob/master/examples/images/itowns_logo.png' +logo: 'https://raw.githubusercontent.com/iTowns/itowns/refs/heads/master/examples/images/itowns_logo.png' platforms: - web categories: From eb73b45c21533a49617c9fdb17a2ee0ca36bc166 Mon Sep 17 00:00:00 2001 From: Bouillaguet Quentin Date: Thu, 17 Oct 2024 19:36:54 +0200 Subject: [PATCH 24/33] fix(babel): include ts files in prerequisites --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b1ef73b80a..3a7a448920 100644 --- a/package.json +++ b/package.json @@ -21,13 +21,13 @@ "base-test-unit": "cross-env BABEL_DISABLE_CACHE=1 mocha --file test/unit/bootstrap.js --import=./config/babel-register/register.mjs", "build": "cross-env NODE_ENV=production webpack", "build-dev": "cross-env NODE_ENV=development webpack", - "transpile": "cross-env BABEL_DISABLE_CACHE=1 babel src --out-dir lib", + "transpile": "cross-env BABEL_DISABLE_CACHE=1 babel src --out-dir lib --extensions .js,.ts", "start": "cross-env NODE_ENV=development webpack serve", "start-https": "cross-env NODE_ENV=development webpack serve --https", "debug": "cross-env noInline=true npm start", "prepublishOnly": "npm run build && npm run transpile", "prepare": "cross-env NO_UPDATE_NOTIFIER=true node ./config/prepare.mjs && node ./config/replace.config.mjs", - "watch": "cross-env BABEL_DISABLE_CACHE=1 babel --watch src --out-dir lib", + "watch": "npm run transpile -- --watch", "changelog": "conventional-changelog -n ./config/conventionalChangelog/config.cjs -i changelog.md -s", "bump": "if [ -z $npm_config_level ]; then grunt bump:minor; else grunt bump:$npm_config_level; fi && npm run changelog && npm install && git add -A && git commit --amend --no-edit", "publish-next": "npm version prerelease --preid next && npm publish --access public --tag=next --provenance", From d884ba684819bb7968f53485a75437c360cc1a82 Mon Sep 17 00:00:00 2001 From: Bouillaguet Quentin Date: Thu, 3 Oct 2024 10:29:31 +0200 Subject: [PATCH 25/33] refacto: migrate Crs to typescript --- src/Core/Geographic/{Crs.js => Crs.ts} | 103 ++++++++++++------------- test/unit/crs.js | 8 ++ 2 files changed, 58 insertions(+), 53 deletions(-) rename src/Core/Geographic/{Crs.js => Crs.ts} (52%) diff --git a/src/Core/Geographic/Crs.js b/src/Core/Geographic/Crs.ts similarity index 52% rename from src/Core/Geographic/Crs.js rename to src/Core/Geographic/Crs.ts index b7c40cfc4f..d1a46f943e 100644 --- a/src/Core/Geographic/Crs.js +++ b/src/Core/Geographic/Crs.ts @@ -2,50 +2,55 @@ import proj4 from 'proj4'; proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'); -function isString(s) { +/** + * A projection as a CRS identifier string. + */ +export type ProjectionLike = string; + +function isString(s: unknown): s is string { return typeof s === 'string' || s instanceof String; } -function mustBeString(crs) { +function mustBeString(crs: string) { if (!isString(crs)) { throw new Error(`Crs parameter value must be a string: '${crs}'`); } } -function isTms(crs) { +function isTms(crs: string) { return isString(crs) && crs.startsWith('TMS'); } -function isEpsg(crs) { +function isEpsg(crs: string) { return isString(crs) && crs.startsWith('EPSG'); } -function formatToTms(crs) { +function formatToTms(crs: string) { mustBeString(crs); - return isTms(crs) ? crs : `TMS:${crs.match(/\d+/)[0]}`; + return isTms(crs) ? crs : `TMS:${crs.match(/\d+/)?.[0]}`; } -function formatToEPSG(crs) { +function formatToEPSG(crs: string) { mustBeString(crs); - return isEpsg(crs) ? crs : `EPSG:${crs.match(/\d+/)[0]}`; + return isEpsg(crs) ? crs : `EPSG:${crs.match(/\d+/)?.[0]}`; } const UNIT = { DEGREE: 1, METER: 2, -}; +} as const; -function is4326(crs) { +function is4326(crs: ProjectionLike) { return crs === 'EPSG:4326'; } -function isGeocentric(crs) { +function isGeocentric(crs: ProjectionLike) { mustBeString(crs); const projection = proj4.defs(crs); return !projection ? false : projection.projName == 'geocent'; } -function _unitFromProj4Unit(projunit) { +function _unitFromProj4Unit(projunit: string) { if (projunit === 'degrees') { return UNIT.DEGREE; } else if (projunit === 'm') { @@ -55,14 +60,14 @@ function _unitFromProj4Unit(projunit) { } } -function toUnit(crs) { +function toUnit(crs: ProjectionLike) { mustBeString(crs); switch (crs) { case 'EPSG:4326' : return UNIT.DEGREE; case 'EPSG:4978' : return UNIT.METER; default: { const p = proj4.defs(formatToEPSG(crs)); - if (!p) { + if (!p?.units) { return undefined; } return _unitFromProj4Unit(p.units); @@ -70,7 +75,7 @@ function toUnit(crs) { } } -function toUnitWithError(crs) { +function toUnitWithError(crs: ProjectionLike) { mustBeString(crs); const u = toUnit(crs); if (u === undefined) { @@ -80,82 +85,74 @@ function toUnitWithError(crs) { } /** - * This module provides basic methods to manipulate a CRS (as a string). - * - * @module CRS + * This module provides basic methods to manipulate a CRS. */ export default { /** * Units that can be used for a CRS. - * - * @enum {number} */ UNIT, /** - * Assert that the CRS is valid one. + * Assert that the CRS is a valid one. * - * @param {string} crs - The CRS to validate. + * @param crs - The CRS to validate. * - * @throws {Error} if the CRS is not valid. + * @throws {@link Error} if the CRS is not valid. */ - isValid(crs) { + isValid(crs: ProjectionLike) { toUnitWithError(crs); }, /** * Assert that the CRS is geographic. * - * @param {string} crs - The CRS to validate. - * @return {boolean} - * @throws {Error} if the CRS is not valid. + * @param crs - The CRS to validate. + * @throws {@link Error} if the CRS is not valid. */ - isGeographic(crs) { + isGeographic(crs: ProjectionLike) { return (toUnitWithError(crs) == UNIT.DEGREE); }, /** * Assert that the CRS is using metric units. * - * @param {string} crs - The CRS to validate. - * @return {boolean} - * @throws {Error} if the CRS is not valid. + * @param crs - The CRS to validate. + * @throws {@link Error} if the CRS is not valid. */ - isMetricUnit(crs) { + isMetricUnit(crs: ProjectionLike) { return (toUnit(crs) == UNIT.METER); }, /** * Get the unit to use with the CRS. * - * @param {string} crs - The CRS to get the unit from. - * @return {number} Either `UNIT.METER`, `UNIT.DEGREE` or `undefined`. + * @param crs - The CRS to get the unit from. + * @returns Either `UNIT.METER`, `UNIT.DEGREE` or `undefined`. */ toUnit, /** - * Is the CRS EPSG:4326 ? + * Is the CRS EPSG:4326? * - * @param {string} crs - The CRS to test. - * @return {boolean} + * @param crs - The CRS to test. */ is4326, /** - * Is the CRS geocentric ? + * Is the CRS geocentric? * if crs isn't defined the method returns false. * - * @param {string} crs - The CRS to test. - * @return {boolean} + * @param crs - The CRS to test. */ isGeocentric, /** * Give a reasonnable epsilon to use with this CRS. * - * @param {string} crs - The CRS to use. - * @return {number} 0.01 if the CRS is EPSG:4326, 0.001 otherwise. + * @param crs - The CRS to use. + * @returns 0.01 if the CRS is EPSG:4326, 0.001 otherwise. */ - reasonnableEpsilon(crs) { + reasonnableEpsilon(crs: ProjectionLike) { if (is4326(crs)) { return 0.01; } else { @@ -163,17 +160,17 @@ export default { } }, /** - * format crs to European Petroleum Survey Group notation : EPSG:XXXX. + * Format crs to European Petroleum Survey Group notation: EPSG:XXXX. * - * @param {string} crs The crs to format - * @return {string} formated crs + * @param crs - The crs to format + * @returns formated crs */ formatToEPSG, /** - * format crs to tile matrix set notation : TMS:XXXX. + * Format crs to tile matrix set notation: TMS:XXXX. * - * @param {string} crs The crs to format - * @return {string} formated crs + * @param crs - The crs to format + * @returns formated crs */ formatToTms, isTms, @@ -183,9 +180,9 @@ export default { /** * Define a proj4 projection as a string and reference. * - * @param {string} code code is the projection's SRS code (only used internally by the Proj4js library) - * @param {string} proj4def is the Proj4 definition string for the projection to use - * @return {undefined} + * @param code - projection's SRS code (only used internally by the Proj4js + * library) + * @param proj4def - Proj4 definition string for the projection to use */ - defs: (code, proj4def) => proj4.defs(code, proj4def), + defs: (code: string, proj4def: string) => proj4.defs(code, proj4def), }; diff --git a/test/unit/crs.js b/test/unit/crs.js index 3e62240ac7..00c1f3a3b1 100644 --- a/test/unit/crs.js +++ b/test/unit/crs.js @@ -34,6 +34,7 @@ describe('CRS assertions', function () { assert.strictEqual(CRS.toUnit('EPSG:7133'), CRS.UNIT.DEGREE); assert.strictEqual(CRS.toUnit('EPSG:4978'), CRS.UNIT.METER); assert.strictEqual(CRS.toUnit('EPSG:3857'), CRS.UNIT.METER); + assert.strictEqual(CRS.toUnit('EPSG:INVALID'), undefined); }); it('should check if the CRS is EPSG:4326', function () { @@ -41,6 +42,13 @@ describe('CRS assertions', function () { assert.ok(!CRS.is4326('EPSG:3857')); }); + it('should assert that the CRS is geocentric', function () { + assert.ok(!CRS.isGeocentric('EPSG:4326')); + assert.ok(!CRS.isGeocentric('EPSG:7133')); + assert.ok(CRS.isGeocentric('EPSG:4978')); + assert.ok(!CRS.isGeocentric('EPSG:3857')); + }); + it('should return a reasonnable epsilon', function () { assert.strictEqual(CRS.reasonnableEpsilon('EPSG:4326'), 0.01); assert.strictEqual(CRS.reasonnableEpsilon('EPSG:3857'), 0.001); From ea397ee7bc816b384fcd3224bd8f2a001245a1fc Mon Sep 17 00:00:00 2001 From: Bouillaguet Quentin Date: Thu, 17 Oct 2024 20:50:52 +0200 Subject: [PATCH 26/33] refacto(Crs): cleanup unit handling --- src/Core/Geographic/Crs.ts | 45 ++++++++++++++++++-------------------- test/unit/crs.js | 2 +- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/Core/Geographic/Crs.ts b/src/Core/Geographic/Crs.ts index d1a46f943e..ecb57a4c75 100644 --- a/src/Core/Geographic/Crs.ts +++ b/src/Core/Geographic/Crs.ts @@ -1,5 +1,7 @@ import proj4 from 'proj4'; +import type { ProjectionDefinition } from 'proj4'; + proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'); /** @@ -50,10 +52,14 @@ function isGeocentric(crs: ProjectionLike) { return !projection ? false : projection.projName == 'geocent'; } -function _unitFromProj4Unit(projunit: string) { - if (projunit === 'degrees') { +function unitFromProj4Unit(proj: ProjectionDefinition) { + if (proj.units === 'degrees') { return UNIT.DEGREE; - } else if (projunit === 'm') { + } else if (proj.units === 'm') { + return UNIT.METER; + } else if (proj.units === undefined && proj.to_meter === undefined) { + // See https://proj.org/en/9.4/usage/projections.html [17/10/2024] + // > The default unit for projected coordinates is the meter. return UNIT.METER; } else { return undefined; @@ -62,26 +68,11 @@ function _unitFromProj4Unit(projunit: string) { function toUnit(crs: ProjectionLike) { mustBeString(crs); - switch (crs) { - case 'EPSG:4326' : return UNIT.DEGREE; - case 'EPSG:4978' : return UNIT.METER; - default: { - const p = proj4.defs(formatToEPSG(crs)); - if (!p?.units) { - return undefined; - } - return _unitFromProj4Unit(p.units); - } - } -} - -function toUnitWithError(crs: ProjectionLike) { - mustBeString(crs); - const u = toUnit(crs); - if (u === undefined) { - throw new Error(`No unit found for crs: '${crs}'`); + const p = proj4.defs(formatToEPSG(crs)); + if (!p) { + return undefined; } - return u; + return unitFromProj4Unit(p); } /** @@ -101,7 +92,13 @@ export default { * @throws {@link Error} if the CRS is not valid. */ isValid(crs: ProjectionLike) { - toUnitWithError(crs); + const proj = proj4.defs(crs); + if (!proj) { + throw new Error(`Undefined crs '${crs}'. Add it with proj4.defs('${crs}', string)`); + } + if (!unitFromProj4Unit(proj)) { + throw new Error(`No valid unit found for crs '${crs}', found ${proj.units}`); + } }, /** @@ -111,7 +108,7 @@ export default { * @throws {@link Error} if the CRS is not valid. */ isGeographic(crs: ProjectionLike) { - return (toUnitWithError(crs) == UNIT.DEGREE); + return (toUnit(crs) == UNIT.DEGREE); }, /** diff --git a/test/unit/crs.js b/test/unit/crs.js index 00c1f3a3b1..bb754de8e3 100644 --- a/test/unit/crs.js +++ b/test/unit/crs.js @@ -3,7 +3,7 @@ import proj4 from 'proj4'; import CRS from 'Core/Geographic/Crs'; proj4.defs('EPSG:7133', '+proj=longlat +ellps=GRS80 +no_defs +units=degrees'); -proj4.defs('EPSG:INVALID', '+no_defs'); +proj4.defs('EPSG:INVALID', '+units=invalid +no_defs'); describe('CRS assertions', function () { it('should assert that the CRS is valid', function () { From fca5a29b62e07d9706e55c2a74dc183ca290e26a Mon Sep 17 00:00:00 2001 From: Bouillaguet Quentin Date: Fri, 15 Nov 2024 16:57:23 +0100 Subject: [PATCH 27/33] refacto(Crs): use named exports instead of default export --- src/Converter/Feature2Mesh.js | 2 +- src/Converter/textureConverter.js | 2 +- src/Core/Feature.js | 2 +- src/Core/Geographic/Coordinates.js | 2 +- src/Core/Geographic/Crs.ts | 224 ++++++++++++-------------- src/Core/Geographic/Extent.js | 2 +- src/Core/Geographic/GeoidGrid.js | 2 +- src/Core/Prefab/Globe/GlobeLayer.js | 2 +- src/Core/Prefab/GlobeView.js | 3 +- src/Core/Prefab/Planar/PlanarLayer.js | 2 +- src/Core/Tile/Tile.js | 2 +- src/Core/Tile/TileGrid.js | 2 +- src/Core/TileMesh.js | 2 +- src/Core/View.js | 2 +- src/Main.js | 2 +- src/Renderer/OBB.js | 2 +- src/Renderer/RasterTile.js | 2 +- src/Source/FileSource.js | 2 +- src/Source/Source.js | 2 +- src/Source/TMSSource.js | 2 +- src/Source/WFSSource.js | 2 +- test/unit/crs.js | 2 +- 22 files changed, 127 insertions(+), 140 deletions(-) diff --git a/src/Converter/Feature2Mesh.js b/src/Converter/Feature2Mesh.js index 27135ba50f..d3d4905ec9 100644 --- a/src/Converter/Feature2Mesh.js +++ b/src/Converter/Feature2Mesh.js @@ -1,10 +1,10 @@ import * as THREE from 'three'; +import * as Crs from 'Core/Geographic/Crs'; import Earcut from 'earcut'; import { FEATURE_TYPES } from 'Core/Feature'; import ReferLayerProperties from 'Layer/ReferencingLayerProperties'; import { deprecatedFeature2MeshOptions } from 'Core/Deprecated/Undeprecator'; import Extent from 'Core/Geographic/Extent'; -import Crs from 'Core/Geographic/Crs'; import OrientationUtils from 'Utils/OrientationUtils'; import Coordinates from 'Core/Geographic/Coordinates'; import Style, { StyleContext } from 'Core/Style'; diff --git a/src/Converter/textureConverter.js b/src/Converter/textureConverter.js index 41f103620f..9db079146e 100644 --- a/src/Converter/textureConverter.js +++ b/src/Converter/textureConverter.js @@ -1,7 +1,7 @@ import * as THREE from 'three'; +import * as CRS from 'Core/Geographic/Crs'; import Feature2Texture from 'Converter/Feature2Texture'; import Extent from 'Core/Geographic/Extent'; -import CRS from 'Core/Geographic/Crs'; const extentTexture = new Extent('EPSG:4326', [0, 0, 0, 0]); diff --git a/src/Core/Feature.js b/src/Core/Feature.js index 82980a747d..0e250a533b 100644 --- a/src/Core/Feature.js +++ b/src/Core/Feature.js @@ -1,7 +1,7 @@ import * as THREE from 'three'; +import * as CRS from 'Core/Geographic/Crs'; import Extent from 'Core/Geographic/Extent'; import Coordinates from 'Core/Geographic/Coordinates'; -import CRS from 'Core/Geographic/Crs'; import Style from 'Core/Style'; function defaultExtent(crs) { diff --git a/src/Core/Geographic/Coordinates.js b/src/Core/Geographic/Coordinates.js index 8fec118a7a..d0934066ea 100644 --- a/src/Core/Geographic/Coordinates.js +++ b/src/Core/Geographic/Coordinates.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; import proj4 from 'proj4'; -import CRS from 'Core/Geographic/Crs'; +import * as CRS from 'Core/Geographic/Crs'; import Ellipsoid from 'Core/Math/Ellipsoid'; proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'); diff --git a/src/Core/Geographic/Crs.ts b/src/Core/Geographic/Crs.ts index ecb57a4c75..b5513d2ce2 100644 --- a/src/Core/Geographic/Crs.ts +++ b/src/Core/Geographic/Crs.ts @@ -19,37 +19,54 @@ function mustBeString(crs: string) { } } -function isTms(crs: string) { +export function isTms(crs: string) { return isString(crs) && crs.startsWith('TMS'); } -function isEpsg(crs: string) { +export function isEpsg(crs: string) { return isString(crs) && crs.startsWith('EPSG'); } -function formatToTms(crs: string) { +/** + * Format crs to tile matrix set notation: TMS:XXXX. + * + * @param crs - The crs to format + * @returns formated crs + */ +export function formatToTms(crs: string) { mustBeString(crs); return isTms(crs) ? crs : `TMS:${crs.match(/\d+/)?.[0]}`; } -function formatToEPSG(crs: string) { +/** + * Format crs to European Petroleum Survey Group notation: EPSG:XXXX. + * + * @param crs - The crs to format + * @returns formated crs + */ +export function formatToEPSG(crs: string) { mustBeString(crs); return isEpsg(crs) ? crs : `EPSG:${crs.match(/\d+/)?.[0]}`; } -const UNIT = { +/** + * Units that can be used for a CRS. + */ +export const UNIT = { DEGREE: 1, METER: 2, } as const; -function is4326(crs: ProjectionLike) { - return crs === 'EPSG:4326'; -} +export const tms_3857 = 'TMS:3857'; +export const tms_4326 = 'TMS:4326'; -function isGeocentric(crs: ProjectionLike) { - mustBeString(crs); - const projection = proj4.defs(crs); - return !projection ? false : projection.projName == 'geocent'; +/** + * Is the CRS EPSG:4326? + * + * @param crs - The CRS to test. + */ +export function is4326(crs: ProjectionLike) { + return crs === 'EPSG:4326'; } function unitFromProj4Unit(proj: ProjectionDefinition) { @@ -66,7 +83,13 @@ function unitFromProj4Unit(proj: ProjectionDefinition) { } } -function toUnit(crs: ProjectionLike) { +/** + * Get the unit to use with the CRS. + * + * @param crs - The CRS to get the unit from. + * @returns Either `UNIT.METER`, `UNIT.DEGREE` or `undefined`. + */ +export function toUnit(crs: ProjectionLike) { mustBeString(crs); const p = proj4.defs(formatToEPSG(crs)); if (!p) { @@ -76,110 +99,73 @@ function toUnit(crs: ProjectionLike) { } /** - * This module provides basic methods to manipulate a CRS. + * Assert that the CRS is using metric units. + * + * @param crs - The CRS to validate. + * @throws {@link Error} if the CRS is not valid. + */ +export function isMetricUnit(crs: ProjectionLike) { + return (toUnit(crs) == UNIT.METER); +} + +/** + * Assert that the CRS is geographic. + * + * @param crs - The CRS to validate. + * @throws {@link Error} if the CRS is not valid. + */ +export function isGeographic(crs: ProjectionLike) { + return (toUnit(crs) == UNIT.DEGREE); +} + +/** + * Is the CRS geocentric? + * if crs isn't defined the method returns false. + * + * @param crs - The CRS to test. + */ +export function isGeocentric(crs: ProjectionLike) { + mustBeString(crs); + const projection = proj4.defs(crs); + return !projection ? false : projection.projName == 'geocent'; +} + +/** + * Assert that the CRS is a valid one. + * + * @param crs - The CRS to validate. + * + * @throws {@link Error} if the CRS is not valid. + */ +export function isValid(crs: ProjectionLike) { + const proj = proj4.defs(crs); + if (!proj) { + throw new Error(`Undefined crs '${crs}'. Add it with proj4.defs('${crs}', string)`); + } + if (!unitFromProj4Unit(proj)) { + throw new Error(`No valid unit found for crs '${crs}', found ${proj.units}`); + } +} + +/** + * Give a reasonnable epsilon to use with this CRS. + * + * @param crs - The CRS to use. + * @returns 0.01 if the CRS is EPSG:4326, 0.001 otherwise. + */ +export function reasonnableEpsilon(crs: ProjectionLike) { + if (is4326(crs)) { + return 0.01; + } else { + return 0.001; + } +} + +/** + * Define a proj4 projection as a string and reference. + * + * @param code - projection's SRS code (only used internally by the Proj4js + * library) + * @param proj4def - Proj4 definition string for the projection to use */ -export default { - /** - * Units that can be used for a CRS. - */ - UNIT, - - /** - * Assert that the CRS is a valid one. - * - * @param crs - The CRS to validate. - * - * @throws {@link Error} if the CRS is not valid. - */ - isValid(crs: ProjectionLike) { - const proj = proj4.defs(crs); - if (!proj) { - throw new Error(`Undefined crs '${crs}'. Add it with proj4.defs('${crs}', string)`); - } - if (!unitFromProj4Unit(proj)) { - throw new Error(`No valid unit found for crs '${crs}', found ${proj.units}`); - } - }, - - /** - * Assert that the CRS is geographic. - * - * @param crs - The CRS to validate. - * @throws {@link Error} if the CRS is not valid. - */ - isGeographic(crs: ProjectionLike) { - return (toUnit(crs) == UNIT.DEGREE); - }, - - /** - * Assert that the CRS is using metric units. - * - * @param crs - The CRS to validate. - * @throws {@link Error} if the CRS is not valid. - */ - isMetricUnit(crs: ProjectionLike) { - return (toUnit(crs) == UNIT.METER); - }, - - /** - * Get the unit to use with the CRS. - * - * @param crs - The CRS to get the unit from. - * @returns Either `UNIT.METER`, `UNIT.DEGREE` or `undefined`. - */ - toUnit, - - /** - * Is the CRS EPSG:4326? - * - * @param crs - The CRS to test. - */ - is4326, - /** - * Is the CRS geocentric? - * if crs isn't defined the method returns false. - * - * @param crs - The CRS to test. - */ - isGeocentric, - - /** - * Give a reasonnable epsilon to use with this CRS. - * - * @param crs - The CRS to use. - * @returns 0.01 if the CRS is EPSG:4326, 0.001 otherwise. - */ - reasonnableEpsilon(crs: ProjectionLike) { - if (is4326(crs)) { - return 0.01; - } else { - return 0.001; - } - }, - /** - * Format crs to European Petroleum Survey Group notation: EPSG:XXXX. - * - * @param crs - The crs to format - * @returns formated crs - */ - formatToEPSG, - /** - * Format crs to tile matrix set notation: TMS:XXXX. - * - * @param crs - The crs to format - * @returns formated crs - */ - formatToTms, - isTms, - isEpsg, - tms_3857: 'TMS:3857', - tms_4326: 'TMS:4326', - /** - * Define a proj4 projection as a string and reference. - * - * @param code - projection's SRS code (only used internally by the Proj4js - * library) - * @param proj4def - Proj4 definition string for the projection to use - */ - defs: (code: string, proj4def: string) => proj4.defs(code, proj4def), -}; +export const defs = (code: string, proj4def: string) => proj4.defs(code, proj4def); diff --git a/src/Core/Geographic/Extent.js b/src/Core/Geographic/Extent.js index f1ab0cd0dd..0fa844dfdb 100644 --- a/src/Core/Geographic/Extent.js +++ b/src/Core/Geographic/Extent.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; +import * as CRS from './Crs'; import Coordinates from './Coordinates'; -import CRS from './Crs'; /** * Extent is a SIG-area (so 2D) diff --git a/src/Core/Geographic/GeoidGrid.js b/src/Core/Geographic/GeoidGrid.js index 2d6fdd8819..1d5d3cbfa7 100644 --- a/src/Core/Geographic/GeoidGrid.js +++ b/src/Core/Geographic/GeoidGrid.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; +import * as CRS from 'Core/Geographic/Crs'; import Coordinates from 'Core/Geographic/Coordinates'; -import CRS from 'Core/Geographic/Crs'; const coord = new Coordinates('EPSG:4326'); diff --git a/src/Core/Prefab/Globe/GlobeLayer.js b/src/Core/Prefab/Globe/GlobeLayer.js index abc0f44ee1..629b085e59 100644 --- a/src/Core/Prefab/Globe/GlobeLayer.js +++ b/src/Core/Prefab/Globe/GlobeLayer.js @@ -1,9 +1,9 @@ import * as THREE from 'three'; +import * as CRS from 'Core/Geographic/Crs'; import TiledGeometryLayer from 'Layer/TiledGeometryLayer'; import { ellipsoidSizes } from 'Core/Math/Ellipsoid'; import { globalExtentTMS, schemeTiles } from 'Core/Tile/TileGrid'; import BuilderEllipsoidTile from 'Core/Prefab/Globe/BuilderEllipsoidTile'; -import CRS from 'Core/Geographic/Crs'; // matrix to convert sphere to ellipsoid const worldToScaledEllipsoid = new THREE.Matrix4(); diff --git a/src/Core/Prefab/GlobeView.js b/src/Core/Prefab/GlobeView.js index fbe2fd686f..ce70a36ebb 100644 --- a/src/Core/Prefab/GlobeView.js +++ b/src/Core/Prefab/GlobeView.js @@ -1,5 +1,7 @@ import * as THREE from 'three'; +import * as CRS from 'Core/Geographic/Crs'; + import View, { VIEW_EVENTS } from 'Core/View'; import GlobeControls from 'Controls/GlobeControls'; import Coordinates from 'Core/Geographic/Coordinates'; @@ -8,7 +10,6 @@ import GlobeLayer from 'Core/Prefab/Globe/GlobeLayer'; import Atmosphere from 'Core/Prefab/Globe/Atmosphere'; import CameraUtils from 'Utils/CameraUtils'; -import CRS from 'Core/Geographic/Crs'; import { ellipsoidSizes } from 'Core/Math/Ellipsoid'; /** diff --git a/src/Core/Prefab/Planar/PlanarLayer.js b/src/Core/Prefab/Planar/PlanarLayer.js index 0bee5987a8..d1fbb9f7d6 100644 --- a/src/Core/Prefab/Planar/PlanarLayer.js +++ b/src/Core/Prefab/Planar/PlanarLayer.js @@ -1,8 +1,8 @@ import * as THREE from 'three'; +import * as CRS from 'Core/Geographic/Crs'; import TiledGeometryLayer from 'Layer/TiledGeometryLayer'; import { globalExtentTMS } from 'Core/Tile/TileGrid'; -import CRS from 'Core/Geographic/Crs'; import PlanarTileBuilder from './PlanarTileBuilder'; /** diff --git a/src/Core/Tile/Tile.js b/src/Core/Tile/Tile.js index 1bd053a122..bda353c21f 100644 --- a/src/Core/Tile/Tile.js +++ b/src/Core/Tile/Tile.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; +import * as CRS from '../Geographic/Crs'; import Coordinates from '../Geographic/Coordinates'; -import CRS from '../Geographic/Crs'; import Extent from '../Geographic/Extent'; import { getInfoTms, getCountTiles } from './TileGrid'; diff --git a/src/Core/Tile/TileGrid.js b/src/Core/Tile/TileGrid.js index 063b3c91ce..0a60f8d586 100644 --- a/src/Core/Tile/TileGrid.js +++ b/src/Core/Tile/TileGrid.js @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import CRS from '../Geographic/Crs'; +import * as CRS from '../Geographic/Crs'; import Extent from '../Geographic/Extent'; const _countTiles = new THREE.Vector2(); diff --git a/src/Core/TileMesh.js b/src/Core/TileMesh.js index 675a2822bf..a647ee6f97 100644 --- a/src/Core/TileMesh.js +++ b/src/Core/TileMesh.js @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import CRS from 'Core/Geographic/Crs'; +import * as CRS from 'Core/Geographic/Crs'; import { geoidLayerIsVisible } from 'Layer/GeoidLayer'; import { tiledCovering } from 'Core/Tile/Tile'; diff --git a/src/Core/View.js b/src/Core/View.js index 3465ee34ff..71a00bdd45 100644 --- a/src/Core/View.js +++ b/src/Core/View.js @@ -1,4 +1,5 @@ import * as THREE from 'three'; +import * as CRS from 'Core/Geographic/Crs'; import Camera from 'Renderer/Camera'; import initializeWebXR from 'Renderer/WebXR'; import MainLoop, { MAIN_LOOP_EVENTS, RENDERING_PAUSED } from 'Core/MainLoop'; @@ -6,7 +7,6 @@ import Capabilities from 'Core/System/Capabilities'; import { COLOR_LAYERS_ORDER_CHANGED } from 'Renderer/ColorLayersOrdering'; import c3DEngine from 'Renderer/c3DEngine'; import RenderMode from 'Renderer/RenderMode'; -import CRS from 'Core/Geographic/Crs'; import Coordinates from 'Core/Geographic/Coordinates'; import FeaturesUtils from 'Utils/FeaturesUtils'; import { getMaxColorSamplerUnitsCount } from 'Renderer/LayeredMaterial'; diff --git a/src/Main.js b/src/Main.js index fd2f7c3f6b..3b5d49c448 100644 --- a/src/Main.js +++ b/src/Main.js @@ -7,7 +7,7 @@ export const REVISION = conf.version; export { default as Extent } from 'Core/Geographic/Extent'; export { default as Coordinates } from 'Core/Geographic/Coordinates'; export { default as GeoidGrid } from 'Core/Geographic/GeoidGrid'; -export { default as CRS } from 'Core/Geographic/Crs'; +export * as CRS from 'Core/Geographic/Crs'; export { default as Ellipsoid, ellipsoidSizes } from 'Core/Math/Ellipsoid'; export { default as GlobeView, GLOBE_VIEW_EVENTS } from 'Core/Prefab/GlobeView'; diff --git a/src/Renderer/OBB.js b/src/Renderer/OBB.js index bd4c0a9974..6b00000986 100644 --- a/src/Renderer/OBB.js +++ b/src/Renderer/OBB.js @@ -1,8 +1,8 @@ import * as THREE from 'three'; +import * as CRS from 'Core/Geographic/Crs'; import TileGeometry from 'Core/TileGeometry'; import BuilderEllipsoidTile from 'Core/Prefab/Globe/BuilderEllipsoidTile'; import Coordinates from 'Core/Geographic/Coordinates'; -import CRS from 'Core/Geographic/Crs'; // get oriented bounding box of tile const builder = new BuilderEllipsoidTile({ crs: 'EPSG:4978', uvCount: 1 }); diff --git a/src/Renderer/RasterTile.js b/src/Renderer/RasterTile.js index 05e936cddc..a218be7b8b 100644 --- a/src/Renderer/RasterTile.js +++ b/src/Renderer/RasterTile.js @@ -1,7 +1,7 @@ import * as THREE from 'three'; +import * as CRS from 'Core/Geographic/Crs'; import { ELEVATION_MODES } from 'Renderer/LayeredMaterial'; import { checkNodeElevationTextureValidity, insertSignificantValuesFromParent, computeMinMaxElevation } from 'Parser/XbilParser'; -import CRS from 'Core/Geographic/Crs'; export const EMPTY_TEXTURE_ZOOM = -1; diff --git a/src/Source/FileSource.js b/src/Source/FileSource.js index b4dc474add..d5d7eda6bf 100644 --- a/src/Source/FileSource.js +++ b/src/Source/FileSource.js @@ -1,6 +1,6 @@ +import * as CRS from 'Core/Geographic/Crs'; import Source from 'Source/Source'; import Cache from 'Core/Scheduler/Cache'; -import CRS from 'Core/Geographic/Crs'; /** * An object defining the source of a single resource to get from a direct diff --git a/src/Source/Source.js b/src/Source/Source.js index d942276b7f..491e2de0c6 100644 --- a/src/Source/Source.js +++ b/src/Source/Source.js @@ -1,3 +1,4 @@ +import * as CRS from 'Core/Geographic/Crs'; import Extent from 'Core/Geographic/Extent'; import GeoJsonParser from 'Parser/GeoJsonParser'; import KMLParser from 'Parser/KMLParser'; @@ -8,7 +9,6 @@ import ISGParser from 'Parser/ISGParser'; import VectorTileParser from 'Parser/VectorTileParser'; import Fetcher from 'Provider/Fetcher'; import Cache from 'Core/Scheduler/Cache'; -import CRS from 'Core/Geographic/Crs'; /** @private */ export const supportedParsers = new Map([ diff --git a/src/Source/TMSSource.js b/src/Source/TMSSource.js index dbb51c1814..2359b98363 100644 --- a/src/Source/TMSSource.js +++ b/src/Source/TMSSource.js @@ -1,9 +1,9 @@ +import * as CRS from 'Core/Geographic/Crs'; import Source from 'Source/Source'; import URLBuilder from 'Provider/URLBuilder'; import Extent from 'Core/Geographic/Extent'; import Tile from 'Core/Tile/Tile'; import { globalExtentTMS } from 'Core/Tile/TileGrid'; -import CRS from 'Core/Geographic/Crs'; const _tile = new Tile(CRS.tms_4326, 0, 0, 0); diff --git a/src/Source/WFSSource.js b/src/Source/WFSSource.js index 73471dbb35..4f68286453 100644 --- a/src/Source/WFSSource.js +++ b/src/Source/WFSSource.js @@ -1,6 +1,6 @@ +import * as CRS from 'Core/Geographic/Crs'; import Source from 'Source/Source'; import URLBuilder from 'Provider/URLBuilder'; -import CRS from 'Core/Geographic/Crs'; import Extent from 'Core/Geographic/Extent'; const _extent = new Extent('EPSG:4326', [0, 0, 0, 0]); diff --git a/test/unit/crs.js b/test/unit/crs.js index bb754de8e3..f96d1d32d3 100644 --- a/test/unit/crs.js +++ b/test/unit/crs.js @@ -1,6 +1,6 @@ import assert from 'assert'; import proj4 from 'proj4'; -import CRS from 'Core/Geographic/Crs'; +import * as CRS from 'Core/Geographic/Crs'; proj4.defs('EPSG:7133', '+proj=longlat +ellps=GRS80 +no_defs +units=degrees'); proj4.defs('EPSG:INVALID', '+units=invalid +no_defs'); From d467a290aa4bbb21fb0ba6d27b793876e494100b Mon Sep 17 00:00:00 2001 From: Bouillaguet Quentin Date: Sat, 16 Nov 2024 10:25:02 +0100 Subject: [PATCH 28/33] chore(Crs): update and refine documentation --- src/Core/Geographic/Crs.ts | 83 ++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/src/Core/Geographic/Crs.ts b/src/Core/Geographic/Crs.ts index b5513d2ce2..311967dc35 100644 --- a/src/Core/Geographic/Crs.ts +++ b/src/Core/Geographic/Crs.ts @@ -5,7 +5,9 @@ import type { ProjectionDefinition } from 'proj4'; proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'); /** - * A projection as a CRS identifier string. + * A projection as a CRS identifier string. This identifier references a + * projection definition previously defined with + * [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections). */ export type ProjectionLike = string; @@ -19,19 +21,31 @@ function mustBeString(crs: string) { } } +/** + * Checks that a given CRS identifier follows the TMS:XXXX format. + * @internal + * + * @param crs - The CRS identifier string. + */ export function isTms(crs: string) { return isString(crs) && crs.startsWith('TMS'); } +/** + * Checks that a given CRS identifier follows the EPSG:XXXX format. + * @internal + * + * @param crs - The CRS identifier string. + */ export function isEpsg(crs: string) { return isString(crs) && crs.startsWith('EPSG'); } /** - * Format crs to tile matrix set notation: TMS:XXXX. + * Converts the input to the tile matrix set notation: TMS:XXXX. + * @internal * - * @param crs - The crs to format - * @returns formated crs + * @param crs - The input to format. */ export function formatToTms(crs: string) { mustBeString(crs); @@ -39,10 +53,10 @@ export function formatToTms(crs: string) { } /** - * Format crs to European Petroleum Survey Group notation: EPSG:XXXX. + * Converts the input to European Petroleum Survey Group notation: EPSG:XXXX. + * @internal * - * @param crs - The crs to format - * @returns formated crs + * @param crs - The input to format. */ export function formatToEPSG(crs: string) { mustBeString(crs); @@ -50,18 +64,33 @@ export function formatToEPSG(crs: string) { } /** - * Units that can be used for a CRS. + * System units supported for a coordinate system. See + * [proj](https://proj4.org/en/9.5/operations/conversions/unitconvert.html#angular-units). + * Note that only degree and meters units are supported for now. */ export const UNIT = { + /** + * Angular unit in degree. + */ DEGREE: 1, + /** + * Distance unit in meter. + */ METER: 2, } as const; +/** + * @internal + */ export const tms_3857 = 'TMS:3857'; +/** + * @internal + */ export const tms_4326 = 'TMS:4326'; /** - * Is the CRS EPSG:4326? + * Checks that the CRS is EPSG:4326. + * @internal * * @param crs - The CRS to test. */ @@ -84,9 +113,9 @@ function unitFromProj4Unit(proj: ProjectionDefinition) { } /** - * Get the unit to use with the CRS. + * Returns the horizontal coordinates system units associated with this CRS. * - * @param crs - The CRS to get the unit from. + * @param crs - The CRS to extract the unit from. * @returns Either `UNIT.METER`, `UNIT.DEGREE` or `undefined`. */ export function toUnit(crs: ProjectionLike) { @@ -99,9 +128,9 @@ export function toUnit(crs: ProjectionLike) { } /** - * Assert that the CRS is using metric units. + * Asserts that the CRS is using metric units. * - * @param crs - The CRS to validate. + * @param crs - The CRS to check. * @throws {@link Error} if the CRS is not valid. */ export function isMetricUnit(crs: ProjectionLike) { @@ -109,9 +138,9 @@ export function isMetricUnit(crs: ProjectionLike) { } /** - * Assert that the CRS is geographic. + * Asserts that the CRS is geographic. * - * @param crs - The CRS to validate. + * @param crs - The CRS to check. * @throws {@link Error} if the CRS is not valid. */ export function isGeographic(crs: ProjectionLike) { @@ -119,10 +148,10 @@ export function isGeographic(crs: ProjectionLike) { } /** - * Is the CRS geocentric? - * if crs isn't defined the method returns false. + * Asserts that the CRS is geocentric. * * @param crs - The CRS to test. + * @returns false if the crs isn't defined. */ export function isGeocentric(crs: ProjectionLike) { mustBeString(crs); @@ -131,11 +160,11 @@ export function isGeocentric(crs: ProjectionLike) { } /** - * Assert that the CRS is a valid one. + * Asserts that the CRS is valid, meaning it has been previously defined and + * includes an unit. * - * @param crs - The CRS to validate. - * - * @throws {@link Error} if the CRS is not valid. + * @param crs - The CRS to test. + * @throws {@link Error} if the crs is not valid. */ export function isValid(crs: ProjectionLike) { const proj = proj4.defs(crs); @@ -148,7 +177,7 @@ export function isValid(crs: ProjectionLike) { } /** - * Give a reasonnable epsilon to use with this CRS. + * Gives a reasonnable epsilon for this CRS. * * @param crs - The CRS to use. * @returns 0.01 if the CRS is EPSG:4326, 0.001 otherwise. @@ -162,10 +191,12 @@ export function reasonnableEpsilon(crs: ProjectionLike) { } /** - * Define a proj4 projection as a string and reference. + * Defines a proj4 projection as a named alias. + * This function is a specialized wrapper over the + * [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections) + * function. * - * @param code - projection's SRS code (only used internally by the Proj4js - * library) - * @param proj4def - Proj4 definition string for the projection to use + * @param code - Named alias of the currently defined projection. + * @param proj4def - Proj4 or WKT string of the defined projection. */ export const defs = (code: string, proj4def: string) => proj4.defs(code, proj4def); From 205c27f93d9d4a577d83e4f8a412ad229b7cf840 Mon Sep 17 00:00:00 2001 From: Bouillaguet Quentin Date: Sat, 16 Nov 2024 10:36:48 +0100 Subject: [PATCH 29/33] fix(Crs): correctly renamed reasonableEpsilon function BREAKING CHANGE: CRS.reasonnableEspsilon renamed to CRS.reasonableEpsilon --- src/Core/Geographic/Crs.ts | 4 ++-- src/Core/Geographic/Extent.js | 2 +- test/unit/crs.js | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Core/Geographic/Crs.ts b/src/Core/Geographic/Crs.ts index 311967dc35..b53f2cee96 100644 --- a/src/Core/Geographic/Crs.ts +++ b/src/Core/Geographic/Crs.ts @@ -177,12 +177,12 @@ export function isValid(crs: ProjectionLike) { } /** - * Gives a reasonnable epsilon for this CRS. + * Gives a reasonable epsilon for this CRS. * * @param crs - The CRS to use. * @returns 0.01 if the CRS is EPSG:4326, 0.001 otherwise. */ -export function reasonnableEpsilon(crs: ProjectionLike) { +export function reasonableEpsilon(crs: ProjectionLike) { if (is4326(crs)) { return 0.01; } else { diff --git a/src/Core/Geographic/Extent.js b/src/Core/Geographic/Extent.js index 0fa844dfdb..10482b7fa9 100644 --- a/src/Core/Geographic/Extent.js +++ b/src/Core/Geographic/Extent.js @@ -234,7 +234,7 @@ class Extent { */ isInside(extent, epsilon) { extent.as(this.crs, _extent); - epsilon = epsilon == undefined ? CRS.reasonnableEpsilon(this.crs) : epsilon; + epsilon = epsilon ?? CRS.reasonableEpsilon(this.crs); return this.east - _extent.east <= epsilon && _extent.west - this.west <= epsilon && this.north - _extent.north <= epsilon && diff --git a/test/unit/crs.js b/test/unit/crs.js index f96d1d32d3..950299da77 100644 --- a/test/unit/crs.js +++ b/test/unit/crs.js @@ -49,8 +49,8 @@ describe('CRS assertions', function () { assert.ok(!CRS.isGeocentric('EPSG:3857')); }); - it('should return a reasonnable epsilon', function () { - assert.strictEqual(CRS.reasonnableEpsilon('EPSG:4326'), 0.01); - assert.strictEqual(CRS.reasonnableEpsilon('EPSG:3857'), 0.001); + it('should return a reasonable epsilon', function () { + assert.strictEqual(CRS.reasonableEpsilon('EPSG:4326'), 0.01); + assert.strictEqual(CRS.reasonableEpsilon('EPSG:3857'), 0.001); }); }); From 2fdf15ae8565ba8b00f8c9a8a74939b72ab52106 Mon Sep 17 00:00:00 2001 From: Bouillaguet Quentin Date: Sat, 16 Nov 2024 10:45:04 +0100 Subject: [PATCH 30/33] refacto(Crs): rename toUnit to getUnit BREAKING CHANGE: CRS.toUnit renamed to CRS.getUnit --- src/Core/Geographic/Crs.ts | 6 +++--- test/unit/crs.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Core/Geographic/Crs.ts b/src/Core/Geographic/Crs.ts index b53f2cee96..73bbcbddc7 100644 --- a/src/Core/Geographic/Crs.ts +++ b/src/Core/Geographic/Crs.ts @@ -118,7 +118,7 @@ function unitFromProj4Unit(proj: ProjectionDefinition) { * @param crs - The CRS to extract the unit from. * @returns Either `UNIT.METER`, `UNIT.DEGREE` or `undefined`. */ -export function toUnit(crs: ProjectionLike) { +export function getUnit(crs: ProjectionLike) { mustBeString(crs); const p = proj4.defs(formatToEPSG(crs)); if (!p) { @@ -134,7 +134,7 @@ export function toUnit(crs: ProjectionLike) { * @throws {@link Error} if the CRS is not valid. */ export function isMetricUnit(crs: ProjectionLike) { - return (toUnit(crs) == UNIT.METER); + return getUnit(crs) === UNIT.METER; } /** @@ -144,7 +144,7 @@ export function isMetricUnit(crs: ProjectionLike) { * @throws {@link Error} if the CRS is not valid. */ export function isGeographic(crs: ProjectionLike) { - return (toUnit(crs) == UNIT.DEGREE); + return getUnit(crs) === UNIT.DEGREE; } /** diff --git a/test/unit/crs.js b/test/unit/crs.js index 950299da77..4f9a1caff1 100644 --- a/test/unit/crs.js +++ b/test/unit/crs.js @@ -30,11 +30,11 @@ describe('CRS assertions', function () { }); it('should get the correct unit for this CRS', function () { - assert.strictEqual(CRS.toUnit('EPSG:4326'), CRS.UNIT.DEGREE); - assert.strictEqual(CRS.toUnit('EPSG:7133'), CRS.UNIT.DEGREE); - assert.strictEqual(CRS.toUnit('EPSG:4978'), CRS.UNIT.METER); - assert.strictEqual(CRS.toUnit('EPSG:3857'), CRS.UNIT.METER); - assert.strictEqual(CRS.toUnit('EPSG:INVALID'), undefined); + assert.strictEqual(CRS.getUnit('EPSG:4326'), CRS.UNIT.DEGREE); + assert.strictEqual(CRS.getUnit('EPSG:7133'), CRS.UNIT.DEGREE); + assert.strictEqual(CRS.getUnit('EPSG:4978'), CRS.UNIT.METER); + assert.strictEqual(CRS.getUnit('EPSG:3857'), CRS.UNIT.METER); + assert.strictEqual(CRS.getUnit('EPSG:INVALID'), undefined); }); it('should check if the CRS is EPSG:4326', function () { From 83eb0d92c631bd5bd19eaffb3c9a089b8c5c8f62 Mon Sep 17 00:00:00 2001 From: Bouillaguet Quentin Date: Wed, 20 Nov 2024 18:50:03 +0100 Subject: [PATCH 31/33] refacto(Crs): remove tms/epsg family of functions BREAKING CHANGE: CRS.isEPSG and CRS.isTMS have been removed BREAKING CHANGE: CRS.formatToESPG and CRS.formatToTMS have been removed --- src/Converter/Feature2Mesh.js | 3 +- src/Converter/textureConverter.js | 3 +- src/Core/Feature.js | 3 +- src/Core/Geographic/Crs.ts | 53 +------------------ src/Core/Geographic/Extent.js | 4 -- src/Core/Prefab/Globe/GlobeLayer.js | 9 ++-- src/Core/Prefab/GlobeView.js | 6 +-- src/Core/Prefab/Planar/PlanarLayer.js | 4 +- src/Core/Tile/Tile.js | 6 +-- src/Core/Tile/TileGrid.js | 17 +++--- src/Core/TileMesh.js | 3 +- src/Core/View.js | 2 +- src/Renderer/OBB.js | 2 +- src/Renderer/RasterTile.js | 3 +- src/Source/FileSource.js | 3 +- src/Source/TMSSource.js | 5 +- src/Source/WFSSource.js | 3 +- test/functional/vector_tile_3d_mesh_mapbox.js | 2 +- test/unit/layeredmaterial.js | 2 +- test/unit/layeredmaterialnodeprocessing.js | 8 +-- test/unit/layerupdatestrategy.js | 8 +-- test/unit/provider_url.js | 4 +- test/unit/source.js | 10 ++-- test/unit/tile.js | 20 +++---- test/unit/tilemesh.js | 4 +- 25 files changed, 58 insertions(+), 129 deletions(-) diff --git a/src/Converter/Feature2Mesh.js b/src/Converter/Feature2Mesh.js index d3d4905ec9..85dca95ac1 100644 --- a/src/Converter/Feature2Mesh.js +++ b/src/Converter/Feature2Mesh.js @@ -1,5 +1,4 @@ import * as THREE from 'three'; -import * as Crs from 'Core/Geographic/Crs'; import Earcut from 'earcut'; import { FEATURE_TYPES } from 'Core/Feature'; import ReferLayerProperties from 'Layer/ReferencingLayerProperties'; @@ -64,7 +63,7 @@ class FeatureMesh extends THREE.Group { } else { // calculate the scale transformation to transform the feature.extent // to feature.extent.as(crs) - coord.crs = Crs.formatToEPSG(this.#originalCrs); + coord.crs = this.#originalCrs; // TODO: An extent here could be either a geographic extent (for // features from WFS) or a tiled extent (for features from MVT). // Unify both behavior. diff --git a/src/Converter/textureConverter.js b/src/Converter/textureConverter.js index 9db079146e..5455e9d96f 100644 --- a/src/Converter/textureConverter.js +++ b/src/Converter/textureConverter.js @@ -1,5 +1,4 @@ import * as THREE from 'three'; -import * as CRS from 'Core/Geographic/Crs'; import Feature2Texture from 'Converter/Feature2Texture'; import Extent from 'Core/Geographic/Extent'; @@ -27,7 +26,7 @@ export default { new THREE.Color(backgroundLayer.paint['background-color']) : undefined; - destinationTile.toExtent(CRS.formatToEPSG(layer.crs), extentTexture); + destinationTile.toExtent(layer.crs, extentTexture); texture = Feature2Texture.createTextureFromFeature(data, extentTexture, layer.subdivisionThreshold, layer.style, backgroundColor); texture.features = data; texture.extent = destinationTile; diff --git a/src/Core/Feature.js b/src/Core/Feature.js index 0e250a533b..018bb45c95 100644 --- a/src/Core/Feature.js +++ b/src/Core/Feature.js @@ -1,5 +1,4 @@ import * as THREE from 'three'; -import * as CRS from 'Core/Geographic/Crs'; import Extent from 'Core/Geographic/Extent'; import Coordinates from 'Core/Geographic/Coordinates'; import Style from 'Core/Style'; @@ -347,7 +346,7 @@ export class FeatureCollection extends THREE.Object3D { constructor(options) { super(); this.isFeatureCollection = true; - this.crs = CRS.formatToEPSG(options.accurate || !options.source?.crs ? options.crs : options.source.crs); + this.crs = options.accurate || !options.source?.crs ? options.crs : options.source.crs; this.features = []; this.mergeFeatures = options.mergeFeatures === undefined ? true : options.mergeFeatures; this.size = options.structure == '3d' ? 3 : 2; diff --git a/src/Core/Geographic/Crs.ts b/src/Core/Geographic/Crs.ts index 73bbcbddc7..9580dcfb77 100644 --- a/src/Core/Geographic/Crs.ts +++ b/src/Core/Geographic/Crs.ts @@ -21,48 +21,6 @@ function mustBeString(crs: string) { } } -/** - * Checks that a given CRS identifier follows the TMS:XXXX format. - * @internal - * - * @param crs - The CRS identifier string. - */ -export function isTms(crs: string) { - return isString(crs) && crs.startsWith('TMS'); -} - -/** - * Checks that a given CRS identifier follows the EPSG:XXXX format. - * @internal - * - * @param crs - The CRS identifier string. - */ -export function isEpsg(crs: string) { - return isString(crs) && crs.startsWith('EPSG'); -} - -/** - * Converts the input to the tile matrix set notation: TMS:XXXX. - * @internal - * - * @param crs - The input to format. - */ -export function formatToTms(crs: string) { - mustBeString(crs); - return isTms(crs) ? crs : `TMS:${crs.match(/\d+/)?.[0]}`; -} - -/** - * Converts the input to European Petroleum Survey Group notation: EPSG:XXXX. - * @internal - * - * @param crs - The input to format. - */ -export function formatToEPSG(crs: string) { - mustBeString(crs); - return isEpsg(crs) ? crs : `EPSG:${crs.match(/\d+/)?.[0]}`; -} - /** * System units supported for a coordinate system. See * [proj](https://proj4.org/en/9.5/operations/conversions/unitconvert.html#angular-units). @@ -79,15 +37,6 @@ export const UNIT = { METER: 2, } as const; -/** - * @internal - */ -export const tms_3857 = 'TMS:3857'; -/** - * @internal - */ -export const tms_4326 = 'TMS:4326'; - /** * Checks that the CRS is EPSG:4326. * @internal @@ -120,7 +69,7 @@ function unitFromProj4Unit(proj: ProjectionDefinition) { */ export function getUnit(crs: ProjectionLike) { mustBeString(crs); - const p = proj4.defs(formatToEPSG(crs)); + const p = proj4.defs(crs); if (!p) { return undefined; } diff --git a/src/Core/Geographic/Extent.js b/src/Core/Geographic/Extent.js index 10482b7fa9..39c9ddf1ab 100644 --- a/src/Core/Geographic/Extent.js +++ b/src/Core/Geographic/Extent.js @@ -49,10 +49,6 @@ class Extent { throw new Error(`${crs} is a geocentric projection, it doesn't make sense with a geographical extent`); } - if (CRS.isTms(crs)) { - throw new Error(`${crs} is a tiled projection, use Tile instead`); - } - this.isExtent = true; this.crs = crs; diff --git a/src/Core/Prefab/Globe/GlobeLayer.js b/src/Core/Prefab/Globe/GlobeLayer.js index 629b085e59..1fed4cd883 100644 --- a/src/Core/Prefab/Globe/GlobeLayer.js +++ b/src/Core/Prefab/Globe/GlobeLayer.js @@ -1,5 +1,4 @@ import * as THREE from 'three'; -import * as CRS from 'Core/Geographic/Crs'; import TiledGeometryLayer from 'Layer/TiledGeometryLayer'; import { ellipsoidSizes } from 'Core/Math/Ellipsoid'; import { globalExtentTMS, schemeTiles } from 'Core/Tile/TileGrid'; @@ -48,13 +47,13 @@ class GlobeLayer extends TiledGeometryLayer { */ constructor(id, object3d, config = {}) { // Configure tiles - const scheme = schemeTiles.get(CRS.tms_4326); + const scheme = schemeTiles.get('EPSG:4326'); const schemeTile = globalExtentTMS.get('EPSG:4326').subdivisionByScheme(scheme); // Supported tile matrix set for color/elevation layer config.tileMatrixSets = [ - CRS.tms_4326, - CRS.tms_3857, + 'EPSG:4326', + 'EPSG:3857', ]; const uvCount = config.tileMatrixSets.length; const builder = new BuilderEllipsoidTile({ crs: 'EPSG:4978', uvCount }); @@ -104,7 +103,7 @@ class GlobeLayer extends TiledGeometryLayer { subdivision(context, layer, node) { if (node.level == 5) { - const row = node.getExtentsByProjection(CRS.tms_4326)[0].row; + const row = node.getExtentsByProjection('EPSG:4326')[0].row; if (row == 31 || row == 0) { // doesn't subdivise the pole return false; diff --git a/src/Core/Prefab/GlobeView.js b/src/Core/Prefab/GlobeView.js index ce70a36ebb..ce90a53661 100644 --- a/src/Core/Prefab/GlobeView.js +++ b/src/Core/Prefab/GlobeView.js @@ -1,7 +1,5 @@ import * as THREE from 'three'; -import * as CRS from 'Core/Geographic/Crs'; - import View, { VIEW_EVENTS } from 'Core/View'; import GlobeControls from 'Controls/GlobeControls'; import Coordinates from 'Core/Geographic/Coordinates'; @@ -139,11 +137,11 @@ class GlobeView extends View { return Promise.reject(new Error('Add Layer type object')); } if (layer.isColorLayer) { - if (!this.tileLayer.tileMatrixSets.includes(CRS.formatToTms(layer.source.crs))) { + if (!this.tileLayer.tileMatrixSets.includes(layer.source.crs)) { return layer._reject(`Only ${this.tileLayer.tileMatrixSets} tileMatrixSet are currently supported for color layers`); } } else if (layer.isElevationLayer) { - if (CRS.formatToTms(layer.source.crs) !== this.tileLayer.tileMatrixSets[0]) { + if (layer.source.crs !== this.tileLayer.tileMatrixSets[0]) { return layer._reject(`Only ${this.tileLayer.tileMatrixSets[0]} tileMatrixSet is currently supported for elevation layers`); } } diff --git a/src/Core/Prefab/Planar/PlanarLayer.js b/src/Core/Prefab/Planar/PlanarLayer.js index d1fbb9f7d6..0a3bad7079 100644 --- a/src/Core/Prefab/Planar/PlanarLayer.js +++ b/src/Core/Prefab/Planar/PlanarLayer.js @@ -1,6 +1,5 @@ import * as THREE from 'three'; -import * as CRS from 'Core/Geographic/Crs'; import TiledGeometryLayer from 'Layer/TiledGeometryLayer'; import { globalExtentTMS } from 'Core/Tile/TileGrid'; import PlanarTileBuilder from './PlanarTileBuilder'; @@ -35,8 +34,7 @@ class PlanarLayer extends TiledGeometryLayer { * @throws {Error} `object3d` must be a valid `THREE.Object3d`. */ constructor(id, extent, object3d, config = {}) { - const tms = CRS.formatToTms(extent.crs); - const tileMatrixSets = [tms]; + const tileMatrixSets = [extent.crs]; if (!globalExtentTMS.get(extent.crs)) { // Add new global extent for this new crs projection. globalExtentTMS.set(extent.crs, extent); diff --git a/src/Core/Tile/Tile.js b/src/Core/Tile/Tile.js index bda353c21f..d0912b7c90 100644 --- a/src/Core/Tile/Tile.js +++ b/src/Core/Tile/Tile.js @@ -169,10 +169,10 @@ class Tile { * @returns {Tile[]} */ export function tiledCovering(e, tms) { - if (e.crs == 'EPSG:4326' && tms == CRS.tms_3857) { + if (e.crs == 'EPSG:4326' && tms == 'EPSG:3857') { const WMTS_PM = []; - const extent = _extent.copy(e).as(CRS.formatToEPSG(tms), _extent2); - const { globalExtent, globalDimension, sTs } = getInfoTms(CRS.formatToEPSG(tms)); + const extent = _extent.copy(e).as(tms, _extent2); + const { globalExtent, globalDimension, sTs } = getInfoTms(tms); extent.clampByExtent(globalExtent); extent.planarDimensions(_dimensionTile); diff --git a/src/Core/Tile/TileGrid.js b/src/Core/Tile/TileGrid.js index 0a60f8d586..a6eb619b35 100644 --- a/src/Core/Tile/TileGrid.js +++ b/src/Core/Tile/TileGrid.js @@ -1,5 +1,4 @@ import * as THREE from 'three'; -import * as CRS from '../Geographic/Crs'; import Extent from '../Geographic/Extent'; const _countTiles = new THREE.Vector2(); @@ -19,27 +18,25 @@ extent3857.clampSouthNorth(extent3857.west, extent3857.east); globalExtentTMS.set('EPSG:3857', extent3857); schemeTiles.set('default', new THREE.Vector2(1, 1)); -schemeTiles.set(CRS.tms_3857, schemeTiles.get('default')); -schemeTiles.set(CRS.tms_4326, new THREE.Vector2(2, 1)); +schemeTiles.set('EPSG:3857', schemeTiles.get('default')); +schemeTiles.set('EPSG:4326', new THREE.Vector2(2, 1)); export function getInfoTms(/** @type {string} */ crs) { - const epsg = CRS.formatToEPSG(crs); - const globalExtent = globalExtentTMS.get(epsg); + const globalExtent = globalExtentTMS.get(crs); const globalDimension = globalExtent.planarDimensions(_dim); - const tms = CRS.formatToTms(crs); - const sTs = schemeTiles.get(tms) || schemeTiles.get('default'); + const sTs = schemeTiles.get(crs) || schemeTiles.get('default'); // The isInverted parameter is to be set to the correct value, true or false // (default being false) if the computation of the coordinates needs to be // inverted to match the same scheme as OSM, Google Maps or other system. // See link below for more information // https://alastaira.wordpress.com/2011/07/06/converting-tms-tile-coordinates-to-googlebingosm-tile-coordinates/ // in crs includes ':NI' => tms isn't inverted (NOT INVERTED) - const isInverted = !tms.includes(':NI'); - return { epsg, globalExtent, globalDimension, sTs, isInverted }; + const isInverted = !crs.includes(':NI'); + return { epsg: crs, globalExtent, globalDimension, sTs, isInverted }; } export function getCountTiles(/** @type {string} */ crs, /** @type {number} */ zoom) { - const sTs = schemeTiles.get(CRS.formatToTms(crs)) || schemeTiles.get('default'); + const sTs = schemeTiles.get(crs) || schemeTiles.get('default'); const count = 2 ** zoom; _countTiles.set(count, count).multiply(sTs); return _countTiles; diff --git a/src/Core/TileMesh.js b/src/Core/TileMesh.js index a647ee6f97..a12b4f967a 100644 --- a/src/Core/TileMesh.js +++ b/src/Core/TileMesh.js @@ -1,5 +1,4 @@ import * as THREE from 'three'; -import * as CRS from 'Core/Geographic/Crs'; import { geoidLayerIsVisible } from 'Layer/GeoidLayer'; import { tiledCovering } from 'Core/Tile/Tile'; @@ -77,7 +76,7 @@ class TileMesh extends THREE.Mesh { } getExtentsByProjection(crs) { - return this.#_tms.get(CRS.formatToTms(crs)); + return this.#_tms.get(crs); } /** diff --git a/src/Core/View.js b/src/Core/View.js index 71a00bdd45..72dff1f4ea 100644 --- a/src/Core/View.js +++ b/src/Core/View.js @@ -55,7 +55,7 @@ function _preprocessLayer(view, layer, parentLayer) { // Find crs projection layer, this is projection destination layer.crs = view.referenceCrs; } else if (!layer.crs) { - if (parentLayer && parentLayer.tileMatrixSets && parentLayer.tileMatrixSets.includes(CRS.formatToTms(source.crs))) { + if (parentLayer && parentLayer.tileMatrixSets && parentLayer.tileMatrixSets.includes(source.crs)) { layer.crs = source.crs; } else { layer.crs = parentLayer && parentLayer.extent.crs; diff --git a/src/Renderer/OBB.js b/src/Renderer/OBB.js index 6b00000986..567cce69f6 100644 --- a/src/Renderer/OBB.js +++ b/src/Renderer/OBB.js @@ -126,7 +126,7 @@ class OBB extends THREE.Object3D { this.position.copy(position); this.quaternion.copy(quaternion); this.updateMatrixWorld(true); - } else if (!CRS.isTms(extent.crs) && CRS.isMetricUnit(extent.crs)) { + } else if (CRS.isMetricUnit(extent.crs)) { extent.center(coord).toVector3(this.position); extent.planarDimensions(dimension); size.set(dimension.x, dimension.y, Math.abs(maxHeight - minHeight)); diff --git a/src/Renderer/RasterTile.js b/src/Renderer/RasterTile.js index a218be7b8b..af87699426 100644 --- a/src/Renderer/RasterTile.js +++ b/src/Renderer/RasterTile.js @@ -1,5 +1,4 @@ import * as THREE from 'three'; -import * as CRS from 'Core/Geographic/Crs'; import { ELEVATION_MODES } from 'Renderer/LayeredMaterial'; import { checkNodeElevationTextureValidity, insertSignificantValuesFromParent, computeMinMaxElevation } from 'Parser/XbilParser'; @@ -32,7 +31,7 @@ class RasterTile extends THREE.EventDispatcher { constructor(material, layer) { super(); this.layer = layer; - this.crs = layer.parent.tileMatrixSets.indexOf(CRS.formatToTms(layer.crs)); + this.crs = layer.parent.tileMatrixSets.indexOf(layer.crs); if (this.crs == -1) { console.error('Unknown crs:', layer.crs); } diff --git a/src/Source/FileSource.js b/src/Source/FileSource.js index d5d7eda6bf..a65ce63271 100644 --- a/src/Source/FileSource.js +++ b/src/Source/FileSource.js @@ -1,4 +1,3 @@ -import * as CRS from 'Core/Geographic/Crs'; import Source from 'Source/Source'; import Cache from 'Core/Scheduler/Cache'; @@ -159,7 +158,7 @@ class FileSource extends Source { if (!features) { options.out.buildExtent = this.crs != 'EPSG:4978'; if (options.out.buildExtent) { - options.out.forcedExtentCrs = options.out.crs != 'EPSG:4978' ? options.out.crs : CRS.formatToEPSG(this.crs); + options.out.forcedExtentCrs = options.out.crs != 'EPSG:4978' ? options.out.crs : this.crs; } features = this.parser(this.fetchedData, options); this._featuresCaches[options.out.crs].setByArray(features, [0]); diff --git a/src/Source/TMSSource.js b/src/Source/TMSSource.js index 2359b98363..6d669ceec3 100644 --- a/src/Source/TMSSource.js +++ b/src/Source/TMSSource.js @@ -1,11 +1,10 @@ -import * as CRS from 'Core/Geographic/Crs'; import Source from 'Source/Source'; import URLBuilder from 'Provider/URLBuilder'; import Extent from 'Core/Geographic/Extent'; import Tile from 'Core/Tile/Tile'; import { globalExtentTMS } from 'Core/Tile/TileGrid'; -const _tile = new Tile(CRS.tms_4326, 0, 0, 0); +const _tile = new Tile('EPSG:4326', 0, 0, 0); /** * An object defining the source of resources to get from a @@ -95,7 +94,7 @@ class TMSSource extends Source { this.zoom = source.zoom; this.isInverted = source.isInverted || false; - this.crs = CRS.formatToTms(source.crs); + this.crs = source.crs; this.tileMatrixSetLimits = source.tileMatrixSetLimits; this.extentSetlimits = {}; this.tileMatrixCallback = source.tileMatrixCallback || (zoomLevel => zoomLevel); diff --git a/src/Source/WFSSource.js b/src/Source/WFSSource.js index 4f68286453..5d2c771fa6 100644 --- a/src/Source/WFSSource.js +++ b/src/Source/WFSSource.js @@ -1,4 +1,3 @@ -import * as CRS from 'Core/Geographic/Crs'; import Source from 'Source/Source'; import URLBuilder from 'Provider/URLBuilder'; import Extent from 'Core/Geographic/Extent'; @@ -161,7 +160,7 @@ class WFSSource extends Source { } requestToKey(extent) { - if (CRS.isTms(extent.crs)) { + if (extent.isTile) { return super.requestToKey(extent); } else { return [extent.zoom, extent.south, extent.west]; diff --git a/test/functional/vector_tile_3d_mesh_mapbox.js b/test/functional/vector_tile_3d_mesh_mapbox.js index 98093a678b..b307e8e0cc 100644 --- a/test/functional/vector_tile_3d_mesh_mapbox.js +++ b/test/functional/vector_tile_3d_mesh_mapbox.js @@ -13,7 +13,7 @@ describe('vector_tile_3d_mesh_mapbox', function _describe() { it('should correctly load building features on a given TMS tile', async function _it() { const featuresCollection = await page.evaluate(async function _() { const layers = view.getLayers(l => l.source && l.source.isVectorSource); - const res = await layers[0].source.loadData({ zoom: 15, row: 11634, col: 16859 }, { crs: 'EPSG:4978', source: { crs: 'TMS:3857' } }); + const res = await layers[0].source.loadData({ zoom: 15, row: 11634, col: 16859 }, { crs: 'EPSG:4978', source: { crs: 'EPSG:3857' } }); return res; }); assert.ok(featuresCollection.isFeatureCollection); diff --git a/test/unit/layeredmaterial.js b/test/unit/layeredmaterial.js index 6a4fe7a85e..89f171abf0 100644 --- a/test/unit/layeredmaterial.js +++ b/test/unit/layeredmaterial.js @@ -35,7 +35,7 @@ describe('material state vs layer state', function () { view.tileLayer.colorLayersOrder = [layer.id]; view.addLayer(layer); - const extent = new Tile('TMS:4326', 3, 0, 0).toExtent('EPSG:4326'); + const extent = new Tile('EPSG:4326', 3, 0, 0).toExtent('EPSG:4326'); material = new LayeredMaterial(); const geom = new THREE.BufferGeometry(); geom.OBB = new OBB(new THREE.Vector3(), new THREE.Vector3(1, 1, 1)); diff --git a/test/unit/layeredmaterialnodeprocessing.js b/test/unit/layeredmaterialnodeprocessing.js index 9aae1ea00c..0f7e92af02 100644 --- a/test/unit/layeredmaterialnodeprocessing.js +++ b/test/unit/layeredmaterialnodeprocessing.js @@ -41,12 +41,12 @@ describe('updateLayeredMaterialNodeImagery', function () { crs: 'EPSG:4326', info: { update: () => {} }, tileMatrixSets: [ - 'TMS:4326', - 'TMS:3857', + 'EPSG:4326', + 'EPSG:3857', ], parent: { tileMatrixSets: [ - 'TMS:4326', - 'TMS:3857', + 'EPSG:4326', + 'EPSG:3857', ], }, }); diff --git a/test/unit/layerupdatestrategy.js b/test/unit/layerupdatestrategy.js index 2e4ebe488a..89b7abceda 100644 --- a/test/unit/layerupdatestrategy.js +++ b/test/unit/layerupdatestrategy.js @@ -44,12 +44,12 @@ describe('Handling no data source error', function () { crs: 'EPSG:4326', info: { update: () => {} }, tileMatrixSets: [ - 'TMS:4326', - 'TMS:3857', + 'EPSG:4326', + 'EPSG:3857', ], parent: { tileMatrixSets: [ - 'TMS:4326', - 'TMS:3857', + 'EPSG:4326', + 'EPSG:3857', ], }, }); diff --git a/test/unit/provider_url.js b/test/unit/provider_url.js index 599dc94249..28a714e95f 100644 --- a/test/unit/provider_url.js +++ b/test/unit/provider_url.js @@ -8,14 +8,14 @@ describe('URL creations', function () { const layer = { tileMatrixCallback: (zoomLevel => zoomLevel) }; it('should correctly replace ${x}, ${y} and ${z} by 359, 512 and 10', function () { - const coords = new Tile('TMS:4857', 10, 512, 359); + const coords = new Tile('EPSG:4857', 10, 512, 359); layer.url = 'http://server.geo/tms/${z}/${y}/${x}.jpg'; const result = URLBuilder.xyz(coords, layer); assert.equal(result, 'http://server.geo/tms/10/512/359.jpg'); }); it('should correctly replace %COL, %ROW, %TILEMATRIX by 2072, 1410 and 12', function () { - const coords = new Tile('TMS:4326', 12, 1410, 2072); + const coords = new Tile('EPSG:4326', 12, 1410, 2072); layer.url = 'http://server.geo/wmts/SERVICE=WMTS&TILEMATRIX=%TILEMATRIX&TILEROW=%ROW&TILECOL=%COL'; const result = URLBuilder.xyz(coords, layer); assert.equal(result, 'http://server.geo/wmts/SERVICE=WMTS&TILEMATRIX=12&TILEROW=1410&TILECOL=2072'); diff --git a/test/unit/source.js b/test/unit/source.js index 2a3a404d06..9e9d5a9b22 100644 --- a/test/unit/source.js +++ b/test/unit/source.js @@ -79,7 +79,7 @@ describe('Sources', function () { it('should return keys from request', function () { const source = new WFSSource(paramsWFS); - const tile = new Tile('TMS:4326', 5, 10, 15); + const tile = new Tile('EPSG:4326', 5, 10, 15); const keys = source.requestToKey(tile); assert.equal(tile.zoom, keys[0]); assert.equal(tile.row, keys[1]); @@ -105,7 +105,7 @@ describe('Sources', function () { it('should instance and use WMTSSource', function () { const source = new WMTSSource(paramsWMTS); - const extent = new Tile('TMS:3857', 5, 0, 0); + const extent = new Tile('EPSG:3857', 5, 0, 0); assert.ok(source.isWMTSSource); assert.ok(source.urlFromExtent(extent)); assert.ok(source.extentInsideLimit(extent, 5)); @@ -122,7 +122,7 @@ describe('Sources', function () { 5: { minTileRow: 0, maxTileRow: 32, minTileCol: 0, maxTileCol: 32 }, }; const source = new WMTSSource(paramsWMTS); - const extent = new Tile('TMS:3857', 5, 0, 0); + const extent = new Tile('EPSG:3857', 5, 0, 0); source.onLayerAdded({ out: { crs: 'EPSG:4326' } }); assert.ok(source.isWMTSSource); assert.ok(source.urlFromExtent(extent)); @@ -132,7 +132,7 @@ describe('Sources', function () { it('should use vendor specific parameters for the creation of the WMTS url', function () { paramsWMTS.vendorSpecific = vendorSpecific; const source = new WMTSSource(paramsWMTS); - const tile = new Tile('TMS:4326', 0, 10, 0); + const tile = new Tile('EPSG:4326', 0, 10, 0); const url = source.urlFromExtent(tile); const end = '&buffer=4096&format_options=dpi:300;quantizer:octree&tiled=true'; assert.ok(url.endsWith(end)); @@ -203,7 +203,7 @@ describe('Sources', function () { it('should instance and use TMSSource', function () { const source = new TMSSource(paramsTMS); source.onLayerAdded({ out: { crs: 'EPSG:4326' } }); - const extent = new Tile('TMS:3857', 5, 0, 0); + const extent = new Tile('EPSG:3857', 5, 0, 0); assert.ok(source.isTMSSource); assert.ok(source.urlFromExtent(extent)); assert.ok(source.extentInsideLimit(extent, extent.zoom)); diff --git a/test/unit/tile.js b/test/unit/tile.js index 3512ef27c5..845c29f5b5 100644 --- a/test/unit/tile.js +++ b/test/unit/tile.js @@ -9,24 +9,24 @@ describe('Tile', function () { const row = 22; const col = 10; - it('should convert tile TMS:4326 like expected', function () { - const withValues = new Tile('TMS:4326', 0, 0, 0).toExtent('EPSG:4326'); + it('should convert tile EPSG:4326 like expected', function () { + const withValues = new Tile('EPSG:4326', 0, 0, 0).toExtent('EPSG:4326'); assert.equal(-180, withValues.west); assert.equal(0, withValues.east); assert.equal(-90, withValues.south); assert.equal(90, withValues.north); }); - it('should convert TMS:3857 tile to EPSG:3857 extent like expected', function () { - const withValues = new Tile('TMS:3857', 0, 0, 0).toExtent('EPSG:3857'); + it('should convert EPSG:3857 tile to EPSG:3857 extent like expected', function () { + const withValues = new Tile('EPSG:3857', 0, 0, 0).toExtent('EPSG:3857'); assert.equal(-20037508.342789244, withValues.west); assert.equal(20037508.342789244, withValues.east); assert.equal(-20037508.342789244, withValues.south); assert.equal(20037508.342789244, withValues.north); }); - it('should convert TMS:3857 tile to EPSG:4326 extent like expected', function () { - const withValues = new Tile('TMS:3857', 0, 0, 0); + it('should convert EPSG:3857 tile to EPSG:4326 extent like expected', function () { + const withValues = new Tile('EPSG:3857', 0, 0, 0); const result = withValues.toExtent('EPSG:4326'); assert.equal(-180.00000000000003, result.west); assert.equal(180.00000000000003, result.east); @@ -35,8 +35,8 @@ describe('Tile', function () { }); it('should return expected offset using tiled extent', function () { - const withValues = new Tile('TMS:4326', zoom, row, col); - const parent = new Tile('TMS:4326', zoom - 2, row, col); + const withValues = new Tile('EPSG:4326', zoom, row, col); + const parent = new Tile('EPSG:4326', zoom - 2, row, col); const offset = withValues.offsetToParent(parent); assert.equal(offset.x, 0.5); assert.equal(offset.y, 0.5); @@ -45,7 +45,7 @@ describe('Tile', function () { }); it('should return expected tiled extent parent', function () { - const withValues = new Tile('TMS:4326', zoom, row, col); + const withValues = new Tile('EPSG:4326', zoom, row, col); const parent = withValues.tiledExtentParent(zoom - 2); assert.equal(parent.zoom, 3); assert.equal(parent.row, 5); @@ -53,7 +53,7 @@ describe('Tile', function () { }); it('should convert TMS extent values to string', function () { - const withValues = new Tile('TMS:4326', 0, 1, 2); + const withValues = new Tile('EPSG:4326', 0, 1, 2); const tostring = withValues.toString(','); const toValues = tostring.split(',').map(s => Number(s)); assert.equal(toValues[0], withValues.zoom); diff --git a/test/unit/tilemesh.js b/test/unit/tilemesh.js index efe12df674..bcdca3e261 100644 --- a/test/unit/tilemesh.js +++ b/test/unit/tilemesh.js @@ -23,7 +23,7 @@ FakeTileMesh.prototype.constructor = FakeTileMesh; FakeTileMesh.prototype.findCommonAncestor = TileMesh.prototype.findCommonAncestor; describe('TileMesh', function () { - const tile = new Tile('TMS:3857', 5, 10, 10); + const tile = new Tile('EPSG:3857', 5, 10, 10); const geom = new THREE.BufferGeometry(); geom.OBB = new OBB(); @@ -221,7 +221,7 @@ describe('TileMesh', function () { const tileMesh = new TileMesh(geom, material, planarlayer, tile.toExtent('EPSG:3857'), 0); const rasterNode = elevationLayer.setupRasterNode(tileMesh); const texture = new THREE.Texture(); - texture.extent = new Tile('TMS:3857', 4, 10, 10); + texture.extent = new Tile('EPSG:3857', 4, 10, 10); texture.image = { width: 3, height: 3, From cf41e8dd48bc82b3fb7db5a2e8292ab6deeb0042 Mon Sep 17 00:00:00 2001 From: Bouillaguet Quentin Date: Mon, 22 Jul 2024 01:24:21 +0200 Subject: [PATCH 32/33] refactor(Layer): remove Object.assign of config --- examples/layers/JSONLayers/GeoidMNT.json | 4 +- examples/source_file_geojson_3d.html | 1 - examples/source_stream_wfs_raster.html | 7 -- src/Converter/Feature2Texture.js | 2 +- src/Core/Prefab/Globe/Atmosphere.js | 6 +- src/Core/Prefab/Globe/GlobeLayer.js | 26 +++-- src/Core/Prefab/Planar/PlanarLayer.js | 22 +++- src/Layer/C3DTilesLayer.js | 10 +- src/Layer/ColorLayer.js | 47 ++++++-- src/Layer/CopcLayer.js | 5 + src/Layer/ElevationLayer.js | 52 +++++++-- src/Layer/EntwinePointTileLayer.js | 17 ++- src/Layer/FeatureGeometryLayer.js | 29 ++++- src/Layer/GeometryLayer.js | 52 +++++++-- src/Layer/LabelLayer.js | 16 ++- src/Layer/Layer.js | 129 +++++++++++++-------- src/Layer/OrientedImageLayer.js | 16 ++- src/Layer/PointCloudLayer.js | 96 +++++++++++---- src/Layer/Potree2Layer.js | 9 +- src/Layer/PotreeLayer.js | 11 +- src/Layer/RasterLayer.js | 16 ++- src/Layer/TiledGeometryLayer.js | 88 +++++++++++--- test/unit/dataSourceProvider.js | 4 +- test/unit/layeredmaterialnodeprocessing.js | 13 ++- test/unit/layerupdatestrategy.js | 13 ++- utils/debug/3dTilesDebug.js | 2 +- utils/debug/TileDebug.js | 2 +- 27 files changed, 503 insertions(+), 192 deletions(-) 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; } From 683e55d51e610e106749b78d8a3ef83158532d2a Mon Sep 17 00:00:00 2001 From: Anthony GULLIENT Date: Mon, 25 Nov 2024 15:44:18 +0100 Subject: [PATCH 33/33] fix(i3dm): use instanceId to get info --- src/Layer/OGC3DTilesLayer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Layer/OGC3DTilesLayer.js b/src/Layer/OGC3DTilesLayer.js index c43be234a3..181ae392b6 100644 --- a/src/Layer/OGC3DTilesLayer.js +++ b/src/Layer/OGC3DTilesLayer.js @@ -356,14 +356,14 @@ class OGC3DTilesLayer extends GeometryLayer { getC3DTileFeatureFromIntersectsArray(intersects) { if (!intersects.length) { return null; } - const { face, index, object } = intersects[0]; + const { face, index, object, instanceId } = intersects[0]; /** @type{number|null} */ let batchId; if (object.isPoints && index) { batchId = object.geometry.getAttribute('_BATCHID')?.getX(index) ?? index; } else if (object.isMesh && face) { - batchId = object.geometry.getAttribute('_BATCHID')?.getX(face.a); + batchId = object.geometry.getAttribute('_BATCHID')?.getX(face.a) ?? instanceId; } if (batchId === undefined) {