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

Add filterType source param and use QGIS EXP_FILTER when param is qgis #1536

Merged
merged 25 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
97ccb80
Separate filter creation, add `filterType` param
MattiasSp Apr 26, 2022
57f15d9
Support QGIS expression filters; `EXP_FILTER`
MattiasSp Apr 27, 2022
c4394a7
Support `getFeature` on QGIS Server layers
MattiasSp May 3, 2022
1c486ba
Remove duplicate statement
MattiasSp May 3, 2022
a63e0b9
Missing semicolon
MattiasSp May 3, 2022
9d529ca
Support QGIS's `EXP_FILTER` in offline wfs layer
MattiasSp May 3, 2022
7be7efd
Fix for QGIS filters when not using extent
MattiasSp May 3, 2022
d38cdf8
Merge branch 'master' into wfs-qgis-filter-support
MattiasSp Feb 20, 2024
01eb917
Fixed linter errors
MattiasSp Feb 20, 2024
473e211
Fixed spelling error
MattiasSp Feb 22, 2024
ae5ae79
Removed default that is already set on the source
MattiasSp Feb 22, 2024
8af3134
Merge branch 'master' of https://github.com/haninge-geodata/origo int…
MattiasSp Feb 22, 2024
d156d32
Moved source defaults back to wfs.js
MattiasSp Feb 26, 2024
91b627e
Returned standard queries using BBOX parameter
MattiasSp Feb 26, 2024
cdb2abc
Add layer names to ids for QGIS support
MattiasSp Feb 27, 2024
381b1c6
Indentation fix
MattiasSp Feb 28, 2024
33f35c7
Revert "Support QGIS's `EXP_FILTER` in offline wfs layer"
MattiasSp Mar 14, 2024
eb16eeb
Revert "Missing semicolon"
MattiasSp Mar 14, 2024
d098cc7
Revert "Remove duplicate statement"
MattiasSp Mar 14, 2024
0807452
Mark _createQueryFilter() as private
MattiasSp Mar 14, 2024
da39627
Fix incorrect comment
MattiasSp Mar 14, 2024
211894e
Revert "Support `getFeature` on QGIS Server layers"
MattiasSp Mar 14, 2024
8f29b57
Fix incorrect function call
MattiasSp Mar 14, 2024
6c5055b
Allow filterType on layer level
MattiasSp Mar 15, 2024
5858999
Revert "Allow filterType on layer level"
MattiasSp Mar 28, 2024
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
9 changes: 6 additions & 3 deletions src/controls/offline/wfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,26 @@ wfs.request = function request(layer) {
sourceOptions.filter = layer.get('filter');
sourceOptions.projectionCode = viewer.getProjectionCode();
sourceOptions.extent = layer.get('extent');
sourceOptions.projectionCode = viewer.getProjectionCode();

const req = createRequest(sourceOptions);
return req;

function createRequest(options) {
console.log('createRequest')
console.log('createRequest');
const format = new GeoJSONFormat({
geometryName: options.geometryName
});

const serverUrl = options.url;
let queryFilter;

// If cql filter then bbox must be used in the filter.
if (options.filter) {
if (options.filterType === 'qgis') {
queryFilter = `&BBOX=${options.extent.join(',')},${options.projectionCode}&EXP_FILTER=${options.filter}`;
} else {
// If cql filter then bbox must be used in the filter.
queryFilter = `&CQL_FILTER=${options.filter} AND BBOX(${options.geometryName},${options.extent.join(',')},'${options.projectionCode}')`;
}
} else {
queryFilter = `&BBOX=${options.extent.join(',')},${options.projectionCode}`;
}
Expand Down
17 changes: 13 additions & 4 deletions src/getfeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ export default function getfeature(id, layer, source, projCode, proj, extent) {
projectionCode = projCode;
projection = proj;
const serverUrl = source[layer.get('sourceName')].url;
const filterType = source[layer.get('sourceName')].filterType;
const type = layer.get('type');
// returns a promise with features as result
if (type === 'AGS_FEATURE') {
return sourceType.AGS_FEATURE(id, layer, serverUrl);
}
return sourceType.WFS(id, layer, serverUrl, extent);
return sourceType.WFS(id, layer, serverUrl, filterType, extent);
}

function fail(response) {
Expand Down Expand Up @@ -53,7 +54,7 @@ sourceType.AGS_FEATURE = function agsFeature(id, layer, serverUrl) {
})).catch(error => console.error(error));
};

