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