From f539eb140fdc21824f8711f19f6a7d2957a2cb3f Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 6 Dec 2013 10:49:24 +0100 Subject: [PATCH 1/6] Use standard x, y axis order if we do not know better --- src/ol/parser/ogc/gmlparser_v2.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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]; } From b3b9add06e1dfe4ed8e51e057ed8e5537c2f1b24 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 6 Dec 2013 10:57:58 +0100 Subject: [PATCH 2/6] Add Insert, Update and Delete writers This change also adds some type annotations for better type checking, introduces different write options for writing transactions and queries, and provides new writeGetFeature and writeTransaction methods. --- src/objectliterals.jsdoc | 26 ++ src/ol/parser/ogc/wfsparser_v1.js | 235 +++++++++++++++--- test/spec/ol/parser/ogc/wfs_v1.test.js | 82 ++++-- .../ol/parser/ogc/xml/wfs_v1/Transaction.xml | 2 +- .../ogc/xml/wfs_v1/TransactionMulti.xml | 45 ++++ 5 files changed, 324 insertions(+), 66 deletions(-) create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1/TransactionMulti.xml diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 83e0e9a6e1..ba449c5349 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -595,6 +595,32 @@ * 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 {Array.} nativeElements Native elements. Currently not + * supported. + * @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/wfsparser_v1.js b/src/ol/parser/ogc/wfsparser_v1.js index 2fb5f685fa..4fae1ef137 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,147 @@ 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); + } + if (goog.isDef(options.featureNS)) { + node.setAttribute('xmlns:' + options.featurePrefix, options.featureNS); + } + + // 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|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 { + goog.asserts.assertString(obj); + node = this.createElementNS('wfs:Value'); + node.appendChild(this.createTextNode(obj)); + } + return node; + }, + /** + * @param {{feature: ol.Feature, + * options: ol.parser.WFSWriteTransactionOptions}} obj Object. + * @return {Element} Node. + * @this {ol.parser.ogc.GML} + */ + '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, options.defaultNamespaceURI, 'handle', + options.handle); + } + if (goog.isDef(options.featureNS)) { + node.setAttribute('xmlns:' + options.featurePrefix, options.featureNS); + } + 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 +347,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 From 35b8fdc00c768a10512459172d92ad8fc54ade8c Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 6 Dec 2013 11:06:12 +0100 Subject: [PATCH 3/6] Export getParser() This reduces the API footprint of versioned parsers, because only the base parser needs to be exported as symbol, and versioned parsers can be accessed using the getParser() method. --- src/ol/parser/ogc/versionedparser.exports | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/ol/parser/ogc/versionedparser.exports 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 From 198b05047e723a7efcb54e9468a1717bb3fbdbf3 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 6 Dec 2013 11:06:39 +0100 Subject: [PATCH 4/6] Export the WFS parser and its write methods For using the WFS parser, users need to be able to write GetFeature queries and transactions. --- src/ol/parser/ogc/wfsparser.exports | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/ol/parser/ogc/wfsparser.exports 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 From e49464a573bf7b53c1678f26e9441c4b381687d2 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 6 Dec 2013 16:49:44 +0100 Subject: [PATCH 5/6] No native element on GetFeature queries --- src/objectliterals.jsdoc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index ba449c5349..f6ee1dfae0 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -604,8 +604,6 @@ * set on geometries when this is not provided. * @property {string|undefined} handle Handle. * @property {string|undefined} outputFormat Output format. - * @property {Array.} nativeElements Native elements. Currently not - * supported. * @property {number} maxFeatures Maximum number of features to fetch. */ From 19cb3c58353f7dc578f6fe378f7d479e824b24c4 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 6 Dec 2013 19:15:44 +0100 Subject: [PATCH 6/6] Addressing review comments * Do not write xmlns * Make @this annotations consistent * Handle numbers and strings for attribute values * Fix defaultNamespaceURI scope --- src/ol/parser/ogc/wfsparser_v1.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/ol/parser/ogc/wfsparser_v1.js b/src/ol/parser/ogc/wfsparser_v1.js index 4fae1ef137..0f188f9074 100644 --- a/src/ol/parser/ogc/wfsparser_v1.js +++ b/src/ol/parser/ogc/wfsparser_v1.js @@ -152,9 +152,6 @@ ol.parser.ogc.WFS_v1 = function(opt_options) { this.setAttributeNS(node, this.defaultNamespaceURI, 'handle', options.handle); } - if (goog.isDef(options.featureNS)) { - node.setAttribute('xmlns:' + options.featurePrefix, options.featureNS); - } // add in fields var original = feature.getOriginal(); @@ -200,7 +197,7 @@ ol.parser.ogc.WFS_v1 = function(opt_options) { return node; }, /** - * @param {string|ol.geom.Geometry} obj Object. + * @param {string|number|ol.geom.Geometry} obj Object. * @return {Element} Node. * @this {ol.parser.XML} */ @@ -211,9 +208,8 @@ ol.parser.ogc.WFS_v1 = function(opt_options) { node.appendChild( this.getFilterParser().getGmlParser().writeGeometry(obj)); } else { - goog.asserts.assertString(obj); node = this.createElementNS('wfs:Value'); - node.appendChild(this.createTextNode(obj)); + node.appendChild(this.createTextNode(/** @type {string} */ (obj))); } return node; }, @@ -221,7 +217,7 @@ ol.parser.ogc.WFS_v1 = function(opt_options) { * @param {{feature: ol.Feature, * options: ol.parser.WFSWriteTransactionOptions}} obj Object. * @return {Element} Node. - * @this {ol.parser.ogc.GML} + * @this {ol.parser.XML} */ 'Delete': function(obj) { var feature = obj.feature; @@ -231,12 +227,9 @@ ol.parser.ogc.WFS_v1 = function(opt_options) { (goog.isDef(options.featureNS) ? options.featurePrefix + ':' : '') + options.featureType); if (goog.isDef(options.handle)) { - this.setAttributeNS(node, options.defaultNamespaceURI, 'handle', + this.setAttributeNS(node, this.defaultNamespaceURI, 'handle', options.handle); } - if (goog.isDef(options.featureNS)) { - node.setAttribute('xmlns:' + options.featurePrefix, options.featureNS); - } var fid = feature.getId(); goog.asserts.assert(goog.isDef(fid)); this.writeNode('Filter', new ol.expr.Call(new ol.expr.Identifier(