Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow percent sign in getFeature filter #1877

Merged
merged 2 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 39 additions & 42 deletions src/getfeature.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import EsriJSONFormat from 'ol/format/EsriJSON';
import GeoJSONFormat from 'ol/format/GeoJSON';
import replacer from './utils/replacer';
import WfsSource from './layer/wfssource';

let projectionCode;
let projection;
const sourceType = {};

/**
* Fetches features from a layer's source but does not add them to the layer. Supports WFS and AGS_FEATURE altough functionality differs. Mainly used by search, but
* is also exposed as an api function that MultiSelect uses. As q quirky bonus it also support fetching features from WMS layers if there is a WFS service at the same endpoint.
*
* @param {any} id Comma separated list of ids. If specified layer's filter and parameter extent is ignored (even configured map and layer extent is ignored).
* @param {any} layer Layer instance to fetch from
* @param {any} source Array of know sources. Probably the configuration section source.
* @param {any} projCode Projection code for the returned features. Ignored by WFS as map projection is used
* @param {any} proj projection like object for the returned features. Ignored by WFS as map projection is used
* @param {any} extent Extent to fetch inside. Layer configuration extent is honored.
* @returns {Promise<any[]>}
*/
export default function getfeature(id, layer, source, projCode, proj, extent) {
projectionCode = projCode;
projection = proj;
Expand All @@ -15,6 +26,7 @@ export default function getfeature(id, layer, source, projCode, proj, extent) {
if (type === 'AGS_FEATURE') {
return sourceType.AGS_FEATURE(id, layer, serverUrl);
}
// Note that this includes WMS which MultiSelect utilizes to make an WFS request to an unknown WFS layer assumed to reside on same place as WMS layer!
return sourceType.WFS(id, layer, serverUrl, extent);
}

Expand Down Expand Up @@ -54,47 +66,32 @@ sourceType.AGS_FEATURE = function agsFeature(id, layer, serverUrl) {
};

sourceType.WFS = function wfsSourceType(id, layer, serverUrl, extent) {
const geometryName = layer.get('geometryName');
const format = new GeoJSONFormat({
geometryName
});
let queryFilter = '';
if (!id) {
const filter = replacer.replace(layer.get('filter'), window);
const layerExtent = layer.get('extent');
let minExtent;
if (extent && layerExtent) {
minExtent = [Math.max(extent[0], layerExtent[0]),
Math.max(extent[1], layerExtent[1]),
Math.min(extent[2], layerExtent[2]),
Math.min(extent[3], layerExtent[3])];
if (!(minExtent[0] < minExtent[2] && minExtent[1] < minExtent[3])) {
return [];
}
} else if (extent) {
minExtent = extent;
} else if (layerExtent) {
minExtent = layerExtent;
}
if (filter) {
if (minExtent) {
queryFilter = layer.get('geometryName') ? `&CQL_FILTER=${filter} AND BBOX(${layer.get('geometryName')},${minExtent.toString()})` : `&CQL_FILTER=${filter}&BBOX=${minExtent.toString()}`;
} else {
queryFilter = `&CQL_FILTER=${filter}`;
}
} else if (minExtent) {
queryFilter = layer.get('geometryName') ? `&CQL_FILTER=BBOX(${layer.get('geometryName')},${minExtent.toString()})` : `&BBOX=${minExtent.toString()}`;
}
let wfsSource;
const layerType = layer.get('type');
// Create a temporary WFS source if layer is WMS.
// This is a special case for multiselect which utlizes the fact that Geoserver usually has an WFS endpoint
// at the same place as an WMS endpoint
if (layerType !== 'WFS') {
// Create the necessary configuration to create a request to WFS endpoint from a WMS layer
const sourceOpts = {
geometryName: layer.get('geometryName'),
dataProjection: projectionCode,
projectionCode,
loadingstrategy: 'all',
requestMethod: 'GET',
url: serverUrl,
customExtent: layer.get('extent'),
featureType: layer.get('id')
};
wfsSource = new WfsSource(sourceOpts);
} else {
queryFilter = `&featureId=${id}`;
wfsSource = layer.getSource();
}
const url = `${serverUrl}?`;
const data = ['service=WFS',
'&version=1.0.0',
`&request=GetFeature&typeName=${layer.get('name')}`,
'&outputFormat=json',
queryFilter
].join('');

return fetch(url + data, { type: 'GET', dataType: 'json' }).then(res => res.json()).then(json => format.readFeatures(json)).catch(error => console.error(error));
if (id) {
return wfsSource.getFeatureFromSourceByIds(id);
}
// Have to pick up filter from layer as MultiSelect changes filter on layer instead of source
const filter = layer.get('filter');
return wfsSource.getFeaturesFromSource(extent, filter, true);
};
53 changes: 40 additions & 13 deletions src/layer/wfssource.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,28 @@ class WfsSource extends VectorSource {
}

/**
* Called by VectorSource
* Called by VectorSource. VectorSource always calls with extent specified. If strategy = 'all' it is an infinite extent.
* @param {any} extent
*/
onLoad(extent, resolution, projection, success, failure) {
this._loaderHelper(extent)
.then(f => success(f))
.then(f => {
super.addFeatures(f);
success(f);
})
.catch(() => failure());
}

/**
* Set request method for layer
* Set request method for source
* @param {any} method
*/
setMethod(method) {
this._options.requestMethod = method;
}

/**
* Set filter on layer
* Set filter on source
* @param {any} cql
*/
setFilter(cql) {
Expand All @@ -81,24 +84,26 @@ class WfsSource extends VectorSource {
}

/**
* Clear filter on layer
* Clear filter on source
*/
clearFilter() {
this._options.filter = '';
this.refresh();
}

/**
* Helper to reuse code. Consider it to be private to this class
* @param {any} extent
* @param {any} cql if provided, extent is ignored
* Helper to reuse code. Consider it to be private to this class.
* @param {any} extent Extent to query. If specified the result is limited to the intersection of this parameter and layer's extent configuration.
* @param {any} cql Optional extra cql for this call.
* @param {any} ignoreOriginalFilter true if configured filter should be ignored for this call making parameter cql only filter (if specified)
* @param {any} ids Comma separated list of feature ids. If specified you probably want to call with extent and cql empty and ignoreOriginalFilter = true
*/
async _loaderHelper(extent, cql) {
async _loaderHelper(extent, cql, ignoreOriginalFilter, ids) {
const serverUrl = this._options.url;

// Set up the cql filter as a combination of the layer filter and the temporary cql parameter
let cqlfilter = '';
if (this._options.filter) {
if (this._options.filter && !ignoreOriginalFilter) {
cqlfilter = replacer.replace(this._options.filter, window);
if (cql) {
cqlfilter += ' AND ';
Expand All @@ -110,7 +115,7 @@ class WfsSource extends VectorSource {

// Create the complete CQL query string
let queryFilter = '';
if (this._options.strategy === 'all' || cql || this._options.isTable) {
if (!extent || this._options.isTable) {
queryFilter = cqlfilter ? `&CQL_FILTER=${cqlfilter}` : '';
} else {
// Extent should be used. Depending if there also is a filter, the queryfilter looks different
Expand All @@ -129,10 +134,14 @@ class WfsSource extends VectorSource {
}

// Create the complete URL
// FIXME: rewrite using URL class
let url = [`${serverUrl}${serverUrl.indexOf('?') < 0 ? '?' : '&'}service=WFS`,
`&version=1.1.0&request=GetFeature&typeName=${this._options.featureType}&outputFormat=application/json`,
`&srsname=${this._options.dataProjection}`].join('');
url += queryFilter;
if (ids) {
url += `&FeatureId=${ids}`;
}
url = encodeURI(url);

// Actually fetch some features
Expand Down Expand Up @@ -162,7 +171,6 @@ class WfsSource extends VectorSource {
f.unset(f.getGeometryName(), true);
});
}
super.addFeatures(features);
return features;
}

Expand All @@ -172,7 +180,26 @@ class WfsSource extends VectorSource {
* @param {any} cql
*/
async ensureLoaded(cql) {
await this._loaderHelper(null, cql);
const features = await this._loaderHelper(null, cql, false);
super.addFeatures(features);
}

/**
* Fetches features by id. Extent and filters are ignored. Does NOT add the feature to the layer
* @param {any} ids Comma separated list of ids
*/
async getFeatureFromSourceByIds(ids) {
return this._loaderHelper(null, null, true, ids);
}

/**
* Fetches features from server without adding them to the source. Honors filter configuration unless ignoreOriginalFilter is specified.
* @param {any} extent Optional extent
* @param {any} cql Optional additional cql filter for this call
* @param {any} ignoreOriginalFilter true if configured cql filter should be ignored for this request
*/
async getFeaturesFromSource(extent, cql, ignoreOriginalFilter) {
return this._loaderHelper(extent, cql, ignoreOriginalFilter);
}
}

Expand Down
Loading