UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

199 lines (198 loc) 8.6 kB
import * as Cesium from 'cesium'; import proj4 from 'proj4'; import WmsManager3d from './wmsmanager3d.js'; import GroupLayer from '../../../models/layers/grouplayer.js'; import LayerWms from '../../../models/layers/layerwms.js'; import { applyOpacityToLayers } from '../../../tools/utils/utils.js'; /** * The 3D Globe, based on Cesium and synchronized with the OpenLayers map by OLCesium. * * This module is the only place where Cesium and OLCesium are used. It is loaded on demand * by the map-3d Component, so that those (heavy) libraries stay out of the initial bundle * as long as the 3D Map is not displayed. */ export default class Globe { context; toolName; pixelTolerance; // TODO REG : Howto use the right type here without importing the whole library (it needs to be imported only on demand) ? // This works but needs the library: type OLCesiumType = typeof OLCesium; olCesium; scene; wmsManager3d; get state() { return this.context.stateManager.state; } get config() { return this.context.configManager.Config.map3d; } constructor(context, toolName, pixelTolerance) { this.context = context; this.toolName = toolName; this.pixelTolerance = pixelTolerance; } /** * Loads Cesium and OLCesium and initializes the globe on the given target element. */ async initialize(target) { window.Cesium = Cesium; const olcs = await import('olcs'); this.olCesium = new olcs.default({ map: this.context.mapManager.getMap(), target: target, time: () => { const date = new Date(this.state.globe.shadowsTimestamp); return Number.isNaN(date.getTime()) ? Cesium.JulianDate.now() : Cesium.JulianDate.fromDate(date); } }); const scene = this.olCesium.getCesiumScene(); this.scene = scene; const config = this.config; scene.screenSpaceCameraController.maximumZoomDistance = config.maximumZoomDistance ?? 30000; // Add terrain if (config.terrainUrl) { scene.terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl(config.terrainUrl); } // Add terrain imagery let coverage = Cesium.Rectangle.MAX_VALUE; if (config.terrainImagery) { if (config.terrainImagery.coverageArea) { coverage = Cesium.Rectangle.fromDegrees(...config.terrainImagery.coverageArea); } scene.imageryLayers.addImageryProvider(new Cesium.UrlTemplateImageryProvider({ url: config.terrainImagery.url, minimumLevel: config.terrainImagery.minLoD ?? 0, maximumLevel: config.terrainImagery.maxLoD, tilingScheme: config.terrainImagery.srid === 3857 ? new Cesium.WebMercatorTilingScheme() : new Cesium.GeographicTilingScheme(), rectangle: coverage })); } // Add 3D-Tiles layers const tilesetOptions = { // If the error of the model is higher than this, we increase the resolution maximumScreenSpaceError: 0.5, // Enable different level of details based on the distance from the camera dynamicScreenSpaceError: true, // Model error at the max distance from the camera (higher = distant models are of lower quality) dynamicScreenSpaceErrorFactor: config.tilesetsMaxError ?? 7 }; config.tilesetsUrls.forEach((tilesetUrl) => { Cesium.Cesium3DTileset.fromUrl(tilesetUrl, tilesetOptions).then((t) => scene.primitives.add(t)); }); // REG: ambientOcclusion was temporary deactivated because of performance impact and horizontal lines artefacts // const ambientOcclusion = scene.postProcessStages.ambientOcclusion; // ambientOcclusion.enabled = true; // ambientOcclusion.uniforms.bias = 0.5; // ambientOcclusion.uniforms.stepSize = 1; // ambientOcclusion.uniforms.blurStepSize = 1; // REG: Adding the following line solves the problem, but it remains a log less performatn with ambiant occlusion. // So I don't know what we want to do with it. // See https://github.com/CesiumGS/cesium/issues/13039#issuecomment-3583233494 // viewer.camera.frustum.near = 1.0; this.registerSelection(); this.wmsManager3d = new WmsManager3d(scene, this.context); this.state.layers.layersList.forEach((l) => this.addAllActiveLayers(l)); const camera = scene.camera; camera.changed.addEventListener(() => { console.debug('Cesium camera moved'); this.state.globe.camera = { heading: camera.heading, pitch: camera.pitch, roll: camera.roll }; }, 1); } registerSelection() { const scene = this.scene; const pickOnGlobe = (position) => { const ray = scene.camera.getPickRay(position); return ray == undefined ? undefined : scene.globe.pick(ray, scene); }; const cesiumScreenToLocalCoord = (position) => { const cart = Cesium.Cartographic.fromCartesian(pickOnGlobe(position)); const longLat = [Cesium.Math.toDegrees(cart.longitude), Cesium.Math.toDegrees(cart.latitude)]; return proj4('EPSG:4326', this.context.configManager.Config.map.srid, longLat); }; this.context.userInteractionManager.registerListener('globe.select', true, this.toolName); const eventHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); eventHandler.setInputAction((event) => { // If the click is on the map and selection is allowed if (Cesium.defined(event.position) && this.context.userInteractionManager.canListenerExecute('globe.select', this.toolName)) { const topLeftScreen = event.position.clone(); topLeftScreen.x -= this.pixelTolerance; topLeftScreen.y -= this.pixelTolerance; const bottomRightScreen = event.position.clone(); bottomRightScreen.x += this.pixelTolerance; bottomRightScreen.y += this.pixelTolerance; const topLeft = cesiumScreenToLocalCoord(topLeftScreen); const bottomRight = cesiumScreenToLocalCoord(bottomRightScreen); this.context.selectionManager.select([topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]]); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); } setEnabled(enabled) { this.olCesium.setEnabled(enabled); } setShadows(shadows) { this.scene.shadowMap.enabled = this.scene.globe.enableLighting = shadows; } setCamera(camera) { if (!camera) { return; } this.scene.camera.setView({ destination: this.scene.camera.position, // Keep current position orientation: { heading: camera.heading, pitch: camera.pitch, roll: camera.roll } }); } addAllActiveLayers(layer) { if (layer instanceof LayerWms) { if (layer.active) { this.wmsManager3d.addLayer(layer); } } else if (layer instanceof GroupLayer) { layer.children.forEach((l) => this.addAllActiveLayers(l)); } } onLayerToggled(layer) { if (layer.active) { this.wmsManager3d.addLayer(layer); } else { this.wmsManager3d.removeLayer(layer); } } changeOpacity(layer) { if (layer instanceof LayerWms) { this.wmsManager3d.changeOpacity(layer); } } changeBasemapOpacity(basemap) { applyOpacityToLayers(basemap.opacity, basemap.layersList, (layer) => this.changeOpacity(layer)); } changeFilter(layer) { this.wmsManager3d.changeFilter(layer); } onChangeBasemaps(basemaps) { this.wmsManager3d.removeAllBasemapLayers(); for (const layer of basemaps.flatMap((basemap) => basemap.layersList)) { if (layer instanceof LayerWms) { this.wmsManager3d.addBasemapLayer(layer); } } // Apply default opacity for (const basemap of basemaps) { if (!basemap.opacityDisabled) { this.changeBasemapOpacity(basemap); } } } }