Merge pull request #11740 from ger-benjamin/wfs_bbox_per_feature_type_11187

Wfs bbox per feature type
This commit is contained in:
Andreas Hocevar
2020-11-16 13:58:50 +01:00
committed by GitHub
3 changed files with 144 additions and 52 deletions

View File

@@ -47,7 +47,7 @@ The default `geometryFunction` can only handle `ol/geom/Point` geometries.
### 11
`options.featureTypes` should be an Array.
`options.featureTypes` must be an Array.
### 12
@@ -252,4 +252,4 @@ A layer can only be added to the map once. Use either `layer.setMap()` or `map.a
### 68
Data from this source can only be rendered if it has a projection compatible with the view projection.
Data from this source can only be rendered if it has a projection compatible with the view projection.

View File

@@ -19,7 +19,7 @@ import {
pushParseAndPop,
pushSerializeAndPop,
} from '../xml.js';
import {and as andFilter, bbox as bboxFilter} from './filter.js';
import {and as andFilterFn, bbox as bboxFilterFn} from './filter.js';
import {assert} from '../asserts.js';
import {assign} from '../obj.js';
import {get as getProjection} from '../proj.js';
@@ -129,7 +129,9 @@ const TRANSACTION_SERIALIZERS = {
* @typedef {Object} WriteGetFeatureOptions
* @property {string} featureNS The namespace URI used for features.
* @property {string} featurePrefix The prefix for the feature namespace.
* @property {Array<string>} featureTypes The feature type names.
* @property {Array<string|FeatureType>} featureTypes The feature type names or FeatureType objects to
* define a unique bbox filter per feature type name (in this case, options `bbox` and `geometryName` are
* ignored.).
* @property {string} [srsName] SRS name. No srsName attribute will be set on
* geometries when this is not provided.
* @property {string} [handle] Handle.
@@ -143,13 +145,21 @@ const TRANSACTION_SERIALIZERS = {
* @property {number} [count] Number of features to retrieve when paging. This is a
* WFS 2.0 feature backported to WFS 1.1.0 by some Web Feature Services. Please note that some
* Web Feature Services have repurposed `maxfeatures` instead.
* @property {import("../extent.js").Extent} [bbox] Extent to use for the BBOX filter.
* @property {import("../extent.js").Extent} [bbox] Extent to use for the BBOX filter. The `geometryName`
* option must be set.
* @property {import("./filter/Filter.js").default} [filter] Filter condition. See
* {@link module:ol/format/Filter} for more information.
* @property {string} [resultType] Indicates what response should be returned,
* E.g. `hits` only includes the `numberOfFeatures` attribute in the response and no features.
*/
/**
* @typedef {Object} FeatureType
* @property {!string} name The feature type name.
* @property {!import("../extent.js").Extent} bbox Extent to use for the BBOX filter.
* @property {!string} geometryName Geometry name to use in the BBOX filter.
*/
/**
* @typedef {Object} WriteTransactionOptions
* @property {string} featureNS The namespace URI used for features.
@@ -471,44 +481,26 @@ class WFS extends XMLFeature {
const node = createElementNS(WFSNS[this.version_], 'GetFeature');
node.setAttribute('service', 'WFS');
node.setAttribute('version', this.version_);
let filter;
if (options) {
if (options.handle) {
node.setAttribute('handle', options.handle);
}
if (options.outputFormat) {
node.setAttribute('outputFormat', options.outputFormat);
}
if (options.maxFeatures !== undefined) {
node.setAttribute('maxFeatures', String(options.maxFeatures));
}
if (options.resultType) {
node.setAttribute('resultType', options.resultType);
}
if (options.startIndex !== undefined) {
node.setAttribute('startIndex', String(options.startIndex));
}
if (options.count !== undefined) {
node.setAttribute('count', String(options.count));
}
if (options.viewParams !== undefined) {
node.setAttribute('viewParams', options.viewParams);
}
filter = options.filter;
if (options.bbox) {
assert(options.geometryName, 12); // `options.geometryName` must also be provided when `options.bbox` is set
const bbox = bboxFilter(
/** @type {string} */ (options.geometryName),
options.bbox,
options.srsName
);
if (filter) {
// if bbox and filter are both set, combine the two into a single filter
filter = andFilter(filter, bbox);
} else {
filter = bbox;
}
}
if (options.handle) {
node.setAttribute('handle', options.handle);
}
if (options.outputFormat) {
node.setAttribute('outputFormat', options.outputFormat);
}
if (options.maxFeatures !== undefined) {
node.setAttribute('maxFeatures', String(options.maxFeatures));
}
if (options.resultType) {
node.setAttribute('resultType', options.resultType);
}
if (options.startIndex !== undefined) {
node.setAttribute('startIndex', String(options.startIndex));
}
if (options.count !== undefined) {
node.setAttribute('count', String(options.count));
}
if (options.viewParams !== undefined) {
node.setAttribute('viewParams', options.viewParams);
}
node.setAttributeNS(
XML_SCHEMA_INSTANCE_URI,
@@ -524,20 +516,67 @@ class WFS extends XMLFeature {
'srsName': options.srsName,
'featureNS': options.featureNS ? options.featureNS : this.featureNS_,
'featurePrefix': options.featurePrefix,
'geometryName': options.geometryName,
'filter': filter,
'propertyNames': options.propertyNames ? options.propertyNames : [],
});
assert(Array.isArray(options.featureTypes), 11); // `options.featureTypes` should be an Array
writeGetFeature(
node,
/** @type {!Array<string>} */ (options.featureTypes),
[context]
);
assert(Array.isArray(options.featureTypes), 11); // `options.featureTypes` must be an Array
if (typeof options.featureTypes[0] === 'string') {
let filter = options.filter;
if (options.bbox) {
assert(options.geometryName, 12); // `options.geometryName` must also be provided when `options.bbox` is set
filter = this.combineBboxAndFilter(
options.geometryName,
options.bbox,
options.srsName,
filter
);
}
assign(context, {
'geometryName': options.geometryName,
'filter': filter,
});
writeGetFeature(
node,
/** @type {!Array<string>} */ (options.featureTypes),
[context]
);
} else {
// Write one query node per element in featuresType.
options.featureTypes.forEach((/** @type {FeatureType} */ featureType) => {
const completeFilter = this.combineBboxAndFilter(
featureType.geometryName,
featureType.bbox,
options.srsName,
options.filter
);
assign(context, {
'geometryName': featureType.geometryName,
'filter': completeFilter,
});
writeGetFeature(node, [featureType.name], [context]);
});
}
return node;
}
/**
* Create a bbox filter and combine it with another optional filter.
*
* @param {!string} geometryName Geometry name to use.
* @param {!import("../extent.js").Extent} extent Extent.
* @param {string=} opt_srsName SRS name. No srsName attribute will be
* set on geometries when this is not provided.
* @param {import("./filter/Filter.js").default=} opt_filter Filter condition.
* @return {import("./filter/Filter.js").default} The filter.
*/
combineBboxAndFilter(geometryName, extent, opt_srsName, opt_filter) {
const bboxFilter = bboxFilterFn(geometryName, extent, opt_srsName);
if (opt_filter) {
// if bbox and filter are both set, combine the two into a single filter
return andFilterFn(opt_filter, bboxFilter);
}
return bboxFilter;
}
/**
* Encode format as WFS `Transaction` and return the Node.
*

View File

@@ -316,6 +316,59 @@ describe('ol.format.WFS', function () {
expect(serialized.firstElementChild).to.xmleql(parse(text));
});
it('creates one BBOX filter per feature type', function () {
const textQuery1 =
'<wfs:Query xmlns:wfs="http://www.opengis.net/wfs" ' +
' typeName="topp:states_1" srsName="urn:ogc:def:crs:EPSG::4326" ' +
' xmlns:topp="http://www.openplans.org/topp">' +
' <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
' <ogc:BBOX>' +
' <ogc:PropertyName>the_geom_1</ogc:PropertyName>' +
' <gml:Envelope xmlns:gml="http://www.opengis.net/gml" ' +
' srsName="urn:ogc:def:crs:EPSG::4326">' +
' <gml:lowerCorner>1 2</gml:lowerCorner>' +
' <gml:upperCorner>3 4</gml:upperCorner>' +
' </gml:Envelope>' +
' </ogc:BBOX>' +
' </ogc:Filter>' +
'</wfs:Query>';
const textQuery2 =
'<wfs:Query xmlns:wfs="http://www.opengis.net/wfs" ' +
' typeName="topp:states_2" srsName="urn:ogc:def:crs:EPSG::4326" ' +
' xmlns:topp="http://www.openplans.org/topp">' +
' <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
' <ogc:BBOX>' +
' <ogc:PropertyName>the_geom_2</ogc:PropertyName>' +
' <gml:Envelope xmlns:gml="http://www.opengis.net/gml" ' +
' srsName="urn:ogc:def:crs:EPSG::4326">' +
' <gml:lowerCorner>5 6</gml:lowerCorner>' +
' <gml:upperCorner>7 8</gml:upperCorner>' +
' </gml:Envelope>' +
' </ogc:BBOX>' +
' </ogc:Filter>' +
'</wfs:Query>';
const serialized = new WFS().writeGetFeature({
srsName: 'urn:ogc:def:crs:EPSG::4326',
featureNS: 'http://www.openplans.org/topp',
featurePrefix: 'topp',
featureTypes: [
{
name: 'states_1',
geometryName: 'the_geom_1',
bbox: [1, 2, 3, 4],
},
{
name: 'states_2',
geometryName: 'the_geom_2',
bbox: [5, 6, 7, 8],
},
],
});
expect(serialized.children.length).to.equal(2);
expect(serialized.firstElementChild).to.xmleql(parse(textQuery1));
expect(serialized.lastElementChild).to.xmleql(parse(textQuery2));
});
it('creates a property filter', function () {
const text =
'<wfs:Query xmlns:wfs="http://www.opengis.net/wfs" ' +