UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

421 lines (420 loc) 18.1 kB
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _WmsClient_instances, _WmsClient_manageLayerOptions; import { Image as ImageLayer } from 'ol/layer'; import ImageWMS from 'ol/source/ImageWMS'; import WMSGetFeatureInfo from 'ol/format/WMSGetFeatureInfo'; import StateManager from '../state/statemanager'; import SelectionParam from '../../models/selectionparam'; import LayerManager from '../layers/layermanager'; import ConfigManager from '../configuration/configmanager'; class WmsClient { get state() { return StateManager.getInstance().state; } get audienceExcludedPaths() { return (this.configManager.Config.oauth?.issuer.audienceExcludedPaths ?? this.configManager.Config.gmfauth?.audienceExcludedPaths ?? []); } constructor(ogcServer, map) { _WmsClient_instances.add(this); Object.defineProperty(this, "map", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "ogcServer", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "layerManager", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "configManager", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "resolutionTolerance", { enumerable: true, configurable: true, writable: true, value: 5 }); // The Id of this dictionary if an unique ID that allow the differenciantion of server queries. // For example, a combination of server URL and ImageType could be used. // Each element of this dictionary will generate 1 WMS server query Object.defineProperty(this, "layers", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "olayer", { enumerable: true, configurable: true, writable: true, value: void 0 }); // Independent layers are layers that need to be queried alone // (not combine to other WMS layers in the same query) // The treeItemId will be used as key for this dictionary Object.defineProperty(this, "independentLayers", { enumerable: true, configurable: true, writable: true, value: {} }); Object.defineProperty(this, "basemapLayers", { enumerable: true, configurable: true, writable: true, value: [] }); this.ogcServer = ogcServer; this.map = map; this.layerManager = LayerManager.getInstance(); this.configManager = ConfigManager.getInstance(); } get uniqueQueryId() { return this.ogcServer.uniqueWmsQueryId; } removeAllBasemapLayers() { this.basemapLayers.forEach((basemap) => { this.map.removeLayer(basemap); }); this.basemapLayers = []; } addLayer(layerWms) { this.layers.push(layerWms); if (!this.olayer) { // Create a new ol layer and add it to the right server this.olayer = new ImageLayer(); // Set zindex for this new layer // (The bigger the order is, the deeper in the map it should be displayed.) // (order is the inverse of z-index) this.olayer.setZIndex(-layerWms.order); this.map.addLayer(this.olayer); } const source = this.createImageWMSSource(); this.olayer.setSource(source); __classPrivateFieldGet(this, _WmsClient_instances, "m", _WmsClient_manageLayerOptions).call(this, layerWms); } createImageWMSSource(layerList = this.layers) { const url = this.ogcServer.url; const imageType = this.ogcServer.imageType; const orderedLayers = layerList.slice().sort((l1, l2) => { return l2.order - l1.order; }); const orderedLayerNames = this.getOpenLayerLayerNames(orderedLayers); const requestedUrl = new URL(url); const shouldExclude = this.audienceExcludedPaths.some((pattern) => new RegExp(pattern).test(requestedUrl.pathname)); const crossOrigin = this.state.oauth.audience?.includes(requestedUrl.hostname) && !shouldExclude ? 'use-credentials' : 'anonymous'; const source = new ImageWMS({ url: url, params: { LAYERS: orderedLayerNames, FORMAT: imageType }, crossOrigin: crossOrigin }); // We intercept the event in order to set an error icon if the WMS query has an error // Otherwise we do no see anything on the client. source.on('imageloaderror', () => { for (const layerWms of layerList) { this.layerManager.setError(layerWms, 'Image cannot be loaded from WMS Server'); } }); source.on('imageloadend', () => { for (const layerWms of layerList) { this.layerManager.unsetError(layerWms); } }); return source; } addBasemapLayer(layerWms) { const source = this.createImageWMSSource([layerWms]); const olayer = new ImageLayer({ source: source, opacity: layerWms.opacity }); // For basemap, set a minimal number (arbitrary defined to less than -5000) olayer.setZIndex(-5000 - layerWms.order); this.basemapLayers.push(olayer); this.map.addLayer(olayer); } removeLayer(layerWms) { if (this.layerExists(layerWms)) { if (layerWms.treeItemId in this.independentLayers) { const olayer = this.independentLayers[layerWms.treeItemId].olayer; delete this.independentLayers[layerWms.treeItemId]; this.map.removeLayer(olayer); } else if (this.layerInStandardLayers(layerWms)) { // Get existing ol layer for this server and remove the wms layer from the source this.layers = this.layers.filter((l) => l.treeItemId !== layerWms.treeItemId); if (this.layers.length > 0) { // There are still layers in the list. => We update the layer source const source = this.createImageWMSSource(); this.olayer.setSource(source); } else if (this.olayer) { // No more layer here. // => We simply remove the whole layer this.map.removeLayer(this.olayer); delete this.olayer; } } else { console.warn('Nothing to remove !'); } } else { console.error(`Cannot remove WMS-Layer ${layerWms.name} from the map: it does not exist!`); } } layerExists(layerWms) { return this.layerInStandardLayers(layerWms) || this.layerIsIndependantLayer(layerWms); } layerInStandardLayers(layerWms) { return this.layers.some((l) => l.treeItemId === layerWms.treeItemId); } layerIsIndependantLayer(layerWms) { return layerWms.treeItemId in this.independentLayers; } getOLayer(layerWms) { if (layerWms.treeItemId in this.independentLayers) { return this.independentLayers[layerWms.treeItemId].olayer; } if (this.layerInStandardLayers(layerWms)) { return this.olayer; } return null; } // TODO SMS: Refactor this so it's actually a helper for the private function changeOpacity(layerWms) { __classPrivateFieldGet(this, _WmsClient_instances, "m", _WmsClient_manageLayerOptions).call(this, layerWms); } // TODO SMS: Refactor this so it's actually a helper for the private function changeFilter(layerWms) { __classPrivateFieldGet(this, _WmsClient_instances, "m", _WmsClient_manageLayerOptions).call(this, layerWms); } changeTimeRestriction(layerWms) { __classPrivateFieldGet(this, _WmsClient_instances, "m", _WmsClient_manageLayerOptions).call(this, layerWms); } makeLayerIndependent(layerWms) { if (layerWms.treeItemId in this.independentLayers) { // The layer is already independent. => nothing to do here. } else if (this.layerInStandardLayers(layerWms)) { // First, we remove the layer from the default layer this.removeLayer(layerWms); // Then, we create a new layer const source = this.createImageWMSSource([layerWms]); const olayer = new ImageLayer({ source: source, opacity: layerWms.opacity }); this.independentLayers[layerWms.treeItemId] = { layerWms: layerWms, olayer: olayer }; this.updateLayerFilter(layerWms); this.updateTimeRestriction(layerWms); this.map.addLayer(olayer); } else { throw new Error('A layer can be made independent only if it has already been added to the map.'); } } updateLayerFilter(layerWms) { if (layerWms.treeItemId in this.independentLayers) { const olayer = this.independentLayers[layerWms.treeItemId].olayer; const source = olayer.getSource(); if (layerWms.hasFilter) { const filterStr = this.buildFilterQuery(layerWms); source.updateParams({ FILTER: filterStr }); } else { // If present, remove the filter parameter const params = source.getParams(); if (params.FILTER) { delete params.FILTER; source.updateParams(params); } } } } updateTimeRestriction(layerWms) { const olayer = this.independentLayers[layerWms.treeItemId].olayer; const source = olayer.getSource(); if (layerWms.hasTimeRestriction) { olayer.getSource().updateParams({ TIME: layerWms.timeRestriction }); } else { // If present, remove the time parameter const params = source.getParams(); if (params.TIME) { delete params.TIME; source.updateParams(params); } } } selectFeatures(extent) { const selectionParams = []; selectionParams.push(new SelectionParam(this.ogcServer, this.layers, extent, this.state.projection, this.olayer)); for (const key in this.independentLayers) { const indepLayer = this.independentLayers[key]; selectionParams.push(new SelectionParam(this.ogcServer, [indepLayer.layerWms], extent, this.state.projection, indepLayer.olayer)); } this.state.selection.selectionParameters.push(...selectionParams); } async getFeatureInfo(selectionParam) { //const promises: Promise<void>[] = []; const urlsAndLayerNames = this.getFeatureInfoUrl(selectionParam); const promises = Object.keys(urlsAndLayerNames).map((url) => fetch(url) .then((r) => r.text()) .then((response) => this.handleGetFeatureInfoResponse(response, url, urlsAndLayerNames))); return (await Promise.all(promises)).flat(); } getFeatureInfoUrl(param) { /* Url-layerName (feature id) objects. */ const urlsAndLayerNames = {}; param._layers.forEach((layer) => { const olLayer = param._oLayer ?? this.getOLayer(layer); if (!layer.queryable || !olLayer || !layer.isVisibleAtResolution(this.state.position.resolution)) { return; } // Layer is queryable through WMS and has an OL layer. const url = olLayer .getSource() ?.getFeatureInfoUrl([(param.selectionBox[0] + param.selectionBox[2]) / 2, (param.selectionBox[1] + param.selectionBox[3]) / 2], (olLayer.getMapInternal()?.getView().getResolution() ?? this.state.position.resolution) + this.resolutionTolerance, this.state.projection, { INFO_FORMAT: 'application/vnd.ogc.gml', FEATURE_COUNT: 300 }); if (url !== undefined) { // TODO: urlsAndLayerNames[url] points to the last layer using this URL?? inconsistent urlsAndLayerNames[url] = layer.name; } else throw new Error(`Unable to construct GetFeatureInfo URL for layer ${layer.name}`); }); return urlsAndLayerNames; } handleGetFeatureInfoResponse(response, url, urlsAndLayerNames) { const gmlFeatures = new WMSGetFeatureInfo().readFeatures(response, { dataProjection: this.state.projection, featureProjection: this.state.projection }); // Set the feature id with the layer name. gmlFeatures.forEach((feature) => { if (!feature.getId()) { // TODO: setting id to urlsAndLayerNames[url] sets id to the last layer used for the url in getFeatureInfoUrl()...? feature.setId(urlsAndLayerNames[url]); } }); return gmlFeatures; } refreshZIndexes() { // Recalculate source for Layers if (this.layers.length > 0) { // TODO: openLayer zindex = -order of the last layer in this.layers? not consistent: to improve? for (const layerWms of this.layers) { const zindex = -layerWms.order; this.olayer.setZIndex(zindex); } const source = this.createImageWMSSource(this.layers); this.olayer.setSource(source); } // Manage independant layers for (const obj of Object.values(this.independentLayers)) { const zindex = -obj.layerWms.order; obj.olayer.setZIndex(zindex); } } } _WmsClient_instances = new WeakSet(), _WmsClient_manageLayerOptions = function _WmsClient_manageLayerOptions(layerWms) { if (!this.layerExists(layerWms)) { throw new Error('Cannot change filter for this layer: it does not exist'); } if (!layerWms.hasFilter && !layerWms.hasTimeRestriction && !layerWms.isTransparent && layerWms.swiped === 'no') { // There is no more filter or opacity => Back to normal if (layerWms.treeItemId in this.independentLayers) { const olayer = this.independentLayers[layerWms.treeItemId].olayer; // We delete the layer from the transparent layers delete this.independentLayers[layerWms.treeItemId]; this.map.removeLayer(olayer); // And add it to the normal layer again this.addLayer(layerWms); } } else if (layerWms.treeItemId in this.independentLayers) { // The layer has already a configured filter or opacity => We just change the opacity and/or filter const olayer = this.independentLayers[layerWms.treeItemId].olayer; if (layerWms.isTransparent) { olayer.setOpacity(layerWms.opacity); } this.updateLayerFilter(layerWms); this.updateTimeRestriction(layerWms); } else if (this.layerInStandardLayers(layerWms)) { this.makeLayerIndependent(layerWms); } }; export default WmsClient; export class WmsClientQgis extends WmsClient { /** QGIS-server does not filter on a WMS layer made from multiple underlying WFS queryLayers * Solution: directly query the queryLayers */ getOpenLayerLayerNames(layerList) { const hasFilter = layerList.some((layerWms) => layerWms.hasFilter); if (hasFilter) { const layerNames = layerList.map((l) => l.queryLayers?.split(',')).flat(); return layerNames; } else { const layerNames = layerList.map((l) => l.layers ?? l.name); return layerNames; } } /** MapServer wants 1 filter per underlying WFS queryLayer, all included in parentheses * QGIS-server wants 1 filter per underlying WFS queryLayer, each in its own in parentheses * * MapServer: (<filter>...</filter><filter>...</filter>) * QGIS-server: (<filter>...</filter>)(<filter>...</filter>) */ buildFilterQuery(layerWms) { let filterStr = ''; if (layerWms.hasFilter) { const filter = layerWms.filter; const nbQuerylayers = layerWms.queryLayers.split(',').length; filterStr = ('(' + filter.toWmsGetMapFilter() + ')').repeat(nbQuerylayers); } return filterStr; } } export class WmsClientMapServer extends WmsClient { getOpenLayerLayerNames(layerList) { return layerList.map((l) => l.layers); } buildFilterQuery(layerWms) { let filterStr = ''; if (layerWms.hasFilter) { const filter = layerWms.filter; const nbQuerylayers = layerWms.queryLayers.split(',').length; filterStr = '(' + filter.toWmsGetMapFilter().repeat(nbQuerylayers) + ')'; } return filterStr; } } export const WmsClientDefault = WmsClientQgis; export const WmsClientGeoServer = WmsClientMapServer;