From af294bd44ed0c39a8ec513f47f92146b8912d054 Mon Sep 17 00:00:00 2001 From: tsauerwein Date: Tue, 19 Apr 2016 16:25:44 +0200 Subject: [PATCH] Add filter option to ol.format.WFS#writeGetFeature --- externs/olx.js | 11 +- src/ol/format/ogc/filter.js | 637 ++++++++++++++++++++++++++ src/ol/format/ogc/filter.jsdoc | 29 ++ src/ol/format/readme.md | 4 +- src/ol/format/wfsformat.js | 218 ++++++++- test/spec/ol/format/wfsformat.test.js | 255 +++++++++++ 6 files changed, 1131 insertions(+), 23 deletions(-) create mode 100644 src/ol/format/ogc/filter.js create mode 100644 src/ol/format/ogc/filter.jsdoc diff --git a/externs/olx.js b/externs/olx.js index 730c14f24c..6d648659a3 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -2049,7 +2049,8 @@ olx.format.WFSOptions.prototype.schemaLocation; * propertyNames: (Array.|undefined), * startIndex: (number|undefined), * count: (number|undefined), - * bbox: (ol.Extent|undefined)}} + * bbox: (ol.Extent|undefined), + * filter: (ol.format.ogc.filter.Filter|undefined)}} * @api */ olx.format.WFSWriteGetFeatureOptions; @@ -2155,6 +2156,14 @@ olx.format.WFSWriteGetFeatureOptions.prototype.count; olx.format.WFSWriteGetFeatureOptions.prototype.bbox; +/** + * OGC filter condition. See {@link ol.format.ogc.filter} for more information. + * @type {ol.format.ogc.filter.Filter|undefined} + * @api + */ +olx.format.WFSWriteGetFeatureOptions.prototype.filter; + + /** * @typedef {{featureNS: string, * featurePrefix: string, diff --git a/src/ol/format/ogc/filter.js b/src/ol/format/ogc/filter.js new file mode 100644 index 0000000000..0af819fc19 --- /dev/null +++ b/src/ol/format/ogc/filter.js @@ -0,0 +1,637 @@ +goog.provide('ol.format.ogc.filter'); +goog.provide('ol.format.ogc.filter.Filter'); +goog.provide('ol.format.ogc.filter.Logical'); +goog.provide('ol.format.ogc.filter.LogicalBinary'); +goog.provide('ol.format.ogc.filter.And'); +goog.provide('ol.format.ogc.filter.Or'); +goog.provide('ol.format.ogc.filter.Not'); +goog.provide('ol.format.ogc.filter.Bbox'); +goog.provide('ol.format.ogc.filter.Comparison'); +goog.provide('ol.format.ogc.filter.ComparisonBinary'); +goog.provide('ol.format.ogc.filter.EqualTo'); +goog.provide('ol.format.ogc.filter.NotEqualTo'); +goog.provide('ol.format.ogc.filter.LessThan'); +goog.provide('ol.format.ogc.filter.LessThanOrEqualTo'); +goog.provide('ol.format.ogc.filter.GreaterThan'); +goog.provide('ol.format.ogc.filter.GreaterThanOrEqualTo'); +goog.provide('ol.format.ogc.filter.IsNull'); +goog.provide('ol.format.ogc.filter.IsBetween'); +goog.provide('ol.format.ogc.filter.IsLike'); + +goog.require('ol.Object'); + + +/** + * Create a logical `` operator between two filter conditions. + * + * @param {!ol.format.ogc.filter.Filter} conditionA First filter condition. + * @param {!ol.format.ogc.filter.Filter} conditionB Second filter condition. + * @returns {!ol.format.ogc.filter.And} `` operator. + * @api + */ +ol.format.ogc.filter.and = function(conditionA, conditionB) { + return new ol.format.ogc.filter.And(conditionA, conditionB); +}; + + +/** + * Create a logical `` operator between two filter conditions. + * + * @param {!ol.format.ogc.filter.Filter} conditionA First filter condition. + * @param {!ol.format.ogc.filter.Filter} conditionB Second filter condition. + * @returns {!ol.format.ogc.filter.Or} `` operator. + * @api + */ +ol.format.ogc.filter.or = function(conditionA, conditionB) { + return new ol.format.ogc.filter.Or(conditionA, conditionB); +}; + + +/** + * Represents a logical `` operator for a filter condition. + * + * @param {!ol.format.ogc.filter.Filter} condition Filter condition. + * @returns {!ol.format.ogc.filter.Not} `` operator. + * @api + */ +ol.format.ogc.filter.not = function(condition) { + return new ol.format.ogc.filter.Not(condition); +}; + + +/** + * Create a `` operator to test whether a geometry-valued property + * intersects a fixed bounding box + * + * @param {!string} geometryName Geometry name to use. + * @param {!ol.Extent} extent Extent. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @returns {!ol.format.ogc.filter.Bbox} `` operator. + * @api + */ +ol.format.ogc.filter.bbox = function(geometryName, extent, opt_srsName) { + return new ol.format.ogc.filter.Bbox(geometryName, extent, opt_srsName); +}; + + +/** + * Creates a `` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @returns {!ol.format.ogc.filter.EqualTo} `` operator. + * @api + */ +ol.format.ogc.filter.equalTo = function(propertyName, expression, opt_matchCase) { + return new ol.format.ogc.filter.EqualTo(propertyName, expression, opt_matchCase); +}; + + +/** + * Creates a `` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @returns {!ol.format.ogc.filter.NotEqualTo} `` operator. + * @api + */ +ol.format.ogc.filter.notEqualTo = function(propertyName, expression, opt_matchCase) { + return new ol.format.ogc.filter.NotEqualTo(propertyName, expression, opt_matchCase); +}; + + +/** + * Creates a `` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @returns {!ol.format.ogc.filter.LessThan} `` operator. + * @api + */ +ol.format.ogc.filter.lessThan = function(propertyName, expression) { + return new ol.format.ogc.filter.LessThan(propertyName, expression); +}; + + +/** + * Creates a `` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @returns {!ol.format.ogc.filter.LessThanOrEqualTo} `` operator. + * @api + */ +ol.format.ogc.filter.lessThanOrEqualTo = function(propertyName, expression) { + return new ol.format.ogc.filter.LessThanOrEqualTo(propertyName, expression); +}; + + +/** + * Creates a `` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @returns {!ol.format.ogc.filter.GreaterThan} `` operator. + * @api + */ +ol.format.ogc.filter.greaterThan = function(propertyName, expression) { + return new ol.format.ogc.filter.GreaterThan(propertyName, expression); +}; + + +/** + * Creates a `` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @returns {!ol.format.ogc.filter.GreaterThanOrEqualTo} `` operator. + * @api + */ +ol.format.ogc.filter.greaterThanOrEqualTo = function(propertyName, expression) { + return new ol.format.ogc.filter.GreaterThanOrEqualTo(propertyName, expression); +}; + + +/** + * Creates a `` comparison operator to test whether a property value + * is null. + * + * @param {!string} propertyName Name of the context property to compare. + * @returns {!ol.format.ogc.filter.IsNull} `` operator. + * @api + */ +ol.format.ogc.filter.isNull = function(propertyName) { + return new ol.format.ogc.filter.IsNull(propertyName); +}; + + +/** + * Creates a `` comparison operator to test whether an expression + * value lies within a range given by a lower and upper bound (inclusive). + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} lowerBoundary The lower bound of the range. + * @param {!number} upperBoundary The upper bound of the range. + * @returns {!ol.format.ogc.filter.IsBetween} `` operator. + * @api + */ +ol.format.ogc.filter.between = function(propertyName, lowerBoundary, upperBoundary) { + return new ol.format.ogc.filter.IsBetween(propertyName, lowerBoundary, upperBoundary); +}; + + +/** + * Represents a `` comparison operator that matches a string property + * value against a text pattern. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!string} pattern Text pattern. + * @param {string=} opt_wildCard Pattern character which matches any sequence of + * zero or more string characters. Default is '*'. + * @param {string=} opt_singleChar pattern character which matches any single + * string character. Default is '.'. + * @param {string=} opt_escapeChar Escape character which can be used to escape + * the pattern characters. Default is '!'. + * @param {boolean=} opt_matchCase Case-sensitive? + * @returns {!ol.format.ogc.filter.IsLike} `` operator. + * @api + */ +ol.format.ogc.filter.like = function(propertyName, pattern, + opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase) { + return new ol.format.ogc.filter.IsLike(propertyName, pattern, + opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase); +}; + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @extends {ol.Object} + * @api + */ +ol.format.ogc.filter.Filter = function(tagName) { + + goog.base(this); + + /** + * @private + * @type {!string} + */ + this.tagName_ = tagName; +}; +goog.inherits(ol.format.ogc.filter.Filter, ol.Object); + +/** + * The XML tag name for a filter. + * @returns {!string} Name. + */ +ol.format.ogc.filter.Filter.prototype.getTagName = function() { + return this.tagName_; +}; + + +// Logical filters + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature logical filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @extends {ol.format.ogc.filter.Filter} + */ +ol.format.ogc.filter.Logical = function(tagName) { + goog.base(this, tagName); +}; +goog.inherits(ol.format.ogc.filter.Logical, ol.format.ogc.filter.Filter); + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature binary logical filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @param {!ol.format.ogc.filter.Filter} conditionA First filter condition. + * @param {!ol.format.ogc.filter.Filter} conditionB Second filter condition. + * @extends {ol.format.ogc.filter.Logical} + */ +ol.format.ogc.filter.LogicalBinary = function(tagName, conditionA, conditionB) { + + goog.base(this, tagName); + + /** + * @public + * @type {!ol.format.ogc.filter.Filter} + */ + this.conditionA = conditionA; + + /** + * @public + * @type {!ol.format.ogc.filter.Filter} + */ + this.conditionB = conditionB; + +}; +goog.inherits(ol.format.ogc.filter.LogicalBinary, ol.format.ogc.filter.Logical); + + +/** + * @classdesc + * Represents a logical `` operator between two filter conditions. + * + * @constructor + * @param {!ol.format.ogc.filter.Filter} conditionA First filter condition. + * @param {!ol.format.ogc.filter.Filter} conditionB Second filter condition. + * @extends {ol.format.ogc.filter.LogicalBinary} + * @api + */ +ol.format.ogc.filter.And = function(conditionA, conditionB) { + goog.base(this, 'And', conditionA, conditionB); +}; +goog.inherits(ol.format.ogc.filter.And, ol.format.ogc.filter.LogicalBinary); + + +/** + * @classdesc + * Represents a logical `` operator between two filter conditions. + * + * @constructor + * @param {!ol.format.ogc.filter.Filter} conditionA First filter condition. + * @param {!ol.format.ogc.filter.Filter} conditionB Second filter condition. + * @extends {ol.format.ogc.filter.LogicalBinary} + * @api + */ +ol.format.ogc.filter.Or = function(conditionA, conditionB) { + goog.base(this, 'Or', conditionA, conditionB); +}; +goog.inherits(ol.format.ogc.filter.Or, ol.format.ogc.filter.LogicalBinary); + + +/** + * @classdesc + * Represents a logical `` operator for a filter condition. + * + * @constructor + * @param {!ol.format.ogc.filter.Filter} condition Filter condition. + * @extends {ol.format.ogc.filter.Logical} + * @api + */ +ol.format.ogc.filter.Not = function(condition) { + + goog.base(this, 'Not'); + + /** + * @public + * @type {!ol.format.ogc.filter.Filter} + */ + this.condition = condition; +}; +goog.inherits(ol.format.ogc.filter.Not, ol.format.ogc.filter.Logical); + + +// Spatial filters + + +/** + * @classdesc + * Represents a `` operator to test whether a geometry-valued property + * intersects a fixed bounding box + * + * @constructor + * @param {!string} geometryName Geometry name to use. + * @param {!ol.Extent} extent Extent. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @extends {ol.format.ogc.filter.Filter} + * @api + */ +ol.format.ogc.filter.Bbox = function(geometryName, extent, opt_srsName) { + + goog.base(this, 'BBOX'); + + /** + * @public + * @type {!string} + */ + this.geometryName = geometryName; + + /** + * @public + * @type {!ol.Extent} + */ + this.extent = extent; + + /** + * @public + * @type {string|undefined} + */ + this.srsName = opt_srsName; +}; +goog.inherits(ol.format.ogc.filter.Bbox, ol.format.ogc.filter.Filter); + + +// Property comparison filters + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature property comparison filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @param {!string} propertyName Name of the context property to compare. + * @extends {ol.format.ogc.filter.Filter} + * @api + */ +ol.format.ogc.filter.Comparison = function(tagName, propertyName) { + + goog.base(this, tagName); + + /** + * @public + * @type {!string} + */ + this.propertyName = propertyName; +}; +goog.inherits(ol.format.ogc.filter.Comparison, ol.format.ogc.filter.Filter); + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature property binary comparison filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @extends {ol.format.ogc.filter.Comparison} + * @api + */ +ol.format.ogc.filter.ComparisonBinary = function( + tagName, propertyName, expression, opt_matchCase) { + + goog.base(this, tagName, propertyName); + + /** + * @public + * @type {!(string|number)} + */ + this.expression = expression; + + /** + * @public + * @type {boolean|undefined} + */ + this.matchCase = opt_matchCase; +}; +goog.inherits(ol.format.ogc.filter.ComparisonBinary, ol.format.ogc.filter.Comparison); + + +/** + * @classdesc + * Represents a `` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @extends {ol.format.ogc.filter.ComparisonBinary} + * @api + */ +ol.format.ogc.filter.EqualTo = function(propertyName, expression, opt_matchCase) { + goog.base(this, 'PropertyIsEqualTo', propertyName, expression, opt_matchCase); +}; +goog.inherits(ol.format.ogc.filter.EqualTo, ol.format.ogc.filter.ComparisonBinary); + + +/** + * @classdesc + * Represents a `` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @extends {ol.format.ogc.filter.ComparisonBinary} + * @api + */ +ol.format.ogc.filter.NotEqualTo = function(propertyName, expression, opt_matchCase) { + goog.base(this, 'PropertyIsNotEqualTo', propertyName, expression, opt_matchCase); +}; +goog.inherits(ol.format.ogc.filter.NotEqualTo, ol.format.ogc.filter.ComparisonBinary); + + +/** + * @classdesc + * Represents a `` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @extends {ol.format.ogc.filter.ComparisonBinary} + * @api + */ +ol.format.ogc.filter.LessThan = function(propertyName, expression) { + goog.base(this, 'PropertyIsLessThan', propertyName, expression); +}; +goog.inherits(ol.format.ogc.filter.LessThan, ol.format.ogc.filter.ComparisonBinary); + + +/** + * @classdesc + * Represents a `` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @extends {ol.format.ogc.filter.ComparisonBinary} + * @api + */ +ol.format.ogc.filter.LessThanOrEqualTo = function(propertyName, expression) { + goog.base(this, 'PropertyIsLessThanOrEqualTo', propertyName, expression); +}; +goog.inherits(ol.format.ogc.filter.LessThanOrEqualTo, ol.format.ogc.filter.ComparisonBinary); + + +/** + * @classdesc + * Represents a `` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @extends {ol.format.ogc.filter.ComparisonBinary} + * @api + */ +ol.format.ogc.filter.GreaterThan = function(propertyName, expression) { + goog.base(this, 'PropertyIsGreaterThan', propertyName, expression); +}; +goog.inherits(ol.format.ogc.filter.GreaterThan, ol.format.ogc.filter.ComparisonBinary); + + +/** + * @classdesc + * Represents a `` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @extends {ol.format.ogc.filter.ComparisonBinary} + * @api + */ +ol.format.ogc.filter.GreaterThanOrEqualTo = function(propertyName, expression) { + goog.base(this, 'PropertyIsGreaterThanOrEqualTo', propertyName, expression); +}; +goog.inherits(ol.format.ogc.filter.GreaterThanOrEqualTo, ol.format.ogc.filter.ComparisonBinary); + + +/** + * @classdesc + * Represents a `` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @extends {ol.format.ogc.filter.Comparison} + * @api + */ +ol.format.ogc.filter.IsNull = function(propertyName) { + goog.base(this, 'PropertyIsNull', propertyName); +}; +goog.inherits(ol.format.ogc.filter.IsNull, ol.format.ogc.filter.Comparison); + + +/** + * @classdesc + * Represents a `` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} lowerBoundary The lower bound of the range. + * @param {!number} upperBoundary The upper bound of the range. + * @extends {ol.format.ogc.filter.Comparison} + * @api + */ +ol.format.ogc.filter.IsBetween = function(propertyName, lowerBoundary, upperBoundary) { + goog.base(this, 'PropertyIsBetween', propertyName); + + /** + * @public + * @type {!number} + */ + this.lowerBoundary = lowerBoundary; + + /** + * @public + * @type {!number} + */ + this.upperBoundary = upperBoundary; +}; +goog.inherits(ol.format.ogc.filter.IsBetween, ol.format.ogc.filter.Comparison); + + +/** + * @classdesc + * Represents a `` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!string} pattern Text pattern. + * @param {string=} opt_wildCard Pattern character which matches any sequence of + * zero or more string characters. Default is '*'. + * @param {string=} opt_singleChar pattern character which matches any single + * string character. Default is '.'. + * @param {string=} opt_escapeChar Escape character which can be used to escape + * the pattern characters. Default is '!'. + * @param {boolean=} opt_matchCase Case-sensitive? + * @extends {ol.format.ogc.filter.Comparison} + * @api + */ +ol.format.ogc.filter.IsLike = function(propertyName, pattern, + opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase) { + goog.base(this, 'PropertyIsLike', propertyName); + + /** + * @public + * @type {!string} + */ + this.pattern = pattern; + + /** + * @public + * @type {!string} + */ + this.wildCard = (opt_wildCard !== undefined) ? opt_wildCard : '*'; + + /** + * @public + * @type {!string} + */ + this.singleChar = (opt_singleChar !== undefined) ? opt_singleChar : '.'; + + /** + * @public + * @type {!string} + */ + this.escapeChar = (opt_escapeChar !== undefined) ? opt_escapeChar : '!'; + + /** + * @public + * @type {boolean|undefined} + */ + this.matchCase = opt_matchCase; +}; +goog.inherits(ol.format.ogc.filter.IsLike, ol.format.ogc.filter.Comparison); diff --git a/src/ol/format/ogc/filter.jsdoc b/src/ol/format/ogc/filter.jsdoc new file mode 100644 index 0000000000..b0a85d1a59 --- /dev/null +++ b/src/ol/format/ogc/filter.jsdoc @@ -0,0 +1,29 @@ +/** + * This namespace contains convenience functions to create filters for + * {@link ol.format.WFS#writeGetFeature}. + * + * For example to generate a `GetFeature` request with a `PropertyIsEqualTo` filter: + * + * var request = new ol.format.WFS().writeGetFeature({ + * srsName: 'urn:ogc:def:crs:EPSG::4326', + * featureNS: 'http://www.openplans.org/topp', + * featurePrefix: 'topp', + * featureTypes: ['states'], + * filter: ol.format.wfs.filter.equalTo('name', 'New York') + * }); + * + * Or to combine a `BBOX` filter with a `PropertyIsLike` filter: + * + * var f = ol.format.wfs.filter; + * var request = new ol.format.WFS().writeGetFeature({ + * srsName: 'urn:ogc:def:crs:EPSG::4326', + * featureNS: 'http://www.openplans.org/topp', + * featurePrefix: 'topp', + * featureTypes: ['states'], + * filter: f.and( + * f.bbox('the_geom', [1, 2, 3, 4], 'urn:ogc:def:crs:EPSG::4326'), + * f.like('name', 'New*') + * ) + * }); + * @namespace ol.format.ogc.filter + */ diff --git a/src/ol/format/readme.md b/src/ol/format/readme.md index fedb94fa51..cf65f83e4a 100644 --- a/src/ol/format/readme.md +++ b/src/ol/format/readme.md @@ -5,7 +5,7 @@ * `readFeature` returning an `ol.Feature` * `readGeometry` returning an `ol.geom.Geometry` -Having different functions for multiple return types allows both the user to specify what type of data he wants and for the Compiler to properly type check the code. Depending on the format, it is entirely reasonable to leave one or more of these methods unimplemented, or provide sensible default implementations. +Having different functions for multiple return types allows both the user to specify what type of data they want and for the compiler to properly type check the code. Depending on the format, it is entirely reasonable to leave one or more of these methods unimplemented, or provide sensible default implementations. For example, `ol.format.GPX` only supports reading multiple features. Therefore `readFeature` and `readGeometry` are unimplemented and will raise an exception if called. @@ -19,7 +19,7 @@ If a file cannot be parsed, then the return value should be `null` for all three # Implementing XML formats This is an introduction for people looking to contribute an XML format reader to OpenLayers 3. After having read this document, you should read the code of and make sure that you understand the simpler XML format readers like `ol.format.GPX` before embarking on writing your own format reader. -The document ends with guildelines for implementing a new format. +The document ends with guidelines for implementing a new format. The `ol.xml` namespace contains a number of useful functions for parsing XML documents. All code in OpenLayers 3 that reads data from XML documents should use it. It has several features: diff --git a/src/ol/format/wfsformat.js b/src/ol/format/wfsformat.js index c9f1633b86..fa9e660566 100644 --- a/src/ol/format/wfsformat.js +++ b/src/ol/format/wfsformat.js @@ -5,6 +5,14 @@ goog.require('goog.dom.NodeType'); goog.require('ol'); goog.require('ol.format.GML3'); goog.require('ol.format.GMLBase'); +goog.require('ol.format.ogc.filter'); +goog.require('ol.format.ogc.filter.Bbox'); +goog.require('ol.format.ogc.filter.ComparisonBinary'); +goog.require('ol.format.ogc.filter.LogicalBinary'); +goog.require('ol.format.ogc.filter.Not'); +goog.require('ol.format.ogc.filter.IsBetween'); +goog.require('ol.format.ogc.filter.IsNull'); +goog.require('ol.format.ogc.filter.IsLike'); goog.require('ol.format.XMLFeature'); goog.require('ol.format.XSD'); goog.require('ol.geom.Geometry'); @@ -551,24 +559,163 @@ ol.format.WFS.writeQuery_ = function(node, featureType, objectStack) { ol.format.WFS.QUERY_SERIALIZERS_, ol.xml.makeSimpleNodeFactory('PropertyName'), propertyNames, objectStack); - var bbox = context['bbox']; - if (bbox) { + var filter = context['filter']; + if (filter) { var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter'); - ol.format.WFS.writeOgcBBOX_(child, bbox, objectStack); node.appendChild(child); + ol.format.WFS.writeFilterCondition_(child, filter, objectStack); } }; /** * @param {Node} node Node. - * @param {string} value PropertyName value. + * @param {ol.format.ogc.filter.Filter} filter Filter. * @param {Array.<*>} objectStack Node stack. * @private */ -ol.format.WFS.writeOgcPropertyName_ = function(node, value, objectStack) { - var property = ol.xml.createElementNS('http://www.opengis.net/ogc', - 'PropertyName'); +ol.format.WFS.writeFilterCondition_ = function(node, filter, objectStack) { + var item = {node: node}; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory(filter.getTagName()), + [filter], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.ogc.filter.Filter} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeBboxFilter_ = function(node, filter, objectStack) { + goog.asserts.assertInstanceof(filter, ol.format.ogc.filter.Bbox, + 'must be bbox filter'); + + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + context.srsName = filter.srsName; + + ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName); + ol.format.GML3.prototype.writeGeometryElement(node, filter.extent, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.ogc.filter.Filter} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeLogicalFilter_ = function(node, filter, objectStack) { + goog.asserts.assertInstanceof(filter, ol.format.ogc.filter.LogicalBinary, + 'must be logical filter'); + var item = {node: node}; + var conditionA = filter.conditionA; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory(conditionA.getTagName()), + [conditionA], objectStack); + var conditionB = filter.conditionB; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory(conditionB.getTagName()), + [conditionB], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.ogc.filter.Filter} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeNotFilter_ = function(node, filter, objectStack) { + goog.asserts.assertInstanceof(filter, ol.format.ogc.filter.Not, + 'must be Not filter'); + var item = {node: node}; + var condition = filter.condition; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory(condition.getTagName()), + [condition], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.ogc.filter.Filter} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeComparisonFilter_ = function(node, filter, objectStack) { + goog.asserts.assertInstanceof(filter, ol.format.ogc.filter.ComparisonBinary, + 'must be binary comparison filter'); + if (filter.matchCase !== undefined) { + node.setAttribute('matchCase', filter.matchCase.toString()); + } + ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName); + ol.format.WFS.writeOgcLiteral_(node, '' + filter.expression); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.ogc.filter.Filter} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeIsNullFilter_ = function(node, filter, objectStack) { + goog.asserts.assertInstanceof(filter, ol.format.ogc.filter.IsNull, + 'must be IsNull comparison filter'); + ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.ogc.filter.Filter} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeIsBetweenFilter_ = function(node, filter, objectStack) { + goog.asserts.assertInstanceof(filter, ol.format.ogc.filter.IsBetween, + 'must be IsBetween comparison filter'); + ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName); + ol.format.WFS.writeOgcExpression_('LowerBoundary', node, '' + filter.lowerBoundary); + ol.format.WFS.writeOgcExpression_('UpperBoundary', node, '' + filter.upperBoundary); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.ogc.filter.Filter} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeIsLikeFilter_ = function(node, filter, objectStack) { + goog.asserts.assertInstanceof(filter, ol.format.ogc.filter.IsLike, + 'must be IsLike comparison filter'); + node.setAttribute('wildCard', filter.wildCard); + node.setAttribute('singleChar', filter.singleChar); + node.setAttribute('escapeChar', filter.escapeChar); + if (filter.matchCase !== undefined) { + node.setAttribute('matchCase', filter.matchCase.toString()); + } + ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName); + ol.format.WFS.writeOgcLiteral_(node, '' + filter.pattern); +}; + + +/** + * @param {string} tagName Tag name. + * @param {Node} node Node. + * @param {string} value Value. + * @private + */ +ol.format.WFS.writeOgcExpression_ = function(tagName, node, value) { + var property = ol.xml.createElementNS('http://www.opengis.net/ogc', tagName); ol.format.XSD.writeStringTextNode(property, value); node.appendChild(property); }; @@ -576,18 +723,21 @@ ol.format.WFS.writeOgcPropertyName_ = function(node, value, objectStack) { /** * @param {Node} node Node. - * @param {ol.Extent} bbox Bounding box. - * @param {Array.<*>} objectStack Node stack. + * @param {string} value PropertyName value. * @private */ -ol.format.WFS.writeOgcBBOX_ = function(node, bbox, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context), 'context should be an Object'); - var geometryName = context['geometryName']; - var bboxNode = ol.xml.createElementNS('http://www.opengis.net/ogc', 'BBOX'); - node.appendChild(bboxNode); - ol.format.WFS.writeOgcPropertyName_(bboxNode, geometryName, objectStack); - ol.format.GML3.prototype.writeGeometryElement(bboxNode, bbox, objectStack); +ol.format.WFS.writeOgcPropertyName_ = function(node, value) { + ol.format.WFS.writeOgcExpression_('PropertyName', node, value); +}; + + +/** + * @param {Node} node Node. + * @param {string} value PropertyName value. + * @private + */ +ol.format.WFS.writeOgcLiteral_ = function(node, value) { + ol.format.WFS.writeOgcExpression_('Literal', node, value); }; @@ -597,8 +747,22 @@ ol.format.WFS.writeOgcBBOX_ = function(node, bbox, objectStack) { */ ol.format.WFS.GETFEATURE_SERIALIZERS_ = { 'http://www.opengis.net/wfs': { - 'Query': ol.xml.makeChildAppender( - ol.format.WFS.writeQuery_) + 'Query': ol.xml.makeChildAppender(ol.format.WFS.writeQuery_) + }, + 'http://www.opengis.net/ogc': { + 'And': ol.xml.makeChildAppender(ol.format.WFS.writeLogicalFilter_), + 'Or': ol.xml.makeChildAppender(ol.format.WFS.writeLogicalFilter_), + 'Not': ol.xml.makeChildAppender(ol.format.WFS.writeNotFilter_), + 'BBOX': ol.xml.makeChildAppender(ol.format.WFS.writeBboxFilter_), + 'PropertyIsEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsNotEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsLessThan': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsLessThanOrEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsGreaterThan': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsGreaterThanOrEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsNull': ol.xml.makeChildAppender(ol.format.WFS.writeIsNullFilter_), + 'PropertyIsBetween': ol.xml.makeChildAppender(ol.format.WFS.writeIsBetweenFilter_), + 'PropertyIsLike': ol.xml.makeChildAppender(ol.format.WFS.writeIsLikeFilter_) } }; @@ -633,6 +797,7 @@ ol.format.WFS.prototype.writeGetFeature = function(options) { 'GetFeature'); node.setAttribute('service', 'WFS'); node.setAttribute('version', '1.1.0'); + var filter; if (options) { if (options.handle) { node.setAttribute('handle', options.handle); @@ -652,6 +817,19 @@ ol.format.WFS.prototype.writeGetFeature = function(options) { if (options.count !== undefined) { node.setAttribute('count', options.count); } + filter = options.filter; + if (options.bbox) { + goog.asserts.assert(options.geometryName, + 'geometryName must be set when using bbox filter'); + var bbox = ol.format.ogc.filter.bbox( + options.geometryName, options.bbox, options.srsName); + if (filter) { + // if bbox and filter are both set, combine the two into a single filter + filter = ol.format.ogc.filter.and(filter, bbox); + } else { + filter = bbox; + } + } } ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation', this.schemaLocation_); @@ -661,7 +839,7 @@ ol.format.WFS.prototype.writeGetFeature = function(options) { featureNS: options.featureNS ? options.featureNS : this.featureNS_, featurePrefix: options.featurePrefix, geometryName: options.geometryName, - bbox: options.bbox, + filter: filter, propertyNames: options.propertyNames ? options.propertyNames : [] }; goog.asserts.assert(Array.isArray(options.featureTypes), diff --git a/test/spec/ol/format/wfsformat.test.js b/test/spec/ol/format/wfsformat.test.js index 3c26d26ebc..cd49f089af 100644 --- a/test/spec/ol/format/wfsformat.test.js +++ b/test/spec/ol/format/wfsformat.test.js @@ -1,4 +1,5 @@ goog.provide('ol.test.format.WFS'); +goog.require('ol.format.ogc.filter'); describe('ol.format.WFS', function() { @@ -249,6 +250,260 @@ describe('ol.format.WFS', function() { expect(serialized.firstElementChild).to.xmleql(ol.xml.parse(text)); }); + it('creates a property filter', function() { + var text = + '' + + ' ' + + ' ' + + ' name' + + ' New York' + + ' ' + + ' ' + + ''; + var serialized = new ol.format.WFS().writeGetFeature({ + srsName: 'urn:ogc:def:crs:EPSG::4326', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'], + filter: ol.format.ogc.filter.equalTo('name', 'New York', false) + }); + expect(serialized.firstElementChild).to.xmleql(ol.xml.parse(text)); + }); + + it('creates two property filters', function() { + var text = + '' + + ' ' + + ' ' + + ' ' + + ' name' + + ' New York' + + ' ' + + ' ' + + ' area' + + ' 1234' + + ' ' + + ' ' + + ' ' + + ''; + var f = ol.format.ogc.filter; + var serialized = new ol.format.WFS().writeGetFeature({ + srsName: 'urn:ogc:def:crs:EPSG::4326', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'], + filter: f.or(f.equalTo('name', 'New York'), f.equalTo('area', 1234)) + }); + expect(serialized.firstElementChild).to.xmleql(ol.xml.parse(text)); + }); + + it('creates greater/less than property filters', function() { + var text = + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' area' + + ' 100' + + ' ' + + ' ' + + ' pop' + + ' 20000' + + ' ' + + ' ' + + ' ' + + ' ' + + ' area' + + ' 100' + + ' ' + + ' ' + + ' pop' + + ' 20000' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + var f = ol.format.ogc.filter; + var serialized = new ol.format.WFS().writeGetFeature({ + srsName: 'urn:ogc:def:crs:EPSG::4326', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'], + filter: f.or( + f.and( + f.greaterThan('area', 100), + f.greaterThanOrEqualTo('pop', 20000) + ), + f.and( + f.lessThan('area', 100), + f.lessThanOrEqualTo('pop', 20000) + ) + ) + }); + expect(serialized.firstElementChild).to.xmleql(ol.xml.parse(text)); + }); + + it('creates isBetween property filter', function() { + var text = + '' + + ' ' + + ' ' + + ' area' + + ' 100' + + ' 1000' + + ' ' + + ' ' + + ''; + var f = ol.format.ogc.filter; + var serialized = new ol.format.WFS().writeGetFeature({ + srsName: 'urn:ogc:def:crs:EPSG::4326', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'], + filter: f.between('area', 100, 1000) + }); + expect(serialized.firstElementChild).to.xmleql(ol.xml.parse(text)); + }); + + it('creates isNull property filter', function() { + var text = + '' + + ' ' + + ' ' + + ' area' + + ' ' + + ' ' + + ''; + var f = ol.format.ogc.filter; + var serialized = new ol.format.WFS().writeGetFeature({ + srsName: 'urn:ogc:def:crs:EPSG::4326', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'], + filter: f.isNull('area') + }); + expect(serialized.firstElementChild).to.xmleql(ol.xml.parse(text)); + }); + + it('creates isLike property filter', function() { + var text = + '' + + ' ' + + ' ' + + ' name' + + ' New*' + + ' ' + + ' ' + + ''; + var f = ol.format.ogc.filter; + var serialized = new ol.format.WFS().writeGetFeature({ + srsName: 'urn:ogc:def:crs:EPSG::4326', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'], + filter: f.like('name', 'New*') + }); + expect(serialized.firstElementChild).to.xmleql(ol.xml.parse(text)); + }); + + it('creates isLike property filter with arguments', function() { + var text = + '' + + ' ' + + ' ' + + ' name' + + ' New*' + + ' ' + + ' ' + + ''; + var f = ol.format.ogc.filter; + var serialized = new ol.format.WFS().writeGetFeature({ + srsName: 'urn:ogc:def:crs:EPSG::4326', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'], + filter: f.like('name', 'New*', '*', '.', '!', false) + }); + expect(serialized.firstElementChild).to.xmleql(ol.xml.parse(text)); + }); + + it('creates a Not filter', function() { + var text = + '' + + ' ' + + ' ' + + ' ' + + ' name' + + ' New York' + + ' ' + + ' ' + + ' ' + + ''; + var f = ol.format.ogc.filter; + var serialized = new ol.format.WFS().writeGetFeature({ + srsName: 'urn:ogc:def:crs:EPSG::4326', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'], + filter: f.not(f.equalTo('name', 'New York')) + }); + expect(serialized.firstElementChild).to.xmleql(ol.xml.parse(text)); + }); + + it('creates an AND filter', function() { + var text = + '' + + ' ' + + ' ' + + ' ' + + ' name' + + ' New York' + + ' ' + + ' ' + + ' the_geom' + + ' ' + + ' 1 2' + + ' 3 4' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + var f = ol.format.ogc.filter; + var serialized = new ol.format.WFS().writeGetFeature({ + srsName: 'urn:ogc:def:crs:EPSG::4326', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'], + filter: f.and( + f.equalTo('name', 'New York'), + f.bbox('the_geom', [1, 2, 3, 4], 'urn:ogc:def:crs:EPSG::4326') + ) + }); + expect(serialized.firstElementChild).to.xmleql(ol.xml.parse(text)); + }); + }); describe('when writing out a Transaction request', function() {