From 60825f0f997f18242fd19f61add21ae51ca66eb4 Mon Sep 17 00:00:00 2001 From: Jackie Ng Date: Wed, 6 Nov 2013 00:14:57 +1100 Subject: [PATCH 01/12] Add MapGuide untiled map support to ol3. This adds a new ol.source.MapGuide class that is initialized with an options object that can contain the following values: - projection: The projection of the Map Definition in EPSG format - url: The mapagent URL - useOverlay: Determines whether the GETMAPIMAGE (false) or GETDYNAMICMAPOVERLAYIMAGE (true) will be used for requesting the map image. When using GETMAPIMAGE, you must include a valid MAPDEFINITION parameter in the 'params' option property. When using GETDYNAMICMAPOVERLAYIMAGE, you must include a valid SESSION and MAPNAME parameters in the 'params' option property. - metersPerUnit: A required value used for calculating the map scale needed by the image request. This value can be calculated using the MapGuide Web API or obtained through the new CREATERUNTIMEMAP operation in MapGuide Open Source 2.6 - params: A set of key-value pairs to append to the mapagent request - extent: The bounds of the layer An example is included to demonstrate this new layer source. Like ol2, this example uses the MapGuide Server on data.mapguide.com --- examples/mapguide-untiled.html | 50 +++++++++++ examples/mapguide-untiled.js | 39 +++++++++ src/objectliterals.jsdoc | 12 +++ src/ol/source/mapguidesource.exports | 1 + src/ol/source/mapguidesource.js | 125 +++++++++++++++++++++++++++ 5 files changed, 227 insertions(+) create mode 100644 examples/mapguide-untiled.html create mode 100644 examples/mapguide-untiled.js create mode 100644 src/ol/source/mapguidesource.exports create mode 100644 src/ol/source/mapguidesource.js diff --git a/examples/mapguide-untiled.html b/examples/mapguide-untiled.html new file mode 100644 index 0000000000..0d80284c1b --- /dev/null +++ b/examples/mapguide-untiled.html @@ -0,0 +1,50 @@ + + + + + + + + + + + MapGuide untiled example + + + + + +
+ +
+
+
+
+
+ +
+ +
+

MapGuide untiled example

+

Example of a untiled MapGuide map.

+
+

See the mapguide-untiled.js source to see how this is done.

