@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
199 lines (198 loc) • 8.6 kB
JavaScript
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);
}
}
}
}