diff --git a/doc/errors/index.md b/doc/errors/index.md index 646a6a33b5..de2df3826b 100644 --- a/doc/errors/index.md +++ b/doc/errors/index.md @@ -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. \ No newline at end of file +Data from this source can only be rendered if it has a projection compatible with the view projection. diff --git a/src/ol/format/WFS.js b/src/ol/format/WFS.js index cf539b7479..f30390fa21 100644 --- a/src/ol/format/WFS.js +++ b/src/ol/format/WFS.js @@ -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} featureTypes The feature type names. + * @property {Array} 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. @@ -151,6 +153,13 @@ const TRANSACTION_SERIALIZERS = { * 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. @@ -472,7 +481,6 @@ class WFS extends XMLFeature { const node = createElementNS(WFSNS[this.version_], 'GetFeature'); node.setAttribute('service', 'WFS'); node.setAttribute('version', this.version_); - let filter; if (options.handle) { node.setAttribute('handle', options.handle); } @@ -494,21 +502,6 @@ class WFS extends XMLFeature { 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; - } - } node.setAttributeNS( XML_SCHEMA_INSTANCE_URI, 'xsi:schemaLocation', @@ -523,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} */ (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} */ (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. * diff --git a/test/spec/ol/format/wfs.test.js b/test/spec/ol/format/wfs.test.js index 45be23a0ac..e2a22cf55e 100644 --- a/test/spec/ol/format/wfs.test.js +++ b/test/spec/ol/format/wfs.test.js @@ -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 = + '' + + ' ' + + ' ' + + ' the_geom_1' + + ' ' + + ' 1 2' + + ' 3 4' + + ' ' + + ' ' + + ' ' + + ''; + const textQuery2 = + '' + + ' ' + + ' ' + + ' the_geom_2' + + ' ' + + ' 5 6' + + ' 7 8' + + ' ' + + ' ' + + ' ' + + ''; + 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 = '