+
+
mapguide
+
+ +
+ +
+ + + + + + diff --git a/examples/mapguide-untiled.js b/examples/mapguide-untiled.js new file mode 100644 index 0000000000..b4d3358351 --- /dev/null +++ b/examples/mapguide-untiled.js @@ -0,0 +1,39 @@ +goog.require('ol.Map'); +goog.require('ol.RendererHint'); +goog.require('ol.View2D'); +goog.require('ol.layer.Image'); +goog.require('ol.source.MapGuide'); + +var mdf = 'Library://Samples/Sheboygan/Maps/Sheboygan.MapDefinition'; +var agentUrl = + 'http://data.mapguide.com/mapguide/mapagent/mapagent.fcgi?USERNAME=Anonymous'; +var bounds = [ + -87.865114442365922, + 43.665065564837931, + -87.595394059497067, + 43.823852564430069 +]; +var map = new ol.Map({ + layers: [ + new ol.layer.Image({ + source: new ol.source.MapGuide({ + projection: 'EPSG:4326', + url: agentUrl, + useOverlay: false, + metersPerUnit: 111319.4908, //value returned from mapguide + params: { + MAPDEFINITION: mdf, + FORMAT: 'PNG' + }, + extent: bounds + }) + }) + ], + renderer: ol.RendererHint.CANVAS, + target: 'map', + view: new ol.View2D({ + center: [-87.7302542509315, 43.744459064634], + projection: 'EPSG:4326', + zoom: 12 + }) +}); diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 0c6c3b03fc..2799a01053 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -578,6 +578,18 @@ * @todo stability experimental */ +/** + * @typedef {Object} ol.source.MapGuideOptions + * @property {string} url The mapagent url + * @property {number} metersPerUnit The meters-per-unit value + * @property {ol.Extent|undefined} extent Extent. + * @property {boolean} useOverlay If true, will use GETDYNAMICMAPOVERLAYIMAGE + * @property {ol.proj.ProjectionLike} projection Projection. + * @property {Array.|undefined} resolutions Resolutions. If specified, + * requests will be made for these resolutions only. + * @property {Object} params additional parameters + */ + /** * @typedef {Object} ol.source.MapQuestOptions * @property {ol.TileLoadFunctionType|undefined} tileLoadFunction Optional diff --git a/src/ol/source/mapguidesource.exports b/src/ol/source/mapguidesource.exports new file mode 100644 index 0000000000..c386f721e0 --- /dev/null +++ b/src/ol/source/mapguidesource.exports @@ -0,0 +1 @@ +@exportSymbol ol.source.MapGuide diff --git a/src/ol/source/mapguidesource.js b/src/ol/source/mapguidesource.js new file mode 100644 index 0000000000..7fa5a742ed --- /dev/null +++ b/src/ol/source/mapguidesource.js @@ -0,0 +1,125 @@ +goog.provide('ol.source.MapGuide'); + +goog.require('goog.object'); +goog.require('goog.uri.utils'); +goog.require('ol.ImageUrlFunction'); +goog.require('ol.extent'); +goog.require('ol.source.Image'); + + + +/** + * @constructor + * @extends {ol.source.Image} + * @param {ol.source.MapGuideOptions} options Options. + */ +ol.source.MapGuide = function(options) { + var imageUrlFunction = goog.isDef(options.url) ? + ol.ImageUrlFunction.createFromParamsFunction( + options.url, + options.params, + goog.bind(this.getUrl, this)) : + ol.ImageUrlFunction.nullImageUrlFunction; + + /** + * @private + * @type {number} + */ + this.metersPerUnit_ = options.metersPerUnit; + + /** + * @private + * @type {boolean} + */ + this.useOverlay_ = options.useOverlay; + + /** + * @private + * @type {ol.Image} + */ + this.image_ = null; + + goog.base(this, { + extent: options.extent, + projection: options.projection, + resolutions: options.resolutions, + imageUrlFunction: imageUrlFunction + }); +}; +goog.inherits(ol.source.MapGuide, ol.source.Image); + + +/** + * @inheritDoc + */ +ol.source.MapGuide.prototype.getImage = + function(extent, resolution, projection) { + resolution = this.findNearestResolution(resolution); + + var image = this.image_; + if (!goog.isNull(image) && + image.getResolution() == resolution && + ol.extent.containsExtent(image.getExtent(), extent)) { + return image; + } + + extent = extent.slice(); + ol.extent.scaleFromCenter(extent, 1.0); //this.ratio_); + var width = (extent[2] - extent[0]) / resolution; + var height = (extent[3] - extent[1]) / resolution; + var size = [width, height]; + + this.image_ = this.createImage(extent, resolution, size, projection); + return this.image_; +}; + + +/** + * @param {ol.Extent} extent The map extents + * @param {ol.Size} size the viewport size + * @return {number} The computed map scale + */ +ol.source.MapGuide.prototype.getScale = function(extent, size) { + var mcsW = extent[2] - extent[0]; + var mcsH = extent[3] - extent[1]; + var devW = size[0]; + var devH = size[1]; + var dpi = 96; + var mpu = this.metersPerUnit_; + var mpp = 0.0254 / dpi; + var scale = 0.0; + if (devH * mcsW > devW * mcsH) + scale = mcsW * mpu / (devW * mpp); //width-limited + else + scale = mcsH * mpu / (devH * mpp); //height-limited + return scale; +}; + + +/** + * @param {string} baseUrl The mapagent url. + * @param {Object.} params Request parameters. + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Size. + * @param {ol.proj.Projection} projection Projection. + * @return {string} The mapagent map image request URL. + */ +ol.source.MapGuide.prototype.getUrl = + function(baseUrl, params, extent, size, projection) { + var scale = this.getScale(extent, size); + var baseParams = { + 'OPERATION': this.useOverlay_ ? 'GETDYNAMICMAPOVERLAYIMAGE' : 'GETMAPIMAGE', + 'VERSION': '2.0.0', + 'LOCALE': 'en', + 'CLIENTAGENT': 'ol.source.MapGuide source', + 'CLIP': '1', + 'SETDISPLAYDPI': 96, + 'SETDISPLAYWIDTH': Math.round(size[0]), + 'SETDISPLAYHEIGHT': Math.round(size[1]), + 'SETVIEWSCALE': scale, + 'SETVIEWCENTERX': (extent[0] + extent[2]) / 2, + 'SETVIEWCENTERY': (extent[1] + extent[3]) / 2 + }; + goog.object.extend(baseParams, params); + return goog.uri.utils.appendParamsFromMap(baseUrl, baseParams); +}; From f539eb140fdc21824f8711f19f6a7d2957a2cb3f Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 6 Dec 2013 10:49:24 +0100 Subject: [PATCH 02/12] 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 03/12] 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 04/12] 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 05/12] 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 06/12] 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 43e4292ec97415d0bcef62522f3abc3b951fa7d2 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Fri, 6 Dec 2013 18:11:01 +0100 Subject: [PATCH 07/12] Tidy up ol.source.MapGuide and add ratio option --- examples/mapguide-untiled.js | 1 + src/objectliterals.jsdoc | 14 ++++-- src/ol/source/mapguidesource.js | 81 +++++++++++++++++++-------------- 3 files changed, 57 insertions(+), 39 deletions(-) diff --git a/examples/mapguide-untiled.js b/examples/mapguide-untiled.js index b4d3358351..d8b84060c1 100644 --- a/examples/mapguide-untiled.js +++ b/examples/mapguide-untiled.js @@ -25,6 +25,7 @@ var map = new ol.Map({ MAPDEFINITION: mdf, FORMAT: 'PNG' }, + ratio: 2, extent: bounds }) }) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index eea253f924..0e97a4467d 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -608,14 +608,18 @@ /** * @typedef {Object} ol.source.MapGuideOptions - * @property {string} url The mapagent url - * @property {number} metersPerUnit The meters-per-unit value - * @property {ol.Extent|undefined} extent Extent. - * @property {boolean} useOverlay If true, will use GETDYNAMICMAPOVERLAYIMAGE + * @property {string|undefined} url The mapagent url. + * @property {number|undefined} metersPerUnit The meters-per-unit value. + * @property {ol.Extent|undefined} extent Extent.. + * @property {boolean|undefined} useOverlay If true, will use + * GETDYNAMICMAPOVERLAYIMAGE. * @property {ol.proj.ProjectionLike} projection Projection. + * @property {number|undefined} ratio Ratio. 1 means image requests are the size + * of the map viewport, 2 means twice the size of the map viewport, and so + * on. * @property {Array.|undefined} resolutions Resolutions. If specified, * requests will be made for these resolutions only. - * @property {Object} params additional parameters + * @property {Object|undefined} params Additional parameters. */ /** diff --git a/src/ol/source/mapguidesource.js b/src/ol/source/mapguidesource.js index 7fa5a742ed..974c69b1bc 100644 --- a/src/ol/source/mapguidesource.js +++ b/src/ol/source/mapguidesource.js @@ -14,30 +14,15 @@ goog.require('ol.source.Image'); * @param {ol.source.MapGuideOptions} options Options. */ ol.source.MapGuide = function(options) { - var imageUrlFunction = goog.isDef(options.url) ? - ol.ImageUrlFunction.createFromParamsFunction( - options.url, - options.params, - goog.bind(this.getUrl, this)) : - ol.ImageUrlFunction.nullImageUrlFunction; - /** - * @private - * @type {number} - */ - this.metersPerUnit_ = options.metersPerUnit; - - /** - * @private - * @type {boolean} - */ - this.useOverlay_ = options.useOverlay; - - /** - * @private - * @type {ol.Image} - */ - this.image_ = null; + var imageUrlFunction; + if (goog.isDef(options.url)) { + var params = goog.isDef(options.params) ? options.params : {}; + imageUrlFunction = ol.ImageUrlFunction.createFromParamsFunction( + options.url, params, goog.bind(this.getUrl, this)); + } else { + imageUrlFunction = ol.ImageUrlFunction.nullImageUrlFunction; + } goog.base(this, { extent: options.extent, @@ -45,6 +30,33 @@ ol.source.MapGuide = function(options) { resolutions: options.resolutions, imageUrlFunction: imageUrlFunction }); + + /** + * @private + * @type {number} + */ + this.metersPerUnit_ = goog.isDef(options.metersPerUnit) ? + options.metersPerUnit : 1; + + /** + * @private + * @type {number} + */ + this.ratio_ = goog.isDef(options.ratio) ? options.ratio : 1; + + /** + * @private + * @type {boolean} + */ + this.useOverlay_ = goog.isDef(options.useOverlay) ? + options.useOverlay : false; + + /** + * @private + * @type {ol.Image} + */ + this.image_ = null; + }; goog.inherits(ol.source.MapGuide, ol.source.Image); @@ -63,8 +75,10 @@ ol.source.MapGuide.prototype.getImage = return image; } - extent = extent.slice(); - ol.extent.scaleFromCenter(extent, 1.0); //this.ratio_); + if (this.ratio_ != 1) { + extent = extent.slice(); + ol.extent.scaleFromCenter(extent, this.ratio_); + } var width = (extent[2] - extent[0]) / resolution; var height = (extent[3] - extent[1]) / resolution; var size = [width, height]; @@ -75,9 +89,9 @@ ol.source.MapGuide.prototype.getImage = /** - * @param {ol.Extent} extent The map extents - * @param {ol.Size} size the viewport size - * @return {number} The computed map scale + * @param {ol.Extent} extent The map extents. + * @param {ol.Size} size the viewport size. + * @return {number} The computed map scale. */ ol.source.MapGuide.prototype.getScale = function(extent, size) { var mcsW = extent[2] - extent[0]; @@ -87,12 +101,11 @@ ol.source.MapGuide.prototype.getScale = function(extent, size) { var dpi = 96; var mpu = this.metersPerUnit_; var mpp = 0.0254 / dpi; - var scale = 0.0; - if (devH * mcsW > devW * mcsH) - scale = mcsW * mpu / (devW * mpp); //width-limited - else - scale = mcsH * mpu / (devH * mpp); //height-limited - return scale; + if (devH * mcsW > devW * mcsH) { + return mcsW * mpu / (devW * mpp); // width limited + } else { + return mcsH * mpu / (devH * mpp); // height limited + } }; From 19cb3c58353f7dc578f6fe378f7d479e824b24c4 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 6 Dec 2013 19:15:44 +0100 Subject: [PATCH 08/12] 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( From 8d55e14986f8fb0d82d77de72baa3a7f6bcf3876 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 9 Dec 2013 13:25:14 +0100 Subject: [PATCH 09/12] Safeguard against the case where no features are modifiable --- src/ol/interaction/modifyinteraction.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ol/interaction/modifyinteraction.js b/src/ol/interaction/modifyinteraction.js index 692c0cd74d..f5c4eef831 100644 --- a/src/ol/interaction/modifyinteraction.js +++ b/src/ol/interaction/modifyinteraction.js @@ -252,9 +252,9 @@ ol.interaction.Modify.prototype.removeIndex_ = function(features) { nodesToRemove.push(node); } }); - } - for (i = nodesToRemove.length - 1; i >= 0; --i) { - rBush.remove(nodesToRemove[i]); + for (i = nodesToRemove.length - 1; i >= 0; --i) { + rBush.remove(nodesToRemove[i]); + } } }; From 8328db26d41dd68c56345b2e5a8bc12292dc0b9e Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 9 Dec 2013 16:57:11 +0100 Subject: [PATCH 10/12] Set the srs name when writing GetFeature Also make clear that srsName is only optional for WFS 1.0.0. --- src/objectliterals.jsdoc | 5 +++-- src/ol/parser/ogc/filterparser_v1.js | 2 +- src/ol/parser/ogc/wfsparser_v1.js | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index c2c035b8cb..e9c0fe5c09 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -600,8 +600,9 @@ * @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} srsName SRS name. For WFS 1.1.0, this is + * required. In WFS 1.0.0, 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. diff --git a/src/ol/parser/ogc/filterparser_v1.js b/src/ol/parser/ogc/filterparser_v1.js index 6a5822ee4f..6f44d61074 100644 --- a/src/ol/parser/ogc/filterparser_v1.js +++ b/src/ol/parser/ogc/filterparser_v1.js @@ -648,6 +648,6 @@ ol.parser.ogc.Filter_v1.prototype.setFeatureType = function(featureType) { ol.parser.ogc.Filter_v1.prototype.setSrsName = function(srsName) { this.srsName = srsName; if (goog.isDefAndNotNull(this.gmlParser_)) { - this.gmlParser_.srsName = this.srsName; + this.gmlParser_.applyWriteOptions({}, {srsName: srsName}); } }; diff --git a/src/ol/parser/ogc/wfsparser_v1.js b/src/ol/parser/ogc/wfsparser_v1.js index 0f188f9074..002fc9a9ab 100644 --- a/src/ol/parser/ogc/wfsparser_v1.js +++ b/src/ol/parser/ogc/wfsparser_v1.js @@ -47,6 +47,9 @@ ol.parser.ogc.WFS_v1 = function(opt_options) { if (goog.isDef(options.maxFeatures)) { node.setAttribute('maxFeatures', options.maxFeatures); } + if (goog.isDef(options.srsName)) { + this.setSrsName(options.srsName); + } } for (var i = 0, ii = options.featureTypes.length; i < ii; i++) { options.featureType = options.featureTypes[i]; From 2fc89b967a0b3667ac3493f6e32b4a9ecc7e5067 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 9 Dec 2013 17:19:03 +0100 Subject: [PATCH 11/12] Define type for applyWriteOptions call --- src/ol/parser/ogc/filterparser_v1.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ol/parser/ogc/filterparser_v1.js b/src/ol/parser/ogc/filterparser_v1.js index 6f44d61074..3d3ff44d55 100644 --- a/src/ol/parser/ogc/filterparser_v1.js +++ b/src/ol/parser/ogc/filterparser_v1.js @@ -648,6 +648,7 @@ ol.parser.ogc.Filter_v1.prototype.setFeatureType = function(featureType) { ol.parser.ogc.Filter_v1.prototype.setSrsName = function(srsName) { this.srsName = srsName; if (goog.isDefAndNotNull(this.gmlParser_)) { - this.gmlParser_.applyWriteOptions({}, {srsName: srsName}); + this.gmlParser_.applyWriteOptions({}, + /** @type {ol.parser.GMLWriteOptions} */ ({srsName: srsName})); } }; From 211e288f3335803b2b88583e74b8cc09fea768c3 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 9 Dec 2013 18:33:34 +0100 Subject: [PATCH 12/12] Remove original handling on ol.Feature This is another attempt to bring master closer to the vector-api branch. In anticipation of the ability to keep track of modifications on ol.Object through a beforechange event (d7e4be0), we will be able to manage originals on the application level or in a separate component outside of ol.Feature. --- src/ol/feature.js | 25 ------------------------- src/ol/interaction/modifyinteraction.js | 6 ------ src/ol/parser/ogc/wfsparser_v1.js | 7 +------ 3 files changed, 1 insertion(+), 37 deletions(-) diff --git a/src/ol/feature.js b/src/ol/feature.js index cb9a478d63..59da4fe656 100644 --- a/src/ol/feature.js +++ b/src/ol/feature.js @@ -42,13 +42,6 @@ ol.Feature = function(opt_values) { */ this.geometryName_; - /** - * Original of this feature when it was modified. - * @type {ol.Feature} - * @private - */ - this.original_ = null; - /** * The render intent for this feature. * @type {ol.FeatureRenderIntent|string} @@ -114,15 +107,6 @@ ol.Feature.prototype.getGeometry = function() { }; -/** - * Get the original of this feature when it was modified. - * @return {ol.Feature} Original. - */ -ol.Feature.prototype.getOriginal = function() { - return this.original_; -}; - - /** * Get any symbolizers set directly on the feature. * @return {Array.} Symbolizers (or null if none). @@ -198,15 +182,6 @@ ol.Feature.prototype.setGeometry = function(geometry) { }; -/** - * Set the original of this feature when it was modified. - * @param {ol.Feature} original Original. - */ -ol.Feature.prototype.setOriginal = function(original) { - this.original_ = original; -}; - - /** * Gets the renderIntent for this feature. * @return {string} Render intent. diff --git a/src/ol/interaction/modifyinteraction.js b/src/ol/interaction/modifyinteraction.js index f5c4eef831..fdef1b88e7 100644 --- a/src/ol/interaction/modifyinteraction.js +++ b/src/ol/interaction/modifyinteraction.js @@ -368,12 +368,6 @@ ol.interaction.Modify.prototype.handleDragStart = function(evt) { if (!(goog.getUid(node.feature) in distinctFeatures)) { var feature = node.feature; distinctFeatures[goog.getUid(feature)] = true; - var original = new ol.Feature(feature.getAttributes()); - original.setGeometry(feature.getGeometry().clone()); - original.setId(feature.getId()); - original.setOriginal(feature.getOriginal()); - original.setSymbolizers(feature.getSymbolizers()); - feature.setOriginal(original); } if (renderIntent == ol.FeatureRenderIntent.TEMPORARY) { if (ol.coordinate.equals(segment[0], vertex)) { diff --git a/src/ol/parser/ogc/wfsparser_v1.js b/src/ol/parser/ogc/wfsparser_v1.js index 002fc9a9ab..acc0449c8b 100644 --- a/src/ol/parser/ogc/wfsparser_v1.js +++ b/src/ol/parser/ogc/wfsparser_v1.js @@ -157,17 +157,12 @@ ol.parser.ogc.WFS_v1 = function(opt_options) { } // 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]))) { + if (goog.isDef(attribute)) { this.writeNode('Property', {name: key, value: attribute}, null, node); } }