@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
161 lines (160 loc) • 7.77 kB
JavaScript
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))
]));
}
}