sourceType.WFS = function wfsSourceType(id, layer, serverUrl, extent) {
sourceType.WFS = function wfsSourceType(id, layer, serverUrl, filterType, extent) {
const geometryName = layer.get('geometryName');
const format = new GeoJSONFormat({
geometryName
Expand All @@ -77,14 +78,22 @@ sourceType.WFS = function wfsSourceType(id, layer, serverUrl, extent) {
minExtent = layerExtent;
}
if (filter) {
if (minExtent) {
if (minExtent && filterType === 'qgis') {
queryFilter = `&EXP_FILTER=${filter}&BBOX=${minExtent.toString()}`;
} else if (minExtent) {
queryFilter = layer.get('geometryName') ? `&CQL_FILTER=${filter} AND BBOX(${layer.get('geometryName')},${minExtent.toString()})` : `&CQL_FILTER=${filter}&BBOX=${minExtent.toString()}`;
} else if (filterType === 'qgis') {
queryFilter = `&EXP_FILTER=${filter}`;
} else {
queryFilter = `&CQL_FILTER=${filter}`;
}
} else if (!layer.get('geometryName') || (minExtent && filterType === 'qgis')) {
queryFilter = `&BBOX=${minExtent.toString()}`;
} else if (minExtent) {
queryFilter = layer.get('geometryName') ? `&CQL_FILTER=BBOX(${layer.get('geometryName')},${minExtent.toString()})` : `&BBOX=${minExtent.toString()}`;
queryFilter = `&CQL_FILTER=BBOX(${layer.get('geometryName')},${minExtent.toString()})`;
}
} else if (filterType === 'qgis') {
queryFilter = `&featureId=${layer.get('name')}.${id}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work when using double underscore notation or the 'id'-option on the layer. 'id' should be used instead of 'name' if it really is necessary to prepend the layername. Geoserver can handle with or without layername, but it may be dangerous to assume that it always shall be prepended, When calling from search the search result would most likely not not have layername prepended, but if calling from some arbitrary code it will probably already be prepended.

} else {
queryFilter = `&featureId=${id}`;
}
Expand Down
4 changes: 3 additions & 1 deletion src/layer/wfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ export default function wfs(layerOptions, viewer) {
const wfsDefault = {
layerType: 'vector'
};
const sourceDefault = {};
const sourceDefault = {
filterType: 'cql'
};
const wfsOptions = Object.assign({}, wfsDefault, layerOptions);
const sourceOptions = Object.assign({}, sourceDefault, viewer.getMapSource()[layerOptions.sourceName]);
sourceOptions.featureType = wfsOptions.id;
Expand Down
120 changes: 84 additions & 36 deletions src/layer/wfssource.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,49 +63,97 @@ class WfsSource extends VectorSource {
}

/**
* Helper to reuse code. Consider it to be private to this class
* @param {any} extent
* @param {any} cql if provided, extent is ignored
* Generate a wfs query filter according to the wfs layer source's `filterType`. Combines
* the custom filter parameter with any layer filters already present.
* @param {string} filterType The filter type used by the layer source (`cql`|`qgis`)
* @param {string} customFilter custom filter to combine with preconfigured layer filters
* @param {any} extent ignored when customFilter is supplied
*/
async _loaderHelper(extent, cql) {
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) {
cqlfilter = replacer.replace(this._options.filter, window);
if (cql) {
cqlfilter += ' AND ';
}
createQueryFilter(filterType, customFilter, extent) {
let queryFilter = '';
let layerFilter = '';
if (this.getOptions().filter) {
layerFilter = replacer.replace(this.getOptions().filter, window);
}
if (cql) {
cqlfilter += `${replacer.replace(cql, window)}`;

// Transform the extent if necessary
let requestExtent = extent;
if (extent && this.getOptions().dataProjection !== this.getOptions().projectionCode) {
requestExtent = transformExtent(extent, this.getOptions().projectionCode, this.getOptions().dataProjection);
}

// Create the complete CQL query string
let queryFilter = '';
if (this._options.strategy === 'all' || cql || this._options.isTable) {
queryFilter = cqlfilter ? `&CQL_FILTER=${cqlfilter}` : '';
} else {
// Extent should be used. Depending if there also is a filter, the queryfilter looks different
let requestExtent;
if (this._options.dataProjection !== this._options.projectionCode) {
requestExtent = transformExtent(extent, this._options.projectionCode, this._options.dataProjection);
} else {
requestExtent = extent;
}
if (cqlfilter) {
queryFilter = `&CQL_FILTER=${cqlfilter} AND BBOX(${this._options.geometryName},${requestExtent.join(',')},'${this._options.dataProjection}')`;
} else {
queryFilter = `&BBOX=${requestExtent.join(',')},${this._options.dataProjection}`;
// When using BBOX mode and no filters, just use the BBOX WFS parameter
if (extent && this.getOptions().strategy !== 'all' && !this.getOptions().isTable && !customFilter && !layerFilter) {
queryFilter = `&BBOX=${requestExtent.join(',')},${this.getOptions().dataProjection}`;
} else { // Otherwise, integrate provided layer filters, `customFilter` and extent into a single query filter
switch (filterType) {
case 'cql': {
// Set up the filter as a combination of the layer filter and the temporary filter parameter
let cqlfilter = layerFilter;
if (layerFilter && customFilter) {
cqlfilter += ' AND ';
}
if (customFilter) {
cqlfilter += `${replacer.replace(customFilter, window)}`;
}

// If using extent, no `customFilter`, and the layer is not a geometryless table, integrate it into the query filter
if (extent && this.getOptions().strategy !== 'all' && !customFilter && !this.getOptions().isTable) {
if (cqlfilter) {
cqlfilter += ' AND ';
}
cqlfilter += `BBOX(${this.getOptions().geometryName},${requestExtent.join(',')},'${this.getOptions().dataProjection}')`;
}

// Create the complete CQL query string
if (cqlfilter) {
queryFilter = `&CQL_FILTER=${cqlfilter}`;
}
break;
}
case 'qgis': {
// Set up the filter as a combination of the layer filter and the temporary filter parameter
let qgisFilter = layerFilter;
if (layerFilter && customFilter) {
qgisFilter += ' AND ';
}
if (customFilter) {
qgisFilter += `${replacer.replace(customFilter, window)}`;
}
// Create the complete QGIS EXP_FILTER query string
if (qgisFilter) {
qgisFilter = `&EXP_FILTER=${qgisFilter}`;
}

// If using extent, no `customFilter`, and the layer is not a geometryless table, add a BBOX WFS parameter
// Because of QGIS Server's lack of support for any SRS other than EPSG:4326 in GeoJSON, layers must be requested
// in EPSG:4326. EXP_FILTER comparisons still expects the SRS of the data to be used, which Origo then doesn't know.
if (extent && this.getOptions().strategy !== 'all' && !customFilter && !this.getOptions().isTable) {
queryFilter = `&BBOX=${requestExtent.join(',')},${this.getOptions().dataProjection}${qgisFilter}`;
} else {
queryFilter = qgisFilter;
}
break;
}
default: break;
}
}
return queryFilter;
}

/**
* Helper to reuse code. Consider it to be private to this class
* @param {any} extent
* @param {any} filter if provided, extent is ignored
*/
async _loaderHelper(extent, filter) {
const serverUrl = this._options.url;

// Create the complete URL
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;
url += this.createQueryFilter(this._options.filterType, filter, extent);
url = encodeURI(url);

// Actually fetch some features
Expand All @@ -125,12 +173,12 @@ class WfsSource extends VectorSource {
}

/**
* Makes a call to the server with the provided cql filter and adds all matching records to the layer.
* Makes a call to the server with the provided query filter and adds all matching records to the layer.
* If the layer has a filter it is honoured.
* @param {any} cql
* @param {any} filter
*/
async ensureLoaded(cql) {
await this._loaderHelper(null, cql);
async ensureLoaded(filter) {
await this._loaderHelper(null, filter);
}
}

Expand Down