UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

379 lines (378 loc) 17.3 kB
// SPDX-License-Identifier: Apache-2.0 import { v4 as uuidv4 } from 'uuid'; import GirafeSingleton from '../../base/GirafeSingleton.js'; import Basemap from '../../models/basemaps/basemap.js'; import GroupLayer from '../../models/layers/grouplayer.js'; import LayerOsm from '../../models/layers/layerosm.js'; import LayerVectorTiles from '../../models/layers/layervectortiles.js'; import LayerWmts from '../../models/layers/layerwmts.js'; import LayerWms from '../../models/layers/layerwms.js'; import LayerCog from '../../models/layers/layercog.js'; import LayerXYZ from '../../models/layers/layerxyz.js'; import ServerOgc from '../../models/serverogc.js'; import ThemeLayer from '../../models/layers/themelayer.js'; import BasemapEmpty from '../../models/basemaps/basemapempty.js'; import BasemapSwisstopoVectorTiles from '../../models/basemaps/basemapswisstopovectortiles.js'; import BasemapOsm from '../../models/basemaps/basemaposm.js'; import { DEFAULT_OPACITY, OPACITY_FOR_DEFAULT_BASEMAP } from './themes-config.js'; import Layer from '../../models/layers/layer.js'; import { applyDefaultPrefixToUrl } from '../utils/utils.js'; class ThemesManager extends GirafeSingleton { anonymousUserInfo = { u: 'anonymous' }; get state() { return this.context.stateManager.state; } initializeSingleton() { // We have to wait the authentication to be able to load the themes with the right user-rights this.context.stateManager.subscribe('application.isReadyToLoadThemes', async () => { if (this.state.application.isReadyToLoadThemes) { await this.initialize(); } }); // This is for the next change in the oAuth status. this.context.stateManager.subscribe('oauth.status', async () => { if (this.state.themes.isLoaded && ((this.state.oauth.somethingChanged && this.state.oauth.status === 'loggedIn') || this.state.oauth.status === 'loggedOut')) { await this.initialize(); } }); } async initialize() { try { await this.loadThemes(); console.log('Themes were loaded'); const stateRestored = await this.restoreState(); const themeAddedFromUrl = this.context.themesHelper.addThemesFromUrl(); const groupAddedFromUrl = this.context.themesHelper.addGroupsFromUrl(); const layersAddedFromUrl = this.context.themesHelper.addLayersFromUrl(); const basemapAddedFromUrl = this.addBasemapFromUrl(); if (!stateRestored && !themeAddedFromUrl && !groupAddedFromUrl && !layersAddedFromUrl) { this.setDefaultTheme(); } if (!stateRestored && !basemapAddedFromUrl) { this.setDefaultBasemap(); } this.context.stateManager.state.application.isStateInitialized = true; this.context.sessionManager.beginSession(); } catch (error) { // Themes could not be loaded console.error(error); await window.gAlert('An error occurred while loading the themes. This has nothing to do with GeoGirafe and is very probably a backend-configuration error (wrong content in themes.json, or CORS error). Please check the backend configuration.', 'Backend error'); await window.gAlert('This instance of GeoGirafe cannot be used at the moment.', 'Backend error'); console.error(error); } } async restoreState() { // try to restore state if any let stateRestored = false; if (this.context.shareManager.hasSharedState()) { stateRestored = await this.context.shareManager.setStateFromUrl(); } else if (this.context.sessionManager.hasState()) { stateRestored = this.context.sessionManager.setStateFromSession(); } return stateRestored; } addBasemapFromUrl() { if (this.context.permalinkManager.hasBasemap()) { const bs = this.context.permalinkManager.getBasemap(); for (const basemap of Object.values(this.state.basemaps)) { if (basemap.name === bs) { this.state.activeBasemaps = [basemap]; return true; } } // Nothing found console.warn(`Basemap ${bs} cannot be found`); } return false; } /** * Load themes from backend and configures background layers if needed */ async loadThemes() { this.state.themes.isLoaded = false; const response = await fetch(this.context.configManager.Config.themes.url); const content = await response.json(); this.state.ogcServers = this.prepareOgcServers(content['ogcServers']); this.state.basemaps = this.prepareBasemaps(content['background_layers']); const themesAndFunctionalities = this.prepareThemes(content['themes']); this.state.themes._allThemes = themesAndFunctionalities.themes; this.state.themes._allFunctionalities = themesAndFunctionalities.themesFunctionalities; this.context.customThemesManager.loadCustomThemes(); this.state.themes.isLoaded = true; if (this.context.configManager.Config.themes.showErrorsOnStart) { // Display themes errors only if configured so. // Parse errors if any for (const error of content['errors']) { this.context.errorManager.pushMessage(uuidv4(), error, 'error'); } } } setDefaultTheme() { // Set default theme if any if (!this.isNullOrUndefinedOrBlank(this.context.configManager.Config.themes.defaultTheme)) { const themes = [ ...Object.values(this.state.themes._allThemes), ...Object.values(this.context.customThemesManager.customThemes) ]; const defaultTheme = themes.find((t) => t.name === this.context.configManager.Config.themes.defaultTheme); if (defaultTheme) { this.state.themes.lastSelectedTheme = defaultTheme; } else { // The default theme was not found console.warn(`The default theme ${this.context.configManager.Config.themes.defaultTheme} could not be found.`); } } } setDefaultBasemap() { for (const basemap of Object.values(this.state.basemaps)) { if (basemap.name === this.context.configManager.Config.basemaps.defaultBasemap) { this.state.activeBasemaps = [basemap]; break; } } } prepareOgcServers(ogcServerJson) { const servers = {}; if (ogcServerJson) { for (const serverName of Object.keys(ogcServerJson)) { const server = new ServerOgc(serverName, ogcServerJson[serverName]); servers[serverName] = server; } // Preload WFS FeatureInfos this.preloadWfsServer(servers); } return servers; } /** * Preload all WFS DescribeFeatureType * In order to limit the network overload, the servers calls are done sequetially * and each call will wait the previous one to be done */ async preloadWfsServer(ogcServers) { for (const server of Object.values(ogcServers)) { if (server.wfsSupport) { await this.context.wfsManager.getServerWfs(server); } } } prepareBasemaps(basemapJson) { const basemaps = {}; if (this.context.configManager.Config.basemaps.emptyBasemap) { const basemapEmpty = new BasemapEmpty(); basemaps[basemapEmpty.id] = basemapEmpty; } if (this.context.configManager.Config.basemaps.OSM) { const basemapOsm = new BasemapOsm(); basemaps[basemapOsm.id] = basemapOsm; } if (this.context.configManager.Config.basemaps.SwissTopoVectorTiles) { const basemapSwisstopoVectorTiles = new BasemapSwisstopoVectorTiles(); basemaps[basemapSwisstopoVectorTiles.id] = basemapSwisstopoVectorTiles; } this.addBasemapsFromConfig(basemapJson, basemaps); this.applyOpacityToBasemaps(basemaps); return basemaps; } addBasemapsFromConfig(basemapsJson, basemaps) { basemapsJson.forEach((elem) => { // Create basemap const basemap = new Basemap(elem); basemaps[basemap.id] = basemap; // List all layers in this basemap const order = { value: 0 }; if (elem.children) { // Multiple layers elem.children.forEach((child) => { const layer = this.prepareThemeLayer(child, null, order); if (layer) { basemap.layersList.push(layer); } }); } else { // Only one layer in this basemap const layer = this.prepareThemeLayer(elem, null, order); if (layer) { basemap.layersList.push(layer); } } }); } applyOpacityToBasemaps(basemaps) { for (const basemap of Object.values(basemaps)) { if (this.context.configManager.Config.basemaps.opacityBasemaps.includes(basemap.name)) { this.applyOpacityToBasemap(basemap); } } } applyOpacityToBasemap(basemap) { // If it is the default Basemap the Opacity should NOT be 0 as otherwise the User would end up seeing nothing const isDefaultBasemap = this.context.configManager.Config.basemaps.defaultBasemap == basemap.name; basemap.opacity = isDefaultBasemap ? OPACITY_FOR_DEFAULT_BASEMAP : DEFAULT_OPACITY; for (const basemapLayer of basemap.layersList) { if (basemapLayer instanceof LayerVectorTiles) { // Vector tiles layers are not supported as opacitybasemap, because the tiles cannot reprojected on the fly // And displaying a basemap from some SRID with an VT from another SRID won't work // So for the moment we do not allow VT configured as opacitybasemaps // (But the opposite will still work : a VT basemap with a WMTS opacitybasemap) } else if (basemapLayer instanceof Layer) { basemapLayer.opacity = basemap.opacity; } } } prepareThemes(themesJson) { const themes = {}; const order = { value: 0 }; const themesFunctionalities = {}; themesJson.forEach((themeJson, index) => { if (!themeJson.icon.startsWith('http') && this.context.configManager.Config.themes.imagesUrlPrefix) { themeJson.icon = this.context.configManager.Config.themes.imagesUrlPrefix + themeJson.icon; } themesFunctionalities[themeJson.id] = themeJson['functionalities']; const theme = new ThemeLayer(themeJson['id'], themeJson['name'], index, themeJson['icon'], themeJson['metadata']); themeJson.children.forEach((layerJson) => { const layer = this.prepareThemeLayer(layerJson, null, order); if (layer) { layer.parent = theme; theme.children.push(layer); } }); themes[index] = theme; }); return { themes, themesFunctionalities }; } /** * Will create layer and child layers if elem passed is a group of layers * @param elem either a layer or a group of layers * @param parentServer in case children are not mixed layers, the parentServer will apply for all children * @param order the order in the layer list * @returns the created girafe layer */ prepareThemeLayer(elem, parentServer, order) { // If a server is defined on this node, we use it. // Otherwise, we use the server of the parent const ogcServerName = elem.ogcServer ? elem.ogcServer : parentServer; // Create Layer let layer; switch (elem.type) { case 'OSM': { layer = new LayerOsm(order.value); break; } case 'VectorTiles': { layer = this.createVectorTilesLayer(elem, order); break; } case 'WMTS': { layer = this.createWmtsLayer(elem, order); break; } case 'WMS': { layer = this.createWmsLayer(ogcServerName, elem, order); break; } case 'COG': { layer = new LayerCog(elem.id, elem.name, order.value, elem.url, elem); break; } case 'XYZ': { layer = new LayerXYZ(elem.id, elem.name, order.value, elem.url, elem); break; } default: { // Group layer = this.createGroup(elem, order, ogcServerName); } } order.value = order.value + 1; return layer; } createGroup(elem, order, ogcServerName) { const options = { isDefaultChecked: elem.metadata?.isChecked, metadataUrl: elem.metadata?.metadataUrl, disclaimer: elem.metadata?.disclaimer, isDefaultExpanded: elem.metadata?.isExpanded, isExclusiveGroup: elem.metadata?.exclusiveGroup, isMixed: elem.mixed, time: elem.time }; if (options.metadataUrl) { options.metadataUrl = applyDefaultPrefixToUrl(this.context, options.metadataUrl); } const group = new GroupLayer(elem.id, elem.name, order.value, options); // Append children if (elem.children) { for (const child of elem.children) { // Append time options to child if (options.time) { child.time = { ...options.time }; } const childLayer = this.prepareThemeLayer(child, ogcServerName, order); if (childLayer) { childLayer.parent = group; group.children.push(childLayer); } } } return group; } createWmsLayer(ogcServerName, elem, order) { if (ogcServerName) { const ogcServer = this.state.ogcServers[ogcServerName]; const options = elem; if (options.metadata?.metadataUrl) { options.metadata.metadataUrl = applyDefaultPrefixToUrl(this.context, options.metadata.metadataUrl); } const wmsLayer = new LayerWms(elem.id, elem.name, order.value, ogcServer, elem); // For WMFS queries, the layers attribute will be used. It can be different from the name. // But in order to be able to define the translations only once, we use an translation alias here. if (wmsLayer.layers) { this.context.i18nManager.addTranslationAlias(wmsLayer.name, wmsLayer.layers); } return wmsLayer; } else { // Layer is invalid : it does not have any OGC-Server this.context.errorManager.pushMessage(uuidv4(), `Layer ${elem.name} (id=${elem.id}) is invalid and cannot be created: missing OGC-Server.`, 'error'); } return null; } createWmtsLayer(elem, order) { const ogcServer = elem.metadata?.ogcServer ? this.state.ogcServers[elem.metadata?.ogcServer] : undefined; const options = elem; if (options.metadata?.metadataUrl) { options.metadata.metadataUrl = applyDefaultPrefixToUrl(this.context, options.metadata.metadataUrl); } return new LayerWmts(elem.id, elem.name, order.value, elem.url, elem.layer, options, ogcServer); } createVectorTilesLayer(elem, order) { if (!elem.style || !elem.metadata?.layerName) { // Layer is invalid : it must contain style URL and layername this.context.errorManager.pushMessage(uuidv4(), `VectorTiles-Layer ${elem.name} (id=${elem.id}) is invalid and cannot be created: missing Style Url or layername.`, 'error'); } else { const options = { // TODO REG : At the moment projection is hardcoded to EPSG:3857 because we don't have any other usecase. // But the supported EPSG should be configurable in the backend projection: 'EPSG:3857', isDefaultChecked: elem.metadata?.isChecked, disclaimer: elem.metadata?.disclaimer, metadata: elem.metadata, layerName: elem.metadata.layerName }; if (options.metadata?.metadataUrl) { options.metadata.metadataUrl = applyDefaultPrefixToUrl(this.context, options.metadata.metadataUrl); } return new LayerVectorTiles(elem.id, elem.name, order.value, elem.style, options); } return null; } } export default ThemesManager;