UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

161 lines (160 loc) 7.77 kB
import LayerWms from '../../models/layers/layerwms.js'; import { WfsClientDefault, WfsClientGeoServer, WfsClientMapServer, WfsClientQgis, WfsClientGeorama } from './wfsclient.js'; import VendorSpecificOgcServerManager from '../vendorspecificogcservermanager.js'; import { isTimeAwareLayer } from '../../models/layers/timeawarelayer.js'; export default class WfsManager extends VendorSpecificOgcServerManager { get state() { return this.context.stateManager.state; } getClientId(ogcServer) { return ogcServer.urlWfs ?? ''; } static UnknownFeatureType = 'UNKNOWN'; initializeSingleton() { this.context.stateManager.subscribe(/layers\.layersList\..*\.filter/, (_oldFilter, newFilter) => { void this.onSelectedFeaturesFilterChange(newFilter); }); this.context.stateManager.subscribe(/layers\.layersList\..*\.timeRestriction/, (_oldTime, _newTime, layer) => { if (this.isFirstChildOfTimeAwareGroupOrIndependent(layer)) { void this.onSelectedFeaturesFilterChange(layer.filter); } }); // Register the default client this.registerClientClass('default', WfsClientDefault); this.registerClientClass('geoserver', WfsClientGeoServer); this.registerClientClass('mapserver', WfsClientMapServer); this.registerClientClass('qgisserver', WfsClientQgis); this.registerClientClass('georama.webgis', WfsClientGeorama); } async onSelectedFeaturesFilterChange(filter) { const selectionParamsFilteredLayers = this.state.selection.selectionParameters.map((param) => param.clone((l) => l.wfsQueryable && l.filter === filter)); const filteredSelectionParams = selectionParamsFilteredLayers.filter((param) => param.layers.length > 0); const filteredLayersId = filteredSelectionParams .map((p) => p.layers.map((l) => l.queryLayers)) .flat() .join(';'); if (filteredSelectionParams.length === 0) return; this.state.loading = true; // WFS GetFeature const wfsPromises = filteredSelectionParams.map((param) => { const client = this.getClient(param.ogcServer); const features = client.getFeature(param); return features; }); const filteredSelectedFeatures = (await Promise.all(wfsPromises)).flat(); // keep only the selected features that are not in the filtered layers const selectedFeatures = this.state.selection.selectedFeatures.filter((feature) => { const featureType = WfsManager.extractFeatureTypeFromId(feature); return !filteredLayersId.includes(featureType) && featureType === WfsManager.UnknownFeatureType; }); // add the newly filtered selected features selectedFeatures.push(...filteredSelectedFeatures); this.state.selection.selectedFeatures = selectedFeatures; this.state.loading = false; } /** * Determines whether the specified layer is the first active child layer of a time-aware group layer, * or if it is independent (= with time restriction, but without a time aware parent). * This is needed to prevent repeated identical requests when setting the time restriction on * multiple child layers at once. * * @param {TimeAwareLayer} layer - The layer to check. */ isFirstChildOfTimeAwareGroupOrIndependent(layer) { if (layer instanceof LayerWms && layer.active) { const parent = layer.parent; if (parent && isTimeAwareLayer(parent) && parent.timeRestriction === layer.timeRestriction) { // First active child of a time-aware group layer: it will trigger requests for all children in the group return parent.children.filter((c) => c.active)[0].id === layer.id; } else { // Independent wms layer without a time aware parent return true; } } return false; } async getServerWfs(object) { const ogcServer = object instanceof LayerWms ? object.ogcServer : object; const client = this.getClient(ogcServer); return client.getServerWfs(); } /** * Extracts the feature type from the feature's ID. A feature ID may contain a prefix, * the feature type and a feature-specific ID. It returns "UNKNOWN" if the ID is absent. */ static extractFeatureTypeFromId(feature) { let id = feature.getId(); if (!id) { return WfsManager.UnknownFeatureType; } id = `${id}`; // First, remove the feature prefix if present, e.g. "my_prefix:my_feature_type.123" const idWithPrefix = id.split(':'); if (idWithPrefix.length > 1) { idWithPrefix.shift(); id = idWithPrefix.join(':'); } const splitId = id.split('.'); if (splitId.length <= 1) { return id; } // Remove the feature ID from the type splitId.pop(); return splitId.join('.'); } /** * Compares attribute names between query layers of a LayerWms object. * Returns a list of attributes that are common to all query layers. * If the layer has no query layers, it returns an empty array. * Can optionally include the geometry column name if it is present in all query layers. */ async commonAttributesByLayer(layer, includeGeometryColumn = false) { const serverWfs = await this.getServerWfs(layer); const queryLayers = layer.queryLayers.split(','); const attributesByQueryLayer = []; for (const queryLayer of queryLayers) { attributesByQueryLayer.push(serverWfs.layers[queryLayer] || []); } const layerAttributesCount = {}; for (const la of attributesByQueryLayer.flat()) { layerAttributesCount[la.name] ??= 0; layerAttributesCount[la.name]++; } const commonAttributesNames = new Set(Object.keys(layerAttributesCount).filter((k) => layerAttributesCount[k] === queryLayers.length)); const commonAttributes = attributesByQueryLayer[0] ? attributesByQueryLayer[0].filter((la) => commonAttributesNames.has(la.name)) : []; if (includeGeometryColumn) { const geometryColumnNames = queryLayers.map((queryLayer) => serverWfs.featureTypeToGeometryColumnName[queryLayer]); if (geometryColumnNames.length === queryLayers.length) { return [...commonAttributes, geometryColumnNames[0]]; } } return commonAttributes; } /** * Retrieves the list of valid values for all enumerated attributes within a given layer. */ async getAttributeChoices(layer) { const client = this.getClient(layer); const queryLayers = layer.queryLayers.split(','); if (!layer.enumeratedAttributes) { return new Map(); } const choicesByAttribute = new Map(); for (const queryLayer of queryLayers) { for (const attribute of layer.enumeratedAttributes || []) { const choices = await client.getAttributeValueList(queryLayer, attribute); // Update the set of choices with the new values choicesByAttribute.set(attribute, new Set([...(choicesByAttribute.get(attribute) || []), ...choices])); } } // Cast sets to arrays and sort alphabetically return new Map(Array.from(choicesByAttribute.entries(), ([attribute, choicesSet]) => [ attribute, Array.from(choicesSet).sort((a, b) => a.localeCompare(b)) ])); } }