diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index eea253f924..b783cc0abb 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -595,6 +595,30 @@ * calculations. */ +/** + * @typedef {Object} ol.parser.WFSWriteGetFeatureOptions + * @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 {string|undefined} srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @property {string|undefined} handle Handle. + * @property {string|undefined} outputFormat Output format. + * @property {number} maxFeatures Maximum number of features to fetch. + */ + +/** + * @typedef {Object} ol.parser.WFSWriteTransactionOptions + * @property {string} featureNS The namespace URI used for features. + * @property {string} featurePrefix The prefix for the feature namespace. + * @property {string} featureType The feature type name. + * @property {string|undefined} srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @property {string|undefined} handle Handle. + * @property {Array.} nativeElements Native elements. Currently not + * supported. + */ + /** * @typedef {Object} ol.source.BingMapsOptions * @property {string|undefined} culture Culture code. Default is `en-us`. diff --git a/src/ol/parser/ogc/gmlparser_v2.js b/src/ol/parser/ogc/gmlparser_v2.js index 837b5d844e..37bdd9d191 100644 --- a/src/ol/parser/ogc/gmlparser_v2.js +++ b/src/ol/parser/ogc/gmlparser_v2.js @@ -54,7 +54,8 @@ ol.parser.ogc.GML_v2 = function(opt_options) { for (var i = 0; i < numCoordinates; ++i) { var coord = coordinates[i]; var part = goog.array.concat(coord); - if (this.axisOrientation.substr(0, 2) !== 'en') { + if (goog.isDef(this.axisOrientation) && + this.axisOrientation.substr(0, 2) !== 'en') { part[0] = coord[1]; part[1] = coord[0]; } diff --git a/src/ol/parser/ogc/versionedparser.exports b/src/ol/parser/ogc/versionedparser.exports new file mode 100644 index 0000000000..8d3189fde9 --- /dev/null +++ b/src/ol/parser/ogc/versionedparser.exports @@ -0,0 +1 @@ +@exportProperty ol.parser.ogc.Versioned.prototype.getParser diff --git a/src/ol/parser/ogc/wfsparser.exports b/src/ol/parser/ogc/wfsparser.exports new file mode 100644 index 0000000000..14dbb32df3 --- /dev/null +++ b/src/ol/parser/ogc/wfsparser.exports @@ -0,0 +1,3 @@ +@exportSymbol ol.parser.ogc.WFS +@exportProperty ol.parser.ogc.WFS_v1.prototype.writeGetFeature +@exportProperty ol.parser.ogc.WFS_v1.prototype.writeTransaction diff --git a/src/ol/parser/ogc/wfsparser_v1.js b/src/ol/parser/ogc/wfsparser_v1.js index 2fb5f685fa..0f188f9074 100644 --- a/src/ol/parser/ogc/wfsparser_v1.js +++ b/src/ol/parser/ogc/wfsparser_v1.js @@ -1,31 +1,22 @@ goog.provide('ol.parser.ogc.WFS_v1'); +goog.require('goog.asserts'); goog.require('goog.dom.xml'); +goog.require('ol.expr.Call'); +goog.require('ol.expr.Identifier'); +goog.require('ol.expr.Literal'); +goog.require('ol.geom.Geometry'); goog.require('ol.parser.XML'); -/** - * @typedef {{featureNS: string, - featurePrefix: string, - featureTypes: Array., - handle: string, - outputFormat: string, - nativeElements: Array.<{ - vendorId: string, - safeToIgnore: boolean, - value: string - }>, - maxFeatures: number}} - */ -ol.parser.WFSWriteOptions; - - /** * @constructor * @extends {ol.parser.XML} + * @param {Object=} opt_options Options which will be set on this object. */ -ol.parser.ogc.WFS_v1 = function() { +ol.parser.ogc.WFS_v1 = function(opt_options) { this.defaultNamespaceURI = 'http://www.opengis.net/wfs'; + // TODO set errorProperty this.readers = {}; this.readers[this.defaultNamespaceURI] = { @@ -36,8 +27,13 @@ ol.parser.ogc.WFS_v1 = function() { }; this.writers = {}; this.writers[this.defaultNamespaceURI] = { + /** + * @param {ol.parser.WFSWriteGetFeatureOptions} options Options. + * @return {{node: Node, + * options: ol.parser.WFSWriteGetFeatureOptions}} Object. + * @this {ol.parser.XML} + */ 'GetFeature': function(options) { - options = /** @type {ol.parser.WFSWriteOptions} */(options); var node = this.createElementNS('wfs:GetFeature'); node.setAttribute('service', 'WFS'); node.setAttribute('version', this.version); @@ -61,29 +57,41 @@ ol.parser.ogc.WFS_v1 = function() { 'xsi:schemaLocation', this.schemaLocation); return {node: node, options: options}; }, + /** + * @param {{inserts: Array., + * updates: Array., + * deletes: Array., + * options: ol.parser.WFSWriteTransactionOptions}} obj Object. + * @return {Element} Node. + * @this {ol.parser.XML} + */ 'Transaction': function(obj) { - obj = obj || {}; - var options = /** {ol.parser.WFSWriteOptions} */(obj.options || {}); + var options = obj.options; + this.setFeatureType(options.featureType); + this.setFeatureNS(options.featureNS); + if (goog.isDef(options.srsName)) { + this.setSrsName(options.srsName); + } var node = this.createElementNS('wfs:Transaction'); node.setAttribute('service', 'WFS'); node.setAttribute('version', this.version); if (goog.isDef(options.handle)) { node.setAttribute('handle', options.handle); } - var i, ii; - var features = obj.features; - if (goog.isDefAndNotNull(features)) { - // TODO implement multi option for geometry types - var name, feature; - for (i = 0, ii = features.length; i < ii; ++i) { - feature = features[i]; - // TODO Update (use feature.getOriginal()) - // TODO Insert and Delete - if (goog.isDef(name)) { - this.writeNode(name, { - feature: feature, - options: options - }, null, node); + var i, ii, features, feature; + var operations = { + 'Insert': obj.inserts, + 'Update': obj.updates, + 'Delete': obj.deletes + }; + for (var name in operations) { + features = operations[name]; + if (!goog.isNull(features)) { + // TODO implement multi option for geometry types + for (i = 0, ii = features.length; i < ii; ++i) { + feature = features[i]; + this.writeNode(name, {feature: feature, options: options}, null, + node); } } } @@ -94,12 +102,140 @@ ol.parser.ogc.WFS_v1 = function() { } return node; }, + /** + * @param {{vendorId: string, safeToIgnore: boolean, value: string}} + * nativeElement Native element. + * @return {Node} Node. + * @this {ol.parser.XML} + */ 'Native': function(nativeElement) { var node = this.createElementNS('wfs:Native'); node.setAttribute('vendorId', nativeElement.vendorId); node.setAttribute('safeToIgnore', nativeElement.safeToIgnore); node.appendChild(this.createTextNode(nativeElement.value)); return node; + }, + /** + * @param {{feature: ol.Feature, + * options: ol.parser.WFSWriteTransactionOptions}} obj Object. + * @return {Element} Node. + * @this {ol.parser.XML} + */ + 'Insert': function(obj) { + var feature = obj.feature; + var options = obj.options; + var node = this.createElementNS('wfs:Insert'); + if (goog.isDef(options) && goog.isDef(options.handle)) { + this.setAttributeNS(node, this.defaultNamespaceURI, 'handle', + options.handle); + } + if (goog.isDef(options.srsName)) { + this.setSrsName(options.srsName); + } + this.writeNode('_typeName', feature, options.featureNS, node); + return node; + }, + /** + * @param {{feature: ol.Feature, + * options: ol.parser.WFSWriteTransactionOptions}} obj Object. + * @return {Element} Node. + * @this {ol.parser.XML} + */ + 'Update': function(obj) { + var feature = obj.feature; + var options = obj.options; + var node = this.createElementNS('wfs:Update'); + this.setAttributeNS(node, this.defaultNamespaceURI, 'typeName', + (goog.isDef(options.featureNS) ? options.featurePrefix + ':' : '') + + options.featureType); + if (goog.isDef(options.handle)) { + this.setAttributeNS(node, this.defaultNamespaceURI, 'handle', + options.handle); + } + + // add in fields + var original = feature.getOriginal(); + var originalAttributes = goog.isNull(original) ? + undefined : original.getAttributes(); + var attributes = feature.getAttributes(); + var attribute; + for (var key in attributes) { + attribute = attributes[key]; + // TODO Only add geometries whose values have changed + if (goog.isDef(attribute) && (attribute instanceof ol.geom.Geometry || + (!goog.isDef(originalAttributes) || + attribute != originalAttributes[key]))) { + this.writeNode('Property', {name: key, value: attribute}, null, node); + } + } + + // add feature id filter + var fid = feature.getId(); + goog.asserts.assert(goog.isDef(fid)); + this.writeNode('Filter', new ol.expr.Call(new ol.expr.Identifier( + ol.expr.functions.FID), [new ol.expr.Literal(fid)]), + 'http://www.opengis.net/ogc', node); + + return node; + }, + 'Property': function(obj) { + var node = this.createElementNS('wfs:Property'); + this.writeNode('Name', obj.name, null, node); + if (!goog.isNull(obj.value)) { + this.writeNode('Value', obj.value, null, node); + } + return node; + }, + /** + * @param {string} name Name. + * @return {Element} Node. + * @this {ol.parser.XML} + */ + 'Name': function(name) { + var node = this.createElementNS('wfs:Name'); + node.appendChild(this.createTextNode(name)); + return node; + }, + /** + * @param {string|number|ol.geom.Geometry} obj Object. + * @return {Element} Node. + * @this {ol.parser.XML} + */ + 'Value': function(obj) { + var node; + if (obj instanceof ol.geom.Geometry) { + node = this.createElementNS('wfs:Value'); + node.appendChild( + this.getFilterParser().getGmlParser().writeGeometry(obj)); + } else { + node = this.createElementNS('wfs:Value'); + node.appendChild(this.createTextNode(/** @type {string} */ (obj))); + } + return node; + }, + /** + * @param {{feature: ol.Feature, + * options: ol.parser.WFSWriteTransactionOptions}} obj Object. + * @return {Element} Node. + * @this {ol.parser.XML} + */ + 'Delete': function(obj) { + var feature = obj.feature; + var options = obj.options; + var node = this.createElementNS('wfs:Delete'); + this.setAttributeNS(node, this.defaultNamespaceURI, 'typeName', + (goog.isDef(options.featureNS) ? options.featurePrefix + ':' : '') + + options.featureType); + if (goog.isDef(options.handle)) { + this.setAttributeNS(node, this.defaultNamespaceURI, 'handle', + options.handle); + } + var fid = feature.getId(); + goog.asserts.assert(goog.isDef(fid)); + this.writeNode('Filter', new ol.expr.Call(new ol.expr.Identifier( + ol.expr.functions.FID), [new ol.expr.Literal(fid)]), + 'http://www.opengis.net/ogc', node); + return node; } }; goog.base(this); @@ -204,13 +340,27 @@ ol.parser.ogc.WFS_v1.prototype.read = function(data) { /** - * @param {Array.} features The features to write out. - * @param {ol.parser.WFSWriteOptions} options Write options. + * @param {ol.parser.WFSWriteGetFeatureOptions} options Options. + * @return {string} A serialized WFS GetFeature query. + */ +ol.parser.ogc.WFS_v1.prototype.writeGetFeature = function(options) { + var root = this.writers[this.defaultNamespaceURI]['GetFeature'] + .call(this, options); + return this.serialize(root); +}; + + +/** + * @param {Array.} inserts The features to insert. + * @param {Array.} updates The features to update. + * @param {Array.} deletes The features to delete. + * @param {ol.parser.WFSWriteTransactionOptions} options Write options. * @return {string} A serialized WFS transaction. */ -ol.parser.ogc.WFS_v1.prototype.write = function(features, options) { - var root = this.writeNode('Transaction', {features: features, - options: options}); +ol.parser.ogc.WFS_v1.prototype.writeTransaction = + function(inserts, updates, deletes, options) { + var root = this.writeNode('Transaction', {inserts: inserts, + updates: updates, deletes: deletes, options: options}); this.setAttributeNS( root, 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation', this.schemaLocation); diff --git a/test/spec/ol/parser/ogc/wfs_v1.test.js b/test/spec/ol/parser/ogc/wfs_v1.test.js index 22bf07fc01..69fc103d81 100644 --- a/test/spec/ol/parser/ogc/wfs_v1.test.js +++ b/test/spec/ol/parser/ogc/wfs_v1.test.js @@ -19,17 +19,15 @@ describe('ol.parser.ogc.WFS', function() { var url = 'spec/ol/parser/ogc/xml/wfs_v1/GetFeature.xml'; afterLoadXml(url, function(xml) { var p = new ol.parser.ogc.WFS_v1_0_0(); - var output = p.writers[p.defaultNamespaceURI]['GetFeature']. - apply(p, [{ - featureNS: 'http://www.openplans.org/topp', - featureTypes: ['states'], - featurePrefix: 'topp', - handle: 'handle_g', - maxFeatures: 1, - outputFormat: 'json' - } - ]); - expect(goog.dom.xml.loadXml(p.serialize(output))).to.xmleql(xml); + var output = p.writeGetFeature({ + featureNS: 'http://www.openplans.org/topp', + featureTypes: ['states'], + featurePrefix: 'topp', + handle: 'handle_g', + maxFeatures: 1, + outputFormat: 'json' + }); + expect(goog.dom.xml.loadXml(output)).to.xmleql(xml); done(); }); }); @@ -38,12 +36,44 @@ describe('ol.parser.ogc.WFS', function() { var url = 'spec/ol/parser/ogc/xml/wfs_v1/Transaction.xml'; afterLoadXml(url, function(xml) { var p = new ol.parser.ogc.WFS_v1_0_0(); - var output = p.writers[p.defaultNamespaceURI]['Transaction']. - apply(p, [{ - options: {handle: 'handle_t'} - } - ]); - expect(goog.dom.xml.loadXml(p.serialize(output))).to.xmleql(xml); + var output = p.writeTransaction(null, null, null, {handle: 'handle_t'}); + expect(goog.dom.xml.loadXml(output)).to.xmleql(xml); + done(); + }); + }); + + it('handles writing out transactions', function(done) { + var url = 'spec/ol/parser/ogc/xml/wfs_v1/TransactionMulti.xml'; + afterLoadXml(url, function(xml) { + var parser = new ol.parser.ogc.WFS_v1_0_0(); + + var insertFeature = new ol.Feature({ + the_geom: new ol.geom.MultiPoint([[1, 2]]), + foo: 'bar', + nul: null + }); + var inserts = [insertFeature]; + var updateFeature = new ol.Feature({ + the_geom: new ol.geom.MultiPoint([[1, 2]]), + foo: 'bar', + // null value gets Property element with no Value + nul: null, + // undefined value means don't create a Property element + unwritten: undefined + }); + updateFeature.setId('fid.42'); + var updates = [updateFeature]; + + var deleteFeature = new ol.Feature(); + deleteFeature.setId('fid.37'); + var deletes = [deleteFeature]; + + var output = parser.writeTransaction(inserts, updates, deletes, { + featureNS: 'http://www.openplans.org/topp', + featureType: 'states', + featurePrefix: 'topp' + }); + expect(goog.dom.xml.loadXml(output)).to.xmleql(xml); done(); }); }); @@ -52,7 +82,7 @@ describe('ol.parser.ogc.WFS', function() { var url = 'spec/ol/parser/ogc/xml/wfs_v1/Native.xml'; afterLoadXml(url, function(xml) { var p = new ol.parser.ogc.WFS_v1_1_0(); - var output = p.write(null, {nativeElements: [{ + var output = p.writeTransaction(null, null, null, {nativeElements: [{ vendorId: 'ORACLE', safeToIgnore: true, value: 'ALTER SESSION ENABLE PARALLEL DML' @@ -70,14 +100,12 @@ describe('ol.parser.ogc.WFS', function() { var url = 'spec/ol/parser/ogc/xml/wfs_v1/GetFeatureMultiple.xml'; afterLoadXml(url, function(xml) { var p = new ol.parser.ogc.WFS_v1_0_0(); - var output = p.writers[p.defaultNamespaceURI]['GetFeature']. - apply(p, [{ - featureNS: 'http://www.openplans.org/topp', - featureTypes: ['states', 'cities'], - featurePrefix: 'topp' - } - ]); - expect(goog.dom.xml.loadXml(p.serialize(output))).to.xmleql(xml); + var output = p.writeGetFeature({ + featureNS: 'http://www.openplans.org/topp', + featureTypes: ['states', 'cities'], + featurePrefix: 'topp' + }); + expect(goog.dom.xml.loadXml(output)).to.xmleql(xml); done(); }); }); @@ -87,6 +115,8 @@ describe('ol.parser.ogc.WFS', function() { }); goog.require('goog.dom.xml'); +goog.require('ol.Feature'); +goog.require('ol.geom.MultiPoint'); goog.require('ol.parser.ogc.WFS'); goog.require('ol.parser.ogc.WFS_v1_0_0'); goog.require('ol.parser.ogc.WFS_v1_1_0'); diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1/Transaction.xml b/test/spec/ol/parser/ogc/xml/wfs_v1/Transaction.xml index b147dc07f4..98527bfc2d 100644 --- a/test/spec/ol/parser/ogc/xml/wfs_v1/Transaction.xml +++ b/test/spec/ol/parser/ogc/xml/wfs_v1/Transaction.xml @@ -1 +1 @@ - + diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1/TransactionMulti.xml b/test/spec/ol/parser/ogc/xml/wfs_v1/TransactionMulti.xml new file mode 100644 index 0000000000..22c0886045 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1/TransactionMulti.xml @@ -0,0 +1,45 @@ + + + + + + + + 1,2 + + + + + bar + + + + + the_geom + + + + + 1,2 + + + + + + + foo + bar + + + nul + + + + + + + + + + + \ No newline at end of file