From 557390f2add7e7dc07fd3643871eedd6d179b12d Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Tue, 22 Oct 2013 16:24:27 +0200 Subject: [PATCH] initial version of the WFS parser for 1.0.0 and 1.1.0 --- src/objectliterals.jsdoc | 7 ++ src/ol/parser/ogc/wfsparser.js | 38 +++++++ src/ol/parser/ogc/wfsparser_v1.js | 87 ++++++++++++++++ src/ol/parser/ogc/wfsparser_v1_0_0.js | 99 +++++++++++++++++++ src/ol/parser/ogc/wfsparser_v1_1_0.js | 94 ++++++++++++++++++ test/spec/ol/parser/ogc/wfs_v1_0_0.test.js | 49 +++++++++ .../xml/wfs_v1_0_0/Transaction_Response.xml | 11 +++ .../ol/parser/ogc/xml/wfs_v1_0_0/query0.xml | 10 ++ 8 files changed, 395 insertions(+) create mode 100644 src/ol/parser/ogc/wfsparser.js create mode 100644 src/ol/parser/ogc/wfsparser_v1.js create mode 100644 src/ol/parser/ogc/wfsparser_v1_0_0.js create mode 100644 src/ol/parser/ogc/wfsparser_v1_1_0.js create mode 100644 test/spec/ol/parser/ogc/wfs_v1_0_0.test.js create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1_0_0/Transaction_Response.xml create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1_0_0/query0.xml diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index d416c8b63e..ef153d8169 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -595,6 +595,13 @@ * calculations. */ +/** + * @typedef {Object} ol.parser.WFSOptions + * @property {Array.} featureTypes The feature types to use. + * @property {string} featureNS The featureNS to use. + * @property {string} featurePrefix The prefix to use for the featureNS. + */ + /** * @typedef {Object} ol.source.BingMapsOptions * @property {string|undefined} culture Culture code. Default is `en-us`. diff --git a/src/ol/parser/ogc/wfsparser.js b/src/ol/parser/ogc/wfsparser.js new file mode 100644 index 0000000000..68e3141326 --- /dev/null +++ b/src/ol/parser/ogc/wfsparser.js @@ -0,0 +1,38 @@ +goog.require('ol.parser.ogc.Versioned'); +goog.provide('ol.parser.ogc.WFS'); +goog.require('ol.parser.ogc.WFS_v1_0_0'); +goog.require('ol.parser.ogc.WFS_v1_1_0'); + + +/** + * @define {boolean} Whether to enable OGC WFS version 1.0.0. + */ +ol.ENABLE_WFS_1_0_0 = true; + + +/** + * @define {boolean} Whether to enable OGC WFS version 1.1.0. + */ +ol.ENABLE_WFS_1_1_0 = true; + + + +/** + * @constructor + * @param {ol.parser.WFSOptions=} opt_options + * Optional configuration object. + * @extends {ol.parser.ogc.Versioned} + */ +ol.parser.ogc.WFS = function(opt_options) { + var options = opt_options || {}; + options['defaultVersion'] = '1.0.0'; + this.parsers = {}; + if (ol.ENABLE_WFS_1_0_0) { + this.parsers['v1_0_0'] = ol.parser.ogc.WFS_v1_0_0; + } + if (ol.ENABLE_WFS_1_1_0) { + this.parsers['v1_1_0'] = ol.parser.ogc.WFS_v1_1_0; + } + goog.base(this, options); +}; +goog.inherits(ol.parser.ogc.WFS, ol.parser.ogc.Versioned); diff --git a/src/ol/parser/ogc/wfsparser_v1.js b/src/ol/parser/ogc/wfsparser_v1.js new file mode 100644 index 0000000000..ddee7c2079 --- /dev/null +++ b/src/ol/parser/ogc/wfsparser_v1.js @@ -0,0 +1,87 @@ +goog.provide('ol.parser.ogc.WFS_v1'); +goog.require('goog.dom.xml'); +goog.require('ol.parser.XML'); + + + +/** + * @constructor + * @param {ol.parser.WFSOptions=} opt_options + * Optional configuration object. + * @extends {ol.parser.XML} + */ +ol.parser.ogc.WFS_v1 = function(opt_options) { + if (goog.isDef(opt_options)) { + this.featureTypes = opt_options.featureTypes; + this.featurePrefix = opt_options.featurePrefix; + this.featureNS = opt_options.featureNS; + } + this.defaultNamespaceURI = 'http://www.opengis.net/wfs'; + // TODO set errorProperty + this.readers = {}; + this.readers[this.defaultNamespaceURI] = { + 'FeatureCollection': function(node, obj) { + obj.features = []; + this.readChildNodes(node, obj); + } + }; + this.writers = {}; + this.writers[this.defaultNamespaceURI] = { + 'GetFeature': function(options) { + var node = this.createElementNS('wfs:GetFeature'); + node.setAttribute('service', 'WFS'); + node.setAttribute('version', this.version); + if (goog.isDef(options)) { + if (goog.isDef(options.handle)) { + node.setAttribute('handle', options.handle); + } + if (goog.isDef(options.outputFormat)) { + node.setAttribute('outputFormat', options.outputFormat); + } + if (goog.isDef(options.maxFeatures)) { + node.setAttribute('maxFeatures', options.maxFeatures); + } + } + // TODO set xsi:schemaLocation + for (var i = 0, ii = this.featureTypes.length; i < ii; i++) { + options.featureType = this.featureTypes[i]; + this.writeNode('Query', options, null, node); + } + return node; + } + }; + goog.base(this); +}; +goog.inherits(ol.parser.ogc.WFS_v1, ol.parser.XML); + + +/** + * @param {string|Document|Element} data Data to read. + * @return {Object} An object representing the document. + */ +ol.parser.ogc.WFS_v1.prototype.read = function(data) { + if (goog.isString(data)) { + data = goog.dom.xml.loadXml(data); + } + if (data && data.nodeType == 9) { + data = data.documentElement; + } + var obj = {}; + this.readNode(data, obj); + return obj; +}; + + +/** + * @param {Array.} features The features to write out. + * @param {Object} options Write options. + * @return {string} A serialized WFS transaction. + */ +ol.parser.ogc.WFS_v1.prototype.write = function(features, options) { + var root = this.writeNode('wfs:Transaction', {features: features, + options: options}); + this.setAttributeNS( + root, 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation', this.schemaLocation); + return this.serialize(root); +}; diff --git a/src/ol/parser/ogc/wfsparser_v1_0_0.js b/src/ol/parser/ogc/wfsparser_v1_0_0.js new file mode 100644 index 0000000000..0387940f67 --- /dev/null +++ b/src/ol/parser/ogc/wfsparser_v1_0_0.js @@ -0,0 +1,99 @@ +goog.provide('ol.parser.ogc.WFS_v1_0_0'); + +goog.require('goog.array'); +goog.require('goog.object'); +goog.require('ol.parser.ogc.Filter_v1_0_0'); +goog.require('ol.parser.ogc.WFS_v1'); + + + +/** + * @constructor + * @param {ol.parser.WFSOptions=} opt_options + * Optional configuration object. + * @extends {ol.parser.ogc.WFS_v1} + */ +ol.parser.ogc.WFS_v1_0_0 = function(opt_options) { + goog.base(this, opt_options); + this.version = '1.0.0'; + this.schemaLocation = this.defaultNamespaceURI + ' ' + + 'http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd'; + goog.object.extend(this.readers[this.defaultNamespaceURI], { + 'WFS_TransactionResponse': function(node, obj) { + obj.insertIds = []; + obj.success = false; + this.readChildNodes(node, obj); + }, + 'InsertResult': function(node, container) { + var obj = {fids: []}; + this.readChildNodes(node, obj); + for (var key in obj.fids) { + container.insertIds.push(key); + } + }, + 'TransactionResult': function(node, obj) { + this.readChildNodes(node, obj); + }, + 'Status': function(node, obj) { + this.readChildNodes(node, obj); + }, + 'SUCCESS': function(node, obj) { + obj.success = true; + } + }); + goog.object.extend(this.writers[this.defaultNamespaceURI], { + 'Query': function(options) { + // TODO see if we really need properties on the instance + /*goog.object.extend(options, { + featureNS: this.featureNS, + featurePrefix: this.featurePrefix, + featureType: this.featureType, + srsName: this.srsName, + srsNameInQuery: this.srsNameInQuery + });*/ + var prefix = goog.isDef(this.featurePrefix) ? this.featurePrefix + + ':' : ''; + var node = this.createElementNS('wfs:Query'); + node.setAttribute('typeName', prefix + options.featureType); + if (goog.isDef(options.srsNameInQuery) && goog.isDef(options.srsName)) { + node.setAttribute('srsName', options.srsName); + } + if (goog.isDef(this.featureNS)) { + node.setAttribute('xmlns:' + this.featurePrefix, this.featureNS); + } + if (goog.isDef(options.propertyNames)) { + for (var i = 0, ii = options.propertyNames.length; i < ii; i++) { + this.writeNode('ogc:PropertyName', { + property: options.propertyNames[i] + }, 'http://www.opengis.net/ogc', node); + } + } + if (goog.isDef(options.filter)) { + this.writeNode('Filter', options.filter, + 'http://www.opengis.net/ogc', node); + } + return node; + } + }); + this.filter_ = new ol.parser.ogc.Filter_v1_0_0(); + for (var uri in this.filter_.readers) { + for (var key in this.filter_.readers[uri]) { + if (!goog.isDef(this.readers[uri])) { + this.readers[uri] = {}; + } + this.readers[uri][key] = goog.bind(this.filter_.readers[uri][key], + this.filter_); + } + } + for (uri in this.filter_.writers) { + for (key in this.filter_.writers[uri]) { + if (!goog.isDef(this.writers[uri])) { + this.writers[uri] = {}; + } + this.writers[uri][key] = goog.bind(this.filter_.writers[uri][key], + this.filter_); + } + } +}; +goog.inherits(ol.parser.ogc.WFS_v1_0_0, + ol.parser.ogc.WFS_v1); diff --git a/src/ol/parser/ogc/wfsparser_v1_1_0.js b/src/ol/parser/ogc/wfsparser_v1_1_0.js new file mode 100644 index 0000000000..fee3992e66 --- /dev/null +++ b/src/ol/parser/ogc/wfsparser_v1_1_0.js @@ -0,0 +1,94 @@ +goog.provide('ol.parser.ogc.WFS_v1_1_0'); + +goog.require('goog.functions'); +goog.require('goog.object'); +goog.require('ol.parser.ogc.WFS_v1'); + + + +/** + * @constructor + * @extends {ol.parser.ogc.WFS_v1} + */ +ol.parser.ogc.WFS_v1_1_0 = function() { + goog.base(this); + this.version = '1.1.0'; + this.schemaLocation = this.defaultNamespaceURI + ' ' + + 'http://schemas.opengis.net/wfs/1.1.0/wfs.xsd'; + goog.object.extend(this.readers[this.defaultNamespaceURI], { + 'FeatureCollection': goog.functions.sequence( + function(node, obj) { + obj.numberOfFeatures = parseInt( + node.getAttribute('numberOfFeatures'), 10); + }, + this.readers['http://www.opengis.net/wfs']['FeatureCollection'] + ), + 'TransactionResponse': function(node, obj) { + obj.insertIds = []; + obj.success = false; + this.readChildNodes(node, obj); + }, + 'TransactionSummary': function(node, obj) { + // this is a limited test of success + obj.success = true; + }, + 'InsertResults': function(node, obj) { + this.readChildNodes(node, obj); + }, + 'Feature': function(node, container) { + var obj = {fids: []}; + this.readChildNodes(node, obj); + container.insertIds.push(obj.fids[0]); + } + }); + goog.object.extend(this.writers[this.defaultNamespaceURI], { + 'GetFeature': function(options) { + var node = this.writers['http://www.opengis.net/wfs']['GetFeature']. + apply(this, arguments); + if (goog.isDef(options)) { + node.setAttribute('resultType', options.resultType); + if (goog.isDef(options.startIndex)) { + node.setAttribute('startIndex', options.startIndex); + } + node.setAttribute('count', options.count); + } + return node; + }, + 'Query': function(options) { + goog.object.extend(options, { + featureNS: this.featureNS, + featurePrefix: this.featurePrefix, + featureType: this.featureType, + srsName: this.srsName + }); + var prefix = goog.isDef(options.featurePrefix) ? options.prefix + ':' : + ''; + var node = this.createElementNS('wfs:Query'); + node.setAttribute('typeName', prefix + options.featureType); + node.setAttribute('srsName', options.srsName); + if (goog.isDef(options.featureNS)) { + node.setAttribute('xmlns:' + options.prefix, options.featureNS); + } + if (goog.isDef(options.propertyNames)) { + for (var i = 0, ii = options.propertyNames.length; i < ii; i++) { + this.writeNode('wfs:PropertyName', { + property: options.propertyNames[i] + }, null, node); + } + } + if (goog.isDef(options.filter)) { + this.writeNode('ogc:Filter', options.filter, + 'http://www.opengis.net/ogc', node); + } + return node; + }, + 'PropertyName': function(obj) { + var node = this.createElementNS('wfs:PropertyName'); + node.appendChild(this.createTextNode(obj.property)); + return node; + } + }); + // TODO ogc and gml namespaces +}; +goog.inherits(ol.parser.ogc.WFS_v1_1_0, + ol.parser.ogc.WFS_v1); diff --git a/test/spec/ol/parser/ogc/wfs_v1_0_0.test.js b/test/spec/ol/parser/ogc/wfs_v1_0_0.test.js new file mode 100644 index 0000000000..716d95f644 --- /dev/null +++ b/test/spec/ol/parser/ogc/wfs_v1_0_0.test.js @@ -0,0 +1,49 @@ +goog.provide('ol.test.parser.ogc.WFS_v1_0_0'); + +describe('ol.parser.ogc.WFS_v1_0_0', function() { + + var parser = new ol.parser.ogc.WFS(); + + describe('reading and writing', function() { + + it('handles read of transaction response', function(done) { + var url = 'spec/ol/parser/ogc/xml/wfs_v1_0_0/Transaction_Response.xml'; + afterLoadXml(url, function(xml) { + var obj = parser.read(xml); + expect(obj.insertIds.length).to.equal(2); + expect(obj.insertIds[0]).to.equal('parcelle.40'); + expect(obj.insertIds[1]).to.equal('parcelle.41'); + expect(obj.version).to.equal('1.0.0'); + expect(obj.success).to.be(true); + done(); + }); + }); + + it('handles writing Query with BBOX Filter', function(done) { + var url = 'spec/ol/parser/ogc/xml/wfs_v1_0_0/query0.xml'; + afterLoadXml(url, function(xml) { + var p = new ol.parser.ogc.WFS_v1_0_0({featureTypes: ['states'], + featurePrefix: 'topp', featureNS: 'http://www.openplans.org/topp'}); + var filter = new ol.expr.Call( + new ol.expr.Identifier(ol.expr.functions.EXTENT), + [new ol.expr.Literal(1), new ol.expr.Literal(2), + new ol.expr.Literal(3), new ol.expr.Literal(4), + undefined, + new ol.expr.Identifier('the_geom')]); + var output = p.writers[p.defaultNamespaceURI]['Query'].apply( + p, [{filter: filter, featureType: 'states'}]); + expect(goog.dom.xml.loadXml(p.serialize(output))).to.xmleql(xml); + done(); + }); + }); + + }); + +}); + +goog.require('goog.dom.xml'); +goog.require('ol.expr.Call'); +goog.require('ol.expr.Identifier'); +goog.require('ol.expr.Literal'); +goog.require('ol.parser.ogc.WFS'); +goog.require('ol.parser.ogc.WFS_v1_0_0'); diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1_0_0/Transaction_Response.xml b/test/spec/ol/parser/ogc/xml/wfs_v1_0_0/Transaction_Response.xml new file mode 100644 index 0000000000..5d178b8124 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1_0_0/Transaction_Response.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1_0_0/query0.xml b/test/spec/ol/parser/ogc/xml/wfs_v1_0_0/query0.xml new file mode 100644 index 0000000000..d5fdc88cfa --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1_0_0/query0.xml @@ -0,0 +1,10 @@ + + + + the_geom + + 1,2 3,4 + + + +