diff --git a/src/ol/format/WFS.js b/src/ol/format/WFS.js index 8d842d9509..aadbe7ec68 100644 --- a/src/ol/format/WFS.js +++ b/src/ol/format/WFS.js @@ -925,9 +925,10 @@ const GETFEATURE_SERIALIZERS = { 'Or': makeChildAppender(writeLogicalFilter), 'Not': makeChildAppender(writeNotFilter), 'BBOX': makeChildAppender(writeBboxFilter), - 'Contains': makeChildAppender(writeContainsFilter), - 'Intersects': makeChildAppender(writeIntersectsFilter), - 'Within': makeChildAppender(writeWithinFilter), + 'Contains': makeChildAppender(writeSpatialFilter), + 'Intersects': makeChildAppender(writeSpatialFilter), + 'Within': makeChildAppender(writeSpatialFilter), + 'DWithin': makeChildAppender(writeDWithinFilter), 'PropertyIsEqualTo': makeChildAppender(writeComparisonFilter), 'PropertyIsNotEqualTo': makeChildAppender(writeComparisonFilter), 'PropertyIsLessThan': makeChildAppender(writeComparisonFilter), @@ -944,11 +945,12 @@ const GETFEATURE_SERIALIZERS = { 'Or': makeChildAppender(writeLogicalFilter), 'Not': makeChildAppender(writeNotFilter), 'BBOX': makeChildAppender(writeBboxFilter), - 'Contains': makeChildAppender(writeContainsFilter), - 'Disjoint': makeChildAppender(writeDisjointFilter), - 'Intersects': makeChildAppender(writeIntersectsFilter), + 'Contains': makeChildAppender(writeSpatialFilter), + 'Disjoint': makeChildAppender(writeSpatialFilter), + 'Intersects': makeChildAppender(writeSpatialFilter), 'ResourceId': makeChildAppender(writeResourceIdFilter), - 'Within': makeChildAppender(writeWithinFilter), + 'Within': makeChildAppender(writeSpatialFilter), + 'DWithin': makeChildAppender(writeDWithinFilter), 'PropertyIsEqualTo': makeChildAppender(writeComparisonFilter), 'PropertyIsNotEqualTo': makeChildAppender(writeComparisonFilter), 'PropertyIsLessThan': makeChildAppender(writeComparisonFilter), @@ -1048,54 +1050,6 @@ function writeBboxFilter(node, filter, objectStack) { format.prototype.writeGeometryElement(node, filter.extent, objectStack); } -/** - * @param {Node} node Node. - * @param {import("./filter/Contains.js").default} filter Filter. - * @param {Array<*>} objectStack Node stack. - */ -function writeContainsFilter(node, filter, objectStack) { - const parent = /** @type {Object} */ (objectStack[objectStack.length - 1]); - const context = parent['context']; - const version = context['version']; - parent['srsName'] = filter.srsName; - const format = GML_FORMATS[version]; - - writePropertyName(version, node, filter.geometryName); - format.prototype.writeGeometryElement(node, filter.geometry, objectStack); -} - -/** - * @param {Node} node Node. - * @param {import("./filter/Intersects.js").default} filter Filter. - * @param {Array<*>} objectStack Node stack. - */ -function writeIntersectsFilter(node, filter, objectStack) { - const parent = /** @type {Object} */ (objectStack[objectStack.length - 1]); - const context = parent['context']; - const version = context['version']; - parent['srsName'] = filter.srsName; - const format = GML_FORMATS[version]; - - writePropertyName(version, node, filter.geometryName); - format.prototype.writeGeometryElement(node, filter.geometry, objectStack); -} - -/** - * @param {Node} node Node. - * @param {import("./filter/Disjoint.js").default} filter Filter. - * @param {Array<*>} objectStack Node stack. - */ -function writeDisjointFilter(node, filter, objectStack) { - const parent = /** @type {Object} */ (objectStack[objectStack.length - 1]); - const context = parent['context']; - const version = context['version']; - parent['srsName'] = filter.srsName; - const format = GML_FORMATS[version]; - - writePropertyName(version, node, filter.geometryName); - format.prototype.writeGeometryElement(node, filter.geometry, objectStack); -} - /** * @param {Element} node Element. * @param {import("./filter/ResourceId.js").default} filter Filter. @@ -1107,10 +1061,10 @@ function writeResourceIdFilter(node, filter, objectStack) { /** * @param {Node} node Node. - * @param {import("./filter/Within.js").default} filter Filter. + * @param {import("./filter/Spatial.js").default} filter Filter. * @param {Array<*>} objectStack Node stack. */ -function writeWithinFilter(node, filter, objectStack) { +function writeSpatialFilter(node, filter, objectStack) { const parent = /** @type {Object} */ (objectStack[objectStack.length - 1]); const context = parent['context']; const version = context['version']; @@ -1121,6 +1075,26 @@ function writeWithinFilter(node, filter, objectStack) { format.prototype.writeGeometryElement(node, filter.geometry, objectStack); } +/** + * @param {Node} node Node. + * @param {import("./filter/DWithin.js").default} filter Filter. + * @param {Array<*>} objectStack Node stack. + */ +function writeDWithinFilter(node, filter, objectStack) { + const parent = /** @type {Object} */ (objectStack[objectStack.length - 1]); + const context = parent['context']; + const version = context['version']; + writeSpatialFilter(node, filter, objectStack); + const distance = createElementNS(getFilterNS(version), 'Distance'); + writeStringTextNode(distance, filter.distance.toString()); + if (version === '2.0.0') { + distance.setAttribute('uom', filter.unit); + } else { + distance.setAttribute('units', filter.unit); + } + node.appendChild(distance); +} + /** * @param {Node} node Node. * @param {import("./filter/During.js").default} filter Filter. @@ -1130,11 +1104,8 @@ function writeDuringFilter(node, filter, objectStack) { const parent = /** @type {Object} */ (objectStack[objectStack.length - 1]); const context = parent['context']; const version = context['version']; - const ns = FESNS[version]; - const valueReference = createElementNS(ns, 'ValueReference'); - writeStringTextNode(valueReference, filter.propertyName); - node.appendChild(valueReference); + writeExpression(FESNS[version], 'ValueReference', node, filter.propertyName); const timePeriod = createElementNS(GMLNS, 'TimePeriod'); node.appendChild(timePeriod); @@ -1202,12 +1173,11 @@ function writeComparisonFilter(node, filter, objectStack) { const parent = /** @type {Object} */ (objectStack[objectStack.length - 1]); const context = parent['context']; const version = context['version']; - const ns = OGCNS[context['version']]; if (filter.matchCase !== undefined) { node.setAttribute('matchCase', filter.matchCase.toString()); } writePropertyName(version, node, filter.propertyName); - writeOgcLiteral(ns, node, '' + filter.expression); + writeLiteral(version, node, '' + filter.expression); } /** @@ -1231,17 +1201,17 @@ function writeIsBetweenFilter(node, filter, objectStack) { const parent = /** @type {Object} */ (objectStack[objectStack.length - 1]); const context = parent['context']; const version = context['version']; - const ns = OGCNS[context['version']]; + const ns = getFilterNS(version); writePropertyName(version, node, filter.propertyName); const lowerBoundary = createElementNS(ns, 'LowerBoundary'); node.appendChild(lowerBoundary); - writeOgcLiteral(ns, lowerBoundary, '' + filter.lowerBoundary); + writeLiteral(version, lowerBoundary, '' + filter.lowerBoundary); const upperBoundary = createElementNS(ns, 'UpperBoundary'); node.appendChild(upperBoundary); - writeOgcLiteral(ns, upperBoundary, '' + filter.upperBoundary); + writeLiteral(version, upperBoundary, '' + filter.upperBoundary); } /** @@ -1253,7 +1223,6 @@ function writeIsLikeFilter(node, filter, objectStack) { const parent = /** @type {Object} */ (objectStack[objectStack.length - 1]); const context = parent['context']; const version = context['version']; - const ns = OGCNS[version]; node.setAttribute('wildCard', filter.wildCard); node.setAttribute('singleChar', filter.singleChar); node.setAttribute('escapeChar', filter.escapeChar); @@ -1261,7 +1230,7 @@ function writeIsLikeFilter(node, filter, objectStack) { node.setAttribute('matchCase', filter.matchCase.toString()); } writePropertyName(version, node, filter.propertyName); - writeOgcLiteral(ns, node, '' + filter.pattern); + writeLiteral(version, node, '' + filter.pattern); } /** @@ -1270,12 +1239,21 @@ function writeIsLikeFilter(node, filter, objectStack) { * @param {Node} node Node. * @param {string} value Value. */ -function writeOgcExpression(ns, tagName, node, value) { +function writeExpression(ns, tagName, node, value) { const property = createElementNS(ns, tagName); writeStringTextNode(property, value); node.appendChild(property); } +/** + * @param {string} version Version. + * @param {Node} node Node. + * @param {string} value PropertyName value. + */ +function writeLiteral(version, node, value) { + writeExpression(getFilterNS(version), 'Literal', node, value); +} + /** * @param {string} version Version. * @param {Node} node Node. @@ -1283,30 +1261,12 @@ function writeOgcExpression(ns, tagName, node, value) { */ function writePropertyName(version, node, value) { if (version === '2.0.0') { - writeFesValueReference(FESNS[version], node, value); + writeExpression(FESNS[version], 'ValueReference', node, value); } else { - writeOgcExpression(OGCNS[version], 'PropertyName', node, value); + writeExpression(OGCNS[version], 'PropertyName', node, value); } } -/** - * @param {string} ns Namespace. - * @param {Node} node Node. - * @param {string} value PropertyName value. - */ -function writeFesValueReference(ns, node, value) { - writeOgcExpression(ns, 'ValueReference', node, value); -} - -/** - * @param {string} ns Namespace. - * @param {Node} node Node. - * @param {string} value PropertyName value. - */ -function writeOgcLiteral(ns, node, value) { - writeOgcExpression(ns, 'Literal', node, value); -} - /** * @param {Node} node Node. * @param {string} time PropertyName value. diff --git a/src/ol/format/filter.js b/src/ol/format/filter.js index 05c8be7904..9abdd6e667 100644 --- a/src/ol/format/filter.js +++ b/src/ol/format/filter.js @@ -4,6 +4,7 @@ import And from './filter/And.js'; import Bbox from './filter/Bbox.js'; import Contains from './filter/Contains.js'; +import DWithin from './filter/DWithin.js'; import Disjoint from './filter/Disjoint.js'; import During from './filter/During.js'; import EqualTo from './filter/EqualTo.js'; @@ -131,6 +132,23 @@ export function within(geometryName, geometry, opt_srsName) { return new Within(geometryName, geometry, opt_srsName); } +/** + * Create a `` operator to test whether a geometry-valued property + * is within a distance to a given geometry. + * + * @param {!string} geometryName Geometry name to use. + * @param {!import("../geom/Geometry.js").default} geometry Geometry. + * @param {!number} distance Distance. + * @param {!string} unit Unit. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @returns {!DWithin} `` operator. + * @api + */ +export function dwithin(geometryName, geometry, distance, unit, opt_srsName) { + return new DWithin(geometryName, geometry, distance, unit, opt_srsName); +} + /** * Creates a `` comparison operator. * diff --git a/src/ol/format/filter/DWithin.js b/src/ol/format/filter/DWithin.js new file mode 100644 index 0000000000..7ecc8c1510 --- /dev/null +++ b/src/ol/format/filter/DWithin.js @@ -0,0 +1,38 @@ +/** + * @module ol/format/filter/DWithin + */ +import Spatial from './Spatial.js'; + +/** + * @classdesc + * Represents a `` operator to test whether a geometry-valued property + * is within a distance to a given geometry. + * @api + */ +class DWithin extends Spatial { + /** + * @param {!string} geometryName Geometry name to use. + * @param {!import("../../geom/Geometry.js").default} geometry Geometry. + * @param {!number} distance Distance. + * @param {!string} unit Unit. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + */ + constructor(geometryName, geometry, distance, unit, opt_srsName) { + super('DWithin', geometryName, geometry, opt_srsName); + + /** + * @public + * @type {!number} + */ + this.distance = distance; + + /** + * @public + * @type {!string} + */ + this.unit = unit; + } +} + +export default DWithin; diff --git a/test/spec/ol/format/wfs.test.js b/test/spec/ol/format/wfs.test.js index d3c407f18c..45be23a0ac 100644 --- a/test/spec/ol/format/wfs.test.js +++ b/test/spec/ol/format/wfs.test.js @@ -19,6 +19,7 @@ import { contains as containsFilter, disjoint as disjointFilter, during as duringFilter, + dwithin as dwithinFilter, equalTo as equalToFilter, greaterThan as greaterThanFilter, greaterThanOrEqualTo as greaterThanOrEqualToFilter, @@ -686,6 +687,48 @@ describe('ol.format.WFS', function () { expect(serialized.firstElementChild).to.xmleql(parse(text)); }); + it('creates a dwithin filter', function () { + const text = + '' + + ' ' + + ' ' + + ' the_geom' + + ' ' + + ' ' + + ' ' + + ' ' + + ' 10 20 10 25 15 25 15 20 10 20' + + ' ' + + ' ' + + ' ' + + ' ' + + ' 10' + + ' ' + + ' ' + + ''; + const serialized = new WFS().writeGetFeature({ + srsName: 'EPSG:4326', + featureTypes: ['area'], + filter: dwithinFilter( + 'the_geom', + new Polygon([ + [ + [10, 20], + [10, 25], + [15, 25], + [15, 20], + [10, 20], + ], + ]), + 10, + 'm' + ), + }); + expect(serialized.firstElementChild).to.xmleql(parse(text)); + }); + it('creates During property filter', function () { const text = ' expect(serialized).to.xmleql(parse(text)); }); }); + + it('creates a dwithin filter', function () { + const text = + '' + + ' ' + + ' ' + + ' the_geom' + + ' ' + + ' ' + + ' ' + + ' ' + + ' 10 20 10 25 15 25 15 20 10 20' + + ' ' + + ' ' + + ' ' + + ' ' + + ' 10' + + ' ' + + ' ' + + ''; + const serialized = new WFS({version: '2.0.0'}).writeGetFeature({ + srsName: 'EPSG:4326', + featureTypes: ['area'], + filter: dwithinFilter( + 'the_geom', + new Polygon([ + [ + [10, 20], + [10, 25], + [15, 25], + [15, 20], + [10, 20], + ], + ]), + 10, + 'm' + ), + }); + expect(serialized.firstElementChild).to.xmleql(parse(text)); + }); + + it('creates isLike property filter', function () { + const text = + '' + + ' ' + + ' ' + + ' name' + + ' New*' + + ' ' + + ' ' + + ''; + const serialized = new WFS({version: '2.0.0'}).writeGetFeature({ + srsName: 'urn:ogc:def:crs:EPSG::4326', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'], + filter: likeFilter('name', 'New*'), + }); + expect(serialized.firstElementChild).to.xmleql(parse(text)); + }); + + it('creates isBetween property filter', function () { + const text = + '' + + ' ' + + ' ' + + ' area' + + ' 100' + + ' 1000' + + ' ' + + ' ' + + ''; + const serialized = new WFS({version: '2.0.0'}).writeGetFeature({ + srsName: 'urn:ogc:def:crs:EPSG::4326', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'], + filter: betweenFilter('area', 100, 1000), + }); + expect(serialized.firstElementChild).to.xmleql(parse(text)); + }); + + it('creates greater/less than property filters', function () { + const text = + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' area' + + ' 100' + + ' ' + + ' ' + + ' pop' + + ' 20000' + + ' ' + + ' ' + + ' ' + + ' ' + + ' area' + + ' 100' + + ' ' + + ' ' + + ' pop' + + ' 20000' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + const serialized = new WFS({version: '2.0.0'}).writeGetFeature({ + srsName: 'urn:ogc:def:crs:EPSG::4326', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'], + filter: orFilter( + andFilter( + greaterThanFilter('area', 100), + greaterThanOrEqualToFilter('pop', 20000) + ), + andFilter( + lessThanFilter('area', 100), + lessThanOrEqualToFilter('pop', 20000) + ) + ), + }); + expect(serialized.firstElementChild).to.xmleql(parse(text)); + }); + + it('creates During property filter', function () { + const text = + '' + + ' ' + + ' ' + + ' date_prop' + + ' ' + + ' ' + + ' ' + + ' 2010-01-20T00:00:00Z' + + ' ' + + ' ' + + ' ' + + ' ' + + ' 2012-12-31T00:00:00Z' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + + const serialized = new WFS({version: '2.0.0'}).writeGetFeature({ + srsName: 'EPSG:4326', + featureTypes: ['states'], + filter: duringFilter( + 'date_prop', + '2010-01-20T00:00:00Z', + '2012-12-31T00:00:00Z' + ), + }); + expect(serialized.firstElementChild).to.xmleql(parse(text)); + }); }); });