UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

289 lines (288 loc) 11.1 kB
import GroupLayer from '../../../models/layers/grouplayer.js'; import { getAbsoluteUrl, getWmtsMatrices, getWmtsUrl, MFPVectorEncoder } from '@geoblocks/mapfishprint'; import { toDegrees } from 'ol/math.js'; import LayerLocalFile from '../../../models/layers/layerlocalfile.js'; import LayerWms from '../../../models/layers/layerwms.js'; import LayerWmts from '../../../models/layers/layerwmts.js'; import VectorLayer from 'ol/layer/Vector.js'; import { isLayerVisible } from './printUtils.js'; import VectorSource from 'ol/source/Vector.js'; import LayerWmtsExternal from '../../../models/layers/layerwmtsexternal.js'; import LayerWmsExternal from '../../../models/layers/layerwmsexternal.js'; /** Supported MapFishPRint servers * see https://github.com/mapfish/mapfish-print/blob/master/core/src/main/java/org/mapfish/print/map/image/wms/WmsLayerParam.java#L123 */ const MFPSupportedServerType = new Set(['mapserver', 'geoserver', 'qgisserver']); /** * Class representing a Mapfish Print Encoder. */ export default class MFPEncoder { printResolution = 100; options; /** * Sets the options for encoding the map. */ setOptions(options) { this.options = options; } /** * Encodes the map options, notably the map state and the ol map into a top-level MFPMap object. * @returns A Promise that resolves with the encoded map object. */ encodeMap(options) { this.setOptions(options); const mapManager = options.mapManager; const view = mapManager.getMap().getView(); const center = view.getCenter() ?? [0, 0]; const projection = view.getProjection().getCode(); const rotation = toDegrees(view.getRotation()); this.printResolution = view.getResolution() || 100; const allLayers = this.getAllLayers(options.state); const mfpLayers = this.encodeLayers(allLayers); mfpLayers.unshift(...this.encodeSpecialLayers(mapManager, options.customizer)); return { center, dpi: options.dpi, projection, rotation, scale: options.scale, layers: mfpLayers, useNearestScale: false }; } /** * @returns An array of all active layers from the state object in the right order */ getAllLayers(state) { // Sort layers from layertree const treeLayers = this.getFlatLayers(state.layers.layersList).filter((layer) => layer.active); const sortedLayers = [...treeLayers].sort((a, b) => a.order - b.order); // But basemaps are always at the bottom const baseMap = state.activeBasemaps?.flatMap((activeBasemap) => activeBasemap.layersList) ?? []; return [...sortedLayers, ...baseMap]; } /** * @returns The flattened array of base layers. */ getFlatLayers(baseLayers) { return baseLayers.reduce((layers, layer) => { const children = layer.children; if (children) { const group = layer; // If all the children have the same ogcServer and the group // has no grandchildren, we keep the layers together if (group.isMixed === false && group.hasGrandChildren === false) { layers.push(...group.children); } else { const resultLayers = this.getFlatLayers(group.children); layers.push(...resultLayers); } } else { layers.push(layer); } return layers; }, []); } /** * Encode special layers, meaning not-in-the-layer-tree layers. * These layers are from the mapManager.getLayersToPrint method. * @returns An array of encodedlayers. */ encodeSpecialLayers(mapManager, customizer) { const encoded = [...mapManager.getLayersToPrint()] .sort((olayerA, olayerB) => { const indexA = olayerA.getZIndex() ?? 0; const indexB = olayerB.getZIndex() ?? 0; return indexB - indexA; }) .map((olayer) => { if (olayer instanceof VectorLayer) { return new MFPVectorEncoder(olayer.getLayerState(), customizer).encodeVectorLayer(this.printResolution); } return null; }); return encoded.filter((spec) => spec !== null); } /** * Encode layers recursively. * @returns a list of Mapfish print layer specs for the given layers. */ encodeLayers(baseLayers) { const mfpLayers = []; for (const layer of baseLayers) { const spec = this.encodeLayer(layer); if (spec) { if (Array.isArray(spec)) { mfpLayers.push(...spec); } else { mfpLayers.push(spec); } } } return mfpLayers; } /** * Encodes a layer object according to its type and options. * @returns A promise that resolves to an array of MFP layers, a single MFP layer, or null. */ encodeLayer(layer) { if (layer instanceof GroupLayer) { return this.encodeGroupLayer(layer); } if (layer instanceof LayerWms || layer instanceof LayerWmsExternal) { return this.encodeWmsLayer(layer); } if (layer instanceof LayerWmts || layer instanceof LayerWmtsExternal) { return this.encodeTileWmtsLayer(layer); } if (layer instanceof LayerLocalFile) { return this.encodeLocalFileLayer(layer); } console.warn('Unsupported layer type for encoding: ', layer); return null; } /** * Only non mixed groups of WMS layers are supported. * * Will convert a group of WMS layers sharing the same OGC server into a single MFPWmsLayer * where its 'layers' property is a concatenation of all child layers' 'layers' property. * * @param groupLayer */ encodeGroupLayer(groupLayer) { if (groupLayer.isMixed) { console.error("A mixed group layer is passed to print and shouldn't", groupLayer); return null; } if (groupLayer.hasGrandChildren) { console.error("A group layer that has grandchildren is passed to print and shouldn't", groupLayer); return null; } const firstWmsLayer = groupLayer.children.shift(); const spec = this.encodeWmsLayer(firstWmsLayer); if (!spec) { return this.encodeGroupLayer(groupLayer); } for (const childLayer of groupLayer.children) { spec.layers.push(childLayer.layers || ''); } return spec; } /** * Encodes an image layer from a WMS layer object. * @returns The encoded image layer or null if the layer is not visible. */ encodeWmsLayer(layerWms) { if (!isLayerVisible(layerWms, this.options?.printResolution)) { return null; } let url = layerWms.ogcServer.url; if (layerWms.ogcServer.url.startsWith('//')) { url = window.location.protocol + url; } const url_url = new URL(url); const customParams = { TRANSPARENT: 'TRUE' }; if (url_url.searchParams) { url_url.searchParams.forEach((value, key) => { customParams[key] = value; }); } let serverType = layerWms.ogcServer.type; // Remove unsupported serverType (otherwise, you'll get an error from the print server). if (!MFPSupportedServerType.has(serverType)) { serverType = undefined; } const layers = layerWms.layers?.split(',') ?? ['']; // Add empty styles if needed let styles = layerWms.style?.split(','); styles ??= ['']; // Get the same number of styles than layers to print while (layers.length > styles.length) { styles.push(''); } const object = { baseURL: getAbsoluteUrl(url_url.origin + url_url.pathname), imageFormat: layerWms.ogcServer.imageType ?? 'image/png', layers: layers, customParams: customParams, serverType: serverType, type: 'wms', opacity: layerWms.opacity, useNativeAngle: layerWms.printNativeAngle, styles: styles }; return object; } /** * Encodes a WMTS layer into a MFPWmtsLayer or MFPWmsLayer object. * @returns The encoded layer object, or null if the layer is not visible. */ encodeTileWmtsLayer(layerWmts) { if (!isLayerVisible(layerWmts, this.options?.printResolution)) { return null; } if (layerWmts.printLayers || layerWmts.wmsLayers) { // Print configured wms layer instead. const spec = this.encodeWmsFromWmtsLayer(layerWmts); if (spec) { return spec; } } const oLayer = layerWmts._olayer; const source = oLayer?.getSource(); if (!oLayer || !source) { console.warn('Can not encode tile layer: ', layerWmts); return null; } const dimensionParams = source.getDimensions(); const dimensions = Object.keys(dimensionParams); return { baseURL: getWmtsUrl(source), dimensions, dimensionParams, imageFormat: source.getFormat(), layer: source.getLayer(), matrices: getWmtsMatrices(source), matrixSet: source.getMatrixSet(), name: layerWmts.name, opacity: layerWmts.opacity, requestEncoding: source.getRequestEncoding(), style: source.getStyle(), type: 'wmts', version: source.getVersion() }; } /** * Encodes a local file layer. * @returns The encoded local file layer or null if the layer is not visible. */ encodeLocalFileLayer(layer) { const vectorSource = new VectorSource({ features: layer._features }); const olayer = new VectorLayer({ source: vectorSource }); if (!this.options?.customizer) { return null; } return new MFPVectorEncoder(olayer.getLayerState(), this.options.customizer).encodeVectorLayer(this.printResolution); } /** * Encodes a WMS layer from a WMTS layer. * @returns The encoded WMS layer or null if the ogcServer is missing. */ encodeWmsFromWmtsLayer(layerWmts) { if (!layerWmts.ogcServer) { console.error('Missing ogcServer'); return null; } const printLayers = layerWmts.printLayers ?? layerWmts.wmsLayers ?? ''; const layerWms = new LayerWms(0, printLayers, 0, layerWmts.ogcServer, { layers: printLayers }); layerWms.opacity = layerWmts.opacity; return this.encodeWmsLayer(layerWms); } }