diff --git a/.eslintrc.cjs b/.eslintrc.cjs index bef0ef66c3..0006ab9edd 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -137,7 +137,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', }, 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 }} !!" 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) 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..176cea5c1b 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,45 +87,87 @@ 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); + const light = new AmbientLight(0x404040, 40); view.scene.add(light); // 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 - -
-
- - - - - - - - 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/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/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); 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/package-lock.json b/package-lock.json index a3d630a77d..af2abcdce7 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,15 +6277,14 @@ "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": { - "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" } @@ -5632,6 +6476,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 +6559,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 +6814,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 +7051,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" @@ -6716,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", @@ -7215,7 +8109,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 +8162,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 +8413,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 +9065,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 +9864,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 +9984,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 +10061,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 +10115,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 +10373,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 +10850,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 +10950,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 +11590,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 +11794,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 +12113,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 +12413,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 +12726,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 +12877,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 +12968,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 +13392,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 +13720,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 +13796,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 +13900,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 +14121,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 +14446,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 +14531,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 +15202,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 +15293,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..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", @@ -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", diff --git a/publiccode.yml b/publiccode.yml new file mode 100644 index 0000000000..06f3dc56e9 --- /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://raw.githubusercontent.com/iTowns/itowns/refs/heads/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' 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..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; } } @@ -395,7 +395,9 @@ 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.currentState = this.ZOOM; + this.dispatchEvent({ type: this.ZOOM._event, delta: event.deltaY, viewCoords }); } } diff --git a/src/Converter/Feature2Mesh.js b/src/Converter/Feature2Mesh.js index 27135ba50f..85dca95ac1 100644 --- a/src/Converter/Feature2Mesh.js +++ b/src/Converter/Feature2Mesh.js @@ -4,7 +4,6 @@ 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'; @@ -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/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/Converter/textureConverter.js b/src/Converter/textureConverter.js index 41f103620f..5455e9d96f 100644 --- a/src/Converter/textureConverter.js +++ b/src/Converter/textureConverter.js @@ -1,7 +1,6 @@ import * as THREE from 'three'; 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]); @@ -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 82980a747d..018bb45c95 100644 --- a/src/Core/Feature.js +++ b/src/Core/Feature.js @@ -1,7 +1,6 @@ import * as THREE from 'three'; 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) { @@ -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/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.js b/src/Core/Geographic/Crs.js deleted file mode 100644 index b7c40cfc4f..0000000000 --- a/src/Core/Geographic/Crs.js +++ /dev/null @@ -1,191 +0,0 @@ -import proj4 from 'proj4'; - -proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'); - -function isString(s) { - return typeof s === 'string' || s instanceof String; -} - -function mustBeString(crs) { - if (!isString(crs)) { - throw new Error(`Crs parameter value must be a string: '${crs}'`); - } -} - -function isTms(crs) { - return isString(crs) && crs.startsWith('TMS'); -} - -function isEpsg(crs) { - return isString(crs) && crs.startsWith('EPSG'); -} - -function formatToTms(crs) { - mustBeString(crs); - return isTms(crs) ? crs : `TMS:${crs.match(/\d+/)[0]}`; -} - -function formatToEPSG(crs) { - mustBeString(crs); - return isEpsg(crs) ? crs : `EPSG:${crs.match(/\d+/)[0]}`; -} - -const UNIT = { - DEGREE: 1, - METER: 2, -}; - -function is4326(crs) { - return crs === 'EPSG:4326'; -} - -function isGeocentric(crs) { - mustBeString(crs); - const projection = proj4.defs(crs); - return !projection ? false : projection.projName == 'geocent'; -} - -function _unitFromProj4Unit(projunit) { - if (projunit === 'degrees') { - return UNIT.DEGREE; - } else if (projunit === 'm') { - return UNIT.METER; - } else { - return undefined; - } -} - -function toUnit(crs) { - 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) { - return undefined; - } - return _unitFromProj4Unit(p.units); - } - } -} - -function toUnitWithError(crs) { - mustBeString(crs); - const u = toUnit(crs); - if (u === undefined) { - throw new Error(`No unit found for crs: '${crs}'`); - } - return u; -} - -/** - * This module provides basic methods to manipulate a CRS (as a string). - * - * @module CRS - */ -export default { - /** - * Units that can be used for a CRS. - * - * @enum {number} - */ - UNIT, - - /** - * Assert that the CRS is valid one. - * - * @param {string} crs - The CRS to validate. - * - * @throws {Error} if the CRS is not valid. - */ - isValid(crs) { - 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. - */ - isGeographic(crs) { - 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. - */ - isMetricUnit(crs) { - 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`. - */ - toUnit, - - /** - * Is the CRS EPSG:4326 ? - * - * @param {string} crs - The CRS to test. - * @return {boolean} - */ - is4326, - /** - * Is the CRS geocentric ? - * if crs isn't defined the method returns false. - * - * @param {string} crs - The CRS to test. - * @return {boolean} - */ - 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. - */ - reasonnableEpsilon(crs) { - if (is4326(crs)) { - return 0.01; - } else { - return 0.001; - } - }, - /** - * format crs to European Petroleum Survey Group notation : EPSG:XXXX. - * - * @param {string} crs The crs to format - * @return {string} formated crs - */ - formatToEPSG, - /** - * format crs to tile matrix set notation : TMS:XXXX. - * - * @param {string} crs The crs to format - * @return {string} formated crs - */ - formatToTms, - isTms, - isEpsg, - tms_3857: 'TMS:3857', - tms_4326: 'TMS:4326', - /** - * 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} - */ - defs: (code, proj4def) => proj4.defs(code, proj4def), -}; diff --git a/src/Core/Geographic/Crs.ts b/src/Core/Geographic/Crs.ts new file mode 100644 index 0000000000..9580dcfb77 --- /dev/null +++ b/src/Core/Geographic/Crs.ts @@ -0,0 +1,151 @@ +import proj4 from 'proj4'; + +import type { ProjectionDefinition } from 'proj4'; + +proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'); + +/** + * 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; + +function isString(s: unknown): s is string { + return typeof s === 'string' || s instanceof String; +} + +function mustBeString(crs: string) { + if (!isString(crs)) { + throw new Error(`Crs parameter value must be a string: '${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; + +/** + * Checks that the CRS is EPSG:4326. + * @internal + * + * @param crs - The CRS to test. + */ +export function is4326(crs: ProjectionLike) { + return crs === 'EPSG:4326'; +} + +function unitFromProj4Unit(proj: ProjectionDefinition) { + if (proj.units === 'degrees') { + return UNIT.DEGREE; + } 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; + } +} + +/** + * Returns the horizontal coordinates system units associated with this CRS. + * + * @param crs - The CRS to extract the unit from. + * @returns Either `UNIT.METER`, `UNIT.DEGREE` or `undefined`. + */ +export function getUnit(crs: ProjectionLike) { + mustBeString(crs); + const p = proj4.defs(crs); + if (!p) { + return undefined; + } + return unitFromProj4Unit(p); +} + +/** + * Asserts that the CRS is using metric units. + * + * @param crs - The CRS to check. + * @throws {@link Error} if the CRS is not valid. + */ +export function isMetricUnit(crs: ProjectionLike) { + return getUnit(crs) === UNIT.METER; +} + +/** + * Asserts that the CRS is geographic. + * + * @param crs - The CRS to check. + * @throws {@link Error} if the CRS is not valid. + */ +export function isGeographic(crs: ProjectionLike) { + return getUnit(crs) === UNIT.DEGREE; +} + +/** + * 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); + const projection = proj4.defs(crs); + return !projection ? false : projection.projName == 'geocent'; +} + +/** + * Asserts that the CRS is valid, meaning it has been previously defined and + * includes an unit. + * + * @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); + 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}`); + } +} + +/** + * 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 reasonableEpsilon(crs: ProjectionLike) { + if (is4326(crs)) { + return 0.01; + } else { + return 0.001; + } +} + +/** + * 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 - 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); diff --git a/src/Core/Geographic/Extent.js b/src/Core/Geographic/Extent.js index f1ab0cd0dd..39c9ddf1ab 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) @@ -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; @@ -234,7 +230,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/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/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 1587b3dd19..5885b66c96 100644 --- a/src/Core/Prefab/Globe/GlobeLayer.js +++ b/src/Core/Prefab/Globe/GlobeLayer.js @@ -3,7 +3,6 @@ import TiledGeometryLayer from 'Layer/TiledGeometryLayer'; import { ellipsoidSizes } from 'Core/Math/Ellipsoid'; import { globalExtentTMS, schemeTiles } from 'Core/Tile/TileGrid'; import { GlobeTileBuilder } from 'Core/Prefab/Globe/GlobeTileBuilder'; -import CRS from 'Core/Geographic/Crs'; // matrix to convert sphere to ellipsoid const worldToScaledEllipsoid = new THREE.Matrix4(); @@ -18,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 @@ -47,24 +46,35 @@ 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(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, + const tileMatrixSets = [ + 'EPSG:4326', + 'EPSG:3857', ]; + const uvCount = config.tileMatrixSets.length; const builder = new GlobeTileBuilder({ 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++) { @@ -104,7 +114,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 fff4033d02..ce90a53661 100644 --- a/src/Core/Prefab/GlobeView.js +++ b/src/Core/Prefab/GlobeView.js @@ -8,7 +8,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'; /** @@ -96,11 +95,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; @@ -143,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 52757cd57c..97d620d4bb 100644 --- a/src/Core/Prefab/Planar/PlanarLayer.js +++ b/src/Core/Prefab/Planar/PlanarLayer.js @@ -2,21 +2,19 @@ import * as THREE from 'three'; import TiledGeometryLayer from 'Layer/TiledGeometryLayer'; import { globalExtentTMS } from 'Core/Tile/TileGrid'; -import CRS from 'Core/Geographic/Crs'; 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. @@ -35,18 +33,28 @@ 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 { + 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/Core/Tile/Tile.js b/src/Core/Tile/Tile.js index 1bd053a122..d0912b7c90 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'; @@ -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 063b3c91ce..a6eb619b35 100644 --- a/src/Core/Tile/TileGrid.js +++ b/src/Core/Tile/TileGrid.js @@ -1,5 +1,4 @@ import * as THREE from 'three'; -import 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 675a2822bf..a12b4f967a 100644 --- a/src/Core/TileMesh.js +++ b/src/Core/TileMesh.js @@ -1,5 +1,4 @@ import * as THREE from 'three'; -import 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 044bf6b205..72dff1f4ea 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'; @@ -32,6 +32,7 @@ export const VIEW_EVENTS = { INITIALIZED: 'initialized', COLOR_LAYERS_ORDER_CHANGED, CAMERA_MOVED: 'camera-moved', + DISPOSED: 'disposed', }; /** @@ -54,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; @@ -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/C3DTilesLayer.js b/src/Layer/C3DTilesLayer.js index d8fd1d43f8..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) @@ -105,6 +107,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; @@ -142,7 +145,7 @@ 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} */ + /** @type {Style | null} */ this._style = config.style || null; /** @type {Map} */ @@ -386,21 +389,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 +462,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/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/OGC3DTilesLayer.js b/src/Layer/OGC3DTilesLayer.js index 1a75f9514a..181ae392b6 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 @@ -69,6 +70,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', }; /** @@ -112,6 +125,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. @@ -126,8 +146,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 }); @@ -152,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 @@ -186,29 +203,32 @@ 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; } + /** - * 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; + _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.lruCache = lruCache; - } - if (downloadQueue === null) { - downloadQueue = this.tilesRenderer.downloadQueue; - } 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]; + }); } } @@ -249,6 +269,12 @@ class OGC3DTilesLayer extends GeometryLayer { }); view.notifyChange(this); }); + + + this._setupCacheAndQueues(view); + this._setupEvents(); + + // Start loading tileset and tiles this.tilesRenderer.update(); } @@ -330,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) { @@ -370,7 +396,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; } 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/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/Parser/XbilParser.js b/src/Parser/XbilParser.js index 8ce7c7d588..5f31cf2edd 100644 --- a/src/Parser/XbilParser.js +++ b/src/Parser/XbilParser.js @@ -64,8 +64,17 @@ 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) { diff --git a/src/Renderer/OBB.js b/src/Renderer/OBB.js index f6f820d8e7..e91cf0d6ff 100644 --- a/src/Renderer/OBB.js +++ b/src/Renderer/OBB.js @@ -1,8 +1,8 @@ import * as THREE from 'three'; import { TileGeometry } from 'Core/TileGeometry'; import { GlobeTileBuilder } from 'Core/Prefab/Globe/GlobeTileBuilder'; +import * as CRS from 'Core/Geographic/Crs'; import Coordinates from 'Core/Geographic/Coordinates'; -import CRS from 'Core/Geographic/Crs'; // get oriented bounding box of tile const builder = new GlobeTileBuilder({ uvCount: 1 }); @@ -125,7 +125,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/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/src/Renderer/RasterTile.js b/src/Renderer/RasterTile.js index 05e936cddc..af87699426 100644 --- a/src/Renderer/RasterTile.js +++ b/src/Renderer/RasterTile.js @@ -1,7 +1,6 @@ import * as THREE from 'three'; 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; @@ -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 b4dc474add..a65ce63271 100644 --- a/src/Source/FileSource.js +++ b/src/Source/FileSource.js @@ -1,6 +1,5 @@ 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 @@ -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/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..6d669ceec3 100644 --- a/src/Source/TMSSource.js +++ b/src/Source/TMSSource.js @@ -3,9 +3,8 @@ 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); +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 60d346c657..5d2c771fa6 100644 --- a/src/Source/WFSSource.js +++ b/src/Source/WFSSource.js @@ -1,6 +1,5 @@ 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]); @@ -125,26 +124,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) { @@ -162,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/src/Source/WMSSource.js b/src/Source/WMSSource.js index 0f5d9c8645..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'; @@ -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/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, }, - }); + }; }); }); 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/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 }); }); }); 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/crs.js b/test/unit/crs.js index 3e62240ac7..4f9a1caff1 100644 --- a/test/unit/crs.js +++ b/test/unit/crs.js @@ -1,9 +1,9 @@ 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', '+no_defs'); +proj4.defs('EPSG:INVALID', '+units=invalid +no_defs'); describe('CRS assertions', function () { it('should assert that the CRS is valid', function () { @@ -30,10 +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.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 () { @@ -41,8 +42,15 @@ describe('CRS assertions', function () { assert.ok(!CRS.is4326('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 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 reasonable epsilon', function () { + assert.strictEqual(CRS.reasonableEpsilon('EPSG:4326'), 0.01); + assert.strictEqual(CRS.reasonableEpsilon('EPSG:3857'), 0.001); }); }); diff --git a/test/unit/dataSourceProvider.js b/test/unit/dataSourceProvider.js index 93910d6f7a..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,9 +94,11 @@ describe('Provide in Sources', function () { }, }), }); + featureLayer.update = FeatureProcessing.update; + featureLayer.convert = Feature2Mesh.convert(); 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/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..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: [ - 'TMS:4326', - 'TMS:3857', - ], - parent: { tileMatrixSets: [ - 'TMS:4326', - 'TMS:3857', + '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 2e4ebe488a..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: [ - 'TMS:4326', - 'TMS:3857', - ], - parent: { tileMatrixSets: [ - 'TMS:4326', - 'TMS:3857', + 'EPSG:4326', + 'EPSG:3857', ], - }, - }); + }; const nodeLayer = new RasterColorTile(material, layer); nodeLayer.level = 10; 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 6014e877a3..9e9d5a9b22 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', }; @@ -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]); @@ -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', @@ -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)); @@ -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', @@ -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 1237d5e94c..aea00caef4 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, 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/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); }); 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; }