UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

306 lines (305 loc) 13.6 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 _WfsClient_instances, _WfsClient_describeFeatureType; import { WFS } from 'ol/format'; import GML3 from 'ol/format/GML3'; import ConfigManager from '../configuration/configmanager'; import StateManager from '../state/statemanager'; import ServerWfs from '../../models/serverwfs'; import { xmlTypesStrList } from '../../models/xmlTypes'; import LayerTimeFormatter from '../time/layertimeformatter'; import { and } from 'ol/format/filter'; class WfsClient { get state() { return this.stateManager.state; } constructor(ogcServer, options) { _WfsClient_instances.add(this); Object.defineProperty(this, "stateManager", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "ogcServer", { enumerable: true, configurable: true, writable: true, value: void 0 }); //TODO: make this configurable Object.defineProperty(this, "maxFeatures", { enumerable: true, configurable: true, writable: true, value: 300 }); Object.defineProperty(this, "featurePrefix", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "featureNS", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "urlParameters", { enumerable: true, configurable: true, writable: true, value: new URLSearchParams() }); Object.defineProperty(this, "serverWfs", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.ogcServer = ogcServer; this.featureNS = options.featureNS; this.featurePrefix = options.featurePrefix; this.configMaxFeatures(); this.stateManager = StateManager.getInstance(); } get wfsUrl() { return this.ogcServer.urlWfs ?? ''; } configMaxFeatures() { ConfigManager.getInstance() .loadConfig() .then((config) => { this.maxFeatures = config.selection.maxFeature ?? this.maxFeatures; }); } getServerWfs() { return this.describeFeatureType(); } describeFeatureType() { if (!this.serverWfs) { this.serverWfs = __classPrivateFieldGet(this, _WfsClient_instances, "m", _WfsClient_describeFeatureType).call(this); this.serverWfs.catch((error) => { const msg = 'WFS server with URL ' + this.wfsUrl + ' could not be initialized.'; console.error(msg + ' Error:', error); this.serverWfs = undefined; }); } return this.serverWfs; } initializeAttribute(tag, serverWfs, elementTypeToName) { // takes an xml element, extract the attribute's type and name // adds it to the serverWfs featureType const typeName = tag.getAttribute('name'); if (!typeName) { throw new Error('Could not find a name for the complex type'); } const featureType = elementTypeToName[typeName]; const elements = tag.getElementsByTagName('sequence')[0].getElementsByTagName('element'); let geometryAttributeFound = false; for (const element of elements) { if (this.manageLayerAttribute(serverWfs, element, featureType)) { geometryAttributeFound = true; } } // If we didn't find any geometry attribute for this featureType, then we have a problem // Because the wfs query won't be possible if (!geometryAttributeFound) { throw new Error('No Geometry column for the type ' + featureType); } } manageLayerAttribute(serverWfs, element, featureType) { let geometryAttributeFound = false; const type = element.getAttribute('type'); if (type?.startsWith('gml:')) { // We are on the geometry attribute const geometryAttributeName = element.getAttribute('name'); if (geometryAttributeName) { serverWfs.featureTypeToGeometryColumnName[featureType] = geometryAttributeName; geometryAttributeFound = true; } else { throw new Error('Why is geometryAttributeName null here ?'); } } else { // We are not on an geometry attribute, but on a normal attribute // We update the WMS Layer with its attributes informations const attrName = element.getAttribute('name'); const attrType = element.getAttribute('type'); if (!attrName || !attrType) { console.warn(`Error while loading attribute for layer ${featureType}. Querying or filtering this layer won't work correctly.`); } else if (this.validateLayerAttributeType(attrType)) { serverWfs.addLayerAttribute(featureType, attrName, attrType); } else { console.warn(`Unmanaged layer attribute type: ${attrType} for attribute ${attrName} of featureType ${featureType}. ${attrName} ignored.`); } } return geometryAttributeFound; } validateLayerAttributeType(type) { return xmlTypesStrList.includes(type); } getElementToTypeName(xml) { const elementTypeToName = {}; const elements = xml.querySelectorAll(':scope>element'); for (const element of elements) { if (element.hasAttribute('name') && element.hasAttribute('type')) { const name = element.getAttribute('name'); let type = element.getAttribute('type'); if (type && name) { if (type.includes(':')) { type = type.split(':')[1]; } elementTypeToName[type] = name; } } else { console.log('What happend with this element? element', element); } } return elementTypeToName; } getDescribeFeatureTypeUrl() { const url = new URL(this.wfsUrl); url.searchParams.set('service', 'WFS'); url.searchParams.set('request', 'DescribeFeatureType'); // TODO REG: Manage different WFS versions url.searchParams.set('version', '1.1.0'); return url.href; } async getFeature(selectionParam) { // First, keep only queryable and visible layers // And verify that all layers have the same WFS URL const queryableLayers = selectionParam._layers.filter((l) => l.wfsQueryable && l.isVisibleAtResolution(this.state.position.resolution)); if (queryableLayers.length <= 0) { return []; } // Ensure the WFS server is initialized const serverWfs = await this.getServerWfs(); // Get the geometry column name of each layer // TODO REG : (not sure) This could probably be simplify by initializing a property in the ServerWfs object // containing the name of the geometry column during the WFS initialization. const featureTypes = queryableLayers.map((l) => l.queryLayers.split(',')).flat(1); const geometryColumnNameToFeatureType = serverWfs.getGeometryColumnNameToFeatureTypes(featureTypes); const olFilter = queryableLayers[0].filter?.toOpenLayersFilter(); const timeFilter = this.setTimeRestriction(queryableLayers[0]); const getFeatureOptions = { srsName: selectionParam.srid, bbox: selectionParam.selectionBox, filter: olFilter && timeFilter ? and(olFilter, timeFilter) : olFilter ?? timeFilter }; const getFeatureRequests = Object.entries(geometryColumnNameToFeatureType).map(async ([columnName, featureTypes]) => this.getFeatureRaw(featureTypes, { geometryName: columnName, ...getFeatureOptions })); const getFeatureResponses = await Promise.all(getFeatureRequests); const selectedFeatures = getFeatureResponses.flat(); return selectedFeatures; } completeGetFeatureOptions(featureTypes, options) { return { featurePrefix: this.featurePrefix, featureNS: this.featureNS, ...options, featureTypes: featureTypes }; } async getFeatureRaw(featureTypes, getFeatureOptions) { if (getFeatureOptions.bbox && !getFeatureOptions.geometryName) { throw new Error(`WFS GetFeature: not possible to query bbox ${getFeatureOptions.bbox} without a geometryName.\nFeatureTypes: ${featureTypes}\nWFS: ${this.wfsUrl}`); } const options = this.completeGetFeatureOptions(featureTypes, getFeatureOptions); options.maxFeatures = options.maxFeatures ? options.maxFeatures : this.maxFeatures; // WFS GetFeature const featureRequest = new WFS().writeGetFeature(options); // If URL parameters have been specified, add them to the URL (e.g. TIME parameter) const url = new URL(this.wfsUrl); for (const [key, value] of this.urlParameters.entries()) { url.searchParams.set(key, value); } const response = await fetch(url, { method: 'POST', body: new XMLSerializer().serializeToString(featureRequest) }); const gml = await response.text(); // TODO REG: Do we always want to use the format GML3 here ? const features = new GML3().readFeatures(gml); return features; } /** * Sets or removes a time restriction on the provided query layer. Depending on the presence of a time attribute, * either a temporal XML filter is created or a TIME parameter is added to the URL. * * @param {QueryableLayerWms} queryLayer - The layer on which the time restriction is to be applied. * @return {Filter | undefined} Returns a temporal XML filter if a time attribute exists; otherwise, undefined. */ setTimeRestriction(queryLayer) { if (queryLayer.timeAttribute) { return this.getTimeRestrictionFilter(queryLayer); } else { this.addTimeRestrictionAsUrlParameter(queryLayer); } return undefined; } getTimeRestrictionFilter(queryLayer) { if (queryLayer.timeRestriction && queryLayer.timeAttribute) { // Create temporal XML filter const timeFormatter = new LayerTimeFormatter(queryLayer.timeOptions); return timeFormatter.toOpenLayersWfsFilter(queryLayer.timeRestriction, queryLayer.timeAttribute); } return undefined; } addTimeRestrictionAsUrlParameter(queryLayer) { if (queryLayer.timeRestriction) { // Add time filter as a URL parameter this.urlParameters.set('TIME', queryLayer.timeRestriction); } else if (this.urlParameters.has('TIME')) { // Remove URL parameter if no time restriction is set this.urlParameters.delete('TIME'); } } } _WfsClient_instances = new WeakSet(), _WfsClient_describeFeatureType = async function _WfsClient_describeFeatureType() { const serverWfs = new ServerWfs('', this.wfsUrl); const url = this.getDescribeFeatureTypeUrl(); const response = await fetch(url); const content = await response.text(); const xml = new DOMParser().parseFromString(content, 'text/xml'); // First find all direct "element" childs const elementTypeToName = this.getElementToTypeName(xml); // Then, find all "complexType" elements const tags = xml.getElementsByTagName('complexType'); for (const tag of tags) { this.initializeAttribute(tag, serverWfs, elementTypeToName); } // This WFS is now initialized serverWfs.initialized = true; return serverWfs; }; export default WfsClient; export class WfsClientMapServer extends WfsClient { constructor(ogcServer, options) { super(ogcServer, { featurePrefix: 'feature', featureNS: 'https://mapserver.gis.umn.edu/mapserver', ...options }); } async getFeatureRaw(featureTypes, getFeatureOptions) { console.debug('WFS CLIENT MAPSERVER getFeatureRaw() featureTypes:', featureTypes); return super.getFeatureRaw(featureTypes, getFeatureOptions); } } export class WfsClientQgis extends WfsClient { constructor(ogcServer, options) { super(ogcServer, { featurePrefix: 'feature', featureNS: 'https://www.qgis.org/gml', ...options }); } async getFeatureRaw(featureTypes, getFeatureOptions) { console.debug('WFS CLIENT QGIS getFeatureRaw() featureTypes:', featureTypes); return super.getFeatureRaw(featureTypes, getFeatureOptions); } } export const WfsClientDefault = WfsClientQgis; export const WfsClientGeoServer = WfsClientMapServer;