From 76454516f5e9ad6eaa7a33d43fe83b7c622d5f12 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Tue, 24 Sep 2013 11:33:53 +0200 Subject: [PATCH 01/66] Add skeleton for SLD parser --- examples/data/countries.sld | 42 ++ examples/vector-layer-sld.html | 61 +++ examples/vector-layer-sld.js | 45 ++ src/ol/parser/ogc/sldparser.js | 38 ++ src/ol/parser/ogc/sldparser_v1.js | 429 ++++++++++++++++++ src/ol/parser/ogc/sldparser_v1_0_0.js | 18 + .../parser/ogc/sldparser_v1_0_0_GeoServer.js | 43 ++ test/spec/ol/parser/ogc/sld_v1_0_0.test.js | 78 ++++ test/spec/ol/parser/ogc/xml/sld_v1_0_0.xml | 129 ++++++ 9 files changed, 883 insertions(+) create mode 100644 examples/data/countries.sld create mode 100644 examples/vector-layer-sld.html create mode 100644 examples/vector-layer-sld.js create mode 100644 src/ol/parser/ogc/sldparser.js create mode 100644 src/ol/parser/ogc/sldparser_v1.js create mode 100644 src/ol/parser/ogc/sldparser_v1_0_0.js create mode 100644 src/ol/parser/ogc/sldparser_v1_0_0_GeoServer.js create mode 100644 test/spec/ol/parser/ogc/sld_v1_0_0.test.js create mode 100644 test/spec/ol/parser/ogc/xml/sld_v1_0_0.xml diff --git a/examples/data/countries.sld b/examples/data/countries.sld new file mode 100644 index 0000000000..79fb36d465 --- /dev/null +++ b/examples/data/countries.sld @@ -0,0 +1,42 @@ + + + + countries + + countries + A sample style for countries + 1 + A sample style for countries + + name + + Sample + Sample + + + #ff0000 + 0.6 + + + #00FF00 + 0.5 + 4 + + + + + + + name + + + Arial + 10 + Normal + + + + + + + diff --git a/examples/vector-layer-sld.html b/examples/vector-layer-sld.html new file mode 100644 index 0000000000..52b9b0e80d --- /dev/null +++ b/examples/vector-layer-sld.html @@ -0,0 +1,61 @@ + + + + + + + + + + + Vector layer with styling from SLD example + + + + + +
+ +
+
+
+
+
+ +
+ +
+

Vector layer example

+

Example of a countries vector layer with country information on hover and country labels at higher zoom levels.

+
+

See the vector-layer-sld.js source to see how this is done.

+
+
vector, geojson, style, SLD, Styled Layer Descriptor
+
+
+
+   +
+
+ +
+ +
+ + + + + + diff --git a/examples/vector-layer-sld.js b/examples/vector-layer-sld.js new file mode 100644 index 0000000000..aa8f9f5563 --- /dev/null +++ b/examples/vector-layer-sld.js @@ -0,0 +1,45 @@ +goog.require('ol.Map'); +goog.require('ol.RendererHint'); +goog.require('ol.View2D'); +goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); +goog.require('ol.parser.GeoJSON'); +goog.require('ol.parser.ogc.SLD'); +goog.require('ol.source.MapQuestOpenAerial'); +goog.require('ol.source.Vector'); + + +var raster = new ol.layer.Tile({ + source: new ol.source.MapQuestOpenAerial() +}); + +var xhr = new XMLHttpRequest(); +xhr.open('GET', 'data/countries.sld', true); + + +/** + * onload handler for the XHR request. + */ +xhr.onload = function() { + if (xhr.status == 200) { + var sld = new ol.parser.ogc.SLD().read(xhr.responseText); + var style = sld.namedLayers['countries'].userStyles[0]; + var vector = new ol.layer.Vector({ + source: new ol.source.Vector({ + parser: new ol.parser.GeoJSON(), + url: 'data/countries.geojson' + }), + style: style + }); + new ol.Map({ + layers: [raster, vector], + renderer: ol.RendererHint.CANVAS, + target: 'map', + view: new ol.View2D({ + center: [0, 0], + zoom: 1 + }) + }); + } +}; +xhr.send(); diff --git a/src/ol/parser/ogc/sldparser.js b/src/ol/parser/ogc/sldparser.js new file mode 100644 index 0000000000..8560cdc2e8 --- /dev/null +++ b/src/ol/parser/ogc/sldparser.js @@ -0,0 +1,38 @@ +goog.provide('ol.parser.ogc.SLD'); +goog.require('ol.parser.ogc.SLD_v1_0_0'); +goog.require('ol.parser.ogc.SLD_v1_0_0_GeoServer'); +goog.require('ol.parser.ogc.Versioned'); + + +/** + * @define {boolean} Whether to enable SLD version 1.0.0. + */ +ol.ENABLE_SLD_1_0_0 = true; + + +/** + * @define {boolean} Whether to enable SLD version 1.0.0. + * GeoServer profile. + */ +ol.ENABLE_SLD_1_0_0_GEOSERVER = true; + + + +/** + * @constructor + * @param {Object=} opt_options Options which will be set on this object. + * @extends {ol.parser.ogc.Versioned} + */ +ol.parser.ogc.SLD = function(opt_options) { + opt_options = opt_options || {}; + opt_options['defaultVersion'] = '1.0.0'; + this.parsers = {}; + if (ol.ENABLE_SLD_1_0_0) { + this.parsers['v1_0_0'] = ol.parser.ogc.SLD_v1_0_0; + } + if (ol.ENABLE_SLD_1_0_0_GEOSERVER) { + this.parsers['v1_0_0_GEOSERVER'] = ol.parser.ogc.SLD_v1_0_0_GeoServer; + } + goog.base(this, opt_options); +}; +goog.inherits(ol.parser.ogc.SLD, ol.parser.ogc.Versioned); diff --git a/src/ol/parser/ogc/sldparser_v1.js b/src/ol/parser/ogc/sldparser_v1.js new file mode 100644 index 0000000000..6917240dac --- /dev/null +++ b/src/ol/parser/ogc/sldparser_v1.js @@ -0,0 +1,429 @@ +goog.provide('ol.parser.ogc.SLD_v1'); +goog.require('goog.dom.xml'); +goog.require('goog.object'); +goog.require('ol.parser.XML'); +goog.require('ol.parser.ogc.Filter_v1_0_0'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Rule'); +goog.require('ol.style.Shape'); +goog.require('ol.style.Stroke'); +goog.require('ol.style.Style'); +goog.require('ol.style.Text'); + + + +/** + * Read Styled Layer Descriptor (SLD). + * + * @constructor + * @extends {ol.parser.XML} + */ +ol.parser.ogc.SLD_v1 = function() { + this.defaultNamespaceURI = 'http://www.opengis.net/sld'; + this.readers = { + 'http://www.opengis.net/sld': { + 'StyledLayerDescriptor': function(node, sld) { + sld.version = node.getAttribute('version'); + this.readChildNodes(node, sld); + }, + 'Name': function(node, obj) { + obj.name = this.getChildValue(node); + }, + 'Title': function(node, obj) { + obj.title = this.getChildValue(node); + }, + 'Abstract': function(node, obj) { + obj.description = this.getChildValue(node); + }, + 'NamedLayer': function(node, sld) { + var layer = { + userStyles: [], + namedStyles: [] + }; + this.readChildNodes(node, layer); + sld.namedLayers[layer.name] = layer; + }, + 'NamedStyle': function(node, layer) { + layer.namedStyles.push( + this.getChildValue(node.firstChild) + ); + }, + 'UserStyle': function(node, layer) { + var obj = {rules: []}; + this.featureTypeCounter = -1; + this.readChildNodes(node, obj); + layer.userStyles.push(new ol.style.Style(obj)); + }, + 'IsDefault': function(node, style) { + if (this.getChildValue(node) === '1') { + style.isDefault = true; + } + }, + 'FeatureTypeStyle': function(node, style) { + ++this.featureTypeCounter; + var obj = { + rules: style.rules + }; + this.readChildNodes(node, obj); + }, + 'Rule': function(node, obj) { + var config = {symbolizers: []}; + this.readChildNodes(node, config); + var rule = new ol.style.Rule(config); + obj.rules.push(rule); + }, + 'ElseFilter': function(node, rule) { + rule.elseFilter = true; + }, + 'MinScaleDenominator': function(node, rule) { + rule.minScaleDenominator = parseFloat(this.getChildValue(node)); + }, + 'MaxScaleDenominator': function(node, rule) { + rule.maxScaleDenominator = parseFloat(this.getChildValue(node)); + }, + 'TextSymbolizer': function(node, rule) { + var config = {}; + this.readChildNodes(node, config); + config.zIndex = this.featureTypeCounter; + rule.symbolizers.push( + new ol.style.Text(/** @type {ol.style.TextOptions} */(config)) + ); + }, + 'LabelPlacement': function(node, symbolizer) { + this.readChildNodes(node, symbolizer); + }, + 'PointPlacement': function(node, symbolizer) { + var config = {}; + this.readChildNodes(node, config); + config.labelRotation = config.rotation; + delete config.rotation; + var labelAlign, + x = symbolizer.labelAnchorPointX, + y = symbolizer.labelAnchorPointY; + if (x <= 1 / 3) { + labelAlign = 'l'; + } else if (x > 1 / 3 && x < 2 / 3) { + labelAlign = 'c'; + } else if (x >= 2 / 3) { + labelAlign = 'r'; + } + if (y <= 1 / 3) { + labelAlign += 'b'; + } else if (y > 1 / 3 && y < 2 / 3) { + labelAlign += 'm'; + } else if (y >= 2 / 3) { + labelAlign += 't'; + } + config.labelAlign = labelAlign; + goog.object.extend(symbolizer, config); + }, + 'AnchorPoint': function(node, symbolizer) { + this.readChildNodes(node, symbolizer); + }, + 'AnchorPointX': function(node, symbolizer) { + var ogcreaders = this.readers['http://www.opengis.net/ogc']; + var labelAnchorPointX = ogcreaders._expression.call(this, node); + // always string, could be empty string + if (labelAnchorPointX) { + symbolizer.labelAnchorPointX = labelAnchorPointX; + } + }, + 'AnchorPointY': function(node, symbolizer) { + var ogcreaders = this.readers['http://www.opengis.net/ogc']; + var labelAnchorPointY = ogcreaders._expression.call(this, node); + // always string, could be empty string + if (labelAnchorPointY) { + symbolizer.labelAnchorPointY = labelAnchorPointY; + } + }, + 'Displacement': function(node, symbolizer) { + this.readChildNodes(node, symbolizer); + }, + 'DisplacementX': function(node, symbolizer) { + var ogcreaders = this.readers['http://www.opengis.net/ogc']; + var labelXOffset = ogcreaders._expression.call(this, node); + // always string, could be empty string + if (labelXOffset) { + symbolizer.labelXOffset = labelXOffset; + } + }, + 'DisplacementY': function(node, symbolizer) { + var ogcreaders = this.readers['http://www.opengis.net/ogc']; + var labelYOffset = ogcreaders._expression.call(this, node); + // always string, could be empty string + if (labelYOffset) { + symbolizer.labelYOffset = labelYOffset; + } + }, + 'LinePlacement': function(node, symbolizer) { + this.readChildNodes(node, symbolizer); + }, + 'PerpendicularOffset': function(node, symbolizer) { + var ogcreaders = this.readers['http://www.opengis.net/ogc']; + var labelPerpendicularOffset = ogcreaders._expression.call(this, node); + // always string, could be empty string + if (labelPerpendicularOffset) { + symbolizer.labelPerpendicularOffset = labelPerpendicularOffset; + } + }, + 'Label': function(node, symbolizer) { + var ogcreaders = this.readers['http://www.opengis.net/ogc']; + var value = ogcreaders._expression.call(this, node); + if (value) { + symbolizer.text = value; + } + }, + 'Font': function(node, symbolizer) { + this.readChildNodes(node, symbolizer); + }, + 'Halo': function(node, symbolizer) { + // halo has a fill, so send fresh object + var obj = {}; + this.readChildNodes(node, obj); + symbolizer.haloRadius = obj.haloRadius; + symbolizer.haloColor = obj['fillColor']; + symbolizer.haloOpacity = obj['fillOpacity']; + }, + 'Radius': function(node, symbolizer) { + var ogcreaders = this.readers['http://www.opengis.net/ogc']; + var radius = ogcreaders._expression.call(this, node); + if (goog.isDef(radius)) { + } + }, + 'RasterSymbolizer': function(node, rule) { + var config = {}; + this.readChildNodes(node, config); + config.zIndex = this.featureTypeCounter; + /* TODO + rule.symbolizers.push( + new OpenLayers.Symbolizer.Raster(config) + ); + */ + }, + 'Geometry': function(node, obj) { + obj.geometry = {}; + this.readChildNodes(node, obj.geometry); + }, + 'ColorMap': function(node, symbolizer) { + symbolizer.colorMap = []; + this.readChildNodes(node, symbolizer.colorMap); + }, + 'ColorMapEntry': function(node, colorMap) { + var q = node.getAttribute('quantity'); + var o = node.getAttribute('opacity'); + colorMap.push({ + color: node.getAttribute('color'), + quantity: q !== null ? parseFloat(q) : undefined, + label: node.getAttribute('label') || undefined, + opacity: o !== null ? parseFloat(o) : undefined + }); + }, + 'LineSymbolizer': function(node, rule) { + var config = {}; + this.readChildNodes(node, config); + config.zIndex = this.featureTypeCounter; + rule.symbolizers.push( + new ol.style.Stroke(config) + ); + }, + 'PolygonSymbolizer': function(node, rule) { + var config = { + fill: false, + stroke: false + }; + this.readChildNodes(node, config); + config.zIndex = this.featureTypeCounter; + if (config.fill === true) { + var fill = { + color: config['fillColor'], + opacity: config['fillOpacity'] + }; + rule.symbolizers.push( + new ol.style.Fill(fill) + ); + } + if (config.stroke === true) { + var stroke = { + color: config['strokeColor'], + opacity: config['strokeOpacity'], + width: config['strokeWidth'] + }; + rule.symbolizers.push( + new ol.style.Stroke(stroke) + ); + } + + }, + 'PointSymbolizer': function(node, rule) { + var config = { + fill: null, + stroke: null, + graphic: null + }; + this.readChildNodes(node, config); + config.zIndex = this.featureTypeCounter; + // TODO shape or icon? + rule.symbolizers.push( + new ol.style.Shape(config) + ); + }, + 'Stroke': function(node, symbolizer) { + symbolizer.stroke = true; + this.readChildNodes(node, symbolizer); + }, + 'Fill': function(node, symbolizer) { + symbolizer.fill = true; + this.readChildNodes(node, symbolizer); + }, + 'CssParameter': function(node, symbolizer) { + var cssProperty = node.getAttribute('name'); + var symProperty = ol.parser.ogc.SLD_v1.cssMap_[cssProperty]; + // for labels, fill should map to fontColor and fill-opacity + // to fontOpacity + if (symbolizer.label) { + if (cssProperty === 'fill') { + symProperty = 'fontColor'; + } else if (cssProperty === 'fill-opacity') { + symProperty = 'fontOpacity'; + } + } + if (symProperty) { + // Limited support for parsing of OGC expressions + var ogcreaders = this.readers['http://www.opengis.net/ogc']; + var value = ogcreaders._expression.call(this, node); + // always string, could be an empty string + if (value) { + symbolizer[symProperty] = value; + } + } + }, + 'Graphic': function(node, symbolizer) { + symbolizer.graphic = true; + var graphic = {}; + // painter's order not respected here, clobber previous with next + this.readChildNodes(node, graphic); + // directly properties with names that match symbolizer properties + var properties = [ + 'stroke', 'strokeColor', 'strokeWidth', 'strokeOpacity', + 'strokeLinecap', 'fill', 'fillColor', 'fillOpacity', + 'graphicName', 'rotation', 'graphicFormat' + ]; + var prop, value; + for (var i = 0, ii = properties.length; i < ii; ++i) { + prop = properties[i]; + value = graphic[prop]; + if (goog.isDef(value)) { + symbolizer[prop] = value; + } + } + // set other generic properties with specific graphic property names + if (goog.isDef(graphic.opacity)) { + symbolizer.graphicOpacity = graphic.opacity; + } + if (goog.isDef(graphic.size)) { + var pointRadius = graphic.size / 2; + if (isNaN(pointRadius)) { + // likely a property name + symbolizer.graphicWidth = graphic.size; + } else { + symbolizer.pointRadius = graphic.size / 2; + } + } + if (goog.isDef(graphic.href)) { + symbolizer.externalGraphic = graphic.href; + } + if (goog.isDef(graphic.rotation)) { + symbolizer.rotation = graphic.rotation; + } + }, + 'ExternalGraphic': function(node, graphic) { + this.readChildNodes(node, graphic); + }, + 'Mark': function(node, graphic) { + this.readChildNodes(node, graphic); + }, + 'WellKnownName': function(node, graphic) { + graphic.graphicName = this.getChildValue(node); + }, + 'Opacity': function(node, obj) { + var ogcreaders = this.readers['http://www.opengis.net/ogc']; + var opacity = ogcreaders._expression.call(this, node); + // always string, could be empty string + if (opacity) { + obj.opacity = opacity; + } + }, + 'Size': function(node, obj) { + var ogcreaders = this.readers['http://www.opengis.net/ogc']; + var size = ogcreaders._expression.call(this, node); + // always string, could be empty string + if (size) { + obj.size = size; + } + }, + 'Rotation': function(node, obj) { + var ogcreaders = this.readers['http://www.opengis.net/ogc']; + var rotation = ogcreaders._expression.call(this, node); + // always string, could be empty string + if (rotation) { + obj.rotation = rotation; + } + }, + 'OnlineResource': function(node, obj) { + obj.href = this.getAttributeNS( + node, 'http://www.w3.org/1999/xlink', 'href' + ); + }, + 'Format': function(node, graphic) { + graphic.graphicFormat = this.getChildValue(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_); + } + } + goog.base(this); +}; +goog.inherits(ol.parser.ogc.SLD_v1, ol.parser.XML); + + +/** + * @private + */ +ol.parser.ogc.SLD_v1.cssMap_ = { + 'stroke': 'strokeColor', + 'stroke-opacity': 'strokeOpacity', + 'stroke-width': 'strokeWidth', + 'stroke-linecap': 'strokeLinecap', + 'stroke-dasharray': 'strokeDashstyle', + 'fill': 'fillColor', + 'fill-opacity': 'fillOpacity', + 'font-family': 'fontFamily', + 'font-size': 'fontSize', + 'font-weight': 'fontWeight', + 'font-style': 'fontStyle' +}; + + +/** + * @param {string|Document|Element} data Data to read. + * @return {Object} An object representing the document. + */ +ol.parser.ogc.SLD_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 = {namedLayers: {}}; + this.readNode(data, obj); + return obj; +}; diff --git a/src/ol/parser/ogc/sldparser_v1_0_0.js b/src/ol/parser/ogc/sldparser_v1_0_0.js new file mode 100644 index 0000000000..d86f897f6a --- /dev/null +++ b/src/ol/parser/ogc/sldparser_v1_0_0.js @@ -0,0 +1,18 @@ +goog.provide('ol.parser.ogc.SLD_v1_0_0'); + +goog.require('ol.parser.ogc.SLD_v1'); + + + +/** + * @constructor + * @extends {ol.parser.ogc.SLD_v1} + */ +ol.parser.ogc.SLD_v1_0_0 = function() { + goog.base(this); + this.version = '1.0.0'; + this.schemaLocation = 'http://www.opengis.net/sld ' + + 'http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd'; +}; +goog.inherits(ol.parser.ogc.SLD_v1_0_0, + ol.parser.ogc.SLD_v1); diff --git a/src/ol/parser/ogc/sldparser_v1_0_0_GeoServer.js b/src/ol/parser/ogc/sldparser_v1_0_0_GeoServer.js new file mode 100644 index 0000000000..77f58d1ab5 --- /dev/null +++ b/src/ol/parser/ogc/sldparser_v1_0_0_GeoServer.js @@ -0,0 +1,43 @@ +goog.provide('ol.parser.ogc.SLD_v1_0_0_GeoServer'); + +goog.require('goog.functions'); +goog.require('goog.object'); +goog.require('ol.parser.ogc.SLD_v1_0_0'); + + + +/** + * @constructor + * @extends {ol.parser.ogc.SLD_v1_0_0} + */ +ol.parser.ogc.SLD_v1_0_0_GeoServer = function() { + goog.base(this); + this.profile = 'GeoServer'; + goog.object.extend(this.readers['http://www.opengis.net/sld'], { + 'Priority': function(node, obj) { + var ogcreaders = this.readers['http://www.opengis.net/ogc']; + var value = ogcreaders._expression.call(this, node); + if (value) { + obj.priority = value; + } + }, + 'VendorOption': function(node, obj) { + if (!goog.isDef(obj.vendorOptions)) { + obj.vendorOptions = {}; + } + obj.vendorOptions[node.getAttribute('name')] = + this.getChildValue(node); + }, + 'TextSymbolizer': goog.functions.sequence( + this.readers['http://www.opengis.net/sld']['TextSymbolizer'], + function(node, rule) { + var symbolizer = rule.symbolizers[rule.symbolizers.length - 1]; + if (!goog.isDef(symbolizer.graphic)) { + symbolizer.graphic = false; + } + } + ) + }); +}; +goog.inherits(ol.parser.ogc.SLD_v1_0_0_GeoServer, + ol.parser.ogc.SLD_v1_0_0); diff --git a/test/spec/ol/parser/ogc/sld_v1_0_0.test.js b/test/spec/ol/parser/ogc/sld_v1_0_0.test.js new file mode 100644 index 0000000000..057d322062 --- /dev/null +++ b/test/spec/ol/parser/ogc/sld_v1_0_0.test.js @@ -0,0 +1,78 @@ +goog.provide('ol.test.parser.ogc.SLD_v1_0_0'); + + +describe('ol.parser.ogc.SLD_v1_0_0', function() { + + var parser = new ol.parser.ogc.SLD(); + + describe('reading and writing', function() { + it('Handles reading', function(done) { + var url = 'spec/ol/parser/ogc/xml/sld_v1_0_0.xml'; + afterLoadXml(url, function(xml) { + var obj = parser.read(xml); + expect(obj.version).to.equal('1.0.0'); + var style = obj.namedLayers['AAA161'].userStyles[0]; + expect(style).to.be.a(ol.style.Style); + expect(style.rules_.length).to.equal(2); + var first = style.rules_[0]; + expect(first).to.be.a(ol.style.Rule); + expect(first.filter_).to.be.a(ol.expr.Comparison); + expect(first.filter_.getLeft()).to.be.a(ol.expr.Identifier); + expect(first.filter_.getLeft().getName()).to.equal('CTE'); + expect(first.filter_.getOperator()).to.equal(ol.expr.ComparisonOp.EQ); + expect(first.filter_.getRight()).to.be.a(ol.expr.Literal); + expect(first.filter_.getRight().getValue()).to.equal('V0305'); + expect(first.getSymbolizers().length).to.equal(3); + expect(first.getSymbolizers()[0]).to.be.a(ol.style.Fill); + expect(first.getSymbolizers()[0].getColor().getValue()).to.equal( + '#ffffff'); + expect(first.getSymbolizers()[0].getOpacity().getValue()).to.equal(0.4); + expect(first.getSymbolizers()[1]).to.be.a(ol.style.Stroke); + expect(first.getSymbolizers()[1].getColor().getValue()).to.equal( + '#000000'); + expect(first.getSymbolizers()[2]).to.be.a(ol.style.Text); + expect(first.getSymbolizers()[2].getText()).to.be.a(ol.expr.Call); + expect(first.getSymbolizers()[2].getText().getArgs().length).to.equal( + 3); + expect(first.getSymbolizers()[2].getText().getArgs()[0]).to.be.a( + ol.expr.Literal); + expect(first.getSymbolizers()[2].getText().getArgs()[0].getValue()). + to.equal('A'); + expect(first.getSymbolizers()[2].getText().getArgs()[1]).to.be.a( + ol.expr.Identifier); + expect(first.getSymbolizers()[2].getText().getArgs()[1].getName()). + to.equal('FOO'); + expect(first.getSymbolizers()[2].getText().getArgs()[2]).to.be.a( + ol.expr.Literal); + expect(first.getSymbolizers()[2].getText().getArgs()[2].getValue()). + to.equal('label'); + expect(first.getSymbolizers()[2].getColor().getValue()).to.equal( + ol.style.TextDefaults.color); + expect(first.getSymbolizers()[2].getFontFamily().getValue()).to.equal( + 'Arial'); + // TODO add tests for haloRadius and haloColor + var second = style.rules_[1]; + expect(second.filter_).to.be.a(ol.expr.Comparison); + expect(second.getSymbolizers().length).to.equal(2); + expect(second.getSymbolizers()[0]).to.be.a(ol.style.Fill); + expect(second.getSymbolizers()[1]).to.be.a(ol.style.Stroke); + done(); + }); + }); + }); + +}); + +goog.require('goog.net.XhrIo'); +goog.require('ol.parser.ogc.SLD_v1_0_0'); +goog.require('ol.parser.ogc.SLD'); +goog.require('ol.expr.Call'); +goog.require('ol.expr.Comparison'); +goog.require('ol.expr.ComparisonOp'); +goog.require('ol.expr.Identifier'); +goog.require('ol.expr.Literal'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Rule'); +goog.require('ol.style.Stroke'); +goog.require('ol.style.Style'); +goog.require('ol.style.Text'); diff --git a/test/spec/ol/parser/ogc/xml/sld_v1_0_0.xml b/test/spec/ol/parser/ogc/xml/sld_v1_0_0.xml new file mode 100644 index 0000000000..6bb9cc654f --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/sld_v1_0_0.xml @@ -0,0 +1,129 @@ + + + + AAA161 + + + + stortsteen + + + CTE + V0305 + + + 50000 + + + #ffffff + + + #000000 + + + + + + Arial + 14 + bold + normal + + + + + 0.5 + 0.5 + + + 5 + 5 + + 45 + + + + 3 + + #ffffff + + + + #000000 + + + + + betonbekleding + + + CTE + 1000 + + + 50000 + + + #ffff00 + + + #0000ff + + + + + + + + Second Layer + + + + first rule second layer + + + + number + + 1064866676 + + + 1065512599 + + + + cat + *dog.food!*good + + + + FOO + 5000 + + + + + 10000 + + + + star + + lime + + + olive + 2 + + + SIZE + + + + + + + From 5abedf66d937aea17966984e92d665aeb1d9eebe Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Fri, 27 Sep 2013 15:06:36 +0200 Subject: [PATCH 02/66] Add scale to/from resolution calculations --- examples/data/countries.sld | 1 + examples/vector-layer-sld.html | 7 +------ examples/vector-layer-sld.js | 27 +++++++++++++++++---------- src/objectliterals.jsdoc | 6 ++++++ src/ol/parser/ogc/sldparser_v1.js | 30 +++++++++++++++++++++++++++--- 5 files changed, 52 insertions(+), 19 deletions(-) diff --git a/examples/data/countries.sld b/examples/data/countries.sld index 79fb36d465..9ae5eebea3 100644 --- a/examples/data/countries.sld +++ b/examples/data/countries.sld @@ -25,6 +25,7 @@ + 20000000 name diff --git a/examples/vector-layer-sld.html b/examples/vector-layer-sld.html index 52b9b0e80d..eda42d859b 100644 --- a/examples/vector-layer-sld.html +++ b/examples/vector-layer-sld.html @@ -44,12 +44,7 @@
vector, geojson, style, SLD, Styled Layer Descriptor
-
-
-   -
-
- + diff --git a/examples/vector-layer-sld.js b/examples/vector-layer-sld.js index aa8f9f5563..585cc66c09 100644 --- a/examples/vector-layer-sld.js +++ b/examples/vector-layer-sld.js @@ -1,6 +1,8 @@ goog.require('ol.Map'); goog.require('ol.RendererHint'); goog.require('ol.View2D'); +goog.require('ol.control'); +goog.require('ol.control.ScaleLine'); goog.require('ol.layer.Tile'); goog.require('ol.layer.Vector'); goog.require('ol.parser.GeoJSON'); @@ -22,7 +24,20 @@ xhr.open('GET', 'data/countries.sld', true); */ xhr.onload = function() { if (xhr.status == 200) { - var sld = new ol.parser.ogc.SLD().read(xhr.responseText); + var map = new ol.Map({ + controls: ol.control.defaults().extend([ + new ol.control.ScaleLine() + ]), + layers: [raster], + renderer: ol.RendererHint.CANVAS, + target: 'map', + view: new ol.View2D({ + center: [0, 0], + zoom: 1 + }) + }); + var units = map.getView().getProjection().getUnits(); + var sld = new ol.parser.ogc.SLD().read(xhr.responseText, units); var style = sld.namedLayers['countries'].userStyles[0]; var vector = new ol.layer.Vector({ source: new ol.source.Vector({ @@ -31,15 +46,7 @@ xhr.onload = function() { }), style: style }); - new ol.Map({ - layers: [raster, vector], - renderer: ol.RendererHint.CANVAS, - target: 'map', - view: new ol.View2D({ - center: [0, 0], - zoom: 1 - }) - }); + map.getLayers().push(vector); } }; xhr.send(); diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 78a0e7cbeb..37424a6108 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -567,6 +567,12 @@ * @todo stability experimental */ + /** + * @typedef {Object} ol.parser.SLDReadOptions + * @property {ol.proj.Units} units The units to use in scale to resolution + * calculations. + */ + /** * @typedef {Object} ol.source.BingMapsOptions * @property {string|undefined} culture Culture. diff --git a/src/ol/parser/ogc/sldparser_v1.js b/src/ol/parser/ogc/sldparser_v1.js index 6917240dac..5708a207f0 100644 --- a/src/ol/parser/ogc/sldparser_v1.js +++ b/src/ol/parser/ogc/sldparser_v1.js @@ -76,10 +76,12 @@ ol.parser.ogc.SLD_v1 = function() { rule.elseFilter = true; }, 'MinScaleDenominator': function(node, rule) { - rule.minScaleDenominator = parseFloat(this.getChildValue(node)); + rule.minResolution = this.getResolutionFromScale_( + parseFloat(this.getChildValue(node))); }, 'MaxScaleDenominator': function(node, rule) { - rule.maxScaleDenominator = parseFloat(this.getChildValue(node)); + rule.maxResolution = this.getResolutionFromScale_( + parseFloat(this.getChildValue(node))); }, 'TextSymbolizer': function(node, rule) { var config = {}; @@ -188,6 +190,7 @@ ol.parser.ogc.SLD_v1 = function() { var ogcreaders = this.readers['http://www.opengis.net/ogc']; var radius = ogcreaders._expression.call(this, node); if (goog.isDef(radius)) { + symbolizer.haloRadius = radius; } }, 'RasterSymbolizer': function(node, rule) { @@ -412,11 +415,31 @@ ol.parser.ogc.SLD_v1.cssMap_ = { }; +/** + * @private + * @param {number} scaleDenominator The scale denominator to convert to + * resolution. + * @return {number} resolution. + */ +ol.parser.ogc.SLD_v1.prototype.getResolutionFromScale_ = + function(scaleDenominator) { + var dpi = 25.4 / 0.28; + var mpu = ol.METERS_PER_UNIT[this.units]; + return 1 / ((1 / scaleDenominator) * (mpu * 39.37) * dpi); +}; + + /** * @param {string|Document|Element} data Data to read. + * @param {ol.parser.SLDReadOptions=} opt_options Read options. * @return {Object} An object representing the document. */ -ol.parser.ogc.SLD_v1.prototype.read = function(data) { +ol.parser.ogc.SLD_v1.prototype.read = function(data, opt_options) { + var units = 'm'; + if (goog.isDef(opt_options) && goog.isDef(opt_options.units)) { + units = opt_options.units; + } + this.units = units; if (goog.isString(data)) { data = goog.dom.xml.loadXml(data); } @@ -425,5 +448,6 @@ ol.parser.ogc.SLD_v1.prototype.read = function(data) { } var obj = {namedLayers: {}}; this.readNode(data, obj); + delete this.units; return obj; }; From ee7d46cea1b2ffbf80c3e4bd68d798360010172f Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Tue, 8 Oct 2013 19:01:42 +0200 Subject: [PATCH 03/66] Add initial pass of write support, always write out ol.style.Stroke as LineSymbolizer --- examples/vector-layer-sld.html | 2 +- src/objectliterals.jsdoc | 6 + src/ol/parser/ogc/sldparser_v1.js | 457 +++++++++++++++++++-- src/ol/style/rule.js | 24 ++ src/ol/style/style.js | 16 + src/ol/style/textsymbolizer.js | 9 + test/spec/ol/parser/ogc/sld_v1_0_0.test.js | 1 + 7 files changed, 491 insertions(+), 24 deletions(-) diff --git a/examples/vector-layer-sld.html b/examples/vector-layer-sld.html index eda42d859b..62900c20ac 100644 --- a/examples/vector-layer-sld.html +++ b/examples/vector-layer-sld.html @@ -38,7 +38,7 @@

Vector layer example

-

Example of a countries vector layer with country information on hover and country labels at higher zoom levels.

+

Example of a countries vector layer with country labels at higher zoom levels, styling info coming from SLD.

See the vector-layer-sld.js source to see how this is done.

diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 37424a6108..545573f100 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -573,6 +573,12 @@ * calculations. */ + /** + * @typedef {Object} ol.parser.SLDWriteOptions + * @property {ol.proj.Units} units The units to use in resolution to scale + * calculations. + */ + /** * @typedef {Object} ol.source.BingMapsOptions * @property {string|undefined} culture Culture. diff --git a/src/ol/parser/ogc/sldparser_v1.js b/src/ol/parser/ogc/sldparser_v1.js index 5708a207f0..93bf09eb44 100644 --- a/src/ol/parser/ogc/sldparser_v1.js +++ b/src/ol/parser/ogc/sldparser_v1.js @@ -1,9 +1,12 @@ goog.provide('ol.parser.ogc.SLD_v1'); +goog.require('goog.asserts'); goog.require('goog.dom.xml'); goog.require('goog.object'); +goog.require('ol.expr.Literal'); goog.require('ol.parser.XML'); goog.require('ol.parser.ogc.Filter_v1_0_0'); goog.require('ol.style.Fill'); +goog.require('ol.style.Icon'); goog.require('ol.style.Rule'); goog.require('ol.style.Shape'); goog.require('ol.style.Stroke'); @@ -76,11 +79,11 @@ ol.parser.ogc.SLD_v1 = function() { rule.elseFilter = true; }, 'MinScaleDenominator': function(node, rule) { - rule.minResolution = this.getResolutionFromScale_( + rule.minResolution = this.getResolutionFromScaleDenominator_( parseFloat(this.getChildValue(node))); }, 'MaxScaleDenominator': function(node, rule) { - rule.maxResolution = this.getResolutionFromScale_( + rule.maxResolution = this.getResolutionFromScaleDenominator_( parseFloat(this.getChildValue(node))); }, 'TextSymbolizer': function(node, rule) { @@ -230,53 +233,56 @@ ol.parser.ogc.SLD_v1 = function() { ); }, 'PolygonSymbolizer': function(node, rule) { - var config = { - fill: false, - stroke: false - }; + var config = {}; this.readChildNodes(node, config); config.zIndex = this.featureTypeCounter; - if (config.fill === true) { + if (goog.isDef(config.fill)) { var fill = { - color: config['fillColor'], - opacity: config['fillOpacity'] + color: config.fill.fillColor, + opacity: config.fill.fillOpacity }; rule.symbolizers.push( new ol.style.Fill(fill) ); + delete config.fill; } - if (config.stroke === true) { + if (goog.isDef(config.stroke)) { var stroke = { - color: config['strokeColor'], - opacity: config['strokeOpacity'], - width: config['strokeWidth'] + color: config.stroke.strokeColor, + opacity: config.stroke.strokeOpacity, + width: config.stroke.strokeWidth }; rule.symbolizers.push( new ol.style.Stroke(stroke) ); + delete config.stroke; } }, 'PointSymbolizer': function(node, rule) { - var config = { - fill: null, - stroke: null, - graphic: null - }; + var config = {}; this.readChildNodes(node, config); config.zIndex = this.featureTypeCounter; + if (config.fill) { + config.fill = new ol.style.Fill(config.fill); + } + if (config.stroke) { + config.stroke = new ol.style.Stroke(config.stroke); + } // TODO shape or icon? rule.symbolizers.push( new ol.style.Shape(config) ); }, 'Stroke': function(node, symbolizer) { - symbolizer.stroke = true; - this.readChildNodes(node, symbolizer); + var stroke = {}; + this.readChildNodes(node, stroke); + symbolizer.stroke = stroke; }, 'Fill': function(node, symbolizer) { - symbolizer.fill = true; - this.readChildNodes(node, symbolizer); + var fill = {}; + this.readChildNodes(node, fill); + symbolizer.fill = fill; }, 'CssParameter': function(node, symbolizer) { var cssProperty = node.getAttribute('name'); @@ -382,6 +388,353 @@ ol.parser.ogc.SLD_v1 = function() { } } }; + this.writers = { + 'http://www.opengis.net/sld': { + 'StyledLayerDescriptor': function(sld) { + var node = this.createElementNS('sld:StyledLayerDescriptor'); + node.setAttribute('version', this.version); + if (goog.isDef(sld.name)) { + this.writeNode('Name', sld.name, null, node); + } + if (goog.isDef(sld.title)) { + this.writeNode('Title', sld.title, null, node); + } + if (goog.isDef(sld.description)) { + this.writeNode('Abstract', sld.description, null, node); + } + goog.object.forEach(sld.namedLayers, function(layer) { + this.writeNode('NamedLayer', layer, null, node); + }, this); + return node; + }, + 'Name': function(name) { + var node = this.createElementNS('sld:Name'); + node.appendChild(this.createTextNode(name)); + return node; + }, + 'Title': function(title) { + var node = this.createElementNS('sld:Title'); + node.appendChild(this.createTextNode(title)); + return node; + }, + 'Abstract': function(description) { + var node = this.createElementNS('sld:Abstract'); + node.appendChild(this.createTextNode(description)); + return node; + }, + 'NamedLayer': function(layer) { + var node = this.createElementNS('sld:NamedLayer'); + this.writeNode('Name', layer.name, null, node); + var i, ii; + if (layer.namedStyles) { + for (i = 0, ii = layer.namedStyles.length; i < ii; ++i) { + this.writeNode('NamedStyle', layer.namedStyles[i], null, node); + } + } + if (layer.userStyles) { + for (i = 0, ii = layer.userStyles.length; i < ii; ++i) { + this.writeNode('UserStyle', layer.userStyles[i], null, node); + } + } + return node; + }, + 'NamedStyle': function(name) { + var node = this.createElementNS('sld:NamedStyle'); + this.writeNode('Name', name, null, node); + return node; + }, + 'UserStyle': function(style) { + var node = this.createElementNS('sld:UserStyle'); + if (style.name) { + this.writeNode('Name', style.name, null, node); + } + if (style.title) { + this.writeNode('Title', style.title, null, node); + } + if (style.description) { + this.writeNode('Abstract', style.description, null, node); + } + if (style.isDefault) { + this.writeNode('IsDefault', style.isDefault, null, node); + } + if (style.rules) { + // group style objects by symbolizer zIndex + var rulesByZ = { + 0: [] + }; + var zValues = [0]; + var rule, ruleMap, symbolizer, zIndex, clone; + for (var i = 0, ii = style.rules.length; i < ii; ++i) { + rule = style.rules[i]; + var symbolizers = rule.getSymbolizers(); + if (symbolizers) { + ruleMap = {}; + for (var j = 0, jj = symbolizers.length; j < jj; ++j) { + symbolizer = symbolizers[j]; + zIndex = symbolizer.zIndex; + if (!(zIndex in ruleMap)) { + // TODO check if clone works? + clone = goog.object.clone(rule); + clone.setSymbolizers([]); + ruleMap[zIndex] = clone; + } + // TODO check if clone works + ruleMap[zIndex].getSymbolizers().push( + goog.object.clone(symbolizer)); + } + for (zIndex in ruleMap) { + if (!(zIndex in rulesByZ)) { + zValues.push(zIndex); + rulesByZ[zIndex] = []; + } + rulesByZ[zIndex].push(ruleMap[zIndex]); + } + } else { + // no symbolizers in rule + rulesByZ[0].push(goog.object.clone(rule)); + } + } + // write one FeatureTypeStyle per zIndex + zValues.sort(); + var rules; + for (var i = 0, ii = zValues.length; i < ii; ++i) { + rules = rulesByZ[zValues[i]]; + if (rules.length > 0) { + clone = goog.object.clone(style); + clone.setRules(rulesByZ[zValues[i]]); + this.writeNode('FeatureTypeStyle', clone, null, node); + } + } + } else { + this.writeNode('FeatureTypeStyle', style, null, node); + } + return node; + }, + 'IsDefault': function(bool) { + var node = this.createElementNS('sld:IsDefault'); + node.appendChild(this.createTextNode((bool) ? '1' : '0')); + return node; + }, + 'FeatureTypeStyle': function(style) { + var node = this.createElementNS('sld:FeatureTypeStyle'); + // OpenLayers currently stores no Name, Title, Abstract, + // FeatureTypeName, or SemanticTypeIdentifier information + // related to FeatureTypeStyle + // add in rules + var rules = style.getRules(); + for (var i = 0, ii = rules.length; i < ii; ++i) { + this.writeNode('Rule', rules[i], null, node); + } + return node; + }, + 'Rule': function(rule) { + var node = this.createElementNS('sld:Rule'); + var filter = rule.getFilter(); + if (rule.name) { + this.writeNode('Name', rule.name, null, node); + } + if (rule.title) { + this.writeNode('Title', rule.title, null, node); + } + if (rule.description) { + this.writeNode('Abstract', rule.description, null, node); + } + if (rule.elseFilter) { + this.writeNode('ElseFilter', null, null, node); + } else if (filter) { + this.writeNode('Filter', filter, 'http://www.opengis.net/ogc', node); + } + var minResolution = rule.getMinResolution(); + if (minResolution > 0) { + this.writeNode('MinScaleDenominator', + this.getScaleDenominatorFromResolution_(minResolution), + null, node); + } + var maxResolution = rule.getMaxResolution(); + if (maxResolution < Infinity) { + this.writeNode('MaxScaleDenominator', + this.getScaleDenominatorFromResolution_(maxResolution), + null, node); + } + var type, symbolizer, symbolizers = rule.getSymbolizers(); + if (symbolizers) { + for (var i = 0, ii = symbolizers.length; i < ii; ++i) { + symbolizer = symbolizers[i]; + // TODO other types of symbolizers + if (symbolizer instanceof ol.style.Text) { + type = 'Text'; + } else if (symbolizer instanceof ol.style.Stroke) { + type = 'Line'; + } else if (symbolizer instanceof ol.style.Fill) { + type = 'Polygon'; + } else if (symbolizer instanceof ol.style.Shape || + symbolizer instanceof ol.style.Icon) { + type = 'Point'; + } + if (goog.isDef(type)) { + this.writeNode(type + 'Symbolizer', symbolizer, null, node); + } + } + } + return node; + }, + 'PointSymbolizer': function(symbolizer) { + var node = this.createElementNS('sld:PointSymbolizer'); + this.writeNode('Graphic', symbolizer, null, node); + return node; + }, + 'Mark': function(symbolizer) { + var node = this.createElementNS('sld:Mark'); + this.writeNode('WellKnownName', symbolizer.getType(), null, node); + var fill = symbolizer.getFill(); + if (!goog.isNull(fill)) { + this.writeNode('Fill', fill, null, node); + } + var stroke = symbolizer.getStroke(); + if (!goog.isNull(stroke)) { + this.writeNode('Stroke', stroke, null, node); + } + return node; + }, + 'WellKnownName': function(name) { + var node = this.createElementNS('sld:WellKnownName'); + node.appendChild(this.createTextNode(name)); + return node; + }, + 'Graphic': function(symbolizer) { + var node = this.createElementNS('sld:Graphic'); + var size; + if (symbolizer instanceof ol.style.Icon) { + this.writeNode('ExternalGraphic', symbolizer, null, node); + var opacity = symbolizer.getOpacity(); + goog.asserts.assert(opacity instanceof ol.expr.Literal, + 'Only ol.expr.Literal supported for graphicOpacity'); + this.writeNode('Opacity', opacity.getValue(), null, node); + size = symbolizer.getWidth(); + } else if (symbolizer instanceof ol.style.Shape) { + this.writeNode('Mark', symbolizer, null, node); + size = symbolizer.getSize(); + } + goog.asserts.assert(size instanceof ol.expr.Literal, + 'Only ol.expr.Literal supported for in Size'); + this.writeNode('Size', size.getValue(), null, node); + if (symbolizer instanceof ol.style.Icon) { + var rotation = symbolizer.getRotation(); + goog.asserts.assert(rotation instanceof ol.expr.Literal, + 'Only ol.expr.Literal supported for rotation'); + this.writeNode('Rotation', rotation.getValue(), null, node); + } + return node; + }, + 'PolygonSymbolizer': function(symbolizer) { + var node = this.createElementNS('sld:PolygonSymbolizer'); + this.writeNode('Fill', symbolizer, null, node); + return node; + }, + 'Fill': function(symbolizer) { + var node = this.createElementNS('sld:Fill'); + var fillColor = symbolizer.getColor(); + var msg = 'Only ol.expr.Literal supported for Fill properties'; + goog.asserts.assert(fillColor instanceof ol.expr.Literal, msg); + this.writeNode('CssParameter', { + value: fillColor.getValue(), + key: 'fillColor' + }, null, node); + var fillOpacity = symbolizer.getOpacity(); + goog.asserts.assert(fillOpacity instanceof ol.expr.Literal, msg); + this.writeNode('CssParameter', { + value: fillOpacity.getValue(), + key: 'fillOpacity' + }, null, node); + return node; + }, + 'TextSymbolizer': function(symbolizer) { + var node = this.createElementNS('sld:TextSymbolizer'); + var text = symbolizer.getText(); + // TODO in SLD optional, but in ol3 required? + this.writeNode('Label', text, null, node); + // TODO in SLD optional, but in ol3 required? + this.writeNode('Font', symbolizer, null, node); + // TODO map align to labelAnchorPoint etc. + var stroke = symbolizer.getStroke(); + if (!goog.isNull(stroke)) { + this.writeNode('Halo', stroke, null, node); + } + return node; + }, + 'LineSymbolizer': function(symbolizer) { + var node = this.createElementNS('sld:LineSymbolizer'); + this.writeNode('Stroke', symbolizer, null, node); + return node; + }, + 'Stroke': function(symbolizer) { + var node = this.createElementNS('sld:Stroke'); + var strokeColor = symbolizer.getColor(); + var msg = 'SLD writing of stroke properties only supported ' + + 'for ol.expr.Literal'; + goog.asserts.assert(strokeColor instanceof ol.expr.Literal, msg); + this.writeNode('CssParameter', { + value: strokeColor.getValue(), + key: 'strokeColor' + }, null, node); + var strokeOpacity = symbolizer.getOpacity(); + goog.asserts.assert(strokeOpacity instanceof ol.expr.Literal, msg); + this.writeNode('CssParameter', { + value: strokeOpacity.getValue(), + key: 'strokeOpacity' + }, null, node); + var strokeWidth = symbolizer.getWidth(); + goog.asserts.assert(strokeWidth instanceof ol.expr.Literal, msg); + this.writeNode('CssParameter', { + value: strokeWidth.getValue(), + key: 'strokeWidth' + }, null, node); + // TODO strokeDashstyle and strokeLinecap + return node; + }, + 'CssParameter': function(obj) { + // not handling ogc:expressions for now + var node = this.createElementNS('sld:CssParameter'); + node.setAttribute('name', + ol.parser.ogc.SLD_v1.getCssProperty_(obj.key)); + node.appendChild(this.createTextNode(obj.value)); + return node; + }, + 'Label': function(label) { + var node = this.createElementNS('sld:Label'); + this.filter_.writeOgcExpression(label, node); + return node; + }, + 'Font': function(symbolizer) { + var node = this.createElementNS('sld:Font'); + this.writeNode('CssParameter', { + key: 'fontFamily', + value: symbolizer.getFontFamily().getValue() + }, null, node); + this.writeNode('CssParameter', { + key: 'fontSize', + value: symbolizer.getFontSize().getValue() + }, null, node); + // TODO fontWeight and fontStyle + return node; + }, + 'MinScaleDenominator': function(scale) { + var node = this.createElementNS('sld:MinScaleDenominator'); + node.appendChild(this.createTextNode(scale)); + return node; + }, + 'MaxScaleDenominator': function(scale) { + var node = this.createElementNS('sld:MaxScaleDenominator'); + node.appendChild(this.createTextNode(scale)); + return node; + }, + 'Size': function(value) { + var node = this.createElementNS('sld:Size'); + this.filter_.writeOgcExpression(value, 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]) { @@ -392,6 +745,15 @@ ol.parser.ogc.SLD_v1 = function() { this.filter_); } } + for (var uri in this.filter_.writers) { + for (var 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.base(this); }; goog.inherits(ol.parser.ogc.SLD_v1, ol.parser.XML); @@ -415,13 +777,28 @@ ol.parser.ogc.SLD_v1.cssMap_ = { }; +/** + * @private + * @param {string} sym Symbolizer property. + * @return {string|undefined} The css property that matches the symbolizer + * property. + */ +ol.parser.ogc.SLD_v1.getCssProperty_ = function(sym) { + return goog.object.findKey(ol.parser.ogc.SLD_v1.cssMap_, + function(value, key, obj) { + return (sym === value); + } + ); +}; + + /** * @private * @param {number} scaleDenominator The scale denominator to convert to * resolution. * @return {number} resolution. */ -ol.parser.ogc.SLD_v1.prototype.getResolutionFromScale_ = +ol.parser.ogc.SLD_v1.prototype.getResolutionFromScaleDenominator_ = function(scaleDenominator) { var dpi = 25.4 / 0.28; var mpu = ol.METERS_PER_UNIT[this.units]; @@ -429,6 +806,19 @@ ol.parser.ogc.SLD_v1.prototype.getResolutionFromScale_ = }; +/** + * @private + * @param {number} resolution The resolution to convert to scale denominator. + * @return {number} scale denominator. + */ +ol.parser.ogc.SLD_v1.prototype.getScaleDenominatorFromResolution_ = + function(resolution) { + var dpi = 25.4 / 0.28; + var mpu = ol.METERS_PER_UNIT[this.units]; + return resolution * mpu * 39.37 * dpi; +}; + + /** * @param {string|Document|Element} data Data to read. * @param {ol.parser.SLDReadOptions=} opt_options Read options. @@ -451,3 +841,24 @@ ol.parser.ogc.SLD_v1.prototype.read = function(data, opt_options) { delete this.units; return obj; }; + + +/** + * @param {Object} style The style to write out. + * @param {ol.parser.SLDWriteOptions=} opt_options Write options. + * @return {string} The serialized SLD. + */ +ol.parser.ogc.SLD_v1.prototype.write = function(style, opt_options) { + var units = 'm'; + if (goog.isDef(opt_options) && goog.isDef(opt_options.units)) { + units = opt_options.units; + } + this.units = units; + var root = this.writeNode('StyledLayerDescriptor', style); + this.setAttributeNS( + root, 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation', this.schemaLocation); + var result = this.serialize(root); + delete this.units; + return result; +}; diff --git a/src/ol/style/rule.js b/src/ol/style/rule.js index b06e03cae9..25d342aa5e 100644 --- a/src/ol/style/rule.js +++ b/src/ol/style/rule.js @@ -78,3 +78,27 @@ ol.style.Rule.prototype.applies = function(feature, resolution) { ol.style.Rule.prototype.getSymbolizers = function() { return this.symbolizers_; }; + + +/** + * @return {ol.expr.Expression} + */ +ol.style.Rule.prototype.getFilter = function() { + return this.filter_; +}; + + +/** + * @return {number} + */ +ol.style.Rule.prototype.getMinResolution = function() { + return this.minResolution_; +}; + + +/** + * @return {number} + */ +ol.style.Rule.prototype.getMaxResolution = function() { + return this.maxResolution_; +}; diff --git a/src/ol/style/style.js b/src/ol/style/style.js index 1a99081935..d7e0d38022 100644 --- a/src/ol/style/style.js +++ b/src/ol/style/style.js @@ -207,3 +207,19 @@ ol.style.Style.reduceLiterals_ = function(literals) { } return reduced; }; + + +/** + * @return {Array.} + */ +ol.style.Style.prototype.getRules = function() { + return this.rules_; +}; + + +/** + * @param {Array.} rules The rules to set. + */ +ol.style.Style.prototype.setRules = function(rules) { + this.rules_ = rules; +}; diff --git a/src/ol/style/textsymbolizer.js b/src/ol/style/textsymbolizer.js index bc3e1d3ec7..7b2e8e92cd 100644 --- a/src/ol/style/textsymbolizer.js +++ b/src/ol/style/textsymbolizer.js @@ -215,6 +215,15 @@ ol.style.Text.prototype.getZIndex = function() { }; +/** + * Get the stroke. + * @return {ol.style.Stroke} Stroke. + */ +ol.style.Text.prototype.getStroke = function() { + return this.stroke_; +}; + + /** * Set the font color. * @param {ol.expr.Expression} color Font color. diff --git a/test/spec/ol/parser/ogc/sld_v1_0_0.test.js b/test/spec/ol/parser/ogc/sld_v1_0_0.test.js index 057d322062..5a6b76ef0f 100644 --- a/test/spec/ol/parser/ogc/sld_v1_0_0.test.js +++ b/test/spec/ol/parser/ogc/sld_v1_0_0.test.js @@ -56,6 +56,7 @@ describe('ol.parser.ogc.SLD_v1_0_0', function() { expect(second.getSymbolizers().length).to.equal(2); expect(second.getSymbolizers()[0]).to.be.a(ol.style.Fill); expect(second.getSymbolizers()[1]).to.be.a(ol.style.Stroke); + window.console.log(parser.write(obj)); done(); }); }); From 924e9c6fce980595b2d2523496d5fa5d64adb83e Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Fri, 11 Oct 2013 14:24:33 +0200 Subject: [PATCH 04/66] write out string concatenation in the more common way --- src/ol/parser/ogc/filterparser_v1.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ol/parser/ogc/filterparser_v1.js b/src/ol/parser/ogc/filterparser_v1.js index a030b9bf78..f0a21995ab 100644 --- a/src/ol/parser/ogc/filterparser_v1.js +++ b/src/ol/parser/ogc/filterparser_v1.js @@ -508,7 +508,14 @@ ol.parser.ogc.Filter_v1.prototype.write = function(filter) { */ ol.parser.ogc.Filter_v1.prototype.writeOgcExpression = function(expr, node) { if (expr instanceof ol.expr.Call) { - this.writeNode('Function', expr, null, node); + if (ol.expr.isLibCall(expr) === ol.expr.functions.CONCAT) { + var args = expr.getArgs(); + for (var i = 0, ii = args.length; i < ii; ++i) { + this.writeOgcExpression(args[i], node); + } + } else { + this.writeNode('Function', expr, null, node); + } } else if (expr instanceof ol.expr.Literal) { this.writeNode('Literal', expr, null, node); } else if (expr instanceof ol.expr.Identifier) { From 9a6c1feddc19a3991ba25df3db7bd90a61b67578 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Fri, 11 Oct 2013 14:25:33 +0200 Subject: [PATCH 05/66] Add more changes to the halo functionality, introduce SLD defaults for symbolizers conform the SLD spec --- src/ol/parser/ogc/sldparser_v1.js | 94 ++++++++++++++++------ test/spec/ol/parser/ogc/sld_v1_0_0.test.js | 4 +- 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/ol/parser/ogc/sldparser_v1.js b/src/ol/parser/ogc/sldparser_v1.js index 93bf09eb44..4803ae4db8 100644 --- a/src/ol/parser/ogc/sldparser_v1.js +++ b/src/ol/parser/ogc/sldparser_v1.js @@ -89,6 +89,8 @@ ol.parser.ogc.SLD_v1 = function() { 'TextSymbolizer': function(node, rule) { var config = {}; this.readChildNodes(node, config); + config.color = config.fill.fillColor; + delete config.fill; config.zIndex = this.featureTypeCounter; rule.symbolizers.push( new ol.style.Text(/** @type {ol.style.TextOptions} */(config)) @@ -182,18 +184,24 @@ ol.parser.ogc.SLD_v1 = function() { this.readChildNodes(node, symbolizer); }, 'Halo': function(node, symbolizer) { - // halo has a fill, so send fresh object var obj = {}; this.readChildNodes(node, obj); - symbolizer.haloRadius = obj.haloRadius; - symbolizer.haloColor = obj['fillColor']; - symbolizer.haloOpacity = obj['fillOpacity']; + symbolizer.stroke = new ol.style.Stroke({ + color: goog.isDef(obj.fill.fillColor) ? obj.fill.fillColor : + ol.parser.ogc.SLD_v1.defaults_.haloColor, + width: goog.isDef(obj.haloRadius) ? obj.haloRadius * 2 : + ol.parser.ogc.SLD_v1.defaults_.haloRadius, + opacity: goog.isDef(obj.fill.fillOpacity) ? obj.fill.fillOpacity : + ol.parser.ogc.SLD_v1.defaults_.haloOpacity + }); }, 'Radius': function(node, symbolizer) { var ogcreaders = this.readers['http://www.opengis.net/ogc']; var radius = ogcreaders._expression.call(this, node); + goog.asserts.assertInstanceof(radius, ol.expr.Literal, + 'radius expected to be an ol.expr.Literal'); if (goog.isDef(radius)) { - symbolizer.haloRadius = radius; + symbolizer.haloRadius = radius.getValue(); } }, 'RasterSymbolizer': function(node, rule) { @@ -238,8 +246,10 @@ ol.parser.ogc.SLD_v1 = function() { config.zIndex = this.featureTypeCounter; if (goog.isDef(config.fill)) { var fill = { - color: config.fill.fillColor, - opacity: config.fill.fillOpacity + color: config.fill.fillColor.getValue(), + opacity: goog.isDef(config.fill.fillOpacity) ? + config.fill.fillOpacity : + ol.parser.ogc.SLD_v1.defaults_.fillOpacity }; rule.symbolizers.push( new ol.style.Fill(fill) @@ -248,9 +258,13 @@ ol.parser.ogc.SLD_v1 = function() { } if (goog.isDef(config.stroke)) { var stroke = { - color: config.stroke.strokeColor, - opacity: config.stroke.strokeOpacity, - width: config.stroke.strokeWidth + color: config.stroke.strokeColor.getValue(), + opacity: goog.isDef(config.stroke.strokeOpacity) ? + config.stroke.strokeOpacity : + ol.parser.ogc.SLD_v1.defaults_.strokeOpacity, + width: goog.isDef(config.stroke.strokeWidth) ? + config.stroke.strokeWidth : + ol.parser.ogc.SLD_v1.defaults_.strokeWidth }; rule.symbolizers.push( new ol.style.Stroke(stroke) @@ -607,7 +621,7 @@ ol.parser.ogc.SLD_v1 = function() { if (symbolizer instanceof ol.style.Icon) { this.writeNode('ExternalGraphic', symbolizer, null, node); var opacity = symbolizer.getOpacity(); - goog.asserts.assert(opacity instanceof ol.expr.Literal, + goog.asserts.assertInstanceof(opacity, ol.expr.Literal, 'Only ol.expr.Literal supported for graphicOpacity'); this.writeNode('Opacity', opacity.getValue(), null, node); size = symbolizer.getWidth(); @@ -615,12 +629,12 @@ ol.parser.ogc.SLD_v1 = function() { this.writeNode('Mark', symbolizer, null, node); size = symbolizer.getSize(); } - goog.asserts.assert(size instanceof ol.expr.Literal, + goog.asserts.assertInstanceof(size, ol.expr.Literal, 'Only ol.expr.Literal supported for in Size'); this.writeNode('Size', size.getValue(), null, node); if (symbolizer instanceof ol.style.Icon) { var rotation = symbolizer.getRotation(); - goog.asserts.assert(rotation instanceof ol.expr.Literal, + goog.asserts.assertInstanceof(rotation, ol.expr.Literal, 'Only ol.expr.Literal supported for rotation'); this.writeNode('Rotation', rotation.getValue(), null, node); } @@ -635,13 +649,13 @@ ol.parser.ogc.SLD_v1 = function() { var node = this.createElementNS('sld:Fill'); var fillColor = symbolizer.getColor(); var msg = 'Only ol.expr.Literal supported for Fill properties'; - goog.asserts.assert(fillColor instanceof ol.expr.Literal, msg); + goog.asserts.assertInstanceof(fillColor, ol.expr.Literal, msg); this.writeNode('CssParameter', { value: fillColor.getValue(), key: 'fillColor' }, null, node); var fillOpacity = symbolizer.getOpacity(); - goog.asserts.assert(fillOpacity instanceof ol.expr.Literal, msg); + goog.asserts.assertInstanceof(fillOpacity, ol.expr.Literal, msg); this.writeNode('CssParameter', { value: fillOpacity.getValue(), key: 'fillOpacity' @@ -660,6 +674,24 @@ ol.parser.ogc.SLD_v1 = function() { if (!goog.isNull(stroke)) { this.writeNode('Halo', stroke, null, node); } + var color = symbolizer.getColor(); + goog.asserts.assertInstanceof(color, ol.expr.Literal, + 'font color should be ol.expr.Literal'); + this.writeNode('Fill', symbolizer, null, node); + return node; + }, + 'Halo': function(symbolizer) { + var node = this.createElementNS('sld:Halo'); + goog.asserts.assertInstanceof(symbolizer.getWidth(), ol.expr.Literal, + 'Only ol.expr.Literal supported for haloRadius'); + this.writeNode('Radius', symbolizer.getWidth().getValue() / 2, null, + node); + this.writeNode('Fill', symbolizer, null, node); + return node; + }, + 'Radius': function(value) { + var node = this.createElementNS('sld:Radius'); + node.appendChild(this.createTextNode(value)); return node; }, 'LineSymbolizer': function(symbolizer) { @@ -672,19 +704,19 @@ ol.parser.ogc.SLD_v1 = function() { var strokeColor = symbolizer.getColor(); var msg = 'SLD writing of stroke properties only supported ' + 'for ol.expr.Literal'; - goog.asserts.assert(strokeColor instanceof ol.expr.Literal, msg); + goog.asserts.assertInstanceof(strokeColor, ol.expr.Literal, msg); this.writeNode('CssParameter', { value: strokeColor.getValue(), key: 'strokeColor' }, null, node); var strokeOpacity = symbolizer.getOpacity(); - goog.asserts.assert(strokeOpacity instanceof ol.expr.Literal, msg); + goog.asserts.assertInstanceof(strokeOpacity, ol.expr.Literal, msg); this.writeNode('CssParameter', { value: strokeOpacity.getValue(), key: 'strokeOpacity' }, null, node); var strokeWidth = symbolizer.getWidth(); - goog.asserts.assert(strokeWidth instanceof ol.expr.Literal, msg); + goog.asserts.assertInstanceof(strokeWidth, ol.expr.Literal, msg); this.writeNode('CssParameter', { value: strokeWidth.getValue(), key: 'strokeWidth' @@ -694,11 +726,14 @@ ol.parser.ogc.SLD_v1 = function() { }, 'CssParameter': function(obj) { // not handling ogc:expressions for now - var node = this.createElementNS('sld:CssParameter'); - node.setAttribute('name', - ol.parser.ogc.SLD_v1.getCssProperty_(obj.key)); - node.appendChild(this.createTextNode(obj.value)); - return node; + var name = ol.parser.ogc.SLD_v1.getCssProperty_(obj.key); + if (goog.isDef(name) && obj.value !== + ol.parser.ogc.SLD_v1.defaults_[obj.key]) { + var node = this.createElementNS('sld:CssParameter'); + node.setAttribute('name', name); + node.appendChild(this.createTextNode(obj.value)); + return node; + } }, 'Label': function(label) { var node = this.createElementNS('sld:Label'); @@ -777,6 +812,19 @@ ol.parser.ogc.SLD_v1.cssMap_ = { }; +/** + * @private + */ +ol.parser.ogc.SLD_v1.defaults_ = { + fillOpacity: 1, + strokeOpacity: 1, + strokeWidth: 1, + haloColor: '#FFFFFF', + haloOpacity: 1, + haloRadius: 1 +}; + + /** * @private * @param {string} sym Symbolizer property. diff --git a/test/spec/ol/parser/ogc/sld_v1_0_0.test.js b/test/spec/ol/parser/ogc/sld_v1_0_0.test.js index 5a6b76ef0f..f9ec9e569e 100644 --- a/test/spec/ol/parser/ogc/sld_v1_0_0.test.js +++ b/test/spec/ol/parser/ogc/sld_v1_0_0.test.js @@ -26,7 +26,7 @@ describe('ol.parser.ogc.SLD_v1_0_0', function() { expect(first.getSymbolizers()[0]).to.be.a(ol.style.Fill); expect(first.getSymbolizers()[0].getColor().getValue()).to.equal( '#ffffff'); - expect(first.getSymbolizers()[0].getOpacity().getValue()).to.equal(0.4); + expect(first.getSymbolizers()[0].getOpacity().getValue()).to.equal(1); expect(first.getSymbolizers()[1]).to.be.a(ol.style.Stroke); expect(first.getSymbolizers()[1].getColor().getValue()).to.equal( '#000000'); @@ -47,7 +47,7 @@ describe('ol.parser.ogc.SLD_v1_0_0', function() { expect(first.getSymbolizers()[2].getText().getArgs()[2].getValue()). to.equal('label'); expect(first.getSymbolizers()[2].getColor().getValue()).to.equal( - ol.style.TextDefaults.color); + '#000000'); expect(first.getSymbolizers()[2].getFontFamily().getValue()).to.equal( 'Arial'); // TODO add tests for haloRadius and haloColor From c75082c75dd3eee057a24f29f6c5679b38c7a5c2 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Fri, 11 Oct 2013 16:49:39 +0200 Subject: [PATCH 06/66] add name and title to ol.style.Rule --- src/objectliterals.jsdoc | 2 ++ src/ol/parser/ogc/sldparser_v1.js | 9 +++------ src/ol/style/rule.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 545573f100..8534aef970 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -819,6 +819,8 @@ * a value is provided, the rule will apply at resolutions greater than or * equal to this value. * @property {Array.|undefined} symbolizers Symbolizers. + * @property {string|undefined} name Name. + * @property {string|undefined} title Title. * @todo stability experimental */ diff --git a/src/ol/parser/ogc/sldparser_v1.js b/src/ol/parser/ogc/sldparser_v1.js index 4803ae4db8..feecb404b1 100644 --- a/src/ol/parser/ogc/sldparser_v1.js +++ b/src/ol/parser/ogc/sldparser_v1.js @@ -544,15 +544,12 @@ ol.parser.ogc.SLD_v1 = function() { 'Rule': function(rule) { var node = this.createElementNS('sld:Rule'); var filter = rule.getFilter(); - if (rule.name) { - this.writeNode('Name', rule.name, null, node); + if (!goog.isNull(rule.getName())) { + this.writeNode('Name', rule.getName(), null, node); } - if (rule.title) { + if (!goog.isNull(rule.getTitle())) { this.writeNode('Title', rule.title, null, node); } - if (rule.description) { - this.writeNode('Abstract', rule.description, null, node); - } if (rule.elseFilter) { this.writeNode('ElseFilter', null, null, node); } else if (filter) { diff --git a/src/ol/style/rule.js b/src/ol/style/rule.js index 25d342aa5e..3495422459 100644 --- a/src/ol/style/rule.js +++ b/src/ol/style/rule.js @@ -54,6 +54,20 @@ ol.style.Rule = function(options) { this.maxResolution_ = goog.isDef(options.maxResolution) ? options.maxResolution : Infinity; + /** + * @type {?string} + * @private + */ + this.name_ = goog.isDef(options.name) ? + options.name : null; + + /** + * @type {?string} + * @private + */ + this.title_ = goog.isDef(options.title) ? + options.title : null; + }; @@ -102,3 +116,19 @@ ol.style.Rule.prototype.getMinResolution = function() { ol.style.Rule.prototype.getMaxResolution = function() { return this.maxResolution_; }; + + +/** + * @return {?string} + */ +ol.style.Rule.prototype.getName = function() { + return this.name_; +}; + + +/** + * @return {?string} + */ +ol.style.Rule.prototype.getTitle = function() { + return this.title_; +}; From 5c8fb35227256724bcb51fb3b86d622ddc9666b9 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Fri, 11 Oct 2013 17:19:56 +0200 Subject: [PATCH 07/66] get rid of the readers we don't support in ol3 symbology as yet --- src/ol/parser/ogc/sldparser_v1.js | 105 ------------------------------ 1 file changed, 105 deletions(-) diff --git a/src/ol/parser/ogc/sldparser_v1.js b/src/ol/parser/ogc/sldparser_v1.js index feecb404b1..40d0ef21f8 100644 --- a/src/ol/parser/ogc/sldparser_v1.js +++ b/src/ol/parser/ogc/sldparser_v1.js @@ -96,83 +96,6 @@ ol.parser.ogc.SLD_v1 = function() { new ol.style.Text(/** @type {ol.style.TextOptions} */(config)) ); }, - 'LabelPlacement': function(node, symbolizer) { - this.readChildNodes(node, symbolizer); - }, - 'PointPlacement': function(node, symbolizer) { - var config = {}; - this.readChildNodes(node, config); - config.labelRotation = config.rotation; - delete config.rotation; - var labelAlign, - x = symbolizer.labelAnchorPointX, - y = symbolizer.labelAnchorPointY; - if (x <= 1 / 3) { - labelAlign = 'l'; - } else if (x > 1 / 3 && x < 2 / 3) { - labelAlign = 'c'; - } else if (x >= 2 / 3) { - labelAlign = 'r'; - } - if (y <= 1 / 3) { - labelAlign += 'b'; - } else if (y > 1 / 3 && y < 2 / 3) { - labelAlign += 'm'; - } else if (y >= 2 / 3) { - labelAlign += 't'; - } - config.labelAlign = labelAlign; - goog.object.extend(symbolizer, config); - }, - 'AnchorPoint': function(node, symbolizer) { - this.readChildNodes(node, symbolizer); - }, - 'AnchorPointX': function(node, symbolizer) { - var ogcreaders = this.readers['http://www.opengis.net/ogc']; - var labelAnchorPointX = ogcreaders._expression.call(this, node); - // always string, could be empty string - if (labelAnchorPointX) { - symbolizer.labelAnchorPointX = labelAnchorPointX; - } - }, - 'AnchorPointY': function(node, symbolizer) { - var ogcreaders = this.readers['http://www.opengis.net/ogc']; - var labelAnchorPointY = ogcreaders._expression.call(this, node); - // always string, could be empty string - if (labelAnchorPointY) { - symbolizer.labelAnchorPointY = labelAnchorPointY; - } - }, - 'Displacement': function(node, symbolizer) { - this.readChildNodes(node, symbolizer); - }, - 'DisplacementX': function(node, symbolizer) { - var ogcreaders = this.readers['http://www.opengis.net/ogc']; - var labelXOffset = ogcreaders._expression.call(this, node); - // always string, could be empty string - if (labelXOffset) { - symbolizer.labelXOffset = labelXOffset; - } - }, - 'DisplacementY': function(node, symbolizer) { - var ogcreaders = this.readers['http://www.opengis.net/ogc']; - var labelYOffset = ogcreaders._expression.call(this, node); - // always string, could be empty string - if (labelYOffset) { - symbolizer.labelYOffset = labelYOffset; - } - }, - 'LinePlacement': function(node, symbolizer) { - this.readChildNodes(node, symbolizer); - }, - 'PerpendicularOffset': function(node, symbolizer) { - var ogcreaders = this.readers['http://www.opengis.net/ogc']; - var labelPerpendicularOffset = ogcreaders._expression.call(this, node); - // always string, could be empty string - if (labelPerpendicularOffset) { - symbolizer.labelPerpendicularOffset = labelPerpendicularOffset; - } - }, 'Label': function(node, symbolizer) { var ogcreaders = this.readers['http://www.opengis.net/ogc']; var value = ogcreaders._expression.call(this, node); @@ -204,34 +127,6 @@ ol.parser.ogc.SLD_v1 = function() { symbolizer.haloRadius = radius.getValue(); } }, - 'RasterSymbolizer': function(node, rule) { - var config = {}; - this.readChildNodes(node, config); - config.zIndex = this.featureTypeCounter; - /* TODO - rule.symbolizers.push( - new OpenLayers.Symbolizer.Raster(config) - ); - */ - }, - 'Geometry': function(node, obj) { - obj.geometry = {}; - this.readChildNodes(node, obj.geometry); - }, - 'ColorMap': function(node, symbolizer) { - symbolizer.colorMap = []; - this.readChildNodes(node, symbolizer.colorMap); - }, - 'ColorMapEntry': function(node, colorMap) { - var q = node.getAttribute('quantity'); - var o = node.getAttribute('opacity'); - colorMap.push({ - color: node.getAttribute('color'), - quantity: q !== null ? parseFloat(q) : undefined, - label: node.getAttribute('label') || undefined, - opacity: o !== null ? parseFloat(o) : undefined - }); - }, 'LineSymbolizer': function(node, rule) { var config = {}; this.readChildNodes(node, config); From a7fe89c05d9fd7162404f3f331f9cea332bd0cb3 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Mon, 14 Oct 2013 13:22:19 +0200 Subject: [PATCH 08/66] better write support for PointSymbolizer --- src/ol/parser/ogc/sldparser_v1.js | 98 ++++++++++++++----------------- 1 file changed, 44 insertions(+), 54 deletions(-) diff --git a/src/ol/parser/ogc/sldparser_v1.js b/src/ol/parser/ogc/sldparser_v1.js index 40d0ef21f8..52825b2f24 100644 --- a/src/ol/parser/ogc/sldparser_v1.js +++ b/src/ol/parser/ogc/sldparser_v1.js @@ -173,15 +173,38 @@ ol.parser.ogc.SLD_v1 = function() { this.readChildNodes(node, config); config.zIndex = this.featureTypeCounter; if (config.fill) { - config.fill = new ol.style.Fill(config.fill); + var fillConfig = { + color: goog.isDef(config.fill.fillColor) ? + config.fill.fillColor : + ol.parser.ogc.SLD_v1.defaults_.fillColor, + opacity: goog.isDef(config.fill.fillOpacity) ? + config.fill.fillOpacity : + ol.parser.ogc.SLD_v1.defaults_.fillOpacity + }; + config.fill = new ol.style.Fill(fillConfig); } if (config.stroke) { - config.stroke = new ol.style.Stroke(config.stroke); + var strokeConfig = { + color: goog.isDef(config.stroke.strokeColor) ? + config.stroke.strokeColor : + ol.parser.ogc.SLD_v1.defaults_.strokeColor, + width: goog.isDef(config.stroke.strokeWidth) ? + config.stroke.strokeWidth : + ol.parser.ogc.SLD_v1.defaults_.strokeWidth, + opacity: goog.isDef(config.stroke.strokeOpacity) ? + config.stroke.strokeOpacity : + ol.parser.ogc.SLD_v1.defaults_.strokeOpacity + }; + config.stroke = new ol.style.Stroke(strokeConfig); } - // TODO shape or icon? - rule.symbolizers.push( - new ol.style.Shape(config) - ); + var symbolizer; + if (goog.isDef(config.externalGraphic)) { + config.width = config.height = config.size; + symbolizer = new ol.style.Icon(config); + } else { + symbolizer = new ol.style.Shape(config); + } + rule.symbolizers.push(symbolizer); }, 'Stroke': function(node, symbolizer) { var stroke = {}; @@ -196,35 +219,18 @@ ol.parser.ogc.SLD_v1 = function() { 'CssParameter': function(node, symbolizer) { var cssProperty = node.getAttribute('name'); var symProperty = ol.parser.ogc.SLD_v1.cssMap_[cssProperty]; - // for labels, fill should map to fontColor and fill-opacity - // to fontOpacity - if (symbolizer.label) { - if (cssProperty === 'fill') { - symProperty = 'fontColor'; - } else if (cssProperty === 'fill-opacity') { - symProperty = 'fontOpacity'; - } - } if (symProperty) { - // Limited support for parsing of OGC expressions var ogcreaders = this.readers['http://www.opengis.net/ogc']; - var value = ogcreaders._expression.call(this, node); - // always string, could be an empty string - if (value) { - symbolizer[symProperty] = value; - } + symbolizer[symProperty] = ogcreaders._expression.call(this, node); } }, 'Graphic': function(node, symbolizer) { - symbolizer.graphic = true; var graphic = {}; // painter's order not respected here, clobber previous with next this.readChildNodes(node, graphic); // directly properties with names that match symbolizer properties var properties = [ - 'stroke', 'strokeColor', 'strokeWidth', 'strokeOpacity', - 'strokeLinecap', 'fill', 'fillColor', 'fillOpacity', - 'graphicName', 'rotation', 'graphicFormat' + 'stroke', 'fill', 'rotation', 'opacity' ]; var prop, value; for (var i = 0, ii = properties.length; i < ii; ++i) { @@ -235,23 +241,20 @@ ol.parser.ogc.SLD_v1 = function() { } } // set other generic properties with specific graphic property names - if (goog.isDef(graphic.opacity)) { - symbolizer.graphicOpacity = graphic.opacity; + if (goog.isDef(graphic.graphicName)) { + symbolizer.type = graphic.graphicName; } if (goog.isDef(graphic.size)) { var pointRadius = graphic.size / 2; if (isNaN(pointRadius)) { // likely a property name - symbolizer.graphicWidth = graphic.size; + symbolizer.size = graphic.size; } else { - symbolizer.pointRadius = graphic.size / 2; + symbolizer.size = graphic.size / 2; } } if (goog.isDef(graphic.href)) { - symbolizer.externalGraphic = graphic.href; - } - if (goog.isDef(graphic.rotation)) { - symbolizer.rotation = graphic.rotation; + symbolizer.url = graphic.href; } }, 'ExternalGraphic': function(node, graphic) { @@ -265,27 +268,15 @@ ol.parser.ogc.SLD_v1 = function() { }, 'Opacity': function(node, obj) { var ogcreaders = this.readers['http://www.opengis.net/ogc']; - var opacity = ogcreaders._expression.call(this, node); - // always string, could be empty string - if (opacity) { - obj.opacity = opacity; - } + obj.opacity = ogcreaders._expression.call(this, node); }, 'Size': function(node, obj) { var ogcreaders = this.readers['http://www.opengis.net/ogc']; - var size = ogcreaders._expression.call(this, node); - // always string, could be empty string - if (size) { - obj.size = size; - } + obj.size = ogcreaders._expression.call(this, node); }, 'Rotation': function(node, obj) { var ogcreaders = this.readers['http://www.opengis.net/ogc']; - var rotation = ogcreaders._expression.call(this, node); - // always string, could be empty string - if (rotation) { - obj.rotation = rotation; - } + obj.rotation = ogcreaders._expression.call(this, node); }, 'OnlineResource': function(node, obj) { obj.href = this.getAttributeNS( @@ -521,9 +512,7 @@ ol.parser.ogc.SLD_v1 = function() { this.writeNode('Mark', symbolizer, null, node); size = symbolizer.getSize(); } - goog.asserts.assertInstanceof(size, ol.expr.Literal, - 'Only ol.expr.Literal supported for in Size'); - this.writeNode('Size', size.getValue(), null, node); + this.writeNode('Size', size, null, node); if (symbolizer instanceof ol.style.Icon) { var rotation = symbolizer.getRotation(); goog.asserts.assertInstanceof(rotation, ol.expr.Literal, @@ -619,8 +608,7 @@ ol.parser.ogc.SLD_v1 = function() { 'CssParameter': function(obj) { // not handling ogc:expressions for now var name = ol.parser.ogc.SLD_v1.getCssProperty_(obj.key); - if (goog.isDef(name) && obj.value !== - ol.parser.ogc.SLD_v1.defaults_[obj.key]) { + if (goog.isDef(name)) { var node = this.createElementNS('sld:CssParameter'); node.setAttribute('name', name); node.appendChild(this.createTextNode(obj.value)); @@ -711,9 +699,11 @@ ol.parser.ogc.SLD_v1.defaults_ = { fillOpacity: 1, strokeOpacity: 1, strokeWidth: 1, + strokeColor: '#000000', haloColor: '#FFFFFF', haloOpacity: 1, - haloRadius: 1 + haloRadius: 1, + fillColor: '#808080' }; From 91e834674cc173255d33b2d2f577ef95d02f98e2 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Mon, 14 Oct 2013 15:14:56 +0200 Subject: [PATCH 09/66] deal with elseFilter, some cleanup --- src/objectliterals.jsdoc | 2 + src/ol/parser/ogc/sldparser_v1.js | 98 ++++++------------------------- src/ol/style/style.js | 37 ++++++++++++ 3 files changed, 56 insertions(+), 81 deletions(-) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 8534aef970..bbd675ff59 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -850,6 +850,8 @@ * @property {Array.|undefined} symbolizers Symbolizers * (that apply if no rules are provided or where none of the provided rules * apply). + * @property {string|undefined} name Name. + * @property {string|undefined} title Title. * @todo stability experimental */ diff --git a/src/ol/parser/ogc/sldparser_v1.js b/src/ol/parser/ogc/sldparser_v1.js index 52825b2f24..a48c20dbcc 100644 --- a/src/ol/parser/ogc/sldparser_v1.js +++ b/src/ol/parser/ogc/sldparser_v1.js @@ -57,11 +57,6 @@ ol.parser.ogc.SLD_v1 = function() { this.readChildNodes(node, obj); layer.userStyles.push(new ol.style.Style(obj)); }, - 'IsDefault': function(node, style) { - if (this.getChildValue(node) === '1') { - style.isDefault = true; - } - }, 'FeatureTypeStyle': function(node, style) { ++this.featureTypeCounter; var obj = { @@ -200,7 +195,8 @@ ol.parser.ogc.SLD_v1 = function() { var symbolizer; if (goog.isDef(config.externalGraphic)) { config.width = config.height = config.size; - symbolizer = new ol.style.Icon(config); + symbolizer = new ol.style.Icon( + /** @type {ol.style.IconOptions} */(config)); } else { symbolizer = new ol.style.Shape(config); } @@ -345,86 +341,30 @@ ol.parser.ogc.SLD_v1 = function() { }, 'UserStyle': function(style) { var node = this.createElementNS('sld:UserStyle'); - if (style.name) { - this.writeNode('Name', style.name, null, node); + var name = style.getName(), title = style.getTitle(); + if (!goog.isNull(name)) { + this.writeNode('Name', name, null, node); } - if (style.title) { - this.writeNode('Title', style.title, null, node); + if (!goog.isNull(title)) { + this.writeNode('Title', title, null, node); } - if (style.description) { - this.writeNode('Abstract', style.description, null, node); - } - if (style.isDefault) { - this.writeNode('IsDefault', style.isDefault, null, node); - } - if (style.rules) { - // group style objects by symbolizer zIndex - var rulesByZ = { - 0: [] - }; - var zValues = [0]; - var rule, ruleMap, symbolizer, zIndex, clone; - for (var i = 0, ii = style.rules.length; i < ii; ++i) { - rule = style.rules[i]; - var symbolizers = rule.getSymbolizers(); - if (symbolizers) { - ruleMap = {}; - for (var j = 0, jj = symbolizers.length; j < jj; ++j) { - symbolizer = symbolizers[j]; - zIndex = symbolizer.zIndex; - if (!(zIndex in ruleMap)) { - // TODO check if clone works? - clone = goog.object.clone(rule); - clone.setSymbolizers([]); - ruleMap[zIndex] = clone; - } - // TODO check if clone works - ruleMap[zIndex].getSymbolizers().push( - goog.object.clone(symbolizer)); - } - for (zIndex in ruleMap) { - if (!(zIndex in rulesByZ)) { - zValues.push(zIndex); - rulesByZ[zIndex] = []; - } - rulesByZ[zIndex].push(ruleMap[zIndex]); - } - } else { - // no symbolizers in rule - rulesByZ[0].push(goog.object.clone(rule)); - } - } - // write one FeatureTypeStyle per zIndex - zValues.sort(); - var rules; - for (var i = 0, ii = zValues.length; i < ii; ++i) { - rules = rulesByZ[zValues[i]]; - if (rules.length > 0) { - clone = goog.object.clone(style); - clone.setRules(rulesByZ[zValues[i]]); - this.writeNode('FeatureTypeStyle', clone, null, node); - } - } - } else { - this.writeNode('FeatureTypeStyle', style, null, node); - } - return node; - }, - 'IsDefault': function(bool) { - var node = this.createElementNS('sld:IsDefault'); - node.appendChild(this.createTextNode((bool) ? '1' : '0')); + // TODO sorting by zIndex + this.writeNode('FeatureTypeStyle', style, null, node); return node; }, 'FeatureTypeStyle': function(style) { var node = this.createElementNS('sld:FeatureTypeStyle'); - // OpenLayers currently stores no Name, Title, Abstract, - // FeatureTypeName, or SemanticTypeIdentifier information - // related to FeatureTypeStyle - // add in rules var rules = style.getRules(); for (var i = 0, ii = rules.length; i < ii; ++i) { this.writeNode('Rule', rules[i], null, node); } + var symbolizers = style.getSymbolizers(); + if (symbolizers.length > 0) { + // wrap this in a Rule with an ElseFilter + var rule = new ol.style.Rule({symbolizers: symbolizers}); + rule.elseFilter = true; + this.writeNode('Rule', rule, null, node); + } return node; }, 'Rule': function(rule) { @@ -436,7 +376,7 @@ ol.parser.ogc.SLD_v1 = function() { if (!goog.isNull(rule.getTitle())) { this.writeNode('Title', rule.title, null, node); } - if (rule.elseFilter) { + if (rule.elseFilter === true) { this.writeNode('ElseFilter', null, null, node); } else if (filter) { this.writeNode('Filter', filter, 'http://www.opengis.net/ogc', node); @@ -457,7 +397,6 @@ ol.parser.ogc.SLD_v1 = function() { if (symbolizers) { for (var i = 0, ii = symbolizers.length; i < ii; ++i) { symbolizer = symbolizers[i]; - // TODO other types of symbolizers if (symbolizer instanceof ol.style.Text) { type = 'Text'; } else if (symbolizer instanceof ol.style.Stroke) { @@ -546,11 +485,8 @@ ol.parser.ogc.SLD_v1 = function() { 'TextSymbolizer': function(symbolizer) { var node = this.createElementNS('sld:TextSymbolizer'); var text = symbolizer.getText(); - // TODO in SLD optional, but in ol3 required? this.writeNode('Label', text, null, node); - // TODO in SLD optional, but in ol3 required? this.writeNode('Font', symbolizer, null, node); - // TODO map align to labelAnchorPoint etc. var stroke = symbolizer.getStroke(); if (!goog.isNull(stroke)) { this.writeNode('Halo', stroke, null, node); diff --git a/src/ol/style/style.js b/src/ol/style/style.js index d7e0d38022..224ec2578c 100644 --- a/src/ol/style/style.js +++ b/src/ol/style/style.js @@ -40,6 +40,19 @@ ol.style.Style = function(options) { this.symbolizers_ = goog.isDef(options.symbolizers) ? options.symbolizers : []; + /** + * @type {?string} + * @private + */ + this.name_ = goog.isDef(options.name) ? + options.name : null; + + /** + * @type {?string} + * @private + */ + this.title_ = goog.isDef(options.title) ? + options.title : null; }; @@ -223,3 +236,27 @@ ol.style.Style.prototype.getRules = function() { ol.style.Style.prototype.setRules = function(rules) { this.rules_ = rules; }; + + +/** + * @return {Array.} + */ +ol.style.Style.prototype.getSymbolizers = function() { + return this.symbolizers_; +}; + + +/** + * @return {?string} + */ +ol.style.Style.prototype.getName = function() { + return this.name_; +}; + + +/** + * @return {?string} + */ +ol.style.Style.prototype.getTitle = function() { + return this.title_; +}; From 429a2e455b92a9bde5f1e651dc0c6a2f17c752a0 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Mon, 14 Oct 2013 15:48:56 +0200 Subject: [PATCH 10/66] Add a separate test case for write --- src/ol/parser/ogc/sldparser.js | 11 -- src/ol/parser/ogc/sldparser_v1.js | 6 +- .../parser/ogc/sldparser_v1_0_0_GeoServer.js | 43 ------ test/spec/ol/parser/ogc/sld_v1_0_0.test.js | 19 ++- .../ol/parser/ogc/xml/sld_v1_0_0_write.xml | 133 ++++++++++++++++++ 5 files changed, 152 insertions(+), 60 deletions(-) delete mode 100644 src/ol/parser/ogc/sldparser_v1_0_0_GeoServer.js create mode 100644 test/spec/ol/parser/ogc/xml/sld_v1_0_0_write.xml diff --git a/src/ol/parser/ogc/sldparser.js b/src/ol/parser/ogc/sldparser.js index 8560cdc2e8..c9db020b75 100644 --- a/src/ol/parser/ogc/sldparser.js +++ b/src/ol/parser/ogc/sldparser.js @@ -1,6 +1,5 @@ goog.provide('ol.parser.ogc.SLD'); goog.require('ol.parser.ogc.SLD_v1_0_0'); -goog.require('ol.parser.ogc.SLD_v1_0_0_GeoServer'); goog.require('ol.parser.ogc.Versioned'); @@ -10,13 +9,6 @@ goog.require('ol.parser.ogc.Versioned'); ol.ENABLE_SLD_1_0_0 = true; -/** - * @define {boolean} Whether to enable SLD version 1.0.0. - * GeoServer profile. - */ -ol.ENABLE_SLD_1_0_0_GEOSERVER = true; - - /** * @constructor @@ -30,9 +22,6 @@ ol.parser.ogc.SLD = function(opt_options) { if (ol.ENABLE_SLD_1_0_0) { this.parsers['v1_0_0'] = ol.parser.ogc.SLD_v1_0_0; } - if (ol.ENABLE_SLD_1_0_0_GEOSERVER) { - this.parsers['v1_0_0_GEOSERVER'] = ol.parser.ogc.SLD_v1_0_0_GeoServer; - } goog.base(this, opt_options); }; goog.inherits(ol.parser.ogc.SLD, ol.parser.ogc.Versioned); diff --git a/src/ol/parser/ogc/sldparser_v1.js b/src/ol/parser/ogc/sldparser_v1.js index a48c20dbcc..349603ffb2 100644 --- a/src/ol/parser/ogc/sldparser_v1.js +++ b/src/ol/parser/ogc/sldparser_v1.js @@ -84,7 +84,8 @@ ol.parser.ogc.SLD_v1 = function() { 'TextSymbolizer': function(node, rule) { var config = {}; this.readChildNodes(node, config); - config.color = config.fill.fillColor; + config.color = goog.isDef(config.fill) ? config.fill.fillColor : + ol.parser.ogc.SLD_v1.defaults_.fontColor; delete config.fill; config.zIndex = this.featureTypeCounter; rule.symbolizers.push( @@ -639,7 +640,8 @@ ol.parser.ogc.SLD_v1.defaults_ = { haloColor: '#FFFFFF', haloOpacity: 1, haloRadius: 1, - fillColor: '#808080' + fillColor: '#808080', + fontColor: '#000000' }; diff --git a/src/ol/parser/ogc/sldparser_v1_0_0_GeoServer.js b/src/ol/parser/ogc/sldparser_v1_0_0_GeoServer.js deleted file mode 100644 index 77f58d1ab5..0000000000 --- a/src/ol/parser/ogc/sldparser_v1_0_0_GeoServer.js +++ /dev/null @@ -1,43 +0,0 @@ -goog.provide('ol.parser.ogc.SLD_v1_0_0_GeoServer'); - -goog.require('goog.functions'); -goog.require('goog.object'); -goog.require('ol.parser.ogc.SLD_v1_0_0'); - - - -/** - * @constructor - * @extends {ol.parser.ogc.SLD_v1_0_0} - */ -ol.parser.ogc.SLD_v1_0_0_GeoServer = function() { - goog.base(this); - this.profile = 'GeoServer'; - goog.object.extend(this.readers['http://www.opengis.net/sld'], { - 'Priority': function(node, obj) { - var ogcreaders = this.readers['http://www.opengis.net/ogc']; - var value = ogcreaders._expression.call(this, node); - if (value) { - obj.priority = value; - } - }, - 'VendorOption': function(node, obj) { - if (!goog.isDef(obj.vendorOptions)) { - obj.vendorOptions = {}; - } - obj.vendorOptions[node.getAttribute('name')] = - this.getChildValue(node); - }, - 'TextSymbolizer': goog.functions.sequence( - this.readers['http://www.opengis.net/sld']['TextSymbolizer'], - function(node, rule) { - var symbolizer = rule.symbolizers[rule.symbolizers.length - 1]; - if (!goog.isDef(symbolizer.graphic)) { - symbolizer.graphic = false; - } - } - ) - }); -}; -goog.inherits(ol.parser.ogc.SLD_v1_0_0_GeoServer, - ol.parser.ogc.SLD_v1_0_0); diff --git a/test/spec/ol/parser/ogc/sld_v1_0_0.test.js b/test/spec/ol/parser/ogc/sld_v1_0_0.test.js index f9ec9e569e..e44fb45ac6 100644 --- a/test/spec/ol/parser/ogc/sld_v1_0_0.test.js +++ b/test/spec/ol/parser/ogc/sld_v1_0_0.test.js @@ -4,12 +4,13 @@ goog.provide('ol.test.parser.ogc.SLD_v1_0_0'); describe('ol.parser.ogc.SLD_v1_0_0', function() { var parser = new ol.parser.ogc.SLD(); + var obj; describe('reading and writing', function() { it('Handles reading', function(done) { var url = 'spec/ol/parser/ogc/xml/sld_v1_0_0.xml'; afterLoadXml(url, function(xml) { - var obj = parser.read(xml); + obj = parser.read(xml); expect(obj.version).to.equal('1.0.0'); var style = obj.namedLayers['AAA161'].userStyles[0]; expect(style).to.be.a(ol.style.Style); @@ -50,13 +51,23 @@ describe('ol.parser.ogc.SLD_v1_0_0', function() { '#000000'); expect(first.getSymbolizers()[2].getFontFamily().getValue()).to.equal( 'Arial'); - // TODO add tests for haloRadius and haloColor + expect(first.getSymbolizers()[2].getStroke()).to.be.a(ol.style.Stroke); + expect(first.getSymbolizers()[2].getStroke().getColor().getValue()) + .to.equal('#ffffff'); + expect(first.getSymbolizers()[2].getStroke().getWidth().getValue()) + .to.equal(6); var second = style.rules_[1]; expect(second.filter_).to.be.a(ol.expr.Comparison); expect(second.getSymbolizers().length).to.equal(2); expect(second.getSymbolizers()[0]).to.be.a(ol.style.Fill); expect(second.getSymbolizers()[1]).to.be.a(ol.style.Stroke); - window.console.log(parser.write(obj)); + done(); + }); + }); + it('Handles write', function(done) { + var url = 'spec/ol/parser/ogc/xml/sld_v1_0_0_write.xml'; + afterLoadXml(url, function(xml) { + expect(goog.dom.xml.loadXml(parser.write(obj))).to.xmleql(xml); done(); }); }); @@ -64,7 +75,7 @@ describe('ol.parser.ogc.SLD_v1_0_0', function() { }); -goog.require('goog.net.XhrIo'); +goog.require('goog.dom.xml'); goog.require('ol.parser.ogc.SLD_v1_0_0'); goog.require('ol.parser.ogc.SLD'); goog.require('ol.expr.Call'); diff --git a/test/spec/ol/parser/ogc/xml/sld_v1_0_0_write.xml b/test/spec/ol/parser/ogc/xml/sld_v1_0_0_write.xml new file mode 100644 index 0000000000..eeff7933ac --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/sld_v1_0_0_write.xml @@ -0,0 +1,133 @@ + + + + AAA161 + + + + stortsteen + + + CTE + V0305 + + + 49999.99999999999 + + + #ffffff + 1 + + + + + #000000 + 1 + 1 + + + + AFOOlabel + + Arial + 14 + + + 3 + + #ffffff + 1 + + + + #000000 + 1 + + + + + betonbekleding + + + CTE + 1000 + + + 49999.99999999999 + + + #ffff00 + 1 + + + + + #0000ff + 1 + 1 + + + + + + + + Second Layer + + + + first rule second layer + + + + + FOO + 5000 + + + + cat + *dog.food!*good + + + number + + 1064866676 + + + 1065512599 + + + + + 10000 + + + + star + + lime + 1 + + + olive + 1 + 2 + + + SIZE + + + + + + + From 01c9448c3c520c222094694038b3ec0dc597b4e1 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Mon, 14 Oct 2013 16:08:50 +0200 Subject: [PATCH 11/66] do not hard-code the namespaceURIs multiple times --- src/ol/parser/ogc/sldparser_v1.js | 1103 ++++++++++++++--------------- 1 file changed, 551 insertions(+), 552 deletions(-) diff --git a/src/ol/parser/ogc/sldparser_v1.js b/src/ol/parser/ogc/sldparser_v1.js index 349603ffb2..ed9757b038 100644 --- a/src/ol/parser/ogc/sldparser_v1.js +++ b/src/ol/parser/ogc/sldparser_v1.js @@ -23,568 +23,567 @@ goog.require('ol.style.Text'); */ ol.parser.ogc.SLD_v1 = function() { this.defaultNamespaceURI = 'http://www.opengis.net/sld'; - this.readers = { - 'http://www.opengis.net/sld': { - 'StyledLayerDescriptor': function(node, sld) { - sld.version = node.getAttribute('version'); - this.readChildNodes(node, sld); - }, - 'Name': function(node, obj) { - obj.name = this.getChildValue(node); - }, - 'Title': function(node, obj) { - obj.title = this.getChildValue(node); - }, - 'Abstract': function(node, obj) { - obj.description = this.getChildValue(node); - }, - 'NamedLayer': function(node, sld) { - var layer = { - userStyles: [], - namedStyles: [] - }; - this.readChildNodes(node, layer); - sld.namedLayers[layer.name] = layer; - }, - 'NamedStyle': function(node, layer) { - layer.namedStyles.push( - this.getChildValue(node.firstChild) - ); - }, - 'UserStyle': function(node, layer) { - var obj = {rules: []}; - this.featureTypeCounter = -1; - this.readChildNodes(node, obj); - layer.userStyles.push(new ol.style.Style(obj)); - }, - 'FeatureTypeStyle': function(node, style) { - ++this.featureTypeCounter; - var obj = { - rules: style.rules - }; - this.readChildNodes(node, obj); - }, - 'Rule': function(node, obj) { - var config = {symbolizers: []}; - this.readChildNodes(node, config); - var rule = new ol.style.Rule(config); - obj.rules.push(rule); - }, - 'ElseFilter': function(node, rule) { - rule.elseFilter = true; - }, - 'MinScaleDenominator': function(node, rule) { - rule.minResolution = this.getResolutionFromScaleDenominator_( - parseFloat(this.getChildValue(node))); - }, - 'MaxScaleDenominator': function(node, rule) { - rule.maxResolution = this.getResolutionFromScaleDenominator_( - parseFloat(this.getChildValue(node))); - }, - 'TextSymbolizer': function(node, rule) { - var config = {}; - this.readChildNodes(node, config); - config.color = goog.isDef(config.fill) ? config.fill.fillColor : - ol.parser.ogc.SLD_v1.defaults_.fontColor; - delete config.fill; - config.zIndex = this.featureTypeCounter; - rule.symbolizers.push( - new ol.style.Text(/** @type {ol.style.TextOptions} */(config)) - ); - }, - 'Label': function(node, symbolizer) { - var ogcreaders = this.readers['http://www.opengis.net/ogc']; - var value = ogcreaders._expression.call(this, node); - if (value) { - symbolizer.text = value; - } - }, - 'Font': function(node, symbolizer) { - this.readChildNodes(node, symbolizer); - }, - 'Halo': function(node, symbolizer) { - var obj = {}; - this.readChildNodes(node, obj); - symbolizer.stroke = new ol.style.Stroke({ - color: goog.isDef(obj.fill.fillColor) ? obj.fill.fillColor : - ol.parser.ogc.SLD_v1.defaults_.haloColor, - width: goog.isDef(obj.haloRadius) ? obj.haloRadius * 2 : - ol.parser.ogc.SLD_v1.defaults_.haloRadius, - opacity: goog.isDef(obj.fill.fillOpacity) ? obj.fill.fillOpacity : - ol.parser.ogc.SLD_v1.defaults_.haloOpacity - }); - }, - 'Radius': function(node, symbolizer) { - var ogcreaders = this.readers['http://www.opengis.net/ogc']; - var radius = ogcreaders._expression.call(this, node); - goog.asserts.assertInstanceof(radius, ol.expr.Literal, - 'radius expected to be an ol.expr.Literal'); - if (goog.isDef(radius)) { - symbolizer.haloRadius = radius.getValue(); - } - }, - 'LineSymbolizer': function(node, rule) { - var config = {}; - this.readChildNodes(node, config); - config.zIndex = this.featureTypeCounter; - rule.symbolizers.push( - new ol.style.Stroke(config) - ); - }, - 'PolygonSymbolizer': function(node, rule) { - var config = {}; - this.readChildNodes(node, config); - config.zIndex = this.featureTypeCounter; - if (goog.isDef(config.fill)) { - var fill = { - color: config.fill.fillColor.getValue(), - opacity: goog.isDef(config.fill.fillOpacity) ? - config.fill.fillOpacity : - ol.parser.ogc.SLD_v1.defaults_.fillOpacity - }; - rule.symbolizers.push( - new ol.style.Fill(fill) - ); - delete config.fill; - } - if (goog.isDef(config.stroke)) { - var stroke = { - color: config.stroke.strokeColor.getValue(), - opacity: goog.isDef(config.stroke.strokeOpacity) ? - config.stroke.strokeOpacity : - ol.parser.ogc.SLD_v1.defaults_.strokeOpacity, - width: goog.isDef(config.stroke.strokeWidth) ? - config.stroke.strokeWidth : - ol.parser.ogc.SLD_v1.defaults_.strokeWidth - }; - rule.symbolizers.push( - new ol.style.Stroke(stroke) - ); - delete config.stroke; - } - - }, - 'PointSymbolizer': function(node, rule) { - var config = {}; - this.readChildNodes(node, config); - config.zIndex = this.featureTypeCounter; - if (config.fill) { - var fillConfig = { - color: goog.isDef(config.fill.fillColor) ? - config.fill.fillColor : - ol.parser.ogc.SLD_v1.defaults_.fillColor, - opacity: goog.isDef(config.fill.fillOpacity) ? - config.fill.fillOpacity : - ol.parser.ogc.SLD_v1.defaults_.fillOpacity - }; - config.fill = new ol.style.Fill(fillConfig); - } - if (config.stroke) { - var strokeConfig = { - color: goog.isDef(config.stroke.strokeColor) ? - config.stroke.strokeColor : - ol.parser.ogc.SLD_v1.defaults_.strokeColor, - width: goog.isDef(config.stroke.strokeWidth) ? - config.stroke.strokeWidth : - ol.parser.ogc.SLD_v1.defaults_.strokeWidth, - opacity: goog.isDef(config.stroke.strokeOpacity) ? - config.stroke.strokeOpacity : - ol.parser.ogc.SLD_v1.defaults_.strokeOpacity - }; - config.stroke = new ol.style.Stroke(strokeConfig); - } - var symbolizer; - if (goog.isDef(config.externalGraphic)) { - config.width = config.height = config.size; - symbolizer = new ol.style.Icon( - /** @type {ol.style.IconOptions} */(config)); - } else { - symbolizer = new ol.style.Shape(config); - } - rule.symbolizers.push(symbolizer); - }, - 'Stroke': function(node, symbolizer) { - var stroke = {}; - this.readChildNodes(node, stroke); - symbolizer.stroke = stroke; - }, - 'Fill': function(node, symbolizer) { - var fill = {}; - this.readChildNodes(node, fill); - symbolizer.fill = fill; - }, - 'CssParameter': function(node, symbolizer) { - var cssProperty = node.getAttribute('name'); - var symProperty = ol.parser.ogc.SLD_v1.cssMap_[cssProperty]; - if (symProperty) { - var ogcreaders = this.readers['http://www.opengis.net/ogc']; - symbolizer[symProperty] = ogcreaders._expression.call(this, node); - } - }, - 'Graphic': function(node, symbolizer) { - var graphic = {}; - // painter's order not respected here, clobber previous with next - this.readChildNodes(node, graphic); - // directly properties with names that match symbolizer properties - var properties = [ - 'stroke', 'fill', 'rotation', 'opacity' - ]; - var prop, value; - for (var i = 0, ii = properties.length; i < ii; ++i) { - prop = properties[i]; - value = graphic[prop]; - if (goog.isDef(value)) { - symbolizer[prop] = value; - } - } - // set other generic properties with specific graphic property names - if (goog.isDef(graphic.graphicName)) { - symbolizer.type = graphic.graphicName; - } - if (goog.isDef(graphic.size)) { - var pointRadius = graphic.size / 2; - if (isNaN(pointRadius)) { - // likely a property name - symbolizer.size = graphic.size; - } else { - symbolizer.size = graphic.size / 2; - } - } - if (goog.isDef(graphic.href)) { - symbolizer.url = graphic.href; - } - }, - 'ExternalGraphic': function(node, graphic) { - this.readChildNodes(node, graphic); - }, - 'Mark': function(node, graphic) { - this.readChildNodes(node, graphic); - }, - 'WellKnownName': function(node, graphic) { - graphic.graphicName = this.getChildValue(node); - }, - 'Opacity': function(node, obj) { - var ogcreaders = this.readers['http://www.opengis.net/ogc']; - obj.opacity = ogcreaders._expression.call(this, node); - }, - 'Size': function(node, obj) { - var ogcreaders = this.readers['http://www.opengis.net/ogc']; - obj.size = ogcreaders._expression.call(this, node); - }, - 'Rotation': function(node, obj) { - var ogcreaders = this.readers['http://www.opengis.net/ogc']; - obj.rotation = ogcreaders._expression.call(this, node); - }, - 'OnlineResource': function(node, obj) { - obj.href = this.getAttributeNS( - node, 'http://www.w3.org/1999/xlink', 'href' - ); - }, - 'Format': function(node, graphic) { - graphic.graphicFormat = this.getChildValue(node); + this.readers = {}; + this.readers[this.defaultNamespaceURI] = { + 'StyledLayerDescriptor': function(node, sld) { + sld.version = node.getAttribute('version'); + this.readChildNodes(node, sld); + }, + 'Name': function(node, obj) { + obj.name = this.getChildValue(node); + }, + 'Title': function(node, obj) { + obj.title = this.getChildValue(node); + }, + 'Abstract': function(node, obj) { + obj.description = this.getChildValue(node); + }, + 'NamedLayer': function(node, sld) { + var layer = { + userStyles: [], + namedStyles: [] + }; + this.readChildNodes(node, layer); + sld.namedLayers[layer.name] = layer; + }, + 'NamedStyle': function(node, layer) { + layer.namedStyles.push( + this.getChildValue(node.firstChild) + ); + }, + 'UserStyle': function(node, layer) { + var obj = {rules: []}; + this.featureTypeCounter = -1; + this.readChildNodes(node, obj); + layer.userStyles.push(new ol.style.Style(obj)); + }, + 'FeatureTypeStyle': function(node, style) { + ++this.featureTypeCounter; + var obj = { + rules: style.rules + }; + this.readChildNodes(node, obj); + }, + 'Rule': function(node, obj) { + var config = {symbolizers: []}; + this.readChildNodes(node, config); + var rule = new ol.style.Rule(config); + obj.rules.push(rule); + }, + 'ElseFilter': function(node, rule) { + rule.elseFilter = true; + }, + 'MinScaleDenominator': function(node, rule) { + rule.minResolution = this.getResolutionFromScaleDenominator_( + parseFloat(this.getChildValue(node))); + }, + 'MaxScaleDenominator': function(node, rule) { + rule.maxResolution = this.getResolutionFromScaleDenominator_( + parseFloat(this.getChildValue(node))); + }, + 'TextSymbolizer': function(node, rule) { + var config = {}; + this.readChildNodes(node, config); + config.color = goog.isDef(config.fill) ? config.fill.fillColor : + ol.parser.ogc.SLD_v1.defaults_.fontColor; + delete config.fill; + config.zIndex = this.featureTypeCounter; + rule.symbolizers.push( + new ol.style.Text(/** @type {ol.style.TextOptions} */(config)) + ); + }, + 'Label': function(node, symbolizer) { + var ogcreaders = this.readers[this.filter_.defaultNamespaceURI]; + var value = ogcreaders._expression.call(this, node); + if (value) { + symbolizer.text = value; } + }, + 'Font': function(node, symbolizer) { + this.readChildNodes(node, symbolizer); + }, + 'Halo': function(node, symbolizer) { + var obj = {}; + this.readChildNodes(node, obj); + symbolizer.stroke = new ol.style.Stroke({ + color: goog.isDef(obj.fill.fillColor) ? obj.fill.fillColor : + ol.parser.ogc.SLD_v1.defaults_.haloColor, + width: goog.isDef(obj.haloRadius) ? obj.haloRadius * 2 : + ol.parser.ogc.SLD_v1.defaults_.haloRadius, + opacity: goog.isDef(obj.fill.fillOpacity) ? obj.fill.fillOpacity : + ol.parser.ogc.SLD_v1.defaults_.haloOpacity + }); + }, + 'Radius': function(node, symbolizer) { + var ogcreaders = this.readers[this.filter_.defaultNamespaceURI]; + var radius = ogcreaders._expression.call(this, node); + goog.asserts.assertInstanceof(radius, ol.expr.Literal, + 'radius expected to be an ol.expr.Literal'); + if (goog.isDef(radius)) { + symbolizer.haloRadius = radius.getValue(); + } + }, + 'LineSymbolizer': function(node, rule) { + var config = {}; + this.readChildNodes(node, config); + config.zIndex = this.featureTypeCounter; + rule.symbolizers.push( + new ol.style.Stroke(config) + ); + }, + 'PolygonSymbolizer': function(node, rule) { + var config = {}; + this.readChildNodes(node, config); + config.zIndex = this.featureTypeCounter; + if (goog.isDef(config.fill)) { + var fill = { + color: config.fill.fillColor.getValue(), + opacity: goog.isDef(config.fill.fillOpacity) ? + config.fill.fillOpacity : + ol.parser.ogc.SLD_v1.defaults_.fillOpacity + }; + rule.symbolizers.push( + new ol.style.Fill(fill) + ); + delete config.fill; + } + if (goog.isDef(config.stroke)) { + var stroke = { + color: config.stroke.strokeColor.getValue(), + opacity: goog.isDef(config.stroke.strokeOpacity) ? + config.stroke.strokeOpacity : + ol.parser.ogc.SLD_v1.defaults_.strokeOpacity, + width: goog.isDef(config.stroke.strokeWidth) ? + config.stroke.strokeWidth : + ol.parser.ogc.SLD_v1.defaults_.strokeWidth + }; + rule.symbolizers.push( + new ol.style.Stroke(stroke) + ); + delete config.stroke; + } + + }, + 'PointSymbolizer': function(node, rule) { + var config = {}; + this.readChildNodes(node, config); + config.zIndex = this.featureTypeCounter; + if (config.fill) { + var fillConfig = { + color: goog.isDef(config.fill.fillColor) ? + config.fill.fillColor : + ol.parser.ogc.SLD_v1.defaults_.fillColor, + opacity: goog.isDef(config.fill.fillOpacity) ? + config.fill.fillOpacity : + ol.parser.ogc.SLD_v1.defaults_.fillOpacity + }; + config.fill = new ol.style.Fill(fillConfig); + } + if (config.stroke) { + var strokeConfig = { + color: goog.isDef(config.stroke.strokeColor) ? + config.stroke.strokeColor : + ol.parser.ogc.SLD_v1.defaults_.strokeColor, + width: goog.isDef(config.stroke.strokeWidth) ? + config.stroke.strokeWidth : + ol.parser.ogc.SLD_v1.defaults_.strokeWidth, + opacity: goog.isDef(config.stroke.strokeOpacity) ? + config.stroke.strokeOpacity : + ol.parser.ogc.SLD_v1.defaults_.strokeOpacity + }; + config.stroke = new ol.style.Stroke(strokeConfig); + } + var symbolizer; + if (goog.isDef(config.externalGraphic)) { + config.width = config.height = config.size; + symbolizer = new ol.style.Icon( + /** @type {ol.style.IconOptions} */(config)); + } else { + symbolizer = new ol.style.Shape(config); + } + rule.symbolizers.push(symbolizer); + }, + 'Stroke': function(node, symbolizer) { + var stroke = {}; + this.readChildNodes(node, stroke); + symbolizer.stroke = stroke; + }, + 'Fill': function(node, symbolizer) { + var fill = {}; + this.readChildNodes(node, fill); + symbolizer.fill = fill; + }, + 'CssParameter': function(node, symbolizer) { + var cssProperty = node.getAttribute('name'); + var symProperty = ol.parser.ogc.SLD_v1.cssMap_[cssProperty]; + if (symProperty) { + var ogcreaders = this.readers[this.filter_.defaultNamespaceURI]; + symbolizer[symProperty] = ogcreaders._expression.call(this, node); + } + }, + 'Graphic': function(node, symbolizer) { + var graphic = {}; + // painter's order not respected here, clobber previous with next + this.readChildNodes(node, graphic); + // directly properties with names that match symbolizer properties + var properties = [ + 'stroke', 'fill', 'rotation', 'opacity' + ]; + var prop, value; + for (var i = 0, ii = properties.length; i < ii; ++i) { + prop = properties[i]; + value = graphic[prop]; + if (goog.isDef(value)) { + symbolizer[prop] = value; + } + } + // set other generic properties with specific graphic property names + if (goog.isDef(graphic.graphicName)) { + symbolizer.type = graphic.graphicName; + } + if (goog.isDef(graphic.size)) { + var pointRadius = graphic.size / 2; + if (isNaN(pointRadius)) { + // likely a property name + symbolizer.size = graphic.size; + } else { + symbolizer.size = graphic.size / 2; + } + } + if (goog.isDef(graphic.href)) { + symbolizer.url = graphic.href; + } + }, + 'ExternalGraphic': function(node, graphic) { + this.readChildNodes(node, graphic); + }, + 'Mark': function(node, graphic) { + this.readChildNodes(node, graphic); + }, + 'WellKnownName': function(node, graphic) { + graphic.graphicName = this.getChildValue(node); + }, + 'Opacity': function(node, obj) { + var ogcreaders = this.readers[this.filter_.defaultNamespaceURI]; + obj.opacity = ogcreaders._expression.call(this, node); + }, + 'Size': function(node, obj) { + var ogcreaders = this.readers[this.filter_.defaultNamespaceURI]; + obj.size = ogcreaders._expression.call(this, node); + }, + 'Rotation': function(node, obj) { + var ogcreaders = this.readers[this.filter_.defaultNamespaceURI]; + obj.rotation = ogcreaders._expression.call(this, node); + }, + 'OnlineResource': function(node, obj) { + obj.href = this.getAttributeNS( + node, 'http://www.w3.org/1999/xlink', 'href' + ); + }, + 'Format': function(node, graphic) { + graphic.graphicFormat = this.getChildValue(node); } }; - this.writers = { - 'http://www.opengis.net/sld': { - 'StyledLayerDescriptor': function(sld) { - var node = this.createElementNS('sld:StyledLayerDescriptor'); - node.setAttribute('version', this.version); - if (goog.isDef(sld.name)) { - this.writeNode('Name', sld.name, null, node); + this.writers = {}; + this.writers[this.defaultNamespaceURI] = { + 'StyledLayerDescriptor': function(sld) { + var node = this.createElementNS('sld:StyledLayerDescriptor'); + node.setAttribute('version', this.version); + if (goog.isDef(sld.name)) { + this.writeNode('Name', sld.name, null, node); + } + if (goog.isDef(sld.title)) { + this.writeNode('Title', sld.title, null, node); + } + if (goog.isDef(sld.description)) { + this.writeNode('Abstract', sld.description, null, node); + } + goog.object.forEach(sld.namedLayers, function(layer) { + this.writeNode('NamedLayer', layer, null, node); + }, this); + return node; + }, + 'Name': function(name) { + var node = this.createElementNS('sld:Name'); + node.appendChild(this.createTextNode(name)); + return node; + }, + 'Title': function(title) { + var node = this.createElementNS('sld:Title'); + node.appendChild(this.createTextNode(title)); + return node; + }, + 'Abstract': function(description) { + var node = this.createElementNS('sld:Abstract'); + node.appendChild(this.createTextNode(description)); + return node; + }, + 'NamedLayer': function(layer) { + var node = this.createElementNS('sld:NamedLayer'); + this.writeNode('Name', layer.name, null, node); + var i, ii; + if (layer.namedStyles) { + for (i = 0, ii = layer.namedStyles.length; i < ii; ++i) { + this.writeNode('NamedStyle', layer.namedStyles[i], null, node); } - if (goog.isDef(sld.title)) { - this.writeNode('Title', sld.title, null, node); + } + if (layer.userStyles) { + for (i = 0, ii = layer.userStyles.length; i < ii; ++i) { + this.writeNode('UserStyle', layer.userStyles[i], null, node); } - if (goog.isDef(sld.description)) { - this.writeNode('Abstract', sld.description, null, node); - } - goog.object.forEach(sld.namedLayers, function(layer) { - this.writeNode('NamedLayer', layer, null, node); - }, this); - return node; - }, - 'Name': function(name) { - var node = this.createElementNS('sld:Name'); - node.appendChild(this.createTextNode(name)); - return node; - }, - 'Title': function(title) { - var node = this.createElementNS('sld:Title'); - node.appendChild(this.createTextNode(title)); - return node; - }, - 'Abstract': function(description) { - var node = this.createElementNS('sld:Abstract'); - node.appendChild(this.createTextNode(description)); - return node; - }, - 'NamedLayer': function(layer) { - var node = this.createElementNS('sld:NamedLayer'); - this.writeNode('Name', layer.name, null, node); - var i, ii; - if (layer.namedStyles) { - for (i = 0, ii = layer.namedStyles.length; i < ii; ++i) { - this.writeNode('NamedStyle', layer.namedStyles[i], null, node); - } - } - if (layer.userStyles) { - for (i = 0, ii = layer.userStyles.length; i < ii; ++i) { - this.writeNode('UserStyle', layer.userStyles[i], null, node); - } - } - return node; - }, - 'NamedStyle': function(name) { - var node = this.createElementNS('sld:NamedStyle'); + } + return node; + }, + 'NamedStyle': function(name) { + var node = this.createElementNS('sld:NamedStyle'); + this.writeNode('Name', name, null, node); + return node; + }, + 'UserStyle': function(style) { + var node = this.createElementNS('sld:UserStyle'); + var name = style.getName(), title = style.getTitle(); + if (!goog.isNull(name)) { this.writeNode('Name', name, null, node); - return node; - }, - 'UserStyle': function(style) { - var node = this.createElementNS('sld:UserStyle'); - var name = style.getName(), title = style.getTitle(); - if (!goog.isNull(name)) { - this.writeNode('Name', name, null, node); - } - if (!goog.isNull(title)) { - this.writeNode('Title', title, null, node); - } - // TODO sorting by zIndex - this.writeNode('FeatureTypeStyle', style, null, node); - return node; - }, - 'FeatureTypeStyle': function(style) { - var node = this.createElementNS('sld:FeatureTypeStyle'); - var rules = style.getRules(); - for (var i = 0, ii = rules.length; i < ii; ++i) { - this.writeNode('Rule', rules[i], null, node); - } - var symbolizers = style.getSymbolizers(); - if (symbolizers.length > 0) { - // wrap this in a Rule with an ElseFilter - var rule = new ol.style.Rule({symbolizers: symbolizers}); - rule.elseFilter = true; - this.writeNode('Rule', rule, null, node); - } - return node; - }, - 'Rule': function(rule) { - var node = this.createElementNS('sld:Rule'); - var filter = rule.getFilter(); - if (!goog.isNull(rule.getName())) { - this.writeNode('Name', rule.getName(), null, node); - } - if (!goog.isNull(rule.getTitle())) { - this.writeNode('Title', rule.title, null, node); - } - if (rule.elseFilter === true) { - this.writeNode('ElseFilter', null, null, node); - } else if (filter) { - this.writeNode('Filter', filter, 'http://www.opengis.net/ogc', node); - } - var minResolution = rule.getMinResolution(); - if (minResolution > 0) { - this.writeNode('MinScaleDenominator', - this.getScaleDenominatorFromResolution_(minResolution), - null, node); - } - var maxResolution = rule.getMaxResolution(); - if (maxResolution < Infinity) { - this.writeNode('MaxScaleDenominator', - this.getScaleDenominatorFromResolution_(maxResolution), - null, node); - } - var type, symbolizer, symbolizers = rule.getSymbolizers(); - if (symbolizers) { - for (var i = 0, ii = symbolizers.length; i < ii; ++i) { - symbolizer = symbolizers[i]; - if (symbolizer instanceof ol.style.Text) { - type = 'Text'; - } else if (symbolizer instanceof ol.style.Stroke) { - type = 'Line'; - } else if (symbolizer instanceof ol.style.Fill) { - type = 'Polygon'; - } else if (symbolizer instanceof ol.style.Shape || - symbolizer instanceof ol.style.Icon) { - type = 'Point'; - } - if (goog.isDef(type)) { - this.writeNode(type + 'Symbolizer', symbolizer, null, node); - } + } + if (!goog.isNull(title)) { + this.writeNode('Title', title, null, node); + } + // TODO sorting by zIndex + this.writeNode('FeatureTypeStyle', style, null, node); + return node; + }, + 'FeatureTypeStyle': function(style) { + var node = this.createElementNS('sld:FeatureTypeStyle'); + var rules = style.getRules(); + for (var i = 0, ii = rules.length; i < ii; ++i) { + this.writeNode('Rule', rules[i], null, node); + } + var symbolizers = style.getSymbolizers(); + if (symbolizers.length > 0) { + // wrap this in a Rule with an ElseFilter + var rule = new ol.style.Rule({symbolizers: symbolizers}); + rule.elseFilter = true; + this.writeNode('Rule', rule, null, node); + } + return node; + }, + 'Rule': function(rule) { + var node = this.createElementNS('sld:Rule'); + var filter = rule.getFilter(); + if (!goog.isNull(rule.getName())) { + this.writeNode('Name', rule.getName(), null, node); + } + if (!goog.isNull(rule.getTitle())) { + this.writeNode('Title', rule.title, null, node); + } + if (rule.elseFilter === true) { + this.writeNode('ElseFilter', null, null, node); + } else if (filter) { + this.writeNode('Filter', filter, this.filter_.defaultNamespaceURI, + node); + } + var minResolution = rule.getMinResolution(); + if (minResolution > 0) { + this.writeNode('MinScaleDenominator', + this.getScaleDenominatorFromResolution_(minResolution), + null, node); + } + var maxResolution = rule.getMaxResolution(); + if (maxResolution < Infinity) { + this.writeNode('MaxScaleDenominator', + this.getScaleDenominatorFromResolution_(maxResolution), + null, node); + } + var type, symbolizer, symbolizers = rule.getSymbolizers(); + if (symbolizers) { + for (var i = 0, ii = symbolizers.length; i < ii; ++i) { + symbolizer = symbolizers[i]; + if (symbolizer instanceof ol.style.Text) { + type = 'Text'; + } else if (symbolizer instanceof ol.style.Stroke) { + type = 'Line'; + } else if (symbolizer instanceof ol.style.Fill) { + type = 'Polygon'; + } else if (symbolizer instanceof ol.style.Shape || + symbolizer instanceof ol.style.Icon) { + type = 'Point'; + } + if (goog.isDef(type)) { + this.writeNode(type + 'Symbolizer', symbolizer, null, node); } } - return node; - }, - 'PointSymbolizer': function(symbolizer) { - var node = this.createElementNS('sld:PointSymbolizer'); - this.writeNode('Graphic', symbolizer, null, node); - return node; - }, - 'Mark': function(symbolizer) { - var node = this.createElementNS('sld:Mark'); - this.writeNode('WellKnownName', symbolizer.getType(), null, node); - var fill = symbolizer.getFill(); - if (!goog.isNull(fill)) { - this.writeNode('Fill', fill, null, node); - } - var stroke = symbolizer.getStroke(); - if (!goog.isNull(stroke)) { - this.writeNode('Stroke', stroke, null, node); - } - return node; - }, - 'WellKnownName': function(name) { - var node = this.createElementNS('sld:WellKnownName'); - node.appendChild(this.createTextNode(name)); - return node; - }, - 'Graphic': function(symbolizer) { - var node = this.createElementNS('sld:Graphic'); - var size; - if (symbolizer instanceof ol.style.Icon) { - this.writeNode('ExternalGraphic', symbolizer, null, node); - var opacity = symbolizer.getOpacity(); - goog.asserts.assertInstanceof(opacity, ol.expr.Literal, - 'Only ol.expr.Literal supported for graphicOpacity'); - this.writeNode('Opacity', opacity.getValue(), null, node); - size = symbolizer.getWidth(); - } else if (symbolizer instanceof ol.style.Shape) { - this.writeNode('Mark', symbolizer, null, node); - size = symbolizer.getSize(); - } - this.writeNode('Size', size, null, node); - if (symbolizer instanceof ol.style.Icon) { - var rotation = symbolizer.getRotation(); - goog.asserts.assertInstanceof(rotation, ol.expr.Literal, - 'Only ol.expr.Literal supported for rotation'); - this.writeNode('Rotation', rotation.getValue(), null, node); - } - return node; - }, - 'PolygonSymbolizer': function(symbolizer) { - var node = this.createElementNS('sld:PolygonSymbolizer'); - this.writeNode('Fill', symbolizer, null, node); - return node; - }, - 'Fill': function(symbolizer) { - var node = this.createElementNS('sld:Fill'); - var fillColor = symbolizer.getColor(); - var msg = 'Only ol.expr.Literal supported for Fill properties'; - goog.asserts.assertInstanceof(fillColor, ol.expr.Literal, msg); - this.writeNode('CssParameter', { - value: fillColor.getValue(), - key: 'fillColor' - }, null, node); - var fillOpacity = symbolizer.getOpacity(); - goog.asserts.assertInstanceof(fillOpacity, ol.expr.Literal, msg); - this.writeNode('CssParameter', { - value: fillOpacity.getValue(), - key: 'fillOpacity' - }, null, node); - return node; - }, - 'TextSymbolizer': function(symbolizer) { - var node = this.createElementNS('sld:TextSymbolizer'); - var text = symbolizer.getText(); - this.writeNode('Label', text, null, node); - this.writeNode('Font', symbolizer, null, node); - var stroke = symbolizer.getStroke(); - if (!goog.isNull(stroke)) { - this.writeNode('Halo', stroke, null, node); - } - var color = symbolizer.getColor(); - goog.asserts.assertInstanceof(color, ol.expr.Literal, - 'font color should be ol.expr.Literal'); - this.writeNode('Fill', symbolizer, null, node); - return node; - }, - 'Halo': function(symbolizer) { - var node = this.createElementNS('sld:Halo'); - goog.asserts.assertInstanceof(symbolizer.getWidth(), ol.expr.Literal, - 'Only ol.expr.Literal supported for haloRadius'); - this.writeNode('Radius', symbolizer.getWidth().getValue() / 2, null, - node); - this.writeNode('Fill', symbolizer, null, node); - return node; - }, - 'Radius': function(value) { - var node = this.createElementNS('sld:Radius'); - node.appendChild(this.createTextNode(value)); - return node; - }, - 'LineSymbolizer': function(symbolizer) { - var node = this.createElementNS('sld:LineSymbolizer'); - this.writeNode('Stroke', symbolizer, null, node); - return node; - }, - 'Stroke': function(symbolizer) { - var node = this.createElementNS('sld:Stroke'); - var strokeColor = symbolizer.getColor(); - var msg = 'SLD writing of stroke properties only supported ' + - 'for ol.expr.Literal'; - goog.asserts.assertInstanceof(strokeColor, ol.expr.Literal, msg); - this.writeNode('CssParameter', { - value: strokeColor.getValue(), - key: 'strokeColor' - }, null, node); - var strokeOpacity = symbolizer.getOpacity(); - goog.asserts.assertInstanceof(strokeOpacity, ol.expr.Literal, msg); - this.writeNode('CssParameter', { - value: strokeOpacity.getValue(), - key: 'strokeOpacity' - }, null, node); - var strokeWidth = symbolizer.getWidth(); - goog.asserts.assertInstanceof(strokeWidth, ol.expr.Literal, msg); - this.writeNode('CssParameter', { - value: strokeWidth.getValue(), - key: 'strokeWidth' - }, null, node); - // TODO strokeDashstyle and strokeLinecap - return node; - }, - 'CssParameter': function(obj) { - // not handling ogc:expressions for now - var name = ol.parser.ogc.SLD_v1.getCssProperty_(obj.key); - if (goog.isDef(name)) { - var node = this.createElementNS('sld:CssParameter'); - node.setAttribute('name', name); - node.appendChild(this.createTextNode(obj.value)); - return node; - } - }, - 'Label': function(label) { - var node = this.createElementNS('sld:Label'); - this.filter_.writeOgcExpression(label, node); - return node; - }, - 'Font': function(symbolizer) { - var node = this.createElementNS('sld:Font'); - this.writeNode('CssParameter', { - key: 'fontFamily', - value: symbolizer.getFontFamily().getValue() - }, null, node); - this.writeNode('CssParameter', { - key: 'fontSize', - value: symbolizer.getFontSize().getValue() - }, null, node); - // TODO fontWeight and fontStyle - return node; - }, - 'MinScaleDenominator': function(scale) { - var node = this.createElementNS('sld:MinScaleDenominator'); - node.appendChild(this.createTextNode(scale)); - return node; - }, - 'MaxScaleDenominator': function(scale) { - var node = this.createElementNS('sld:MaxScaleDenominator'); - node.appendChild(this.createTextNode(scale)); - return node; - }, - 'Size': function(value) { - var node = this.createElementNS('sld:Size'); - this.filter_.writeOgcExpression(value, node); + } + return node; + }, + 'PointSymbolizer': function(symbolizer) { + var node = this.createElementNS('sld:PointSymbolizer'); + this.writeNode('Graphic', symbolizer, null, node); + return node; + }, + 'Mark': function(symbolizer) { + var node = this.createElementNS('sld:Mark'); + this.writeNode('WellKnownName', symbolizer.getType(), null, node); + var fill = symbolizer.getFill(); + if (!goog.isNull(fill)) { + this.writeNode('Fill', fill, null, node); + } + var stroke = symbolizer.getStroke(); + if (!goog.isNull(stroke)) { + this.writeNode('Stroke', stroke, null, node); + } + return node; + }, + 'WellKnownName': function(name) { + var node = this.createElementNS('sld:WellKnownName'); + node.appendChild(this.createTextNode(name)); + return node; + }, + 'Graphic': function(symbolizer) { + var node = this.createElementNS('sld:Graphic'); + var size; + if (symbolizer instanceof ol.style.Icon) { + this.writeNode('ExternalGraphic', symbolizer, null, node); + var opacity = symbolizer.getOpacity(); + goog.asserts.assertInstanceof(opacity, ol.expr.Literal, + 'Only ol.expr.Literal supported for graphicOpacity'); + this.writeNode('Opacity', opacity.getValue(), null, node); + size = symbolizer.getWidth(); + } else if (symbolizer instanceof ol.style.Shape) { + this.writeNode('Mark', symbolizer, null, node); + size = symbolizer.getSize(); + } + this.writeNode('Size', size, null, node); + if (symbolizer instanceof ol.style.Icon) { + var rotation = symbolizer.getRotation(); + goog.asserts.assertInstanceof(rotation, ol.expr.Literal, + 'Only ol.expr.Literal supported for rotation'); + this.writeNode('Rotation', rotation.getValue(), null, node); + } + return node; + }, + 'PolygonSymbolizer': function(symbolizer) { + var node = this.createElementNS('sld:PolygonSymbolizer'); + this.writeNode('Fill', symbolizer, null, node); + return node; + }, + 'Fill': function(symbolizer) { + var node = this.createElementNS('sld:Fill'); + var fillColor = symbolizer.getColor(); + var msg = 'Only ol.expr.Literal supported for Fill properties'; + goog.asserts.assertInstanceof(fillColor, ol.expr.Literal, msg); + this.writeNode('CssParameter', { + value: fillColor.getValue(), + key: 'fillColor' + }, null, node); + var fillOpacity = symbolizer.getOpacity(); + goog.asserts.assertInstanceof(fillOpacity, ol.expr.Literal, msg); + this.writeNode('CssParameter', { + value: fillOpacity.getValue(), + key: 'fillOpacity' + }, null, node); + return node; + }, + 'TextSymbolizer': function(symbolizer) { + var node = this.createElementNS('sld:TextSymbolizer'); + var text = symbolizer.getText(); + this.writeNode('Label', text, null, node); + this.writeNode('Font', symbolizer, null, node); + var stroke = symbolizer.getStroke(); + if (!goog.isNull(stroke)) { + this.writeNode('Halo', stroke, null, node); + } + var color = symbolizer.getColor(); + goog.asserts.assertInstanceof(color, ol.expr.Literal, + 'font color should be ol.expr.Literal'); + this.writeNode('Fill', symbolizer, null, node); + return node; + }, + 'Halo': function(symbolizer) { + var node = this.createElementNS('sld:Halo'); + goog.asserts.assertInstanceof(symbolizer.getWidth(), ol.expr.Literal, + 'Only ol.expr.Literal supported for haloRadius'); + this.writeNode('Radius', symbolizer.getWidth().getValue() / 2, null, + node); + this.writeNode('Fill', symbolizer, null, node); + return node; + }, + 'Radius': function(value) { + var node = this.createElementNS('sld:Radius'); + node.appendChild(this.createTextNode(value)); + return node; + }, + 'LineSymbolizer': function(symbolizer) { + var node = this.createElementNS('sld:LineSymbolizer'); + this.writeNode('Stroke', symbolizer, null, node); + return node; + }, + 'Stroke': function(symbolizer) { + var node = this.createElementNS('sld:Stroke'); + var strokeColor = symbolizer.getColor(); + var msg = 'SLD writing of stroke properties only supported ' + + 'for ol.expr.Literal'; + goog.asserts.assertInstanceof(strokeColor, ol.expr.Literal, msg); + this.writeNode('CssParameter', { + value: strokeColor.getValue(), + key: 'strokeColor' + }, null, node); + var strokeOpacity = symbolizer.getOpacity(); + goog.asserts.assertInstanceof(strokeOpacity, ol.expr.Literal, msg); + this.writeNode('CssParameter', { + value: strokeOpacity.getValue(), + key: 'strokeOpacity' + }, null, node); + var strokeWidth = symbolizer.getWidth(); + goog.asserts.assertInstanceof(strokeWidth, ol.expr.Literal, msg); + this.writeNode('CssParameter', { + value: strokeWidth.getValue(), + key: 'strokeWidth' + }, null, node); + // TODO strokeDashstyle and strokeLinecap + return node; + }, + 'CssParameter': function(obj) { + // not handling ogc:expressions for now + var name = ol.parser.ogc.SLD_v1.getCssProperty_(obj.key); + if (goog.isDef(name)) { + var node = this.createElementNS('sld:CssParameter'); + node.setAttribute('name', name); + node.appendChild(this.createTextNode(obj.value)); return node; } + }, + 'Label': function(label) { + var node = this.createElementNS('sld:Label'); + this.filter_.writeOgcExpression(label, node); + return node; + }, + 'Font': function(symbolizer) { + var node = this.createElementNS('sld:Font'); + this.writeNode('CssParameter', { + key: 'fontFamily', + value: symbolizer.getFontFamily().getValue() + }, null, node); + this.writeNode('CssParameter', { + key: 'fontSize', + value: symbolizer.getFontSize().getValue() + }, null, node); + // TODO fontWeight and fontStyle + return node; + }, + 'MinScaleDenominator': function(scale) { + var node = this.createElementNS('sld:MinScaleDenominator'); + node.appendChild(this.createTextNode(scale)); + return node; + }, + 'MaxScaleDenominator': function(scale) { + var node = this.createElementNS('sld:MaxScaleDenominator'); + node.appendChild(this.createTextNode(scale)); + return node; + }, + 'Size': function(value) { + var node = this.createElementNS('sld:Size'); + this.filter_.writeOgcExpression(value, node); + return node; } }; this.filter_ = new ol.parser.ogc.Filter_v1_0_0(); From 16b0e736667b954fd0f74eda92a62f7b59c6ef9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Sat, 16 Nov 2013 23:22:41 +0100 Subject: [PATCH 12/66] Better use of good.dom.createDom in zoom slider --- src/ol/control/zoomslidercontrol.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ol/control/zoomslidercontrol.js b/src/ol/control/zoomslidercontrol.js index 2d7bbbad80..2523812039 100644 --- a/src/ol/control/zoomslidercontrol.js +++ b/src/ol/control/zoomslidercontrol.js @@ -76,21 +76,21 @@ ol.control.ZoomSlider = function(opt_options) { var className = goog.isDef(options.className) ? options.className : 'ol-zoomslider'; - var sliderCssCls = className + ' ' + ol.css.CLASS_UNSELECTABLE; - var thumbCssCls = className + '-thumb' + ' ' + ol.css.CLASS_UNSELECTABLE; - var element = goog.dom.createDom(goog.dom.TagName.DIV, sliderCssCls, - goog.dom.createDom(goog.dom.TagName.DIV, thumbCssCls)); + var thumbElement = goog.dom.createDom(goog.dom.TagName.DIV, + [className + '-thumb', ol.css.CLASS_UNSELECTABLE]); + var sliderElement = goog.dom.createDom(goog.dom.TagName.DIV, + [className, ol.css.CLASS_UNSELECTABLE], thumbElement); - this.dragger_ = this.createDraggable_(element); + this.dragger_ = this.createDraggable_(sliderElement); // FIXME currently only a do nothing function is bound. - goog.events.listen(element, [ + goog.events.listen(sliderElement, [ goog.events.EventType.TOUCHEND, goog.events.EventType.CLICK ], this.handleContainerClick_, false, this); goog.base(this, { - element: element + element: sliderElement }); }; goog.inherits(ol.control.ZoomSlider, ol.control.Control); From 559cad4cf355164b19dc2c59a6e7837521f38b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Sat, 16 Nov 2013 23:29:22 +0100 Subject: [PATCH 13/66] Simplify zoom slider code --- src/ol/control/zoomslidercontrol.js | 42 ++++++++--------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/src/ol/control/zoomslidercontrol.js b/src/ol/control/zoomslidercontrol.js index 2523812039..c55cd51cfb 100644 --- a/src/ol/control/zoomslidercontrol.js +++ b/src/ol/control/zoomslidercontrol.js @@ -68,12 +68,6 @@ ol.control.ZoomSlider = function(opt_options) { */ this.sliderInitialized_ = false; - /** - * @private - * @type {Array.} - */ - this.draggerListenerKeys_ = null; - var className = goog.isDef(options.className) ? options.className : 'ol-zoomslider'; var thumbElement = goog.dom.createDom(goog.dom.TagName.DIV, @@ -81,7 +75,17 @@ ol.control.ZoomSlider = function(opt_options) { var sliderElement = goog.dom.createDom(goog.dom.TagName.DIV, [className, ol.css.CLASS_UNSELECTABLE], thumbElement); - this.dragger_ = this.createDraggable_(sliderElement); + /** + * @type {goog.fx.Dragger} + * @private + */ + this.dragger_ = new goog.fx.Dragger(thumbElement); + this.registerDisposable(this.dragger_); + + goog.events.listen(this.dragger_, [ + goog.fx.Dragger.EventType.DRAG, + goog.fx.Dragger.EventType.END + ], this.handleSliderChange_, undefined, this); // FIXME currently only a do nothing function is bound. goog.events.listen(sliderElement, [ @@ -285,27 +289,3 @@ ol.control.ZoomSlider.prototype.handleSliderChange_ = function(e) { view.setResolution(resolution); } }; - - -/** - * Actually enable draggable behaviour for the thumb of the zoomslider and bind - * relvant event listeners. - * - * @param {Element} elem The element for the slider. - * @return {goog.fx.Dragger} The actual goog.fx.Dragger instance. - * @private - */ -ol.control.ZoomSlider.prototype.createDraggable_ = function(elem) { - if (!goog.isNull(this.draggerListenerKeys_)) { - goog.array.forEach(this.draggerListenerKeys_, goog.events.unlistenByKey); - this.draggerListenerKeys_ = null; - } - var dragger = new goog.fx.Dragger(elem.childNodes[0]); - this.draggerListenerKeys_ = [ - goog.events.listen(dragger, [ - goog.fx.Dragger.EventType.DRAG, - goog.fx.Dragger.EventType.END - ], this.handleSliderChange_, undefined, this) - ]; - return dragger; -}; From 2a20693ffb4344c830513fd2db4d57ccab05f48e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Sat, 16 Nov 2013 23:41:48 +0100 Subject: [PATCH 14/66] Fix issue where map is stuck in drag mode If the control container stops "up" events the map browser event handler won't see these events and will keep triggering "drag" events. --- src/ol/map.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ol/map.js b/src/ol/map.js index 6dabae87e5..c6d2f2fcf8 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -245,11 +245,8 @@ ol.Map = function(options) { goog.events.EventType.CLICK, goog.events.EventType.DBLCLICK, goog.events.EventType.MOUSEDOWN, - goog.events.EventType.MOUSEUP, goog.events.EventType.TOUCHSTART, - goog.events.EventType.TOUCHEND, - goog.events.EventType.MSPOINTERDOWN, - goog.events.EventType.MSPOINTERUP + goog.events.EventType.MSPOINTERDOWN ], goog.events.Event.stopPropagation); goog.dom.appendChild(this.viewport_, this.overlayContainerStopEvent_); From 14f5e5aedc80516b7e10daa36845f89f110d502c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Sat, 16 Nov 2013 23:47:19 +0100 Subject: [PATCH 15/66] Do not pretend the zoom slider supports touch --- src/ol/control/zoomslidercontrol.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ol/control/zoomslidercontrol.js b/src/ol/control/zoomslidercontrol.js index c55cd51cfb..27516911e7 100644 --- a/src/ol/control/zoomslidercontrol.js +++ b/src/ol/control/zoomslidercontrol.js @@ -88,10 +88,8 @@ ol.control.ZoomSlider = function(opt_options) { ], this.handleSliderChange_, undefined, this); // FIXME currently only a do nothing function is bound. - goog.events.listen(sliderElement, [ - goog.events.EventType.TOUCHEND, - goog.events.EventType.CLICK - ], this.handleContainerClick_, false, this); + goog.events.listen(sliderElement, goog.events.EventType.CLICK, + this.handleContainerClick_, false, this); goog.base(this, { element: sliderElement From 9ff7470f657089f9536ea6aaa97d5e3e4c924da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Sat, 16 Nov 2013 23:51:53 +0100 Subject: [PATCH 16/66] Change ol.control.ZoomSlider#amountDragged_ signature --- src/ol/control/zoomslidercontrol.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ol/control/zoomslidercontrol.js b/src/ol/control/zoomslidercontrol.js index 27516911e7..f1bee5d6ce 100644 --- a/src/ol/control/zoomslidercontrol.js +++ b/src/ol/control/zoomslidercontrol.js @@ -209,17 +209,18 @@ ol.control.ZoomSlider.prototype.positionThumbForResolution_ = function(res) { * Calculates the amount the thumb has been dragged to allow for calculation * of the corresponding resolution. * - * @param {goog.fx.DragDropEvent} e The dragdropevent. + * @param {number} x Pixel position relative to the left of the slider. + * @param {number} y Pixel position relative to the top of the slider. * @return {number} The amount the thumb has been dragged. * @private */ -ol.control.ZoomSlider.prototype.amountDragged_ = function(e) { +ol.control.ZoomSlider.prototype.amountDragged_ = function(x, y) { var draggerLimits = this.dragger_.limits, amount = 0; if (this.direction_ === ol.control.ZoomSlider.direction.HORIZONTAL) { - amount = (e.left - draggerLimits.left) / draggerLimits.width; + amount = (x - draggerLimits.left) / draggerLimits.width; } else { - amount = (e.top - draggerLimits.top) / draggerLimits.height; + amount = (y - draggerLimits.top) / draggerLimits.height; } return amount; }; @@ -270,7 +271,7 @@ ol.control.ZoomSlider.prototype.handleSliderChange_ = function(e) { var view = map.getView().getView2D(); var resolution; if (e.type === goog.fx.Dragger.EventType.DRAG) { - var amountDragged = this.amountDragged_(e); + var amountDragged = this.amountDragged_(e.left, e.top); resolution = this.resolutionForAmount_(amountDragged); if (resolution !== this.currentResolution_) { this.currentResolution_ = resolution; From a898b9588e087ff79f0daaabdc31a10ac416ef96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Sat, 16 Nov 2013 23:56:15 +0100 Subject: [PATCH 17/66] Handle click on zoom slider --- src/ol/control/zoomslidercontrol.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/ol/control/zoomslidercontrol.js b/src/ol/control/zoomslidercontrol.js index f1bee5d6ce..75cf39800e 100644 --- a/src/ol/control/zoomslidercontrol.js +++ b/src/ol/control/zoomslidercontrol.js @@ -1,6 +1,5 @@ // FIXME works for View2D only // FIXME should possibly show tooltip when dragging? -// FIXME should possibly be adjustable by clicking on container goog.provide('ol.control.ZoomSlider'); @@ -87,7 +86,6 @@ ol.control.ZoomSlider = function(opt_options) { goog.fx.Dragger.EventType.END ], this.handleSliderChange_, undefined, this); - // FIXME currently only a do nothing function is bound. goog.events.listen(sliderElement, goog.events.EventType.CLICK, this.handleContainerClick_, false, this); @@ -180,7 +178,20 @@ ol.control.ZoomSlider.prototype.handleMapPostrender = function(mapEvent) { * @private */ ol.control.ZoomSlider.prototype.handleContainerClick_ = function(browserEvent) { - // TODO implement proper resolution calculation according to browserEvent + var map = this.getMap(); + var view = map.getView().getView2D(); + var resolution; + var amountDragged = this.amountDragged_(browserEvent.offsetX, + browserEvent.offsetY); + resolution = this.resolutionForAmount_(amountDragged); + goog.asserts.assert(goog.isDef(resolution)); + map.beforeRender(ol.animation.zoom({ + resolution: resolution, + duration: ol.control.ZOOMSLIDER_ANIMATION_DURATION, + easing: ol.easing.easeOut + })); + resolution = view.constrainResolution(resolution); + view.setResolution(resolution); }; From 8d57f0c78b8b26d34fe99afc5b44684b3958378e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Sun, 17 Nov 2013 00:05:02 +0100 Subject: [PATCH 18/66] Stop clicks on zoom slider thumb --- src/ol/control/zoomslidercontrol.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ol/control/zoomslidercontrol.js b/src/ol/control/zoomslidercontrol.js index 75cf39800e..52b9e1f2d4 100644 --- a/src/ol/control/zoomslidercontrol.js +++ b/src/ol/control/zoomslidercontrol.js @@ -8,6 +8,7 @@ goog.require('goog.asserts'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.events'); +goog.require('goog.events.Event'); goog.require('goog.events.EventType'); goog.require('goog.fx.Dragger'); goog.require('goog.fx.Dragger.EventType'); @@ -88,6 +89,8 @@ ol.control.ZoomSlider = function(opt_options) { goog.events.listen(sliderElement, goog.events.EventType.CLICK, this.handleContainerClick_, false, this); + goog.events.listen(thumbElement, goog.events.EventType.CLICK, + goog.events.Event.stopPropagation); goog.base(this, { element: sliderElement From ac730dc1f3e3b3836525fb87fca8f13edfe248de Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Tue, 19 Nov 2013 14:52:29 +0100 Subject: [PATCH 19/66] override URL parameter values in the GetFeatureInfo request if someone provides a param in getFeatureInfoOptions with the same name --- src/ol/source/wmssource.js | 5 +++++ test/spec/ol/source/wmssource.test.js | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/ol/source/wmssource.js b/src/ol/source/wmssource.js index dac71c6b10..230a5a5889 100644 --- a/src/ol/source/wmssource.js +++ b/src/ol/source/wmssource.js @@ -95,6 +95,11 @@ ol.source.wms.getFeatureInfo = goog.object.extend(params, {'X': x, 'Y': y}); } goog.object.extend(params, localOptions.params); + for (var key in params) { + if (goog.uri.utils.hasParam(url, key)) { + url = goog.uri.utils.removeParam(url, key); + } + } url = goog.uri.utils.appendParamsFromMap(url, params); if (localOptions.method == ol.source.WMSGetFeatureInfoMethod.IFRAME) { goog.global.setTimeout(function() { diff --git a/test/spec/ol/source/wmssource.test.js b/test/spec/ol/source/wmssource.test.js index fb23af0dbf..f5241abc9f 100644 --- a/test/spec/ol/source/wmssource.test.js +++ b/test/spec/ol/source/wmssource.test.js @@ -45,6 +45,17 @@ describe('ol.source.wms', function() { done(); }); }); + it('overrides any existing parameters', function(done) { + ol.source.wms.getFeatureInfo('?REQUEST=GetMap&VERSION=1.3&LAYERS=' + + 'foo&STYLES=x', + [5, 10], {params: {'INFO_FORMAT': 'text/plain', STYLES: 'y'}}, + function(info) { + expect(info).to.eql(''); + done(); + }); + }); }); }); From bb53087541826e30feb4f8cd0e391265ee6f1255 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Tue, 19 Nov 2013 17:09:02 +0100 Subject: [PATCH 20/66] make sure parameter name lookups are case-insensitive --- src/ol/source/wmssource.js | 8 ++++---- test/spec/ol/source/wmssource.test.js | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ol/source/wmssource.js b/src/ol/source/wmssource.js index 230a5a5889..962fb6b199 100644 --- a/src/ol/source/wmssource.js +++ b/src/ol/source/wmssource.js @@ -1,6 +1,7 @@ goog.provide('ol.source.WMSGetFeatureInfoMethod'); goog.provide('ol.source.wms'); +goog.require('goog.Uri'); goog.require('goog.net.XhrIo'); goog.require('goog.object'); goog.require('goog.uri.utils'); @@ -95,12 +96,11 @@ ol.source.wms.getFeatureInfo = goog.object.extend(params, {'X': x, 'Y': y}); } goog.object.extend(params, localOptions.params); + var uri = new goog.Uri(url, true); for (var key in params) { - if (goog.uri.utils.hasParam(url, key)) { - url = goog.uri.utils.removeParam(url, key); - } + uri.setParameterValue(key, params[key]); } - url = goog.uri.utils.appendParamsFromMap(url, params); + url = uri.toString(); if (localOptions.method == ol.source.WMSGetFeatureInfoMethod.IFRAME) { goog.global.setTimeout(function() { success(''); diff --git a/test/spec/ol/source/wmssource.test.js b/test/spec/ol/source/wmssource.test.js index f5241abc9f..af0e6b7c58 100644 --- a/test/spec/ol/source/wmssource.test.js +++ b/test/spec/ol/source/wmssource.test.js @@ -32,8 +32,8 @@ describe('ol.source.wms', function() { [5, 10], {params: {'INFO_FORMAT': 'text/plain'}}, function(info) { expect(info).to.eql(''); + '?request=GetFeatureInfo&version=1.3&layers=foo&query_layers=' + + 'foo&info_format=text%2Fplain&i=5&j=10">'); done(); }); }); @@ -47,12 +47,12 @@ describe('ol.source.wms', function() { }); it('overrides any existing parameters', function(done) { ol.source.wms.getFeatureInfo('?REQUEST=GetMap&VERSION=1.3&LAYERS=' + - 'foo&STYLES=x', + 'foo&styles=x', [5, 10], {params: {'INFO_FORMAT': 'text/plain', STYLES: 'y'}}, function(info) { expect(info).to.eql(''); + '?request=GetFeatureInfo&version=1.3&layers=foo&query_layers=' + + 'foo&styles=y&info_format=text%2Fplain&i=5&j=10">'); done(); }); }); From 0238fa54de9840fdd238080977c6669cab218c0e Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 20 Nov 2013 13:05:06 +0100 Subject: [PATCH 21/66] The source determines who can do GetFeatureInfo Now that the ol.renderer.Layer base class has a getFeatureInfoForPixel method, we have to check whether the source supports GetFeatureInfo, not the layer renderer. --- src/ol/renderer/maprenderer.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ol/renderer/maprenderer.js b/src/ol/renderer/maprenderer.js index a0463fc944..231a155228 100644 --- a/src/ol/renderer/maprenderer.js +++ b/src/ol/renderer/maprenderer.js @@ -122,12 +122,13 @@ ol.renderer.Map.prototype.getFeatureInfoForPixel = } }; - var layer, layerRenderer; + var layer, layerRenderer, source; for (var i = 0; i < numLayers; ++i) { layer = layers[i]; - layerRenderer = this.getLayerRenderer(layer); - if (goog.isFunction(layerRenderer.getFeatureInfoForPixel)) { + source = layer.getSource(); + if (goog.isFunction(source.getFeatureInfoForPixel)) { ++callbackCount; + layerRenderer = this.getLayerRenderer(layer); layerRenderer.getFeatureInfoForPixel(pixel, callback, opt_error); } } From 053d80180d56e98c475335ae7098ed20ac56efa7 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Wed, 20 Nov 2013 14:54:12 +0100 Subject: [PATCH 22/66] have the GML parser read and write out multiple geometry attributes with their correct name --- src/objectliterals.jsdoc | 3 -- src/ol/parser/ogc/filterparser_v1_0_0.js | 2 +- src/ol/parser/ogc/filterparser_v1_1_0.js | 2 +- src/ol/parser/ogc/gmlparser.js | 29 +++++++----- src/ol/parser/ogc/gmlparser_v3.js | 5 +- test/spec/ol/parser/ogc/gml_v2.test.js | 16 +++---- test/spec/ol/parser/ogc/gml_v3.test.js | 38 +++++++++------ .../ol/parser/ogc/xml/gml_v3/more-geoms.xml | 47 +++++++++++++++++++ 8 files changed, 100 insertions(+), 42 deletions(-) create mode 100644 test/spec/ol/parser/ogc/xml/gml_v3/more-geoms.xml diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 2d012d4677..569772428c 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -537,9 +537,6 @@ * will be automatically configured from the GML. * @property {Array.|string|undefined} featureType The local * (without prefix) feature typeName(s). - * @property {string|undefined} geometryName Name of geometry element. - * Defaults to `geometry`. If null, it will be set on when the - * first geometry is parsed. * @property {boolean|undefined} multiCurve Write gml:MultiCurve instead of * gml:MultiLineString. Since the latter is deprecated in GML 3, the * default is `true`. This only applies to GML version 3. diff --git a/src/ol/parser/ogc/filterparser_v1_0_0.js b/src/ol/parser/ogc/filterparser_v1_0_0.js index b96b1d5552..e9b3a22bac 100644 --- a/src/ol/parser/ogc/filterparser_v1_0_0.js +++ b/src/ol/parser/ogc/filterparser_v1_0_0.js @@ -166,7 +166,7 @@ ol.parser.ogc.Filter_v1_0_0.prototype.writeSpatial_ = function(filter, name) { } else { var child; if (geom !== null) { - child = this.writeNode('_geometry', geom, + child = this.writeNode('_geometry', {value: geom}, this.gml_.featureNS).firstChild; } else if (bbox.length === 4) { child = this.writeNode('Box', bbox, diff --git a/src/ol/parser/ogc/filterparser_v1_1_0.js b/src/ol/parser/ogc/filterparser_v1_1_0.js index a718fa18f3..77423733fa 100644 --- a/src/ol/parser/ogc/filterparser_v1_1_0.js +++ b/src/ol/parser/ogc/filterparser_v1_1_0.js @@ -224,7 +224,7 @@ ol.parser.ogc.Filter_v1_1_0.prototype.writeSpatial_ = function(filter, name) { } else { var child; if (geom !== null) { - child = this.writeNode('_geometry', geom, + child = this.writeNode('_geometry', {value: geom}, this.gml_.featureNS).firstChild; } else if (bbox.length === 4) { child = this.writeNode('Envelope', bbox, diff --git a/src/ol/parser/ogc/gmlparser.js b/src/ol/parser/ogc/gmlparser.js index 872abfe166..088cfe13ac 100644 --- a/src/ol/parser/ogc/gmlparser.js +++ b/src/ol/parser/ogc/gmlparser.js @@ -317,10 +317,10 @@ ol.parser.ogc.GML = function(opt_options) { obj.features.push(feature); }, '_geometry': function(node, obj) { - if (!this.geometryName) { - this.geometryName = node.nodeName.split(':').pop(); - } + var local = node.localName || node.nodeName.split(':').pop(); this.readChildNodes(node, obj); + obj.properties[local] = this.createGeometry({geometry: obj.geometry}); + delete obj.geometry; }, '_attribute': function(node, obj) { var local = node.localName || node.nodeName.split(':').pop(); @@ -387,7 +387,8 @@ ol.parser.ogc.GML = function(opt_options) { }, 'geometryMember': function(geometry) { var node = this.createElementNS('gml:geometryMember'); - var child = this.writeNode('_geometry', geometry, this.featureNS); + var child = this.writeNode('_geometry', {value: geometry}, + this.featureNS); node.appendChild(child.firstChild); return node; } @@ -418,23 +419,25 @@ ol.parser.ogc.GML = function(opt_options) { if (goog.isDef(fid)) { this.setAttributeNS(node, this.defaultNamespaceURI, 'fid', fid); } - if (feature.getGeometry() !== null) { - this.writeNode('_geometry', feature.getGeometry(), this.featureNS, - node); - } - var attributes = feature.getAttributes(true); + var attributes = feature.getAttributes(); for (var name in attributes) { var value = attributes[name]; if (goog.isDefAndNotNull(value)) { - this.writeNode('_attribute', {name: name, value: value}, - this.featureNS, node); + if (value instanceof ol.geom.Geometry) { + this.writeNode('_geometry', {name: name, value: value}, + this.featureNS, node); + } else { + this.writeNode('_attribute', {name: name, value: value}, + this.featureNS, node); + } } } return node; }, - '_geometry': function(geometry) { - var node = this.createElementNS('feature:' + this.geometryName, + '_geometry': function(obj) { + var node = this.createElementNS('feature:' + obj.name, this.featureNS); + var geometry = obj.value; var type = geometry.getType(), child; if (type === ol.geom.GeometryType.POINT) { child = this.writeNode('Point', geometry, null, node); diff --git a/src/ol/parser/ogc/gmlparser_v3.js b/src/ol/parser/ogc/gmlparser_v3.js index 47f06f9531..d32ef5b78f 100644 --- a/src/ol/parser/ogc/gmlparser_v3.js +++ b/src/ol/parser/ogc/gmlparser_v3.js @@ -22,9 +22,10 @@ ol.parser.ogc.GML_v3 = function(opt_options) { 'http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' + '1.0.0/gmlsf.xsd'; goog.base(this, opt_options); - this.featureNSWiters_['_geometry'] = function(geometry) { - var node = this.createElementNS('feature:' + this.geometryName, + this.featureNSWiters_['_geometry'] = function(obj) { + var node = this.createElementNS('feature:' + obj.name, this.featureNS); + var geometry = obj.value; var type = geometry.getType(), child; if (type === ol.geom.GeometryType.POINT) { child = this.writeNode('Point', geometry, null, node); diff --git a/test/spec/ol/parser/ogc/gml_v2.test.js b/test/spec/ol/parser/ogc/gml_v2.test.js index a52dd35137..e482df3d19 100644 --- a/test/spec/ol/parser/ogc/gml_v2.test.js +++ b/test/spec/ol/parser/ogc/gml_v2.test.js @@ -21,7 +21,7 @@ describe('ol.parser.gml_v2', function() { parser.applyWriteOptions(obj); var geom = parser.createGeometry({geometry: obj.geometry}); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); @@ -50,7 +50,7 @@ describe('ol.parser.gml_v2', function() { var geom = parser.createGeometry({geometry: obj.geometry}); parser.applyWriteOptions(obj); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); @@ -80,7 +80,7 @@ describe('ol.parser.gml_v2', function() { var geom = parser.createGeometry({geometry: obj.geometry}); parser.applyWriteOptions(obj); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); @@ -109,7 +109,7 @@ describe('ol.parser.gml_v2', function() { var geom = parser.createGeometry({geometry: obj.geometry}); parser.applyWriteOptions(obj); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); @@ -144,7 +144,7 @@ describe('ol.parser.gml_v2', function() { var geom = parser.createGeometry({geometry: obj.geometry}); parser.applyWriteOptions(obj); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); @@ -169,7 +169,7 @@ describe('ol.parser.gml_v2', function() { var geom = parser.createGeometry({geometry: obj.geometry}); parser.applyWriteOptions(obj); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); @@ -188,7 +188,7 @@ describe('ol.parser.gml_v2', function() { var geom = p.createGeometry({geometry: obj.geometry}); p.applyWriteOptions(obj); var node = p.featureNSWiters_['_geometry'].apply(p, - [geom]).firstChild; + [{value: geom}]).firstChild; delete p.srsName; delete p.axisOrientation; expect(goog.dom.xml.loadXml(p.serialize(node))).to.xmleql(xml); @@ -233,7 +233,7 @@ describe('ol.parser.gml_v2', function() { var geom = parser.createGeometry({geometry: obj.geometry}); parser.applyWriteOptions(obj); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); diff --git a/test/spec/ol/parser/ogc/gml_v3.test.js b/test/spec/ol/parser/ogc/gml_v3.test.js index 8914059975..e99089d047 100644 --- a/test/spec/ol/parser/ogc/gml_v3.test.js +++ b/test/spec/ol/parser/ogc/gml_v3.test.js @@ -20,7 +20,7 @@ describe('ol.parser.gml_v3', function() { var geom = parser.createGeometry({geometry: obj.geometry}); parser.applyWriteOptions(obj); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); @@ -37,7 +37,7 @@ describe('ol.parser.gml_v3', function() { var geom = parser.createGeometry({geometry: obj.geometry}); parser.applyWriteOptions(obj); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); @@ -64,7 +64,7 @@ describe('ol.parser.gml_v3', function() { var geom = p.createGeometry({geometry: obj.geometry}); p.applyWriteOptions(obj); var node = p.featureNSWiters_['_geometry'].apply(p, - [geom]).firstChild; + [{value: geom}]).firstChild; delete p.srsName; delete p.axisOrientation; expect(goog.dom.xml.loadXml(p.serialize(node))).to.xmleql(xml); @@ -92,7 +92,7 @@ describe('ol.parser.gml_v3', function() { var geom = p.createGeometry({geometry: obj.geometry}); p.applyWriteOptions(obj); var node = p.featureNSWiters_['_geometry'].apply(p, - [geom]).firstChild; + [{value: geom}]).firstChild; delete p.srsName; delete p.axisOrientation; expect(goog.dom.xml.loadXml(p.serialize(node))).to.xmleql(xml); @@ -109,7 +109,7 @@ describe('ol.parser.gml_v3', function() { var geom = parser.createGeometry({geometry: obj.geometry}); parser.applyWriteOptions(obj); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); @@ -128,7 +128,7 @@ describe('ol.parser.gml_v3', function() { var geom = p.createGeometry({geometry: obj.geometry}); p.applyWriteOptions(obj); var node = p.featureNSWiters_['_geometry'].apply(p, - [geom]).firstChild; + [{value: geom}]).firstChild; delete p.srsName; delete p.axisOrientation; expect(goog.dom.xml.loadXml(p.serialize(node))).to.xmleql(xml); @@ -157,7 +157,7 @@ describe('ol.parser.gml_v3', function() { var geom = parser.createGeometry({geometry: obj.geometry}); parser.applyWriteOptions(obj); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); @@ -186,7 +186,7 @@ describe('ol.parser.gml_v3', function() { var geom = p.createGeometry({geometry: obj.geometry}); p.applyWriteOptions(obj); var node = p.featureNSWiters_['_geometry'].apply(p, - [geom]).firstChild; + [{value: geom}]).firstChild; delete p.srsName; delete p.axisOrientation; expect(goog.dom.xml.loadXml(p.serialize(node))).to.xmleql(xml); @@ -213,7 +213,7 @@ describe('ol.parser.gml_v3', function() { var geom = parser.createGeometry({geometry: obj.geometry}); parser.applyWriteOptions(obj); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); @@ -231,7 +231,7 @@ describe('ol.parser.gml_v3', function() { var geom = p.createGeometry({geometry: obj.geometry}); p.applyWriteOptions(obj); var node = p.featureNSWiters_['_geometry'].apply(p, - [geom]).firstChild; + [{value: geom}]).firstChild; delete p.srsName; delete p.axisOrientation; expect(goog.dom.xml.loadXml(p.serialize(node))).to.xmleql(xml); @@ -248,7 +248,7 @@ describe('ol.parser.gml_v3', function() { var geom = parser.createGeometry({geometry: obj.geometry}); parser.applyWriteOptions(obj); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); @@ -264,7 +264,7 @@ describe('ol.parser.gml_v3', function() { var geom = parser.createGeometry({geometry: obj.geometry}); parser.applyWriteOptions(obj); var node = parser.featureNSWiters_['_geometry'].apply(parser, - [geom]).firstChild; + [{value: geom}]).firstChild; delete parser.srsName; delete parser.axisOrientation; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); @@ -280,7 +280,7 @@ describe('ol.parser.gml_v3', function() { var geom = p.createGeometry({geometry: obj.geometry}); p.applyWriteOptions(obj, {srsName: 'foo'}); var node = p.featureNSWiters_['_geometry'].apply(p, - [geom]).firstChild; + [{value: geom}]).firstChild; delete p.srsName; delete p.axisOrientation; expect(goog.dom.xml.loadXml(p.serialize(node))).to.xmleql(xml); @@ -300,7 +300,6 @@ describe('ol.parser.gml_v3', function() { var obj = p.read(xml); var output = p.write(obj); expect(goog.dom.xml.loadXml(output)).to.xmleql(xml); - expect(p.geometryName).to.eql('the_geom'); expect(obj.features.length).to.eql(10); var feature = obj.features[0]; expect(feature.getGeometry() instanceof @@ -373,10 +372,21 @@ describe('ol.parser.gml_v3', function() { done(); }); }); + it('More than one geometry', function(done) { + var url = 'spec/ol/parser/ogc/xml/gml_v3/more-geoms.xml'; + afterLoadXml(url, function(xml) { + var obj = parser.read(xml); + var feature = obj.features[0]; + expect(feature.get('center')).to.be.a(ol.geom.Point); + expect(feature.get('the_geom')).to.be.a(ol.geom.MultiPolygon); + done(); + }); + }); }); }); goog.require('goog.dom.xml'); +goog.require('ol.geom.Point'); goog.require('ol.geom.MultiPolygon'); goog.require('ol.parser.ogc.GML_v3'); goog.require('ol.proj'); diff --git a/test/spec/ol/parser/ogc/xml/gml_v3/more-geoms.xml b/test/spec/ol/parser/ogc/xml/gml_v3/more-geoms.xml new file mode 100644 index 0000000000..b0fa5c5a8e --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/gml_v3/more-geoms.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + 42.397027571297585 -122.88465674265922 42.39702893980587 -122.88509730796012 42.397029086785146 -122.88511582432085 42.39702379767053 -122.88528111596624 42.39748517484964 -122.88529300380065 42.39748473847452 -122.88509914138723 42.39748482219041 -122.8849959517568 42.397485082635576 -122.8846741899541 42.3974853307826 -122.88436529392652 42.39702663751206 -122.88435664014142 42.397027571297585 -122.88465674265922 + + + + + + + + + 1 2 + + + 1 + YES + I-L + 2004-04-12T00:00:00-06:00 + + + 0.95741118624 + + 835.705330224 + 835.705330224 + 41704.8312728 + 835.705330224 + 41704.8312728 + 0.38745056079 + + + From b8a9aeb14ed6089d9d676c7e8682f0db5a6d5304 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 20 Nov 2013 16:05:00 +0100 Subject: [PATCH 23/66] Avoid source check in map renderer By returning true or false from the layer renderer's getFeatureInfoForPixel method, we know whether the source supports GetFeatureInfo or not. --- src/ol/renderer/layerrenderer.js | 4 ++++ src/ol/renderer/maprenderer.js | 9 +++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js index 8dc5cf489a..64627e09b7 100644 --- a/src/ol/renderer/layerrenderer.js +++ b/src/ol/renderer/layerrenderer.js @@ -48,17 +48,21 @@ goog.inherits(ol.renderer.Layer, goog.Disposable); * successful queries. The passed arguments are the resulting feature * information and the layer. * @param {function()=} opt_error Callback for unsuccessful queries. + * @return {boolean} Whether getFeatureInfoForPixel was called on the source. */ ol.renderer.Layer.prototype.getFeatureInfoForPixel = function(pixel, success, opt_error) { var layer = this.getLayer(); var source = layer.getSource(); + var haveGetFeatureInfo = false; if (goog.isFunction(source.getFeatureInfoForPixel)) { var callback = function(layerFeatureInfo) { success(layerFeatureInfo, layer); }; source.getFeatureInfoForPixel(pixel, this.getMap(), callback, opt_error); + haveGetFeatureInfo = true; } + return haveGetFeatureInfo; }; diff --git a/src/ol/renderer/maprenderer.js b/src/ol/renderer/maprenderer.js index 231a155228..f02a33f6fd 100644 --- a/src/ol/renderer/maprenderer.js +++ b/src/ol/renderer/maprenderer.js @@ -122,14 +122,11 @@ ol.renderer.Map.prototype.getFeatureInfoForPixel = } }; - var layer, layerRenderer, source; + var layerRenderer; for (var i = 0; i < numLayers; ++i) { - layer = layers[i]; - source = layer.getSource(); - if (goog.isFunction(source.getFeatureInfoForPixel)) { + layerRenderer = this.getLayerRenderer(layers[i]); + if (layerRenderer.getFeatureInfoForPixel(pixel, callback, opt_error)) { ++callbackCount; - layerRenderer = this.getLayerRenderer(layer); - layerRenderer.getFeatureInfoForPixel(pixel, callback, opt_error); } } }; From 32c2b5311e5a54f9ff20b69573051a973e357b6f Mon Sep 17 00:00:00 2001 From: Frederic Junod Date: Mon, 4 Nov 2013 10:07:13 +0100 Subject: [PATCH 24/66] Move apidoc CSS style out of layout.css --- apidoc/template/static/styles/ol.css | 39 ++++++++++++++++++++++++++++ apidoc/template/tmpl/layout.tmpl | 1 + resources/layout.css | 38 --------------------------- 3 files changed, 40 insertions(+), 38 deletions(-) create mode 100644 apidoc/template/static/styles/ol.css diff --git a/apidoc/template/static/styles/ol.css b/apidoc/template/static/styles/ol.css new file mode 100644 index 0000000000..418fdef48a --- /dev/null +++ b/apidoc/template/static/styles/ol.css @@ -0,0 +1,39 @@ +a:visited { + color: #08c; +} + +.stability { + padding: 8px 35px 8px 14px; + margin-bottom: 20px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + color: #333; + background-color: #fcfcfc; + border: 1px solid #ccc; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.stability-deprecated { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; +} + +.stability-experimental { + color: #c09853; + background-color: #fcf8e3; + border-color: #fbeed5; +} + +.stability-unstable { + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; +} + +.stability-stable, .stability-locked { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; +} diff --git a/apidoc/template/tmpl/layout.tmpl b/apidoc/template/tmpl/layout.tmpl index dbecd6f857..ed01889839 100644 --- a/apidoc/template/tmpl/layout.tmpl +++ b/apidoc/template/tmpl/layout.tmpl @@ -14,6 +14,7 @@ + diff --git a/resources/layout.css b/resources/layout.css index d9c6ae745f..72a211765c 100644 --- a/resources/layout.css +++ b/resources/layout.css @@ -26,41 +26,3 @@ body, h1, h2, h3, h4, p, li, td, th { color: white; padding: 5px; } - -.stability { - padding: 8px 35px 8px 14px; - margin-bottom: 20px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - color: #333; - background-color: #fcfcfc; - border: 1px solid #ccc; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.stability-deprecated { - color: #b94a48; - background-color: #f2dede; - border-color: #eed3d7; -} - -.stability-experimental { - color: #c09853; - background-color: #fcf8e3; - border-color: #fbeed5; -} - -.stability-unstable { - color: #3a87ad; - background-color: #d9edf7; - border-color: #bce8f1; -} - -.stability-stable, -.stability-locked, -.stability-locked, { - color: #468847; - background-color: #dff0d8; - border-color: #d6e9c6; -} From 6e88d3ba3cc0a1cbec728d3525c36edec0a96d34 Mon Sep 17 00:00:00 2001 From: Frederic Junod Date: Mon, 4 Nov 2013 09:58:39 +0100 Subject: [PATCH 25/66] Minor apidoc updates --- src/objectliterals.jsdoc | 74 ++++++++++++++------------ src/ol/control/mousepositioncontrol.js | 4 +- src/ol/geolocation.js | 5 +- src/ol/geom/linearring.js | 2 +- src/ol/geom/linestring.js | 2 +- src/ol/geom/point.js | 2 +- src/ol/object.js | 14 ++--- src/ol/view2d.js | 6 +-- 8 files changed, 57 insertions(+), 52 deletions(-) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 2d012d4677..289a531736 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -357,7 +357,7 @@ * modifier (i.e. Shift key) that determines if the interaction is active * or not, default is no modifiers. * @property {number|undefined} pixelDelta Pixel The amount to pan on each key - * press + * press. Default is `128` pixels. * @todo stability experimental */ @@ -367,7 +367,7 @@ * @property {ol.events.ConditionType|undefined} condition A conditional * modifier (i.e. Shift key) that determines if the interaction is active * or not, default is no modifiers. - * @property {number|undefined} delta The amount to zoom on each key press. + * @property {number|undefined} delta The amount to zoom on each key press. Default is `1`. * @todo stability experimental */ @@ -409,7 +409,8 @@ /** * @typedef {Object} ol.interaction.TouchRotateOptions - * @property {number|undefined} threshold Minimal angle to start a rotation. + * @property {number|undefined} threshold Minimal angle in radians to start a rotation. + * Default is `0.3`. * @todo stability experimental */ @@ -421,12 +422,12 @@ /** * @typedef {Object} ol.layer.BaseOptions - * @property {number|undefined} brightness Brightness. - * @property {number|undefined} contrast Contrast. - * @property {number|undefined} hue Hue. - * @property {number|undefined} opacity Opacity. - * @property {number|undefined} saturation Saturation. - * @property {boolean|undefined} visible Visibility. + * @property {number|undefined} brightness Brightness. Default is `0`. + * @property {number|undefined} contrast Contrast. Default is `1`. + * @property {number|undefined} hue Hue. Default is `0`. + * @property {number|undefined} opacity Opacity (0, 1). Default is `1`. + * @property {number|undefined} saturation Saturation. Default is `1`. + * @property {boolean|undefined} visible Visibility. Default is `true`. * @property {number|undefined} minResolution The minimum resolution * (inclusive) at which this layer will be visible. * @property {number|undefined} maxResolution The maximum resolution @@ -436,11 +437,11 @@ /** * @typedef {Object} ol.layer.LayerOptions - * @property {number|undefined} brightness Brightness. - * @property {number|undefined} contrast Contrast. - * @property {number|undefined} hue Hue. - * @property {number|undefined} opacity Opacity. 0-1. Default is `1`. - * @property {number|undefined} saturation Saturation. + * @property {number|undefined} brightness Brightness. Default is `0`. + * @property {number|undefined} contrast Contrast. Default is `1`. + * @property {number|undefined} hue Hue. Default is `0`. + * @property {number|undefined} opacity Opacity (0, 1). Default is `1`. + * @property {number|undefined} saturation Saturation. Default is `1`. * @property {ol.source.Source} source Source for this layer. * @property {boolean|undefined} visible Visibility. Default is `true` (visible). * @property {number|undefined} minResolution The minimum resolution @@ -452,12 +453,12 @@ /** * @typedef {Object} ol.layer.GroupOptions - * @property {number|undefined} brightness Brightness. - * @property {number|undefined} contrast Contrast. - * @property {number|undefined} hue Hue. - * @property {number|undefined} opacity Opacity. - * @property {number|undefined} saturation Saturation. - * @property {boolean|undefined} visible Visibility. + * @property {number|undefined} brightness Brightness. Default is `0`. + * @property {number|undefined} contrast Contrast. Default is `1`. + * @property {number|undefined} hue Hue. Default is `0`. + * @property {number|undefined} opacity Opacity (0, 1). Default is `1`. + * @property {number|undefined} saturation Saturation. Default is `1`. + * @property {boolean|undefined} visible Visibility. Default is `true`. * @property {number|undefined} minResolution The minimum resolution * (inclusive) at which this layer will be visible. * @property {number|undefined} maxResolution The maximum resolution @@ -468,12 +469,12 @@ /** * @typedef {Object} ol.layer.TileOptions - * @property {number|undefined} brightness Brightness. - * @property {number|undefined} contrast Contrast. - * @property {number|undefined} hue Hue. - * @property {number|undefined} opacity Opacity. 0-1. Default is `1`. + * @property {number|undefined} brightness Brightness. Default is `0`. + * @property {number|undefined} contrast Contrast. Default is `1`. + * @property {number|undefined} hue Hue. Default is `0`. + * @property {number|undefined} opacity Opacity (0, 1). Default is `1`. * @property {number|undefined} preload Preload. - * @property {number|undefined} saturation Saturation. + * @property {number|undefined} saturation Saturation. Default is `1`. * @property {ol.source.Source} source Source for this layer. * @property {boolean|undefined} visible Visibility. Default is `true` (visible). * @property {number|undefined} minResolution The minimum resolution @@ -489,7 +490,7 @@ * Function to render an array of * features into feature info markup. If not provided, a comma separated * list of the unique ids of the resulting features will be returned. - * @property {number|undefined} opacity Opacity. 0-1. Default is `1`. + * @property {number|undefined} opacity Opacity (0, 1). Default is `1`. * @property {ol.source.Source} source Source for this layer. * @property {ol.style.Style|undefined} style Style. * @property {boolean|undefined} visible Visibility. Default is `true` (visible). @@ -587,7 +588,7 @@ /** * @typedef {Object} ol.source.BingMapsOptions - * @property {string|undefined} culture Culture. + * @property {string|undefined} culture Culture code. Default is `en-us`. * @property {string} key Bing Maps API key. Get yours at * http://bingmapsportal.com/. * @property {string} imagerySet Type of imagery. @@ -617,7 +618,9 @@ * @property {number|undefined} maxZoom Max zoom. * @property {ol.TileLoadFunctionType|undefined} tileLoadFunction Optional * function to load a tile given a URL. - * @property {string|undefined} url URL. + * @property {string|undefined} url URL template. Must include `{x}`, `{y}`, + * and `{z}` placeholders. Default is + * `http://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png`. * @todo stability experimental */ @@ -651,7 +654,8 @@ * @property {boolean|undefined} opaque Whether the layer is opaque. * @property {ol.TileLoadFunctionType|undefined} tileLoadFunction Optional * function to load a tile given a URL. - * @property {string|undefined} url URL. + * @property {string|undefined} url URL template. Must include `{x}`, `{y}`, + * and `{z}` placeholders. * @todo stability experimental */ @@ -674,7 +678,7 @@ * requests. * @property {ol.TileLoadFunctionType|undefined} tileLoadFunction Optional * function to load a tile given a URL. - * @property {string} url URL. + * @property {string} url URL to the TileJSON file. * @todo stability experimental */ @@ -761,7 +765,7 @@ * requests. * @property {ol.Extent|undefined} extent Extent. * @property {string|undefined} logo Logo. - * @property {ol.proj.ProjectionLike} projection Projection. + * @property {ol.proj.ProjectionLike} projection Projection. Default is `EPSG:3857`. * @property {number|undefined} maxZoom Optional max zoom level. Default is `18`. * @property {number|undefined} minZoom Unsupported (TODO: remove this). * @property {ol.TileLoadFunctionType|undefined} tileLoadFunction Optional @@ -783,7 +787,7 @@ * @property {number|ol.expr.Expression|undefined} height Height of the * icon in pixels. Default is the height of the icon image. * @property {number|ol.expr.Expression|undefined} opacity Icon opacity - * (0-1). + * (0, 1). Default is `1`. * @property {number|ol.expr.Expression|undefined} rotation Rotation in * radians (positive rotation clockwise). * @property {number|ol.expr.Expression|undefined} xOffset Pixel offset from the @@ -798,7 +802,7 @@ * @typedef {Object} ol.style.FillOptions * @property {string|ol.expr.Expression|undefined} color Fill color as hex color * code. - * @property {number|ol.expr.Expression|undefined} opacity Opacity (0-1). + * @property {number|ol.expr.Expression|undefined} opacity Opacity (0, 1). Default is `1`. * @property {number|ol.expr.Expression|undefined} zIndex Stack order. * @todo stability experimental */ @@ -830,7 +834,7 @@ * @typedef {Object} ol.style.StrokeOptions * @property {string|ol.expr.Expression|undefined} color Stroke color as hex * color code. - * @property {number|ol.expr.Expression|undefined} opacity Stroke opacity (0-1). + * @property {number|ol.expr.Expression|undefined} opacity Stroke opacity (0, 1). Default is `1`. * @property {number|ol.expr.Expression|undefined} width Stroke width in pixels. * @property {number|ol.expr.Expression|undefined} zIndex Stack order. * @todo stability experimental @@ -852,7 +856,7 @@ * @property {number|ol.expr.Expression|undefined} fontSize Font size in pixels. * @property {string|ol.expr.Expression|undefined} fontWeight Font weight. * @property {string|ol.expr.Expression} text Text for the label. - * @property {number|ol.expr.Expression|undefined} opacity Opacity (0-1). + * @property {number|ol.expr.Expression|undefined} opacity Opacity (0, 1). Default is `1`. * @property {ol.style.Stroke|undefined} stroke Stroke symbolizer for text. * @property {number|ol.expr.Expression|undefined} zIndex Stack order. * @todo stability experimental diff --git a/src/ol/control/mousepositioncontrol.js b/src/ol/control/mousepositioncontrol.js index a29e9a8ac3..3697e9f7b6 100644 --- a/src/ol/control/mousepositioncontrol.js +++ b/src/ol/control/mousepositioncontrol.js @@ -40,8 +40,8 @@ ol.control.MousePositionProperty = { * @todo stability experimental * @todo observable projection {ol.proj.Projection} the projection to report * mouse position in - * @todo observable coordinateFormat {string} the format to render the current - * position in + * @todo observable coordinateFormat {ol.CoordinateFormatType} the format to + * render the current position in */ ol.control.MousePosition = function(opt_options) { diff --git a/src/ol/geolocation.js b/src/ol/geolocation.js index 20058d73d1..884cac198d 100644 --- a/src/ol/geolocation.js +++ b/src/ol/geolocation.js @@ -62,8 +62,9 @@ ol.GeolocationProperty = { * @todo observable speed {number} readonly the instantaneous speed of the * device in meters per second * @todo observable tracking {number} track the device's position. - * @todo observable trackingOptions {number} PositionOptions as defined by the - * HTML5 Geolocation spec at http://dev.w3.org/geo/api/spec-source.html + * @todo observable trackingOptions {GeolocationPositionOptions} PositionOptions + * as defined by the HTML5 Geolocation spec at + * http://dev.w3.org/geo/api/spec-source.html */ ol.Geolocation = function(opt_options) { diff --git a/src/ol/geom/linearring.js b/src/ol/geom/linearring.js index 31f4ba200e..f2e0789b9b 100644 --- a/src/ol/geom/linearring.js +++ b/src/ol/geom/linearring.js @@ -10,7 +10,7 @@ goog.require('ol.geom.LineString'); * @constructor * @extends {ol.geom.LineString} * @param {ol.CoordinateArray} coordinates Vertex array (e.g. - * [[x0, y0], [x1, y1]]). + * `[[x0, y0], [x1, y1]]`). * @todo stability experimental */ ol.geom.LinearRing = function(coordinates) { diff --git a/src/ol/geom/linestring.js b/src/ol/geom/linestring.js index e7ed07843b..ec73a3669b 100644 --- a/src/ol/geom/linestring.js +++ b/src/ol/geom/linestring.js @@ -15,7 +15,7 @@ goog.require('ol.geom.GeometryType'); * @constructor * @extends {ol.geom.Geometry} * @param {ol.CoordinateArray} coordinates Array of coordinates (e.g. - * [[x0, y0], [x1, y1]]). + * `[[x0, y0], [x1, y1]]`). * @todo stability experimental */ ol.geom.LineString = function(coordinates) { diff --git a/src/ol/geom/point.js b/src/ol/geom/point.js index b11f862858..39dd9e933c 100644 --- a/src/ol/geom/point.js +++ b/src/ol/geom/point.js @@ -12,7 +12,7 @@ goog.require('ol.geom.GeometryType'); /** * @constructor * @extends {ol.geom.Geometry} - * @param {ol.Coordinate} coordinates Coordinate values (e.g. [x, y]). + * @param {ol.Coordinate} coordinates Coordinate values (e.g. `[x, y]`). * @todo stability experimental */ ol.geom.Point = function(coordinates) { diff --git a/src/ol/object.js b/src/ol/object.js index 4d82943de0..5b122f601c 100644 --- a/src/ol/object.js +++ b/src/ol/object.js @@ -142,7 +142,7 @@ ol.Object.getAccessors = function(obj) { /** - * @param {string} key Key. + * @param {string} key Key name. * @return {string} Change name. */ ol.Object.getChangeEventType = function(key) { @@ -207,7 +207,7 @@ ol.Object.getSetterName = function(key) { * } * ); * - * @param {string} key Key. + * @param {string} key Key name. * @param {ol.Object} target Target. * @param {string=} opt_targetKey Target key. * @return {ol.ObjectAccessor} @@ -231,7 +231,7 @@ ol.Object.prototype.bindTo = function(key, target, opt_targetKey) { /** * Gets a value. - * @param {string} key Key. + * @param {string} key Key name. * @return {*} Value. * @todo stability experimental */ @@ -291,7 +291,7 @@ ol.Object.prototype.getKeys = function() { * Notify all observers of a change on this property. This notifies both * objects that are bound to the object's property as well as the object * that it is bound to. - * @param {string} key Key. + * @param {string} key Key name. * @todo stability experimental */ ol.Object.prototype.notify = function(key) { @@ -308,7 +308,7 @@ ol.Object.prototype.notify = function(key) { /** - * @param {string} key Key. + * @param {string} key Key name. * @private */ ol.Object.prototype.notifyInternal_ = function(key) { @@ -348,7 +348,7 @@ ol.Object.prototype.once = function(type, listener, opt_scope) { /** * Sets a value. - * @param {string} key Key. + * @param {string} key Key name. * @param {*} value Value. * @todo stability experimental */ @@ -394,7 +394,7 @@ ol.Object.prototype.setValues = function(values) { /** * Removes a binding. Unbinding will set the unbound property to the current * value. The object will not be notified, as the value has not changed. - * @param {string} key Key. + * @param {string} key Key name. * @todo stability experimental */ ol.Object.prototype.unbind = function(key) { diff --git a/src/ol/view2d.js b/src/ol/view2d.js index dc93ceb152..e4716f5726 100644 --- a/src/ol/view2d.js +++ b/src/ol/view2d.js @@ -192,8 +192,8 @@ ol.View2D.prototype.constrainCenter = function(center) { /** * Get the constrained the resolution of this view. * @param {number|undefined} resolution Resolution. - * @param {number=} opt_delta Delta. - * @param {number=} opt_direction Direction. + * @param {number=} opt_delta Delta. Default is `0`. + * @param {number=} opt_direction Direction. Default is `0`. * @return {number|undefined} Constrained resolution. * @todo stability experimental */ @@ -208,7 +208,7 @@ ol.View2D.prototype.constrainResolution = function( /** * Get the constrained rotation of this view. * @param {number|undefined} rotation Rotation. - * @param {number=} opt_delta Delta. + * @param {number=} opt_delta Delta. Default is `0`. * @return {number|undefined} Constrained rotation. * @todo stability experimental */ From 71d3fc49214aced47689ea71be89575bc8854065 Mon Sep 17 00:00:00 2001 From: Frederic Junod Date: Wed, 20 Nov 2013 12:36:35 +0100 Subject: [PATCH 26/66] Add ol.BrowserFeature.DEVICE_PIXEL_RATIO constant --- src/ol/browserfeature.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ol/browserfeature.js b/src/ol/browserfeature.js index 527a709873..a0a428c3c9 100644 --- a/src/ol/browserfeature.js +++ b/src/ol/browserfeature.js @@ -11,6 +11,13 @@ ol.ASSUME_TOUCH = false; * @type {Object} */ ol.BrowserFeature = { + /** + * The ratio between physical pixels and device-independent pixels + * (dips) on the device (`window.devicePixelRatio`). + * @type {number} + */ + DEVICE_PIXEL_RATIO: goog.global.devicePixelRatio || 1, + /** * True if browser supports touch events. * @type {boolean} From 6762e70f4c0439fbd4bd7d33d3d8df8c01389395 Mon Sep 17 00:00:00 2001 From: Frederic Junod Date: Wed, 20 Nov 2013 14:04:59 +0100 Subject: [PATCH 27/66] Better canvas sizing handling (canvas and webgl) --- src/ol/renderer/canvas/canvasmaprenderer.js | 4 +++- src/ol/renderer/webgl/webglmaprenderer.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ol/renderer/canvas/canvasmaprenderer.js b/src/ol/renderer/canvas/canvasmaprenderer.js index 3b73d6957f..769e18e097 100644 --- a/src/ol/renderer/canvas/canvasmaprenderer.js +++ b/src/ol/renderer/canvas/canvasmaprenderer.js @@ -35,6 +35,8 @@ ol.renderer.canvas.Map = function(container, map) { */ this.canvas_ = /** @type {HTMLCanvasElement} */ (goog.dom.createElement(goog.dom.TagName.CANVAS)); + this.canvas_.style.width = '100%'; + this.canvas_.style.height = '100%'; this.canvas_.className = ol.css.CLASS_UNSELECTABLE; goog.dom.insertChildAt(container, this.canvas_, 0); @@ -100,7 +102,7 @@ ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) { } var context = this.context_; - context.clearRect(0, 0, size[0], size[1]); + context.clearRect(0, 0, this.canvas_.width, this.canvas_.height); this.calculateMatrices2D(frameState); diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index eddfab1d73..c8d014030c 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -70,6 +70,8 @@ ol.renderer.webgl.Map = function(container, map) { */ this.canvas_ = /** @type {HTMLCanvasElement} */ (goog.dom.createElement(goog.dom.TagName.CANVAS)); + this.canvas_.style.width = '100%'; + this.canvas_.style.height = '100%'; this.canvas_.className = ol.css.CLASS_UNSELECTABLE; goog.dom.insertChildAt(container, this.canvas_, 0); @@ -564,7 +566,7 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { gl.clearColor(0, 0, 0, 0); gl.clear(goog.webgl.COLOR_BUFFER_BIT); gl.enable(goog.webgl.BLEND); - gl.viewport(0, 0, size[0], size[1]); + gl.viewport(0, 0, this.canvas_.width, this.canvas_.height); this.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_); From ed986afe26364a6ae6bca377530e858acb4b34ac Mon Sep 17 00:00:00 2001 From: Marc Jansen Date: Thu, 21 Nov 2013 22:26:11 +0100 Subject: [PATCH 28/66] Better formatted output for formatted sizes. --- build.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build.py b/build.py index 65bb9f1de3..e52a1efd96 100755 --- a/build.py +++ b/build.py @@ -153,13 +153,16 @@ PROJ4JS_ZIP_MD5 = '17caad64cf6ebc6e6fe62f292b134897' def report_sizes(t): - t.info('uncompressed: %d bytes', os.stat(t.name).st_size) stringio = StringIO() gzipfile = gzip.GzipFile(t.name, 'w', 9, stringio) with open(t.name) as f: shutil.copyfileobj(f, gzipfile) gzipfile.close() - t.info(' compressed: %d bytes', len(stringio.getvalue())) + rawsize = os.stat(t.name).st_size + gzipsize = len(stringio.getvalue()) + savings = '{:.2%}'.format((rawsize - gzipsize)/float(rawsize)) + t.info('uncompressed: %8d bytes', rawsize) + t.info(' compressed: %8d bytes, (saved %s)', gzipsize, savings) virtual('default', 'build') From 9d9c91bbf718ad070bbcd1da82d9837049bd377a Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 22 Nov 2013 17:11:44 +0100 Subject: [PATCH 29/66] Add missing WMSGetFeatureInfo exports --- src/objectliterals.jsdoc | 19 ++++--------------- src/ol/source/imagewmssource.js | 3 ++- src/ol/source/tilewmssource.js | 3 ++- src/ol/source/wmssource.exports | 2 ++ src/ol/source/wmssource.js | 7 +++++++ 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 289a531736..952aeec642 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -630,8 +630,8 @@ * @property {null|string|undefined} crossOrigin crossOrigin setting for image * requests. * @property {ol.Extent|undefined} extent Extent. - * @property {ol.source.WMSGetFeatureInfoOptions|undefined} - * getFeatureInfoOptions Options for GetFeatureInfo. + * @property {ol.source.WMSGetFeatureInfoOptions|undefined} getFeatureInfoOptions + * Options for GetFeatureInfo. * @property {Object.} params WMS request parameters. At least a * `LAYERS` param is required. `STYLES` is `` by default. `VERSION` is * `1.3.0` by default. `WIDTH`, `HEIGHT`, `BBOX` and `CRS` (`SRS` for WMS @@ -692,8 +692,8 @@ * @property {null|string|undefined} crossOrigin crossOrigin setting for image * requests. * @property {ol.Extent|undefined} extent Extent. - * @property {ol.source.WMSGetFeatureInfoOptions|undefined} - * getFeatureInfoOptions Options for GetFeatureInfo. + * @property {ol.source.WMSGetFeatureInfoOptions|undefined} getFeatureInfoOptions + * Options for GetFeatureInfo. * @property {string|undefined} logo Logo. * @property {ol.tilegrid.TileGrid|undefined} tileGrid Tile grid. * @property {number|undefined} maxZoom Maximum zoom. @@ -722,17 +722,6 @@ * @todo stability experimental */ - -/** - * @typedef {Object} ol.source.WMSGetFeatureInfoOptions - * @property {ol.source.WMSGetFeatureInfoMethod} method Method for requesting - * GetFeatureInfo. Default is `ol.source.WMSGetFeatureInfoMethod.IFRAME`. - * @property {Object} params Params for the GetFeatureInfo request. Default is - * `{'INFO_FORMAT': 'text/html'}`. - * @todo stability experimental - */ - - /** * @typedef {Object} ol.source.WMTSOptions * @property {Array.|undefined} attributions Attributions. diff --git a/src/ol/source/imagewmssource.js b/src/ol/source/imagewmssource.js index 788070fe65..4dc4226704 100644 --- a/src/ol/source/imagewmssource.js +++ b/src/ol/source/imagewmssource.js @@ -45,7 +45,8 @@ ol.source.ImageWMS = function(options) { * @type {ol.source.WMSGetFeatureInfoOptions} */ this.getFeatureInfoOptions_ = goog.isDef(options.getFeatureInfoOptions) ? - options.getFeatureInfoOptions : {}; + options.getFeatureInfoOptions : + /** @type {ol.source.WMSGetFeatureInfoOptions} */ ({}); /** * @private diff --git a/src/ol/source/tilewmssource.js b/src/ol/source/tilewmssource.js index 48c2d15876..77cba28dc4 100644 --- a/src/ol/source/tilewmssource.js +++ b/src/ol/source/tilewmssource.js @@ -110,7 +110,8 @@ ol.source.TileWMS = function(options) { * @type {ol.source.WMSGetFeatureInfoOptions} */ this.getFeatureInfoOptions_ = goog.isDef(options.getFeatureInfoOptions) ? - options.getFeatureInfoOptions : {}; + options.getFeatureInfoOptions : + /** @type {ol.source.WMSGetFeatureInfoOptions} */ ({}); }; goog.inherits(ol.source.TileWMS, ol.source.TileImage); diff --git a/src/ol/source/wmssource.exports b/src/ol/source/wmssource.exports index 7e9a24f714..ec75424b4f 100644 --- a/src/ol/source/wmssource.exports +++ b/src/ol/source/wmssource.exports @@ -1 +1,3 @@ @exportSymbol ol.source.WMSGetFeatureInfoMethod +@exportProperty ol.source.WMSGetFeatureInfoMethod.IFRAME +@exportProperty ol.source.WMSGetFeatureInfoMethod.XHR_GET diff --git a/src/ol/source/wmssource.js b/src/ol/source/wmssource.js index dac71c6b10..23eec3dfa2 100644 --- a/src/ol/source/wmssource.js +++ b/src/ol/source/wmssource.js @@ -6,6 +6,13 @@ goog.require('goog.object'); goog.require('goog.uri.utils'); +/** + * @typedef {{method: (ol.source.WMSGetFeatureInfoMethod|undefined), + * params: (Object.|undefined)}} + */ +ol.source.WMSGetFeatureInfoOptions; + + /** * Method to use to get WMS feature info. * @enum {string} From cf5e9ecd4fe8a42c66e6713c81a6ce90e33952d0 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Sat, 23 Nov 2013 11:49:42 +0100 Subject: [PATCH 30/66] Segment distance calculation fails when modifying points By providing a segment even for points, we have less distinction points between points and other geometry types, and are able to do the segment distance calculation also for points. --- src/ol/interaction/modifyinteraction.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ol/interaction/modifyinteraction.js b/src/ol/interaction/modifyinteraction.js index dedabccc05..2966f635e5 100644 --- a/src/ol/interaction/modifyinteraction.js +++ b/src/ol/interaction/modifyinteraction.js @@ -30,7 +30,7 @@ goog.require('ol.structs.RTree'); * geometry: ol.geom.Geometry, * index: (number|undefined), * style: ol.style.Style, - * segment: (Array.|undefined)}} + * segment: Array.}} */ ol.interaction.SegmentDataType; @@ -277,9 +277,11 @@ ol.interaction.Modify.prototype.addSegments_ = var rTree = this.rTree_; var segment, segmentData, coordinates; if (geometry instanceof ol.geom.Point) { + coordinates = geometry.getCoordinates(); segmentData = /** @type {ol.interaction.SegmentDataType} */ ({ feature: feature, geometry: geometry, + segment: [coordinates, coordinates], style: layer.getStyle() }); rTree.insert(geometry.getBounds(), segmentData, uid); @@ -389,20 +391,18 @@ ol.interaction.Modify.prototype.handleDrag = function(evt) { var geometry = segmentData.geometry; var coordinates = geometry.getCoordinates(); - var oldBounds, newBounds; + var segment = segmentData.segment; + var oldBounds = ol.extent.boundingExtent(segment); if (geometry instanceof ol.geom.Point) { - oldBounds = geometry.getBounds(); - geometry.setCoordinates(vertex); - newBounds = geometry.getBounds(); + coordinates = vertex; + segment[0] = segment[1] = vertex; } else { var index = dragSegment[1]; coordinates[segmentData.index + index] = vertex; - geometry.setCoordinates(coordinates); - var segment = segmentData.segment; - oldBounds = ol.extent.boundingExtent(segment); segment[index] = vertex; - newBounds = ol.extent.boundingExtent(segment); } + geometry.setCoordinates(coordinates); + var newBounds = ol.extent.boundingExtent(segment); this.createOrUpdateVertexFeature_(segmentData.style, vertex); this.rTree_.remove(oldBounds, segmentData); this.rTree_.insert(newBounds, segmentData, goog.getUid(feature)); From 900bc176ae6f69a74e1e171c5219bc121b1d4a8f Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 12 Nov 2013 01:26:41 +0100 Subject: [PATCH 31/66] Avoid clearing canvas twice --- src/ol/renderer/canvas/canvasmaprenderer.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ol/renderer/canvas/canvasmaprenderer.js b/src/ol/renderer/canvas/canvasmaprenderer.js index 769e18e097..9a20c3bedd 100644 --- a/src/ol/renderer/canvas/canvasmaprenderer.js +++ b/src/ol/renderer/canvas/canvasmaprenderer.js @@ -95,15 +95,16 @@ ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) { return; } + var context = this.context_; + var size = frameState.size; if (this.canvas_.width != size[0] || this.canvas_.height != size[1]) { this.canvas_.width = size[0]; this.canvas_.height = size[1]; + } else { + context.clearRect(0, 0, this.canvas_.width, this.canvas_.height); } - var context = this.context_; - context.clearRect(0, 0, this.canvas_.width, this.canvas_.height); - this.calculateMatrices2D(frameState); var layerStates = frameState.layerStates; From 25ae9fe78436e453372beccdf424fe18a0df7712 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Mon, 25 Nov 2013 10:48:30 +0100 Subject: [PATCH 32/66] change simple type values to undefined instead of null --- src/ol/parser/ogc/sldparser_v1.js | 13 +++++++------ src/ol/style/rule.js | 12 ++++++------ src/ol/style/style.js | 12 ++++++------ 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/ol/parser/ogc/sldparser_v1.js b/src/ol/parser/ogc/sldparser_v1.js index ed9757b038..a1516d8c5a 100644 --- a/src/ol/parser/ogc/sldparser_v1.js +++ b/src/ol/parser/ogc/sldparser_v1.js @@ -342,10 +342,10 @@ ol.parser.ogc.SLD_v1 = function() { 'UserStyle': function(style) { var node = this.createElementNS('sld:UserStyle'); var name = style.getName(), title = style.getTitle(); - if (!goog.isNull(name)) { + if (goog.isDef(name)) { this.writeNode('Name', name, null, node); } - if (!goog.isNull(title)) { + if (goog.isDef(title)) { this.writeNode('Title', title, null, node); } // TODO sorting by zIndex @@ -370,11 +370,12 @@ ol.parser.ogc.SLD_v1 = function() { 'Rule': function(rule) { var node = this.createElementNS('sld:Rule'); var filter = rule.getFilter(); - if (!goog.isNull(rule.getName())) { - this.writeNode('Name', rule.getName(), null, node); + var name = rule.getName(), title = rule.getTitle(); + if (goog.isDef(name)) { + this.writeNode('Name', name, null, node); } - if (!goog.isNull(rule.getTitle())) { - this.writeNode('Title', rule.title, null, node); + if (goog.isDef(title)) { + this.writeNode('Title', title, null, node); } if (rule.elseFilter === true) { this.writeNode('ElseFilter', null, null, node); diff --git a/src/ol/style/rule.js b/src/ol/style/rule.js index 3495422459..d8234de95b 100644 --- a/src/ol/style/rule.js +++ b/src/ol/style/rule.js @@ -55,18 +55,18 @@ ol.style.Rule = function(options) { options.maxResolution : Infinity; /** - * @type {?string} + * @type {string|undefined} * @private */ this.name_ = goog.isDef(options.name) ? - options.name : null; + options.name : undefined; /** - * @type {?string} + * @type {string|undefined} * @private */ this.title_ = goog.isDef(options.title) ? - options.title : null; + options.title : undefined; }; @@ -119,7 +119,7 @@ ol.style.Rule.prototype.getMaxResolution = function() { /** - * @return {?string} + * @return {string|undefined} */ ol.style.Rule.prototype.getName = function() { return this.name_; @@ -127,7 +127,7 @@ ol.style.Rule.prototype.getName = function() { /** - * @return {?string} + * @return {string|undefined} */ ol.style.Rule.prototype.getTitle = function() { return this.title_; diff --git a/src/ol/style/style.js b/src/ol/style/style.js index 224ec2578c..c8d70ef635 100644 --- a/src/ol/style/style.js +++ b/src/ol/style/style.js @@ -41,18 +41,18 @@ ol.style.Style = function(options) { options.symbolizers : []; /** - * @type {?string} + * @type {string|undefined} * @private */ this.name_ = goog.isDef(options.name) ? - options.name : null; + options.name : undefined; /** - * @type {?string} + * @type {string|undefined} * @private */ this.title_ = goog.isDef(options.title) ? - options.title : null; + options.title : undefined; }; @@ -247,7 +247,7 @@ ol.style.Style.prototype.getSymbolizers = function() { /** - * @return {?string} + * @return {string|undefined} */ ol.style.Style.prototype.getName = function() { return this.name_; @@ -255,7 +255,7 @@ ol.style.Style.prototype.getName = function() { /** - * @return {?string} + * @return {string|undefined} */ ol.style.Style.prototype.getTitle = function() { return this.title_; From dc58d4e5621aa32baf858e1a0a361cc44d8bb97e Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Tue, 22 Oct 2013 16:24:07 +0200 Subject: [PATCH 33/66] srsName should be optional in BBOX --- src/ol/parser/ogc/filterparser_v1_0_0.js | 6 ++++-- src/ol/parser/ogc/gmlparser_v2.js | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ol/parser/ogc/filterparser_v1_0_0.js b/src/ol/parser/ogc/filterparser_v1_0_0.js index e9b3a22bac..0b14d01eb6 100644 --- a/src/ol/parser/ogc/filterparser_v1_0_0.js +++ b/src/ol/parser/ogc/filterparser_v1_0_0.js @@ -91,12 +91,14 @@ ol.parser.ogc.Filter_v1_0_0 = function() { goog.asserts.assert(args[1] instanceof ol.expr.Literal); goog.asserts.assert(args[2] instanceof ol.expr.Literal); goog.asserts.assert(args[3] instanceof ol.expr.Literal); - goog.asserts.assert(args[4] instanceof ol.expr.Literal); var bbox = [ args[0].getValue(), args[1].getValue(), args[2].getValue(), args[3].getValue() ]; - var projection = args[4].getValue(); + var projection; + if (args[4] instanceof ol.expr.Literal) { + projection = args[4].getValue(); + } var property = args[5]; // PropertyName is mandatory in 1.0.0, but e.g. GeoServer also // accepts filters without it. 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 557390f2add7e7dc07fd3643871eedd6d179b12d Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Tue, 22 Oct 2013 16:24:27 +0200 Subject: [PATCH 34/66] 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 + + + + From 0581a49378c5951f5893dc73cfe81e978c71cb0c Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Tue, 22 Oct 2013 17:26:55 +0200 Subject: [PATCH 35/66] ability to write out PropertyNames in a GetFeature request --- src/ol/parser/ogc/wfsparser_v1.js | 4 +++- src/ol/parser/ogc/wfsparser_v1_0_0.js | 5 ++--- src/ol/parser/ogc/wfsparser_v1_1_0.js | 2 +- test/spec/ol/parser/ogc/wfs_v1_0_0.test.js | 14 ++++++++++++++ .../ol/parser/ogc/xml/wfs_v1_0_0/getfeature0.xml | 11 +++++++++++ 5 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1_0_0/getfeature0.xml diff --git a/src/ol/parser/ogc/wfsparser_v1.js b/src/ol/parser/ogc/wfsparser_v1.js index ddee7c2079..dd1acc1f9e 100644 --- a/src/ol/parser/ogc/wfsparser_v1.js +++ b/src/ol/parser/ogc/wfsparser_v1.js @@ -42,11 +42,13 @@ ol.parser.ogc.WFS_v1 = function(opt_options) { 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); } + this.setAttributeNS( + node, 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation', this.schemaLocation); return node; } }; diff --git a/src/ol/parser/ogc/wfsparser_v1_0_0.js b/src/ol/parser/ogc/wfsparser_v1_0_0.js index 0387940f67..f2b55fe5ac 100644 --- a/src/ol/parser/ogc/wfsparser_v1_0_0.js +++ b/src/ol/parser/ogc/wfsparser_v1_0_0.js @@ -63,9 +63,8 @@ ol.parser.ogc.WFS_v1_0_0 = function(opt_options) { } 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); + this.writeNode('PropertyName', options.propertyNames[i], + 'http://www.opengis.net/ogc', node); } } if (goog.isDef(options.filter)) { diff --git a/src/ol/parser/ogc/wfsparser_v1_1_0.js b/src/ol/parser/ogc/wfsparser_v1_1_0.js index fee3992e66..d44ec90dc8 100644 --- a/src/ol/parser/ogc/wfsparser_v1_1_0.js +++ b/src/ol/parser/ogc/wfsparser_v1_1_0.js @@ -77,7 +77,7 @@ ol.parser.ogc.WFS_v1_1_0 = function() { } } if (goog.isDef(options.filter)) { - this.writeNode('ogc:Filter', options.filter, + this.writeNode('Filter', options.filter, 'http://www.opengis.net/ogc', node); } return node; 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 index 716d95f644..c611aab39e 100644 --- a/test/spec/ol/parser/ogc/wfs_v1_0_0.test.js +++ b/test/spec/ol/parser/ogc/wfs_v1_0_0.test.js @@ -37,6 +37,20 @@ describe('ol.parser.ogc.WFS_v1_0_0', function() { }); }); + it('handles writing GetFeature with PropertyName', function(done) { + var url = 'spec/ol/parser/ogc/xml/wfs_v1_0_0/getfeature0.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 output = p.writers[p.defaultNamespaceURI]['GetFeature'].apply( + p, [{propertyNames: [new ol.expr.Identifier('STATE_NAME'), + new ol.expr.Identifier('STATE_FIPS'), + new ol.expr.Identifier('STATE_ABBR')]}]); + expect(goog.dom.xml.loadXml(p.serialize(output))).to.xmleql(xml); + done(); + }); + }); + }); }); diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1_0_0/getfeature0.xml b/test/spec/ol/parser/ogc/xml/wfs_v1_0_0/getfeature0.xml new file mode 100644 index 0000000000..8d7e136cea --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1_0_0/getfeature0.xml @@ -0,0 +1,11 @@ + + + STATE_NAME + STATE_FIPS + STATE_ABBR + + From d2ac206ca3bf617a339bfd7a57177ec4533faae4 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Tue, 22 Oct 2013 20:34:55 +0200 Subject: [PATCH 36/66] starting to add tests for WFS 1.1.0 make sure we don't override the wfs:FeatureCollection reader with the one from GML --- src/ol/parser/ogc/wfsparser_v1.js | 34 +++++++++++++++++ src/ol/parser/ogc/wfsparser_v1_0_0.js | 20 +--------- src/ol/parser/ogc/wfsparser_v1_1_0.js | 9 +++-- test/spec/ol/parser/ogc/wfs_v1_1_0.test.js | 38 +++++++++++++++++++ .../ogc/xml/wfs_v1_1_0/NumberOfFeatures.xml | 9 +++++ .../xml/wfs_v1_1_0/TransactionResponse.xml | 16 ++++++++ 6 files changed, 104 insertions(+), 22 deletions(-) create mode 100644 test/spec/ol/parser/ogc/wfs_v1_1_0.test.js create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1_1_0/NumberOfFeatures.xml create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1_1_0/TransactionResponse.xml diff --git a/src/ol/parser/ogc/wfsparser_v1.js b/src/ol/parser/ogc/wfsparser_v1.js index dd1acc1f9e..531287c5ab 100644 --- a/src/ol/parser/ogc/wfsparser_v1.js +++ b/src/ol/parser/ogc/wfsparser_v1.js @@ -57,6 +57,40 @@ ol.parser.ogc.WFS_v1 = function(opt_options) { goog.inherits(ol.parser.ogc.WFS_v1, ol.parser.XML); +/** + * @param {ol.parser.ogc.Filter_v1_0_0|ol.parser.ogc.Filter_v1_1_0} filter The + * Filter parser to use. + * @protected + */ +ol.parser.ogc.WFS_v1.prototype.setFilterParser = function(filter) { + this.filter_ = filter; + for (var uri in this.filter_.readers) { + for (var key in this.filter_.readers[uri]) { + if (!goog.isDef(this.readers[uri])) { + this.readers[uri] = {}; + } + // do not overwrite any readers + if (!goog.isDef(this.readers[uri][key])) { + 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] = {}; + } + // do not overwrite any writers + if (!goog.isDef(this.writers[uri][key])) { + this.writers[uri][key] = goog.bind(this.filter_.writers[uri][key], + this.filter_); + } + } + } +}; + + /** * @param {string|Document|Element} data Data to read. * @return {Object} An object representing the document. diff --git a/src/ol/parser/ogc/wfsparser_v1_0_0.js b/src/ol/parser/ogc/wfsparser_v1_0_0.js index f2b55fe5ac..df2e93f691 100644 --- a/src/ol/parser/ogc/wfsparser_v1_0_0.js +++ b/src/ol/parser/ogc/wfsparser_v1_0_0.js @@ -74,25 +74,7 @@ ol.parser.ogc.WFS_v1_0_0 = function(opt_options) { 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_); - } - } + this.setFilterParser(new ol.parser.ogc.Filter_v1_0_0()); }; 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 index d44ec90dc8..016fd10a7d 100644 --- a/src/ol/parser/ogc/wfsparser_v1_1_0.js +++ b/src/ol/parser/ogc/wfsparser_v1_1_0.js @@ -2,6 +2,7 @@ goog.provide('ol.parser.ogc.WFS_v1_1_0'); goog.require('goog.functions'); goog.require('goog.object'); +goog.require('ol.parser.ogc.Filter_v1_1_0'); goog.require('ol.parser.ogc.WFS_v1'); @@ -36,9 +37,11 @@ ol.parser.ogc.WFS_v1_1_0 = function() { this.readChildNodes(node, obj); }, 'Feature': function(node, container) { - var obj = {fids: []}; + var obj = {}; this.readChildNodes(node, obj); - container.insertIds.push(obj.fids[0]); + for (var key in obj.fids) { + container.insertIds.push(key); + } } }); goog.object.extend(this.writers[this.defaultNamespaceURI], { @@ -88,7 +91,7 @@ ol.parser.ogc.WFS_v1_1_0 = function() { return node; } }); - // TODO ogc and gml namespaces + this.setFilterParser(new ol.parser.ogc.Filter_v1_1_0()); }; goog.inherits(ol.parser.ogc.WFS_v1_1_0, ol.parser.ogc.WFS_v1); diff --git a/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js b/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js new file mode 100644 index 0000000000..2af072d81a --- /dev/null +++ b/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js @@ -0,0 +1,38 @@ +goog.provide('ol.test.parser.ogc.WFS_v1_1_0'); + +describe('ol.parser.ogc.WFS_v1_1_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_1_0/TransactionResponse.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.1.0'); + expect(obj.success).to.be(true); + done(); + }); + }); + + it('handles read of number of features', function(done) { + var url = 'spec/ol/parser/ogc/xml/wfs_v1_1_0/NumberOfFeatures.xml'; + afterLoadXml(url, function(xml) { + // the XML does not contain a version attribute on the root node + var p = new ol.parser.ogc.WFS_v1_1_0(); + var obj = p.read(xml); + expect(obj.numberOfFeatures).to.equal(625); + done(); + }); + }); + }); + +}); + +goog.require('goog.dom.xml'); +goog.require('ol.parser.ogc.WFS'); +goog.require('ol.parser.ogc.WFS_v1_1_0'); diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/NumberOfFeatures.xml b/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/NumberOfFeatures.xml new file mode 100644 index 0000000000..dcc67c37d7 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/NumberOfFeatures.xml @@ -0,0 +1,9 @@ + + + diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/TransactionResponse.xml b/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/TransactionResponse.xml new file mode 100644 index 0000000000..6a64bddeca --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/TransactionResponse.xml @@ -0,0 +1,16 @@ + + + 0 + 1 + 0 + + + + + + + + + + + From 3184fb02e551062924166f1999b4c1f2349a6002 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Wed, 23 Oct 2013 11:46:57 +0200 Subject: [PATCH 37/66] ability to read boundedBy from FeatureCollection --- src/ol/parser/ogc/gmlparser.js | 3 ++ src/ol/parser/ogc/wfsparser_v1_1_0.js | 12 +++-- test/spec/ol/parser/ogc/wfs_v1_1_0.test.js | 15 ++++++ .../parser/ogc/xml/wfs_v1_1_0/boundedBy.xml | 47 +++++++++++++++++++ 4 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1_1_0/boundedBy.xml diff --git a/src/ol/parser/ogc/gmlparser.js b/src/ol/parser/ogc/gmlparser.js index 088cfe13ac..6431298975 100644 --- a/src/ol/parser/ogc/gmlparser.js +++ b/src/ol/parser/ogc/gmlparser.js @@ -148,6 +148,9 @@ ol.parser.ogc.GML = function(opt_options) { 'polygonMember': function(node, obj) { this.readChildNodes(node, obj); }, + 'boundedBy': function(node, obj) { + this.readChildNodes(node, obj); + }, 'Point': function(node, container) { var coordinates = []; this.readers[this.defaultNamespaceURI]['_inherit'].apply(this, diff --git a/src/ol/parser/ogc/wfsparser_v1_1_0.js b/src/ol/parser/ogc/wfsparser_v1_1_0.js index 016fd10a7d..e788cd4b5b 100644 --- a/src/ol/parser/ogc/wfsparser_v1_1_0.js +++ b/src/ol/parser/ogc/wfsparser_v1_1_0.js @@ -9,18 +9,22 @@ 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_1_0 = function() { - goog.base(this); +ol.parser.ogc.WFS_v1_1_0 = function(opt_options) { + goog.base(this, opt_options); 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); + var numberOfFeatures = node.getAttribute('numberOfFeatures'); + if (!goog.isNull(numberOfFeatures)) { + obj.numberOfFeatures = parseInt(numberOfFeatures, 10); + } }, this.readers['http://www.opengis.net/wfs']['FeatureCollection'] ), diff --git a/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js b/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js index 2af072d81a..5027f1d4f6 100644 --- a/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js +++ b/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js @@ -29,6 +29,21 @@ describe('ol.parser.ogc.WFS_v1_1_0', function() { done(); }); }); + + it('handles read of boundedBy on the FeatureCollection', function(done) { + var url = 'spec/ol/parser/ogc/xml/wfs_v1_1_0/boundedBy.xml'; + afterLoadXml(url, function(xml) { + // the XML does not contain a version attribute on the root node + var p = new ol.parser.ogc.WFS_v1_1_0(); + var obj = p.read(xml); + expect(obj.bounds[0]).to.equal(3197.88); + expect(obj.bounds[1]).to.equal(306457.313); + expect(obj.bounds[2]).to.equal(280339.156); + expect(obj.bounds[3]).to.equal(613850.438); + done(); + }); + }); + }); }); diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/boundedBy.xml b/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/boundedBy.xml new file mode 100644 index 0000000000..4daeb2eadc --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/boundedBy.xml @@ -0,0 +1,47 @@ + + + + 3197.880000 306457.313000 + 280339.156000 613850.438000 + + + + + + + + 196507.469000 502347.938000 + 202430.844000 510383.719000 + + + + + + + + + + 200448.047000 510383.719000 198475.031000 509253.875000 198477.422000 507339.688000 196507.469000 505841.969000 196507.625000 504980.281000 196621.359000 505029.969000 196825.328000 505114.000000 197310.031000 505183.469000 197636.609000 505148.750000 197837.594000 505061.563000 197941.031000 504953.688000 198003.094000 504817.719000 198023.781000 504721.688000 198016.391000 504597.531000 197907.234000 504363.219000 197716.734000 504013.969000 197700.156000 503567.563000 197775.531000 503373.969000 197930.688000 503153.781000 198034.234000 503045.594000 198170.078000 502932.125000 198504.047000 502725.250000 198858.719000 502550.875000 199138.000000 502460.719000 199336.000000 502347.938000 199044.125000 504910.969000 199549.359000 507065.781000 200280.594000 506878.938000 202430.844000 507474.625000 202430.844000 508850.906000 200448.047000 510383.719000 + + + + + + + + 791 + 1800.89 + 4620 + + + 0 + 24305.1 + + + From 4df848fae001f5490eef5aad4ee6261b354dc00a Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Wed, 23 Oct 2013 13:15:16 +0200 Subject: [PATCH 38/66] need the ability to set axisOrientation We need to be able to set axisOrientation on the underlying GML parser of the Filter subparser of ol.parser.WFS*, also only options and no properties on the instance anymore --- src/ol/parser/ogc/filterparser_v1.js | 8 +++++ src/ol/parser/ogc/wfsparser.js | 3 +- src/ol/parser/ogc/wfsparser_v1.js | 21 ++++++------- src/ol/parser/ogc/wfsparser_v1_0_0.js | 20 ++++--------- src/ol/parser/ogc/wfsparser_v1_1_0.js | 18 ++++------- test/spec/ol/parser/ogc/wfs_v1_0_0.test.js | 24 ++++++++++----- test/spec/ol/parser/ogc/wfs_v1_1_0.test.js | 30 +++++++++++++++++++ .../ol/parser/ogc/xml/wfs_v1_1_0/query0.xml | 11 +++++++ 8 files changed, 87 insertions(+), 48 deletions(-) create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1_1_0/query0.xml diff --git a/src/ol/parser/ogc/filterparser_v1.js b/src/ol/parser/ogc/filterparser_v1.js index f0a21995ab..38bd33d2c4 100644 --- a/src/ol/parser/ogc/filterparser_v1.js +++ b/src/ol/parser/ogc/filterparser_v1.js @@ -580,6 +580,14 @@ ol.parser.ogc.Filter_v1.prototype.aggregateLogical_ = function(filters, }; +/** + * @return {ol.parser.ogc.GML_v2|ol.parser.ogc.GML_v3} + */ +ol.parser.ogc.Filter_v1.prototype.getGmlParser = function() { + return this.gml_; +}; + + /** * @param {ol.parser.ogc.GML_v2|ol.parser.ogc.GML_v3} gml The GML parser to * use. diff --git a/src/ol/parser/ogc/wfsparser.js b/src/ol/parser/ogc/wfsparser.js index 68e3141326..33625798bd 100644 --- a/src/ol/parser/ogc/wfsparser.js +++ b/src/ol/parser/ogc/wfsparser.js @@ -19,8 +19,7 @@ ol.ENABLE_WFS_1_1_0 = true; /** * @constructor - * @param {ol.parser.WFSOptions=} opt_options - * Optional configuration object. + * @param {Object=} opt_options Options which will be set on this object. * @extends {ol.parser.ogc.Versioned} */ ol.parser.ogc.WFS = function(opt_options) { diff --git a/src/ol/parser/ogc/wfsparser_v1.js b/src/ol/parser/ogc/wfsparser_v1.js index 531287c5ab..73b12f4ff3 100644 --- a/src/ol/parser/ogc/wfsparser_v1.js +++ b/src/ol/parser/ogc/wfsparser_v1.js @@ -6,16 +6,9 @@ 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; - } +ol.parser.ogc.WFS_v1 = function() { this.defaultNamespaceURI = 'http://www.opengis.net/wfs'; // TODO set errorProperty this.readers = {}; @@ -42,8 +35,8 @@ ol.parser.ogc.WFS_v1 = function(opt_options) { node.setAttribute('maxFeatures', options.maxFeatures); } } - for (var i = 0, ii = this.featureTypes.length; i < ii; i++) { - options.featureType = this.featureTypes[i]; + for (var i = 0, ii = options.featureTypes.length; i < ii; i++) { + options.featureType = options.featureTypes[i]; this.writeNode('Query', options, null, node); } this.setAttributeNS( @@ -57,6 +50,14 @@ ol.parser.ogc.WFS_v1 = function(opt_options) { goog.inherits(ol.parser.ogc.WFS_v1, ol.parser.XML); +/** + * @return {ol.parser.ogc.Filter_v1_0_0|ol.parser.ogc.Filter_v1_1_0} + */ +ol.parser.ogc.WFS_v1.prototype.getFilterParser = function() { + return this.filter_; +}; + + /** * @param {ol.parser.ogc.Filter_v1_0_0|ol.parser.ogc.Filter_v1_1_0} filter The * Filter parser to use. diff --git a/src/ol/parser/ogc/wfsparser_v1_0_0.js b/src/ol/parser/ogc/wfsparser_v1_0_0.js index df2e93f691..1fc20a5bb4 100644 --- a/src/ol/parser/ogc/wfsparser_v1_0_0.js +++ b/src/ol/parser/ogc/wfsparser_v1_0_0.js @@ -9,12 +9,10 @@ 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); +ol.parser.ogc.WFS_v1_0_0 = function() { + goog.base(this); this.version = '1.0.0'; this.schemaLocation = this.defaultNamespaceURI + ' ' + 'http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd'; @@ -43,23 +41,15 @@ ol.parser.ogc.WFS_v1_0_0 = function(opt_options) { }); 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 prefix = goog.isDef(options.featurePrefix) ? options.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.featureNS)) { + node.setAttribute('xmlns:' + options.featurePrefix, options.featureNS); } if (goog.isDef(options.propertyNames)) { for (var i = 0, ii = options.propertyNames.length; i < ii; i++) { diff --git a/src/ol/parser/ogc/wfsparser_v1_1_0.js b/src/ol/parser/ogc/wfsparser_v1_1_0.js index e788cd4b5b..4889ccf1aa 100644 --- a/src/ol/parser/ogc/wfsparser_v1_1_0.js +++ b/src/ol/parser/ogc/wfsparser_v1_1_0.js @@ -9,12 +9,10 @@ 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_1_0 = function(opt_options) { - goog.base(this, opt_options); +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'; @@ -62,19 +60,13 @@ ol.parser.ogc.WFS_v1_1_0 = function(opt_options) { 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 prefix = goog.isDef(options.featurePrefix) ? options.featurePrefix + + ':' : ''; 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); + node.setAttribute('xmlns:' + options.featurePrefix, options.featureNS); } if (goog.isDef(options.propertyNames)) { for (var i = 0, ii = options.propertyNames.length; i < ii; i++) { 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 index c611aab39e..bab4dd44dc 100644 --- a/test/spec/ol/parser/ogc/wfs_v1_0_0.test.js +++ b/test/spec/ol/parser/ogc/wfs_v1_0_0.test.js @@ -22,8 +22,7 @@ describe('ol.parser.ogc.WFS_v1_0_0', function() { 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 p = new ol.parser.ogc.WFS_v1_0_0(); var filter = new ol.expr.Call( new ol.expr.Identifier(ol.expr.functions.EXTENT), [new ol.expr.Literal(1), new ol.expr.Literal(2), @@ -31,7 +30,12 @@ describe('ol.parser.ogc.WFS_v1_0_0', function() { undefined, new ol.expr.Identifier('the_geom')]); var output = p.writers[p.defaultNamespaceURI]['Query'].apply( - p, [{filter: filter, featureType: 'states'}]); + p, [{ + filter: filter, + featureType: 'states', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp' + }]); expect(goog.dom.xml.loadXml(p.serialize(output))).to.xmleql(xml); done(); }); @@ -40,12 +44,16 @@ describe('ol.parser.ogc.WFS_v1_0_0', function() { it('handles writing GetFeature with PropertyName', function(done) { var url = 'spec/ol/parser/ogc/xml/wfs_v1_0_0/getfeature0.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 p = new ol.parser.ogc.WFS_v1_0_0(); var output = p.writers[p.defaultNamespaceURI]['GetFeature'].apply( - p, [{propertyNames: [new ol.expr.Identifier('STATE_NAME'), - new ol.expr.Identifier('STATE_FIPS'), - new ol.expr.Identifier('STATE_ABBR')]}]); + p, [{ + propertyNames: [new ol.expr.Identifier('STATE_NAME'), + new ol.expr.Identifier('STATE_FIPS'), + new ol.expr.Identifier('STATE_ABBR')], + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'] + }]); expect(goog.dom.xml.loadXml(p.serialize(output))).to.xmleql(xml); done(); }); diff --git a/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js b/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js index 5027f1d4f6..4ae4b1b672 100644 --- a/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js +++ b/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js @@ -44,10 +44,40 @@ describe('ol.parser.ogc.WFS_v1_1_0', function() { }); }); + it('handles writing Query with BBOX Filter', function(done) { + var url = 'spec/ol/parser/ogc/xml/wfs_v1_1_0/query0.xml'; + afterLoadXml(url, function(xml) { + var p = new ol.parser.ogc.WFS_v1_1_0(); + var srs = 'urn:ogc:def:crs:EPSG::4326'; + 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), + new ol.expr.Literal(srs), + new ol.expr.Identifier('the_geom')]); + p.getFilterParser().getGmlParser().axisOrientation = + ol.proj.get(srs).getAxisOrientation(); + var output = p.writers[p.defaultNamespaceURI]['Query'].apply( + p, [{ + srsName: srs, + filter: filter, + featureType: 'states', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp' + }]); + 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_1_0'); +goog.require('ol.proj'); diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/query0.xml b/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/query0.xml new file mode 100644 index 0000000000..30be0808f7 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/query0.xml @@ -0,0 +1,11 @@ + + + + the_geom + + 1 2 + 3 4 + + + + From a77632a88cd6a49ef77a2579d7f7dab96d5bbdcb Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Wed, 23 Oct 2013 14:54:21 +0200 Subject: [PATCH 39/66] write out GetFeature in WFS 1.1 --- src/ol/parser/ogc/wfsparser_v1.js | 2 +- src/ol/parser/ogc/wfsparser_v1_0_0.js | 7 ++++ src/ol/parser/ogc/wfsparser_v1_1_0.js | 38 +++++++++++-------- test/spec/ol/parser/ogc/wfs_v1_1_0.test.js | 21 ++++++++++ .../parser/ogc/xml/wfs_v1_1_0/getfeature0.xml | 11 ++++++ 5 files changed, 62 insertions(+), 17 deletions(-) create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1_1_0/getfeature0.xml diff --git a/src/ol/parser/ogc/wfsparser_v1.js b/src/ol/parser/ogc/wfsparser_v1.js index 73b12f4ff3..d4ea23f0d1 100644 --- a/src/ol/parser/ogc/wfsparser_v1.js +++ b/src/ol/parser/ogc/wfsparser_v1.js @@ -42,7 +42,7 @@ ol.parser.ogc.WFS_v1 = function() { this.setAttributeNS( node, 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation', this.schemaLocation); - return node; + return {node: node, options: options}; } }; goog.base(this); diff --git a/src/ol/parser/ogc/wfsparser_v1_0_0.js b/src/ol/parser/ogc/wfsparser_v1_0_0.js index 1fc20a5bb4..602817f5a4 100644 --- a/src/ol/parser/ogc/wfsparser_v1_0_0.js +++ b/src/ol/parser/ogc/wfsparser_v1_0_0.js @@ -1,6 +1,7 @@ goog.provide('ol.parser.ogc.WFS_v1_0_0'); goog.require('goog.array'); +goog.require('goog.functions'); goog.require('goog.object'); goog.require('ol.parser.ogc.Filter_v1_0_0'); goog.require('ol.parser.ogc.WFS_v1'); @@ -40,6 +41,12 @@ ol.parser.ogc.WFS_v1_0_0 = function() { } }); goog.object.extend(this.writers[this.defaultNamespaceURI], { + 'GetFeature': goog.functions.compose( + function(obj) { + return obj.node; + }, + this.writers['http://www.opengis.net/wfs']['GetFeature'] + ), 'Query': function(options) { var prefix = goog.isDef(options.featurePrefix) ? options.featurePrefix + ':' : ''; diff --git a/src/ol/parser/ogc/wfsparser_v1_1_0.js b/src/ol/parser/ogc/wfsparser_v1_1_0.js index 4889ccf1aa..b17488b1d7 100644 --- a/src/ol/parser/ogc/wfsparser_v1_1_0.js +++ b/src/ol/parser/ogc/wfsparser_v1_1_0.js @@ -1,7 +1,9 @@ goog.provide('ol.parser.ogc.WFS_v1_1_0'); +goog.require('goog.asserts'); goog.require('goog.functions'); goog.require('goog.object'); +goog.require('ol.expr.Identifier'); goog.require('ol.parser.ogc.Filter_v1_1_0'); goog.require('ol.parser.ogc.WFS_v1'); @@ -47,18 +49,23 @@ ol.parser.ogc.WFS_v1_1_0 = function() { } }); 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; - }, + 'GetFeature': goog.functions.compose( + function(obj) { + var options = obj.options; + var node = obj.node; + if (goog.isDef(options)) { + node.setAttribute('resultType', options.resultType); + if (goog.isDef(options.startIndex)) { + node.setAttribute('startIndex', options.startIndex); + } + if (goog.isDef(options.count)) { + node.setAttribute('count', options.count); + } + } + return node; + }, + this.writers['http://www.opengis.net/wfs']['GetFeature'] + ), 'Query': function(options) { var prefix = goog.isDef(options.featurePrefix) ? options.featurePrefix + ':' : ''; @@ -70,9 +77,7 @@ ol.parser.ogc.WFS_v1_1_0 = function() { } 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); + this.writeNode('PropertyName', options.propertyNames[i], null, node); } } if (goog.isDef(options.filter)) { @@ -82,8 +87,9 @@ ol.parser.ogc.WFS_v1_1_0 = function() { return node; }, 'PropertyName': function(obj) { + goog.asserts.assertInstanceof(obj, ol.expr.Identifier); var node = this.createElementNS('wfs:PropertyName'); - node.appendChild(this.createTextNode(obj.property)); + node.appendChild(this.createTextNode(obj.getName())); return node; } }); diff --git a/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js b/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js index 4ae4b1b672..c92d02eeea 100644 --- a/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js +++ b/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js @@ -68,6 +68,27 @@ describe('ol.parser.ogc.WFS_v1_1_0', function() { expect(goog.dom.xml.loadXml(p.serialize(output))).to.xmleql(xml); done(); }); + + }); + + it('handles writing GetFeature with PropertyName', function(done) { + var url = 'spec/ol/parser/ogc/xml/wfs_v1_1_0/getfeature0.xml'; + afterLoadXml(url, function(xml) { + var p = new ol.parser.ogc.WFS_v1_1_0(); + var output = p.writers[p.defaultNamespaceURI]['GetFeature'].apply( + p, [{ + resultType: 'hits', + srsName: 'urn:ogc:def:crs:EPSG::4326', + propertyNames: [new ol.expr.Identifier('STATE_NAME'), + new ol.expr.Identifier('STATE_FIPS'), + new ol.expr.Identifier('STATE_ABBR')], + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'] + }]); + expect(goog.dom.xml.loadXml(p.serialize(output))).to.xmleql(xml); + done(); + }); }); }); diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/getfeature0.xml b/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/getfeature0.xml new file mode 100644 index 0000000000..7449d7d826 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/getfeature0.xml @@ -0,0 +1,11 @@ + + + STATE_NAME + STATE_FIPS + STATE_ABBR + + From c4e7d4437cb87a7302c65aaf45ad068150948093 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Wed, 23 Oct 2013 15:24:23 +0200 Subject: [PATCH 40/66] port over another WFS 1.1 testcase --- src/ol/parser/ogc/wfsparser_v1_1_0.js | 4 +++- test/spec/ol/parser/ogc/wfs_v1_1_0.test.js | 20 ++++++++++++++++++- .../parser/ogc/xml/wfs_v1_1_0/getfeature1.xml | 8 ++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1_1_0/getfeature1.xml diff --git a/src/ol/parser/ogc/wfsparser_v1_1_0.js b/src/ol/parser/ogc/wfsparser_v1_1_0.js index b17488b1d7..02452be120 100644 --- a/src/ol/parser/ogc/wfsparser_v1_1_0.js +++ b/src/ol/parser/ogc/wfsparser_v1_1_0.js @@ -54,7 +54,9 @@ ol.parser.ogc.WFS_v1_1_0 = function() { var options = obj.options; var node = obj.node; if (goog.isDef(options)) { - node.setAttribute('resultType', options.resultType); + if (goog.isDef(options.resultType)) { + node.setAttribute('resultType', options.resultType); + } if (goog.isDef(options.startIndex)) { node.setAttribute('startIndex', options.startIndex); } diff --git a/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js b/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js index c92d02eeea..1e24d2ccbc 100644 --- a/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js +++ b/test/spec/ol/parser/ogc/wfs_v1_1_0.test.js @@ -71,7 +71,7 @@ describe('ol.parser.ogc.WFS_v1_1_0', function() { }); - it('handles writing GetFeature with PropertyName', function(done) { + it('handles writing GetFeature with resultType hits', function(done) { var url = 'spec/ol/parser/ogc/xml/wfs_v1_1_0/getfeature0.xml'; afterLoadXml(url, function(xml) { var p = new ol.parser.ogc.WFS_v1_1_0(); @@ -91,6 +91,24 @@ describe('ol.parser.ogc.WFS_v1_1_0', function() { }); }); + it('handles writing GetFeature with paging info', function(done) { + var url = 'spec/ol/parser/ogc/xml/wfs_v1_1_0/getfeature1.xml'; + afterLoadXml(url, function(xml) { + var p = new ol.parser.ogc.WFS_v1_1_0(); + var output = p.writers[p.defaultNamespaceURI]['GetFeature'].apply( + p, [{ + count: 10, + startIndex: 20, + srsName: 'urn:ogc:def:crs:EPSG::4326', + featureNS: 'http://www.openplans.org/topp', + featurePrefix: 'topp', + featureTypes: ['states'] + }]); + expect(goog.dom.xml.loadXml(p.serialize(output))).to.xmleql(xml); + done(); + }); + }); + }); }); diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/getfeature1.xml b/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/getfeature1.xml new file mode 100644 index 0000000000..f79e579d08 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1_1_0/getfeature1.xml @@ -0,0 +1,8 @@ + + + + From 46b79d88f88dc2a24708619f8ad851f2a947741d Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Wed, 23 Oct 2013 17:04:20 +0200 Subject: [PATCH 41/66] ability to parse a FeatureCollection The trick here was to delete featureNS so autoConfig would kick in --- src/ol/parser/ogc/wfsparser_v1_0_0.js | 4 +- test/spec/ol/parser/ogc/wfs_v1.test.js | 23 +++++++++++ .../ogc/xml/wfs_v1/FeatureCollection.xml | 41 +++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 test/spec/ol/parser/ogc/wfs_v1.test.js create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1/FeatureCollection.xml diff --git a/src/ol/parser/ogc/wfsparser_v1_0_0.js b/src/ol/parser/ogc/wfsparser_v1_0_0.js index 602817f5a4..28e64c98b8 100644 --- a/src/ol/parser/ogc/wfsparser_v1_0_0.js +++ b/src/ol/parser/ogc/wfsparser_v1_0_0.js @@ -71,7 +71,9 @@ ol.parser.ogc.WFS_v1_0_0 = function() { return node; } }); - this.setFilterParser(new ol.parser.ogc.Filter_v1_0_0()); + var filter = new ol.parser.ogc.Filter_v1_0_0(); + delete filter.getGmlParser().featureNS; + this.setFilterParser(filter); }; goog.inherits(ol.parser.ogc.WFS_v1_0_0, ol.parser.ogc.WFS_v1); diff --git a/test/spec/ol/parser/ogc/wfs_v1.test.js b/test/spec/ol/parser/ogc/wfs_v1.test.js new file mode 100644 index 0000000000..db767e5fc0 --- /dev/null +++ b/test/spec/ol/parser/ogc/wfs_v1.test.js @@ -0,0 +1,23 @@ +goog.provide('ol.test.parser.ogc.WFS_v1'); + +describe('ol.parser.ogc.WFS', function() { + + var parser = new ol.parser.ogc.WFS(); + + describe('reading and writing', function() { + + it('handles read of FeatureCollection', function(done) { + var url = 'spec/ol/parser/ogc/xml/wfs_v1/FeatureCollection.xml'; + afterLoadXml(url, function(xml) { + var obj = parser.read(xml); + expect(obj.features.length).to.equal(1); + done(); + }); + }); + + }); + +}); + +goog.require('goog.dom.xml'); +goog.require('ol.parser.ogc.WFS'); diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1/FeatureCollection.xml b/test/spec/ol/parser/ogc/xml/wfs_v1/FeatureCollection.xml new file mode 100644 index 0000000000..bb7f9d2ad0 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1/FeatureCollection.xml @@ -0,0 +1,41 @@ + + + + + + + + + + -75.70742,38.557476 -75.71106,38.649551 -75.724937,38.83017 -75.752922,39.141548 -75.761658,39.247753 -75.764664,39.295849 -75.772697,39.383007 -75.791435,39.723755 -75.775269,39.724442 -75.745934,39.774818 -75.695114,39.820347 -75.644341,39.838196 -75.583794,39.840008 -75.470345,39.826435 -75.42083,39.79887 -75.412117,39.789658 -75.428009,39.77813 -75.460754,39.763248 -75.475128,39.741718 -75.476334,39.719971 -75.489639,39.714745 -75.610725,39.612793 -75.562996,39.566723 -75.590187,39.463768 -75.515572,39.36694 -75.402481,39.257637 -75.397728,39.073036 -75.324852,39.012386 -75.307899,38.945911 -75.190941,38.80867 -75.083138,38.799812 -75.045998,38.44949 -75.068298,38.449963 -75.093094,38.450451 -75.350204,38.455208 -75.69915,38.463066 -75.70742,38.557476 + + + + + + + Delaware + 10 + S Atl + DE + 5062.456 + 1385.022 + 666168.0 + 175867.0 + 247497.0 + 322968.0 + 343200.0 + 247566.0 + 258087.0 + 42968.0 + 8069.0 + 335147.0 + 13945.0 + 87973.0 + 44140.0 + 0.485 + 0.515 + 102776.0 + + + From c9569ed9e240d2af6f27b7b82b025b9e63472d2b Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Thu, 24 Oct 2013 14:51:04 +0200 Subject: [PATCH 42/66] more tests and typedefs. Once feature editing is more stable, this format can be extended with Update, Delete and Insert --- src/objectliterals.jsdoc | 11 ++- src/ol/parser/ogc/wfsparser_v1.js | 43 ++++++++++- test/spec/ol/parser/ogc/wfs_v1.test.js | 71 +++++++++++++++++++ .../ol/parser/ogc/xml/wfs_v1/GetFeature.xml | 3 + .../ogc/xml/wfs_v1/GetFeatureMultiple.xml | 4 ++ test/spec/ol/parser/ogc/xml/wfs_v1/Native.xml | 1 + .../ol/parser/ogc/xml/wfs_v1/Transaction.xml | 1 + 7 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1/GetFeature.xml create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1/GetFeatureMultiple.xml create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1/Native.xml create mode 100644 test/spec/ol/parser/ogc/xml/wfs_v1/Transaction.xml diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index ef153d8169..1de0782977 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -597,9 +597,16 @@ /** * @typedef {Object} ol.parser.WFSOptions + * @property {string} featureNS The feature namespace to use. + * @property {string} featurePrefix The prefix for the namespace. * @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.parser.WFSNative + * @property {string} vendorId The vendor id to use. + * @property {boolean} safeToIgnore Is it safe to ignore? + * @property {string} value The value of the Native element. */ /** diff --git a/src/ol/parser/ogc/wfsparser_v1.js b/src/ol/parser/ogc/wfsparser_v1.js index d4ea23f0d1..fff9f4fba2 100644 --- a/src/ol/parser/ogc/wfsparser_v1.js +++ b/src/ol/parser/ogc/wfsparser_v1.js @@ -21,6 +21,7 @@ ol.parser.ogc.WFS_v1 = function() { this.writers = {}; this.writers[this.defaultNamespaceURI] = { 'GetFeature': function(options) { + options = /** @type {ol.parser.WFSOptions} */(options); var node = this.createElementNS('wfs:GetFeature'); node.setAttribute('service', 'WFS'); node.setAttribute('version', this.version); @@ -43,6 +44,46 @@ ol.parser.ogc.WFS_v1 = function() { node, 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation', this.schemaLocation); return {node: node, options: options}; + }, + 'Transaction': function(obj) { + obj = obj || {}; + var options = obj.options || {}; + 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); + } + } + } + if (goog.isDef(options.nativeElements)) { + for (i = 0, ii = options.nativeElements.length; i < ii; ++i) { + this.writeNode('Native', options.nativeElements[i], null, node); + } + } + return node; + }, + '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; } }; goog.base(this); @@ -115,7 +156,7 @@ ol.parser.ogc.WFS_v1.prototype.read = function(data) { * @return {string} A serialized WFS transaction. */ ol.parser.ogc.WFS_v1.prototype.write = function(features, options) { - var root = this.writeNode('wfs:Transaction', {features: features, + var root = this.writeNode('Transaction', {features: features, options: options}); this.setAttributeNS( root, 'http://www.w3.org/2001/XMLSchema-instance', diff --git a/test/spec/ol/parser/ogc/wfs_v1.test.js b/test/spec/ol/parser/ogc/wfs_v1.test.js index db767e5fc0..a64b00b203 100644 --- a/test/spec/ol/parser/ogc/wfs_v1.test.js +++ b/test/spec/ol/parser/ogc/wfs_v1.test.js @@ -15,9 +15,80 @@ describe('ol.parser.ogc.WFS', function() { }); }); + it('handles writing out GetFeature with a handle', function(done) { + 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); + done(); + }); + }); + + it('handles writing out Transaction with a handle', function(done) { + 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); + done(); + }); + }); + + it('handles writing out Native', function(done) { + 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: [{ + vendorId: 'ORACLE', + safeToIgnore: true, + value: 'ALTER SESSION ENABLE PARALLEL DML' + }, { + vendorId: 'ORACLE', + safeToIgnore: false, + value: 'Another native line goes here' + }] + }); + expect(goog.dom.xml.loadXml(output)).to.xmleql(xml); + done(); + }); + }); + + it('handles writing out GetFeature with > 1 typename', function(done) { + 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); + done(); + }); + }); + }); }); goog.require('goog.dom.xml'); 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/GetFeature.xml b/test/spec/ol/parser/ogc/xml/wfs_v1/GetFeature.xml new file mode 100644 index 0000000000..8d597268c0 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1/GetFeature.xml @@ -0,0 +1,3 @@ + + + diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1/GetFeatureMultiple.xml b/test/spec/ol/parser/ogc/xml/wfs_v1/GetFeatureMultiple.xml new file mode 100644 index 0000000000..e1717ddc38 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1/GetFeatureMultiple.xml @@ -0,0 +1,4 @@ + + + + diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1/Native.xml b/test/spec/ol/parser/ogc/xml/wfs_v1/Native.xml new file mode 100644 index 0000000000..d3ff10f227 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1/Native.xml @@ -0,0 +1 @@ +ALTER SESSION ENABLE PARALLEL DMLAnother native line goes here diff --git a/test/spec/ol/parser/ogc/xml/wfs_v1/Transaction.xml b/test/spec/ol/parser/ogc/xml/wfs_v1/Transaction.xml new file mode 100644 index 0000000000..b147dc07f4 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/wfs_v1/Transaction.xml @@ -0,0 +1 @@ + From 279c358af2b4fa5e80794f5d59f56380e62cdbae Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Thu, 24 Oct 2013 15:02:15 +0200 Subject: [PATCH 43/66] same solution for axisOrientation as for WFS 1.1.0 --- src/ol/parser/ogc/gmlparser_v2.js | 3 +-- test/spec/ol/parser/ogc/wfs_v1_0_0.test.js | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ol/parser/ogc/gmlparser_v2.js b/src/ol/parser/ogc/gmlparser_v2.js index 37bdd9d191..837b5d844e 100644 --- a/src/ol/parser/ogc/gmlparser_v2.js +++ b/src/ol/parser/ogc/gmlparser_v2.js @@ -54,8 +54,7 @@ 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 (goog.isDef(this.axisOrientation) && - this.axisOrientation.substr(0, 2) !== 'en') { + if (this.axisOrientation.substr(0, 2) !== 'en') { part[0] = coord[1]; part[1] = coord[0]; } 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 index bab4dd44dc..c616061602 100644 --- a/test/spec/ol/parser/ogc/wfs_v1_0_0.test.js +++ b/test/spec/ol/parser/ogc/wfs_v1_0_0.test.js @@ -29,6 +29,7 @@ describe('ol.parser.ogc.WFS_v1_0_0', function() { new ol.expr.Literal(3), new ol.expr.Literal(4), undefined, new ol.expr.Identifier('the_geom')]); + p.getFilterParser().getGmlParser().axisOrientation = 'enu'; var output = p.writers[p.defaultNamespaceURI]['Query'].apply( p, [{ filter: filter, From d564b5170cd24488de88ecfb4e3bf57d6db51f2e Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Mon, 25 Nov 2013 14:29:00 +0100 Subject: [PATCH 44/66] move the typedefs out of objectliterals --- src/objectliterals.jsdoc | 14 -------------- src/ol/parser/ogc/wfsparser_v1.js | 22 +++++++++++++++++++--- test/spec/ol/parser/ogc/wfs_v1.test.js | 20 +++++++++----------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 1de0782977..d416c8b63e 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -595,20 +595,6 @@ * calculations. */ -/** - * @typedef {Object} ol.parser.WFSOptions - * @property {string} featureNS The feature namespace to use. - * @property {string} featurePrefix The prefix for the namespace. - * @property {Array.} featureTypes The feature types to use. - */ - -/** - * @typedef {Object} ol.parser.WFSNative - * @property {string} vendorId The vendor id to use. - * @property {boolean} safeToIgnore Is it safe to ignore? - * @property {string} value The value of the Native element. - */ - /** * @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 fff9f4fba2..892e7a851f 100644 --- a/src/ol/parser/ogc/wfsparser_v1.js +++ b/src/ol/parser/ogc/wfsparser_v1.js @@ -3,6 +3,22 @@ goog.require('goog.dom.xml'); 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 @@ -21,7 +37,7 @@ ol.parser.ogc.WFS_v1 = function() { this.writers = {}; this.writers[this.defaultNamespaceURI] = { 'GetFeature': function(options) { - options = /** @type {ol.parser.WFSOptions} */(options); + options = /** @type {ol.parser.WFSWriteOptions} */(options); var node = this.createElementNS('wfs:GetFeature'); node.setAttribute('service', 'WFS'); node.setAttribute('version', this.version); @@ -47,7 +63,7 @@ ol.parser.ogc.WFS_v1 = function() { }, 'Transaction': function(obj) { obj = obj || {}; - var options = obj.options || {}; + var options = /** {ol.parser.WFSWriteOptions} */(obj.options || {}); var node = this.createElementNS('wfs:Transaction'); node.setAttribute('service', 'WFS'); node.setAttribute('version', this.version); @@ -152,7 +168,7 @@ ol.parser.ogc.WFS_v1.prototype.read = function(data) { /** * @param {Array.} features The features to write out. - * @param {Object} options Write options. + * @param {ol.parser.WFSWriteOptions} options Write options. * @return {string} A serialized WFS transaction. */ ol.parser.ogc.WFS_v1.prototype.write = function(features, options) { diff --git a/test/spec/ol/parser/ogc/wfs_v1.test.js b/test/spec/ol/parser/ogc/wfs_v1.test.js index a64b00b203..22bf07fc01 100644 --- a/test/spec/ol/parser/ogc/wfs_v1.test.js +++ b/test/spec/ol/parser/ogc/wfs_v1.test.js @@ -52,17 +52,15 @@ 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: [{ - vendorId: 'ORACLE', - safeToIgnore: true, - value: 'ALTER SESSION ENABLE PARALLEL DML' - }, { - vendorId: 'ORACLE', - safeToIgnore: false, - value: 'Another native line goes here' - }] - }); + var output = p.write(null, {nativeElements: [{ + vendorId: 'ORACLE', + safeToIgnore: true, + value: 'ALTER SESSION ENABLE PARALLEL DML' + }, { + vendorId: 'ORACLE', + safeToIgnore: false, + value: 'Another native line goes here' + }]}); expect(goog.dom.xml.loadXml(output)).to.xmleql(xml); done(); }); From 9ed5004aa6f5cdd9f1d142b997db7f22eb8dce7f Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Mon, 25 Nov 2013 15:46:38 +0100 Subject: [PATCH 45/66] rename this.gml_ to this.gmlParser_ --- src/ol/parser/ogc/filterparser_v1.js | 23 ++++++++++++----------- src/ol/parser/ogc/filterparser_v1_0_0.js | 2 +- src/ol/parser/ogc/filterparser_v1_1_0.js | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/ol/parser/ogc/filterparser_v1.js b/src/ol/parser/ogc/filterparser_v1.js index 38bd33d2c4..b141c98279 100644 --- a/src/ol/parser/ogc/filterparser_v1.js +++ b/src/ol/parser/ogc/filterparser_v1.js @@ -182,7 +182,8 @@ ol.parser.ogc.Filter_v1 = function() { var args = [], container = {}; this.readChildNodes(node, container); if (goog.isDef(container.geometry)) { - args.push(new ol.expr.Literal(this.gml_.createGeometry(container))); + args.push(new ol.expr.Literal(this.gmlParser_.createGeometry( + container))); } else { args = [new ol.expr.Literal(container.bounds[0]), new ol.expr.Literal(container.bounds[1]), @@ -584,7 +585,7 @@ ol.parser.ogc.Filter_v1.prototype.aggregateLogical_ = function(filters, * @return {ol.parser.ogc.GML_v2|ol.parser.ogc.GML_v3} */ ol.parser.ogc.Filter_v1.prototype.getGmlParser = function() { - return this.gml_; + return this.gmlParser_; }; @@ -594,23 +595,23 @@ ol.parser.ogc.Filter_v1.prototype.getGmlParser = function() { * @protected */ ol.parser.ogc.Filter_v1.prototype.setGmlParser = function(gml) { - this.gml_ = gml; - for (var uri in this.gml_.readers) { - for (var key in this.gml_.readers[uri]) { + this.gmlParser_ = gml; + for (var uri in this.gmlParser_.readers) { + for (var key in this.gmlParser_.readers[uri]) { if (!goog.isDef(this.readers[uri])) { this.readers[uri] = {}; } - this.readers[uri][key] = goog.bind(this.gml_.readers[uri][key], - this.gml_); + this.readers[uri][key] = goog.bind(this.gmlParser_.readers[uri][key], + this.gmlParser_); } } - for (uri in this.gml_.writers) { - for (key in this.gml_.writers[uri]) { + for (uri in this.gmlParser_.writers) { + for (key in this.gmlParser_.writers[uri]) { if (!goog.isDef(this.writers[uri])) { this.writers[uri] = {}; } - this.writers[uri][key] = goog.bind(this.gml_.writers[uri][key], - this.gml_); + this.writers[uri][key] = goog.bind(this.gmlParser_.writers[uri][key], + this.gmlParser_); } } }; diff --git a/src/ol/parser/ogc/filterparser_v1_0_0.js b/src/ol/parser/ogc/filterparser_v1_0_0.js index 0b14d01eb6..eecebe2504 100644 --- a/src/ol/parser/ogc/filterparser_v1_0_0.js +++ b/src/ol/parser/ogc/filterparser_v1_0_0.js @@ -169,7 +169,7 @@ ol.parser.ogc.Filter_v1_0_0.prototype.writeSpatial_ = function(filter, name) { var child; if (geom !== null) { child = this.writeNode('_geometry', {value: geom}, - this.gml_.featureNS).firstChild; + this.gmlParser_.featureNS).firstChild; } else if (bbox.length === 4) { child = this.writeNode('Box', bbox, 'http://www.opengis.net/gml'); diff --git a/src/ol/parser/ogc/filterparser_v1_1_0.js b/src/ol/parser/ogc/filterparser_v1_1_0.js index 77423733fa..42c8388fdc 100644 --- a/src/ol/parser/ogc/filterparser_v1_1_0.js +++ b/src/ol/parser/ogc/filterparser_v1_1_0.js @@ -225,7 +225,7 @@ ol.parser.ogc.Filter_v1_1_0.prototype.writeSpatial_ = function(filter, name) { var child; if (geom !== null) { child = this.writeNode('_geometry', {value: geom}, - this.gml_.featureNS).firstChild; + this.gmlParser_.featureNS).firstChild; } else if (bbox.length === 4) { child = this.writeNode('Envelope', bbox, 'http://www.opengis.net/gml'); From b235fe25eacc8bf34aef3091a0d0427625dc2ce9 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 25 Nov 2013 15:29:40 -0700 Subject: [PATCH 46/66] Return for getFeatureInfoForPixel In b8a9aeb14ed6089d9d676c7e8682f0db5a6d5304 a return was added to `ol.renderer.Layer.prototype.getFeatureInfoForPixel`. The `ol.renderer.canvas.VectorLayer.prototype.getFeatureInfoForPixel` method needs the same. --- src/ol/renderer/canvas/canvasvectorlayerrenderer.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index 5e39824950..f47ca092ad 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -220,10 +220,7 @@ ol.renderer.canvas.VectorLayer.prototype.getTransform = function() { /** - * @param {ol.Pixel} pixel Pixel coordinate relative to the map viewport. - * @param {function(string, ol.layer.Layer)} success Callback for - * successful queries. The passed arguments are the resulting feature - * information and the layer. + * @inheritDoc */ ol.renderer.canvas.VectorLayer.prototype.getFeatureInfoForPixel = function(pixel, success) { @@ -231,6 +228,7 @@ ol.renderer.canvas.VectorLayer.prototype.getFeatureInfoForPixel = success(layer.getTransformFeatureInfo()(features), layer); }; this.getFeaturesForPixel(pixel, callback); + return true; }; From cf0fdff4e25b42725f2f9f6e2f87870451f7f53d Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 6 Nov 2013 12:06:32 +0100 Subject: [PATCH 47/66] Add ol.extent.extendXY --- src/ol/extent.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 5a28036f51..cf393fd61b 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -201,6 +201,19 @@ ol.extent.extendCoordinate = function(extent, coordinate) { }; +/** + * @param {ol.Extent} extent Extent. + * @param {number} x X. + * @param {number} y Y. + */ +ol.extent.extendXY = function(extent, x, y) { + extent[0] = Math.min(extent[0], x); + extent[1] = Math.min(extent[1], y); + extent[2] = Math.max(extent[2], x); + extent[3] = Math.max(extent[3], y); +}; + + /** * @param {ol.Extent} extent Extent. * @return {ol.Coordinate} Bottom left coordinate. From 9cbd8de188c37c9fe40cdc3bace046cc3a1e0dfb Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 6 Nov 2013 17:46:14 +0100 Subject: [PATCH 48/66] Fix order of arguments to ol.extent.createOrUpdate --- src/ol/extent.js | 10 +++++----- src/ol/tilegrid/tilegrid.js | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ol/extent.js b/src/ol/extent.js index cf393fd61b..1bb73610d3 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -43,10 +43,10 @@ ol.extent.boundingExtentXYs_ = function(xs, ys, opt_extent) { goog.asserts.assert(xs.length > 0); goog.asserts.assert(ys.length > 0); var minX = Math.min.apply(null, xs); - var maxX = Math.max.apply(null, xs); var minY = Math.min.apply(null, ys); + var maxX = Math.max.apply(null, xs); var maxY = Math.max.apply(null, ys); - return ol.extent.createOrUpdate(minX, maxX, minY, maxY, opt_extent); + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); }; @@ -114,18 +114,18 @@ ol.extent.createEmpty = function() { /** * @param {number} minX Minimum X. - * @param {number} maxX Maximum X. * @param {number} minY Minimum Y. + * @param {number} maxX Maximum X. * @param {number} maxY Maximum Y. * @param {ol.Extent=} opt_extent Destination extent. * @return {ol.Extent} Extent. * @todo stability experimental */ -ol.extent.createOrUpdate = function(minX, maxX, minY, maxY, opt_extent) { +ol.extent.createOrUpdate = function(minX, minY, maxX, maxY, opt_extent) { if (goog.isDef(opt_extent)) { opt_extent[0] = minX; - opt_extent[2] = maxX; opt_extent[1] = minY; + opt_extent[2] = maxX; opt_extent[3] = maxY; return opt_extent; } else { diff --git a/src/ol/tilegrid/tilegrid.js b/src/ol/tilegrid/tilegrid.js index 59bf95fb61..e079159cd2 100644 --- a/src/ol/tilegrid/tilegrid.js +++ b/src/ol/tilegrid/tilegrid.js @@ -223,7 +223,7 @@ ol.tilegrid.TileGrid.prototype.getTileRangeExtent = var maxX = origin[0] + (tileRange.maxX + 1) * tileSize[0] * resolution; var minY = origin[1] + tileRange.minY * tileSize[1] * resolution; var maxY = origin[1] + (tileRange.maxY + 1) * tileSize[1] * resolution; - return ol.extent.createOrUpdate(minX, maxX, minY, maxY, opt_extent); + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); }; @@ -287,10 +287,10 @@ ol.tilegrid.TileGrid.prototype.getTileCoordExtent = var resolution = this.getResolution(tileCoord.z); var tileSize = this.getTileSize(tileCoord.z); var minX = origin[0] + tileCoord.x * tileSize[0] * resolution; - var maxX = minX + tileSize[0] * resolution; var minY = origin[1] + tileCoord.y * tileSize[1] * resolution; + var maxX = minX + tileSize[0] * resolution; var maxY = minY + tileSize[1] * resolution; - return ol.extent.createOrUpdate(minX, maxX, minY, maxY, opt_extent); + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); }; From ae9f03c8cb7b26ba4e015ffd862544a575101544 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 6 Nov 2013 19:08:34 +0100 Subject: [PATCH 49/66] Add more extent functions --- src/ol/extent.js | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 1bb73610d3..2346c5fbcf 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -134,6 +134,50 @@ ol.extent.createOrUpdate = function(minX, minY, maxX, maxY, opt_extent) { }; +/** + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateEmpty = function(opt_extent) { + return ol.extent.createOrUpdate( + Infinity, Infinity, -Infinity, -Infinity, opt_extent); +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromCoordinate = function(coordinate, opt_extent) { + var x = coordinate[0]; + var y = coordinate[1]; + return ol.extent.createOrUpdate(x, y, x, y, opt_extent); +}; + + +/** + * @param {Array.} coordinates Coordinates. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromCoordinates = function(coordinates, opt_extent) { + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + return ol.extent.extendCoordinates(extent, coordinates); +}; + + +/** + * @param {Array.>} rings Rings. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromRings = function(rings, opt_extent) { + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + return ol.extent.extendRings(extent, rings); +}; + + /** * Empties extent in place. * @param {ol.Extent} extent Extent. @@ -201,6 +245,34 @@ ol.extent.extendCoordinate = function(extent, coordinate) { }; +/** + * @param {ol.Extent} extent Extent. + * @param {Array.} coordinates Coordinates. + * @return {ol.Extent} Extent. + */ +ol.extent.extendCoordinates = function(extent, coordinates) { + var i, ii; + for (i = 0, ii = coordinates.length; i < ii; ++i) { + ol.extent.extendCoordinate(extent, coordinates[i]); + } + return extent; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {Array.>} rings Rings. + * @return {ol.Extent} Extent. + */ +ol.extent.extendRings = function(extent, rings) { + var i, ii; + for (i = 0, ii = rings.length; i < ii; ++i) { + ol.extent.extendCoordinates(extent, rings[i]); + } + return extent; +}; + + /** * @param {ol.Extent} extent Extent. * @param {number} x X. @@ -362,6 +434,24 @@ ol.extent.normalize = function(extent, coordinate) { }; +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.returnOrUpdate = function(extent, opt_extent) { + if (goog.isDef(opt_extent)) { + opt_extent[0] = extent[0]; + opt_extent[1] = extent[1]; + opt_extent[2] = extent[2]; + opt_extent[3] = extent[3]; + return opt_extent; + } else { + return extent; + } +}; + + /** * @param {ol.Extent} extent Extent. * @param {number} value Value. From 67bf15b5dc6f4dac3e0dbac2678931eebf50253d Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 9 Nov 2013 15:40:15 +0100 Subject: [PATCH 50/66] Add ol.extent.extendFlatCoordinates --- src/ol/extent.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 2346c5fbcf..edd58791a6 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -259,6 +259,21 @@ ol.extent.extendCoordinates = function(extent, coordinates) { }; +/** + * @param {ol.Extent} extent Extent. + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} stride Stride. + * @return {ol.Extent} Extent. + */ +ol.extent.extendFlatCoordinates = function(extent, flatCoordinates, stride) { + var i, ii; + for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { + ol.extent.extendXY(extent, flatCoordinates[i], flatCoordinates[i + 1]); + } + return extent; +}; + + /** * @param {ol.Extent} extent Extent. * @param {Array.>} rings Rings. From d994db46f6545e1d95fa364617a2abeee28a9927 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 9 Nov 2013 15:40:27 +0100 Subject: [PATCH 51/66] Add ol.extent.createOrUpdateFromFlatCoordinates --- src/ol/extent.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index edd58791a6..96f47a11ec 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -167,6 +167,19 @@ ol.extent.createOrUpdateFromCoordinates = function(coordinates, opt_extent) { }; +/** + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} stride Stride. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromFlatCoordinates = + function(flatCoordinates, stride, opt_extent) { + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + return ol.extent.extendFlatCoordinates(extent, flatCoordinates, stride); +}; + + /** * @param {Array.>} rings Rings. * @param {ol.Extent=} opt_extent Extent. From df96c08e0ea6ec03b6b4487aecfd14ceb2f094d2 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 14:14:39 +0100 Subject: [PATCH 52/66] Return extent from ol.extent.extend --- src/ol/extent.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 96f47a11ec..08422a84d8 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -219,6 +219,7 @@ ol.extent.equals = function(extent1, extent2) { /** * @param {ol.Extent} extent1 Extent 1. * @param {ol.Extent} extent2 Extent 2. + * @return {ol.Extent} Extent. * @todo stability experimental */ ol.extent.extend = function(extent1, extent2) { @@ -234,6 +235,7 @@ ol.extent.extend = function(extent1, extent2) { if (extent2[3] > extent1[3]) { extent1[3] = extent2[3]; } + return extent1; }; From b74c244088f473f38ee42783a9f9f2a1f528b9f5 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 14:14:50 +0100 Subject: [PATCH 53/66] Add ol.extent.getArea --- src/ol/extent.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 08422a84d8..ea3e5cb8ce 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -316,6 +316,15 @@ ol.extent.extendXY = function(extent, x, y) { }; +/** + * @param {ol.Extent} extent Extent. + * @return {number} Area. + */ +ol.extent.getArea = function(extent) { + return ol.extent.getWidth(extent) * ol.extent.getHeight(extent); +}; + + /** * @param {ol.Extent} extent Extent. * @return {ol.Coordinate} Bottom left coordinate. From 6b02c7f639e490126663d9e150f4ea1b254ffc7d Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 14:15:07 +0100 Subject: [PATCH 54/66] Add ol.extent.getEnlargedArea --- src/ol/extent.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index ea3e5cb8ce..ee5761541a 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -355,6 +355,20 @@ ol.extent.getCenter = function(extent) { }; +/** + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @return {number} Enlarged area. + */ +ol.extent.getEnlargedArea = function(extent1, extent2) { + var minX = Math.min(extent1[0], extent2[0]); + var minY = Math.min(extent1[1], extent2[1]); + var maxX = Math.max(extent1[2], extent2[2]); + var maxY = Math.max(extent1[3], extent2[3]); + return (maxX - minX) * (maxY - minY); +}; + + /** * @param {ol.Coordinate} center Center. * @param {number} resolution Resolution. From 2a6f5a63962f0c21a5ba38b21a2dbeb6b39613cd Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 14:15:41 +0100 Subject: [PATCH 55/66] Add ol.extent.getIntersectionArea --- src/ol/extent.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index ee5761541a..3a00a840c9 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -409,6 +409,20 @@ ol.extent.getHeight = function(extent) { }; +/** + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @return {number} Intersection area. + */ +ol.extent.getIntersectionArea = function(extent1, extent2) { + var minX = Math.max(extent1[0], extent2[0]); + var minY = Math.max(extent1[1], extent2[1]); + var maxX = Math.min(extent1[2], extent2[2]); + var maxY = Math.min(extent1[3], extent2[3]); + return Math.max(0, maxX - minX) * Math.max(0, maxY - minY); +}; + + /** * @param {ol.Extent} extent Extent. * @return {ol.Size} Size. From 54d22735f209fd0905112c86f4f1a5c6f32f9601 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 14:15:56 +0100 Subject: [PATCH 56/66] Add ol.extent.getMargin --- src/ol/extent.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 3a00a840c9..c2e810acb5 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -423,6 +423,15 @@ ol.extent.getIntersectionArea = function(extent1, extent2) { }; +/** + * @param {ol.Extent} extent Extent. + * @return {number} Margin. + */ +ol.extent.getMargin = function(extent) { + return ol.extent.getWidth(extent) + ol.extent.getHeight(extent); +}; + + /** * @param {ol.Extent} extent Extent. * @return {ol.Size} Size. From f12355a17b989366f7f7c7dc7f3f130b6d64d3ac Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 26 Nov 2013 12:15:09 +0100 Subject: [PATCH 57/66] Add optional destination argument to ol.extent.clone --- src/ol/extent.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ol/extent.js b/src/ol/extent.js index c2e810acb5..b396d8c33d 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -67,11 +67,20 @@ ol.extent.buffer = function(extent, value) { * Creates a clone of an extent. * * @param {ol.Extent} extent Extent to clone. + * @param {ol.Extent=} opt_extent Extent. * @return {ol.Extent} The clone. * @todo stability experimental */ -ol.extent.clone = function(extent) { - return extent.slice(); +ol.extent.clone = function(extent, opt_extent) { + if (goog.isDef(opt_extent)) { + opt_extent[0] = extent[0]; + opt_extent[1] = extent[1]; + opt_extent[2] = extent[2]; + opt_extent[3] = extent[3]; + return opt_extent; + } else { + return extent.slice(); + } }; From e823e7fde3849d29aeb2c899e5a7c456eee99310 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 14:17:08 +0100 Subject: [PATCH 58/66] Add ol.structs.RBush --- src/ol/structs/rbush.js | 537 ++++++++++++++++++++++++++++++++++ test/spec/ol/structs/rbush.js | 105 +++++++ 2 files changed, 642 insertions(+) create mode 100644 src/ol/structs/rbush.js create mode 100644 test/spec/ol/structs/rbush.js diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js new file mode 100644 index 0000000000..e2342e512b --- /dev/null +++ b/src/ol/structs/rbush.js @@ -0,0 +1,537 @@ +// Based on rbush https://github.com/mourner/rbush +// Copyright (c) 2013 Vladimir Agafonkin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// FIXME removal +// FIXME bulk inserts + +goog.provide('ol.structs.RBush'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('ol.extent'); + + + +/** + * @constructor + * @param {ol.Extent} extent Extent. + * @param {number} height Height. + * @param {Array.>} children Children. + * @param {?T} value Value. + * @template T + */ +ol.structs.RBushNode = function(extent, height, children, value) { + + if (height === 0) { + goog.asserts.assert(goog.isNull(children)); + goog.asserts.assert(!goog.isNull(value)); + } else { + goog.asserts.assert(!goog.isNull(children)); + goog.asserts.assert(goog.isNull(value)); + } + + /** + * @type {ol.Extent} + */ + this.extent = extent; + + /** + * @type {number} + */ + this.height = height; + + /** + * @type {Array.>} + */ + this.children = children; + + /** + * @type {?T} + */ + this.value = value; + +}; + + +/** + * @param {ol.structs.RBushNode.} node1 Node 1. + * @param {ol.structs.RBushNode.} node2 Node 2. + * @return {number} Compare minimum X. + * @template T + */ +ol.structs.RBushNode.compareMinX = function(node1, node2) { + return node1.extent[0] - node2.extent[0]; +}; + + +/** + * @param {ol.structs.RBushNode.} node1 Node 1. + * @param {ol.structs.RBushNode.} node2 Node 2. + * @return {number} Compare minimum Y. + * @template T + */ +ol.structs.RBushNode.compareMinY = function(node1, node2) { + return node1.extent[1] - node2.extent[1]; +}; + + +/** + * @param {number} start Start. + * @param {number} stop Stop. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.structs.RBushNode.prototype.getChildrenExtent = + function(start, stop, opt_extent) { + goog.asserts.assert(!this.isLeaf()); + var children = this.children; + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + var i; + for (i = start; i < stop; ++i) { + ol.extent.extend(extent, children[i].extent); + } + return extent; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.RBushNode.prototype.updateExtent = function() { + goog.asserts.assert(!this.isLeaf()); + var extent = ol.extent.createOrUpdateEmpty(this.extent); + var children = this.children; + var i, ii; + for (i = 0, ii = children.length; i < ii; ++i) { + ol.extent.extend(extent, children[i].extent); + } +}; + + +/** + * @return {boolean} Is leaf. + */ +ol.structs.RBushNode.prototype.isLeaf = function() { + return goog.isNull(this.children); +}; + + + +/** + * @constructor + * @param {number=} opt_maxEntries Max entries. + * @see https://github.com/mourner/rbush + * @template T + */ +ol.structs.RBush = function(opt_maxEntries) { + + /** + * @private + * @type {number} + */ + this.maxEntries_ = + Math.max(4, goog.isDef(opt_maxEntries) ? opt_maxEntries : 9); + + /** + * @private + * @type {number} + */ + this.minEntries_ = Math.max(2, Math.ceil(0.4 * this.maxEntries_)); + + /** + * @private + * @type {ol.structs.RBushNode.} + */ + this.root_ = new ol.structs.RBushNode(ol.extent.createEmpty(), 1, [], null); + + /** + * @private + * @type {Object.} + */ + this.valueExtent_ = {}; + +}; + + +/** + * @return {Array.} All. + */ +ol.structs.RBush.prototype.all = function() { + var values = []; + this.forEach( + /** + * @param {T} value Value. + */ + function(value) { + values.push(value); + }); + return values; +}; + + +/** + * @param {ol.structs.RBushNode.} node Node. + * @param {function(ol.structs.RBushNode., ol.structs.RBushNode.): number} + * compare Compare. + * @private + * @return {number} All distance margin. + */ +ol.structs.RBush.prototype.allDistMargin_ = function(node, compare) { + var children = node.children; + var m = this.minEntries_; + var M = children.length; + var i; + goog.array.sort(children, compare); + var leftExtent = node.getChildrenExtent(0, m); + var rightExtent = node.getChildrenExtent(M - m, M); + var margin = + ol.extent.getMargin(leftExtent) + ol.extent.getMargin(rightExtent); + for (i = m; i < M - m; ++i) { + ol.extent.extend(leftExtent, children[i].extent); + margin += ol.extent.getMargin(leftExtent); + } + for (i = M - m - 1; i >= m; --i) { + ol.extent.extend(rightExtent, children[i].extent); + margin += ol.extent.getMargin(rightExtent); + } + return margin; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @return {Array.} All in extent. + */ +ol.structs.RBush.prototype.allInExtent = function(extent) { + var values = []; + this.forEachInExtent(extent, + /** + * @param {T} value Value. + */ + function(value) { + values.push(value); + }); + return values; +}; + + +/** + * @param {ol.structs.RBushNode.} node Node. + * @private + */ +ol.structs.RBush.prototype.chooseSplitAxis_ = function(node) { + var xMargin = this.allDistMargin_(node, ol.structs.RBushNode.compareMinX); + var yMargin = this.allDistMargin_(node, ol.structs.RBushNode.compareMinY); + if (xMargin < yMargin) { + goog.array.sort(node.children, ol.structs.RBushNode.compareMinX); + } +}; + + +/** + * @param {ol.structs.RBushNode.} node Node. + * @private + * @return {number} Split index. + */ +ol.structs.RBush.prototype.chooseSplitIndex_ = function(node) { + var children = node.children; + var m = this.minEntries_; + var M = children.length; + var minOverlap = Infinity; + var minArea = Infinity; + var extent1 = ol.extent.createEmpty(); + var extent2 = ol.extent.createEmpty(); + var index = 0; + var i; + for (i = m; i <= M - m; ++i) { + extent1 = node.getChildrenExtent(0, i, extent1); + extent2 = node.getChildrenExtent(i, M, extent2); + var overlap = ol.extent.getIntersectionArea(extent1, extent2); + var area = ol.extent.getArea(extent1) + ol.extent.getArea(extent2); + if (overlap < minOverlap) { + minOverlap = overlap; + minArea = Math.min(area, minArea); + index = i; + } else if (overlap == minOverlap && area < minArea) { + minArea = area; + index = i; + } + } + return index; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.structs.RBushNode.} node Node. + * @param {number} level Level. + * @param {Array.>} path Path. + * @private + * @return {ol.structs.RBushNode.} Node. + */ +ol.structs.RBush.prototype.chooseSubtree_ = + function(extent, node, level, path) { + while (!node.isLeaf() && path.length - 1 != level) { + var minArea = Infinity; + var minEnlargement = Infinity; + var children = node.children; + var bestChild = null; + var i, ii; + for (i = 0, ii = children.length; i < ii; ++i) { + var child = children[i]; + var area = ol.extent.getArea(child.extent); + var enlargement = ol.extent.getEnlargedArea(child.extent, extent) - area; + if (enlargement < minEnlargement) { + minEnlargement = enlargement; + minArea = Math.min(area, minArea); + bestChild = child; + } else if (enlargement == minEnlargement && area < minArea) { + minArea = area; + bestChild = child; + } + } + goog.asserts.assert(!goog.isNull(bestChild)); + node = bestChild; + path.push(node); + } + return node; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.RBush.prototype.clear = function() { + var node = this.root_; + node.extent = ol.extent.createOrUpdateEmpty(this.root_.extent); + node.height = 1; + node.children.length = 0; + node.value = null; +}; + + +/** + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_obj Scope. + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEach = function(callback, opt_obj) { + return this.forEach_(this.root_, callback, opt_obj); +}; + + +/** + * @param {ol.structs.RBushNode.} node Node. + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_obj Scope. + * @private + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEach_ = function(node, callback, opt_obj) { + goog.asserts.assert(!node.isLeaf()); + /** @type {Array.>} */ + var toVisit = [node]; + var children, i, ii, result; + while (toVisit.length > 0) { + node = toVisit.pop(); + children = node.children; + if (node.height == 1) { + for (i = 0, ii = children.length; i < ii; ++i) { + result = callback.call(opt_obj, children[i].value); + if (result) { + return result; + } + } + } else { + toVisit.push.apply(toVisit, children); + } + } +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_obj Scope. + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEachInExtent = + function(extent, callback, opt_obj) { + /** @type {Array.>} */ + var toVisit = [this.root_]; + var result; + while (toVisit.length > 0) { + var node = toVisit.pop(); + if (ol.extent.intersects(extent, node.extent)) { + if (node.isLeaf()) { + result = callback.call(opt_obj, node.value); + if (result) { + return result; + } + } else if (ol.extent.containsExtent(extent, node.extent)) { + result = this.forEach_(node, callback, opt_obj); + if (result) { + return result; + } + } else { + toVisit.push.apply(toVisit, node.children); + } + } + } + return undefined; +}; + + +/** + * @param {function(this: S, ol.structs.RBushNode.): *} callback Callback. + * @param {S=} opt_obj Scope. + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEachNode = function(callback, opt_obj) { + /** @type {Array.>} */ + var toVisit = [this.root_]; + while (toVisit.length > 0) { + var node = toVisit.pop(); + var result = callback.call(opt_obj, node); + if (result) { + return result; + } + if (!node.isLeaf()) { + toVisit.push.apply(toVisit, node.children); + } + } + return undefined; +}; + + +/** + * @param {T} value Value. + * @private + * @return {string} Key. + */ +ol.structs.RBush.prototype.getKey_ = function(value) { + goog.asserts.assert(goog.isObject(value)); + return goog.getUid(value) + ''; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {T} value Value. + */ +ol.structs.RBush.prototype.insert = function(extent, value) { + var key = this.getKey_(value); + goog.asserts.assert(!this.valueExtent_.hasOwnProperty(key)); + this.insert_(extent, value, this.root_.height - 1); + this.valueExtent_[key] = extent; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {T} value Value. + * @param {number} level Level. + * @private + * @return {ol.structs.RBushNode.} Node. + */ +ol.structs.RBush.prototype.insert_ = function(extent, value, level) { + /** @type {Array.>} */ + var path = [this.root_]; + var node = this.chooseSubtree_(extent, this.root_, level, path); + node.children.push(new ol.structs.RBushNode(extent, 0, null, value)); + ol.extent.extend(node.extent, extent); + var i; + for (i = path.length - 1; i >= 0; --i) { + if (path[i].children.length > this.maxEntries_) { + this.split_(path, i); + } else { + break; + } + } + for (; i >= 0; --i) { + ol.extent.extend(path[i].extent, extent); + } + return node; +}; + + +/** + * @param {T} value Value. + */ +ol.structs.RBush.prototype.remove = function(value) { + var key = this.getKey_(value); + goog.asserts.assert(this.valueExtent_.hasOwnProperty(key)); + var extent = this.valueExtent_[key]; + delete this.valueExtent_[key]; + this.remove_(extent, value); +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {T} value Value. + * @private + */ +ol.structs.RBush.prototype.remove_ = function(extent, value) { + // FIXME + goog.asserts.fail(); +}; + + +/** + * @param {Array.>} path Path. + * @param {number} level Level. + * @private + */ +ol.structs.RBush.prototype.split_ = function(path, level) { + var node = path[level]; + this.chooseSplitAxis_(node); + var splitIndex = this.chooseSplitIndex_(node); + // FIXME too few arguments to splice here + var newChildren = node.children.splice(splitIndex); + var newNode = new ol.structs.RBushNode( + ol.extent.createEmpty(), node.height, newChildren, null); + node.updateExtent(); + newNode.updateExtent(); + if (level) { + path[level - 1].children.push(newNode); + } else { + this.splitRoot_(node, newNode); + } +}; + + +/** + * @param {ol.structs.RBushNode.} node1 Node 1. + * @param {ol.structs.RBushNode.} node2 Node 2. + * @private + */ +ol.structs.RBush.prototype.splitRoot_ = function(node1, node2) { + goog.asserts.assert(node1 === this.root_); + var height = node1.height + 1; + var extent = ol.extent.extend(node1.extent.slice(), node2.extent); + var children = [node1, node2]; + this.root_ = new ol.structs.RBushNode(extent, height, children, null); +}; diff --git a/test/spec/ol/structs/rbush.js b/test/spec/ol/structs/rbush.js new file mode 100644 index 0000000000..6852e43964 --- /dev/null +++ b/test/spec/ol/structs/rbush.js @@ -0,0 +1,105 @@ +goog.provide('ol.test.structs.RBush'); + + +describe('ol.structs.RBush', function() { + + var rBush = new ol.structs.RBush(); + + describe('creation', function() { + it('can insert 1k objects', function() { + var i = 1000; + while (i > 0) { + var min = [Math.random() * 10000, Math.random() * 10000]; + var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; + var bounds = [min[0], min[1], max[0], max[1]]; + rBush.insert(bounds, {id: i}); + i--; + } + expect(rBush.allInExtent([0, 0, 10600, 10600]).length).to.be(1000); + }); + it('can insert 1k more objects', function() { + var i = 1000; + while (i > 0) { + var min = [Math.random() * 10000, Math.random() * 10000]; + var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; + var bounds = [min[0], min[1], max[0], max[1]]; + rBush.insert(bounds, {id: i}); + i--; + } + expect(rBush.allInExtent([0, 0, 10600, 10600]).length).to.be(2000); + }); + }); + + describe('search', function() { + it('can perform 1k out-of-bounds searches', function() { + var i = 1000; + var len = 0; + while (i > 0) { + var min = [-(Math.random() * 10000 + 501), + -(Math.random() * 10000 + 501)]; + var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; + var bounds = [min[0], min[1], max[0], max[1]]; + len += rBush.allInExtent(bounds).length; + i--; + } + expect(len).to.be(0); + }); + it('can perform 1k in-bounds searches', function() { + var i = 1000; + var len = 0; + while (i > 0) { + var min = [Math.random() * 10000, Math.random() * 10000]; + var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; + var bounds = [min[0], min[1], max[0], max[1]]; + len += rBush.allInExtent(bounds).length; + i--; + } + expect(len).not.to.be(0); + }); + }); + + describe.skip('deletion', function() { + var len = 0; + it('can delete half the RBush', function() { + var bounds = [5000, 0, 10500, 10500]; + len += rBush.remove(bounds).length; + expect(len).to.not.be(0); + }); + it('can delete the other half of the RBush', function() { + var bounds = [0, 0, 5000, 10500]; + len += rBush.remove(bounds).length; + expect(len).to.be(2000); + }); + }); + + describe('result plausibility and structure', function() { + + it('filters by rectangle', function() { + var objs = [{}, {}, {}, {}, {}, {}]; + rBush.insert([0, 0, 1, 1], objs[0]); + rBush.insert([1, 1, 4, 4], objs[1]); + rBush.insert([2, 2, 3, 3], objs[2]); + rBush.insert([-5, -5, -4, -4], objs[3]); + rBush.insert([-4, -4, -1, -1], objs[4]); + rBush.insert([-3, -3, -2, -2], objs[5]); + + var result; + result = rBush.allInExtent([2, 2, 3, 3]); + expect(result).to.contain(objs[1]); + expect(result).to.contain(objs[2]); + expect(result.length).to.be(2); + result = rBush.allInExtent([-1, -1, 2, 2]); + expect(result).to.contain(objs[0]); + expect(result).to.contain(objs[1]); + expect(result).to.contain(objs[2]); + expect(result).to.contain(objs[4]); + expect(result.length).to.be(4); + expect(rBush.allInExtent([5, 5, 6, 6]).length).to.be(0); + }); + + }); + +}); + +goog.require('goog.object'); +goog.require('ol.structs.RBush'); From a76eba34e88c3a2989b8b9648f438983d75308e6 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 18:48:15 +0100 Subject: [PATCH 59/66] Add ol.structs.RBush#assertValid --- src/ol/structs/rbush.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js index e2342e512b..2af41213e9 100644 --- a/src/ol/structs/rbush.js +++ b/src/ol/structs/rbush.js @@ -93,6 +93,31 @@ ol.structs.RBushNode.compareMinY = function(node1, node2) { }; +/** + * @param {boolean} isRoot Is root. + * @param {number} minEntries Min entries. + * @param {number} maxEntries Max entries. + */ +ol.structs.RBushNode.prototype.assertValid = + function(isRoot, minEntries, maxEntries) { + if (this.height === 0) { + goog.asserts.assert(goog.isNull(this.children)); + goog.asserts.assert(!goog.isNull(this.value)); + } else { + goog.asserts.assert(!goog.isNull(this.children)); + goog.asserts.assert(goog.isNull(this.value)); + goog.asserts.assert(isRoot || minEntries <= this.children.length); + goog.asserts.assert(this.children.length <= maxEntries); + var i, ii; + for (i = 0, ii = this.children.length; i < ii; ++i) { + var child = this.children[i]; + goog.asserts.assert(ol.extent.containsExtent(this.extent, child.extent)); + child.assertValid(false, minEntries, maxEntries); + } + } +}; + + /** * @param {number} start Start. * @param {number} stop Stop. @@ -233,6 +258,14 @@ ol.structs.RBush.prototype.allInExtent = function(extent) { }; +/** + * FIXME empty description for jsdoc + */ +ol.structs.RBush.prototype.assertValid = function() { + this.root_.assertValid(true, this.minEntries_, this.maxEntries_); +}; + + /** * @param {ol.structs.RBushNode.} node Node. * @private From 0ea7c2cd506a0065c8fae79254d699da18775402 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 25 Nov 2013 17:21:24 +0100 Subject: [PATCH 60/66] Refactor ol.structs.RBush tests --- test/spec/ol/structs/rbush.js | 189 ++++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 79 deletions(-) diff --git a/test/spec/ol/structs/rbush.js b/test/spec/ol/structs/rbush.js index 6852e43964..b1f7eb1bbf 100644 --- a/test/spec/ol/structs/rbush.js +++ b/test/spec/ol/structs/rbush.js @@ -3,98 +3,129 @@ goog.provide('ol.test.structs.RBush'); describe('ol.structs.RBush', function() { - var rBush = new ol.structs.RBush(); - - describe('creation', function() { - it('can insert 1k objects', function() { - var i = 1000; - while (i > 0) { - var min = [Math.random() * 10000, Math.random() * 10000]; - var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; - var bounds = [min[0], min[1], max[0], max[1]]; - rBush.insert(bounds, {id: i}); - i--; - } - expect(rBush.allInExtent([0, 0, 10600, 10600]).length).to.be(1000); - }); - it('can insert 1k more objects', function() { - var i = 1000; - while (i > 0) { - var min = [Math.random() * 10000, Math.random() * 10000]; - var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; - var bounds = [min[0], min[1], max[0], max[1]]; - rBush.insert(bounds, {id: i}); - i--; - } - expect(rBush.allInExtent([0, 0, 10600, 10600]).length).to.be(2000); - }); + var rBush; + beforeEach(function() { + rBush = new ol.structs.RBush(); }); - describe('search', function() { - it('can perform 1k out-of-bounds searches', function() { - var i = 1000; - var len = 0; - while (i > 0) { - var min = [-(Math.random() * 10000 + 501), - -(Math.random() * 10000 + 501)]; - var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; - var bounds = [min[0], min[1], max[0], max[1]]; - len += rBush.allInExtent(bounds).length; - i--; - } - expect(len).to.be(0); - }); - it('can perform 1k in-bounds searches', function() { - var i = 1000; - var len = 0; - while (i > 0) { - var min = [Math.random() * 10000, Math.random() * 10000]; - var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; - var bounds = [min[0], min[1], max[0], max[1]]; - len += rBush.allInExtent(bounds).length; - i--; - } - expect(len).not.to.be(0); + describe('when empty', function() { + + describe('#all', function() { + + it('returns the expected number of objects', function() { + expect(rBush.all()).to.be.empty(); + }); + }); + }); - describe.skip('deletion', function() { - var len = 0; - it('can delete half the RBush', function() { - var bounds = [5000, 0, 10500, 10500]; - len += rBush.remove(bounds).length; - expect(len).to.not.be(0); - }); - it('can delete the other half of the RBush', function() { - var bounds = [0, 0, 5000, 10500]; - len += rBush.remove(bounds).length; - expect(len).to.be(2000); - }); - }); + describe('with a few objects', function() { - describe('result plausibility and structure', function() { - - it('filters by rectangle', function() { - var objs = [{}, {}, {}, {}, {}, {}]; + var objs; + beforeEach(function() { + objs = [{}, {}, {}, {}, {}, {}]; rBush.insert([0, 0, 1, 1], objs[0]); rBush.insert([1, 1, 4, 4], objs[1]); rBush.insert([2, 2, 3, 3], objs[2]); rBush.insert([-5, -5, -4, -4], objs[3]); rBush.insert([-4, -4, -1, -1], objs[4]); rBush.insert([-3, -3, -2, -2], objs[5]); + }); + + describe('#allInExtent', function() { + + it('returns the expected objects', function() { + var result; + result = rBush.allInExtent([2, 2, 3, 3]); + expect(result).to.contain(objs[1]); + expect(result).to.contain(objs[2]); + expect(result.length).to.be(2); + result = rBush.allInExtent([-1, -1, 2, 2]); + expect(result).to.contain(objs[0]); + expect(result).to.contain(objs[1]); + expect(result).to.contain(objs[2]); + expect(result).to.contain(objs[4]); + expect(result.length).to.be(4); + }); + + it('returns an empty array when given a disjoint extent', function() { + expect(rBush.allInExtent([5, 5, 6, 6]).length).to.be(0); + }); + + }); + + }); + + describe('with 1000 objects', function() { + + beforeEach(function() { + var i; + for (i = 0; i < 1000; ++i) { + var min = [Math.random() * 10000, Math.random() * 10000]; + var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; + var extent = [min[0], min[1], max[0], max[1]]; + rBush.insert(extent, {id: i}); + } + }); + + describe('#all', function() { + + it('returns the expected number of objects', function() { + expect(rBush.all().length).to.be(1000); + }); + + }); + + describe('#allInExtent', function() { + + it('returns the expected number of objects', function() { + expect(rBush.allInExtent([0, 0, 10600, 10600]).length).to.be(1000); + }); + + it('can perform 1000 in-extent searches', function() { + var n = 0; + var i; + for (i = 0; i < 1000; ++i) { + var min = [Math.random() * 10000, Math.random() * 10000]; + var max = [min[0] + Math.random() * 500, + min[1] + Math.random() * 500]; + var extent = [min[0], min[1], max[0], max[1]]; + n += rBush.allInExtent(extent).length; + } + expect(n).not.to.be(0); + }); + + it('can perform 1000 out-of-extent searches', function() { + var n = 0; + var i; + for (i = 0; i < 1000; ++i) { + var min = [-(Math.random() * 10000 + 501), + -(Math.random() * 10000 + 501)]; + var max = [min[0] + Math.random() * 500, + min[1] + Math.random() * 500]; + var extent = [min[0], min[1], max[0], max[1]]; + n += rBush.allInExtent(extent).length; + } + expect(n).to.be(0); + }); + + }); + + describe('#insert', function() { + + it('can insert another 1000 objects', function() { + var i; + for (i = 1000; i < 2000; ++i) { + var min = [Math.random() * 10000, Math.random() * 10000]; + var max = [min[0] + Math.random() * 500, + min[1] + Math.random() * 500]; + var extent = [min[0], min[1], max[0], max[1]]; + rBush.insert(extent, {id: i}); + } + expect(rBush.allInExtent([0, 0, 10600, 10600]).length).to.be(2000); + }); - var result; - result = rBush.allInExtent([2, 2, 3, 3]); - expect(result).to.contain(objs[1]); - expect(result).to.contain(objs[2]); - expect(result.length).to.be(2); - result = rBush.allInExtent([-1, -1, 2, 2]); - expect(result).to.contain(objs[0]); - expect(result).to.contain(objs[1]); - expect(result).to.contain(objs[2]); - expect(result).to.contain(objs[4]); - expect(result.length).to.be(4); - expect(rBush.allInExtent([5, 5, 6, 6]).length).to.be(0); }); }); From b865a7c0e1219f082aaab5ac9e094802f93efbac Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 25 Nov 2013 19:08:02 +0100 Subject: [PATCH 61/66] Don't check for under-full nodes when validating ol.structs.RBush Removal can lead to under-full nodes. --- src/ol/structs/rbush.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js index 2af41213e9..4f1688582b 100644 --- a/src/ol/structs/rbush.js +++ b/src/ol/structs/rbush.js @@ -94,25 +94,21 @@ ol.structs.RBushNode.compareMinY = function(node1, node2) { /** - * @param {boolean} isRoot Is root. - * @param {number} minEntries Min entries. * @param {number} maxEntries Max entries. */ -ol.structs.RBushNode.prototype.assertValid = - function(isRoot, minEntries, maxEntries) { +ol.structs.RBushNode.prototype.assertValid = function(maxEntries) { if (this.height === 0) { goog.asserts.assert(goog.isNull(this.children)); goog.asserts.assert(!goog.isNull(this.value)); } else { goog.asserts.assert(!goog.isNull(this.children)); goog.asserts.assert(goog.isNull(this.value)); - goog.asserts.assert(isRoot || minEntries <= this.children.length); goog.asserts.assert(this.children.length <= maxEntries); var i, ii; for (i = 0, ii = this.children.length; i < ii; ++i) { var child = this.children[i]; goog.asserts.assert(ol.extent.containsExtent(this.extent, child.extent)); - child.assertValid(false, minEntries, maxEntries); + child.assertValid(maxEntries); } } }; @@ -262,7 +258,7 @@ ol.structs.RBush.prototype.allInExtent = function(extent) { * FIXME empty description for jsdoc */ ol.structs.RBush.prototype.assertValid = function() { - this.root_.assertValid(true, this.minEntries_, this.maxEntries_); + this.root_.assertValid(this.maxEntries_); }; From 1293294d4051a4125d68b2bbd570931f5c27e830 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 25 Nov 2013 19:08:36 +0100 Subject: [PATCH 62/66] Implement ol.structs.RBush#remove_ --- src/ol/structs/rbush.js | 59 ++++++++++++++++++++++++++-- test/spec/ol/structs/rbush.js | 72 +++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 3 deletions(-) diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js index 4f1688582b..536b3338a9 100644 --- a/src/ol/structs/rbush.js +++ b/src/ol/structs/rbush.js @@ -19,7 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -// FIXME removal // FIXME bulk inserts goog.provide('ol.structs.RBush'); @@ -357,6 +356,27 @@ ol.structs.RBush.prototype.clear = function() { }; +/** + * @param {Array.>} path Path. + * @private + */ +ol.structs.RBush.prototype.condense_ = function(path) { + var i; + for (i = path.length - 1; i >= 0; --i) { + var node = path[i]; + if (node.children.length === 0) { + if (i > 0) { + goog.array.remove(path[i - 1].children, node); + } else { + this.clear(); + } + } else { + node.updateExtent(); + } + } +}; + + /** * @param {function(this: S, T): *} callback Callback. * @param {S=} opt_obj Scope. @@ -524,8 +544,41 @@ ol.structs.RBush.prototype.remove = function(value) { * @private */ ol.structs.RBush.prototype.remove_ = function(extent, value) { - // FIXME - goog.asserts.fail(); + var node = this.root_; + var index = 0; + /** @type {Array.>} */ + var path = [node]; + /** @type {Array.} */ + var indexes = [0]; + var child, children, i, ii; + while (path.length > 0) { + goog.asserts.assert(node.height > 0); + if (node.height == 1) { + children = node.children; + for (i = 0, ii = children.length; i < ii; ++i) { + child = children[i]; + if (child.value === value) { + goog.array.removeAt(children, i); + this.condense_(path); + return; + } + } + ++index; + } else if (index < node.children.length) { + child = node.children[index]; + if (ol.extent.containsExtent(child.extent, extent)) { + path.push(child); + indexes.push(index + 1); + node = child; + index = 0; + } else { + ++index; + } + } else { + node = path.pop(); + index = indexes.pop(); + } + } }; diff --git a/test/spec/ol/structs/rbush.js b/test/spec/ol/structs/rbush.js index b1f7eb1bbf..7549662134 100644 --- a/test/spec/ol/structs/rbush.js +++ b/test/spec/ol/structs/rbush.js @@ -55,6 +55,78 @@ describe('ol.structs.RBush', function() { }); + describe('#remove', function() { + + it('can remove each object', function() { + var i, ii; + for (i = 0, ii = objs.length; i < ii; ++i) { + expect(rBush.all()).to.contain(objs[i]); + rBush.remove(objs[i]); + expect(rBush.all()).not.to.contain(objs[i]); + } + }); + + }); + + }); + + describe('with 100 objects', function() { + + var extents, objs; + beforeEach(function() { + extents = []; + objs = []; + var i; + for (i = 0; i < 100; ++i) { + extents[i] = [i - 0.1, i - 0.1, i + 0.1, i + 0.1]; + objs[i] = {id: i}; + rBush.insert(extents[i], objs[i]); + } + }); + + describe('#allInExtent', function() { + + it('returns the expected objects', function() { + var i, ii; + for (i = 0, ii = objs.length; i < ii; ++i) { + expect(rBush.allInExtent(extents[i])).to.eql([objs[i]]); + } + }); + + }); + + describe('#remove', function() { + + it('can remove each object in turn', function() { + var i, ii; + for (i = 0, ii = objs.length; i < ii; ++i) { + expect(rBush.allInExtent(extents[i])).to.eql([objs[i]]); + rBush.remove(objs[i]); + expect(rBush.allInExtent(extents[i])).to.be.empty(); + } + expect(rBush.all()).to.be.empty(); + }); + + it('can remove objects in random order', function() { + var i, ii, j; + // http://en.wikipedia.org/wiki/Random_permutation + var indexes = []; + for (i = 0, ii = objs.length; i < ii; ++i) { + j = Math.floor(Math.random() * (i + 1)); + indexes[i] = indexes[j]; + indexes[j] = i; + } + for (i = 0, ii = objs.length; i < ii; ++i) { + var index = indexes[i]; + expect(rBush.allInExtent(extents[index])).to.eql([objs[index]]); + rBush.remove(objs[index]); + expect(rBush.allInExtent(extents[index])).to.be.empty(); + } + expect(rBush.all()).to.be.empty(); + }); + + }); + }); describe('with 1000 objects', function() { From 7ec456fa709e6caadfa7ab9575d79710bfce1d52 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 25 Nov 2013 19:27:42 +0100 Subject: [PATCH 63/66] Add FIXME --- src/ol/structs/rbush.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js index 536b3338a9..59e43722de 100644 --- a/src/ol/structs/rbush.js +++ b/src/ol/structs/rbush.js @@ -20,6 +20,7 @@ // THE SOFTWARE. // FIXME bulk inserts +// FIXME is level argument needed to insert_? goog.provide('ol.structs.RBush'); From 880f098f0fc84ad11023b1a8abec40124097e2d9 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 25 Nov 2013 19:27:58 +0100 Subject: [PATCH 64/66] Add ol.structs.RBush#update --- src/ol/structs/rbush.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js index 59e43722de..e52bf5ec50 100644 --- a/src/ol/structs/rbush.js +++ b/src/ol/structs/rbush.js @@ -618,3 +618,19 @@ ol.structs.RBush.prototype.splitRoot_ = function(node1, node2) { var children = [node1, node2]; this.root_ = new ol.structs.RBushNode(extent, height, children, null); }; + + +/** + * @param {ol.Extent} extent Extent. + * @param {T} value Value. + */ +ol.structs.RBush.prototype.update = function(extent, value) { + var key = this.getKey_(value); + var currentExtent = this.valueExtent_[key]; + goog.asserts.assert(goog.isDef(currentExtent)); + if (!ol.extent.equals(currentExtent, extent)) { + this.remove_(currentExtent, value); + this.insert_(extent, value, this.root_.height - 1); + this.valueExtent_[key] = extent; + } +}; From 92469901f751ecd955be8d4f66decd481f8d5fd2 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 26 Nov 2013 12:15:40 +0100 Subject: [PATCH 65/66] Clone extents in ol.structs.RBush to prevent modification --- src/ol/structs/rbush.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js index e52bf5ec50..88c21b6c73 100644 --- a/src/ol/structs/rbush.js +++ b/src/ol/structs/rbush.js @@ -495,7 +495,7 @@ ol.structs.RBush.prototype.insert = function(extent, value) { var key = this.getKey_(value); goog.asserts.assert(!this.valueExtent_.hasOwnProperty(key)); this.insert_(extent, value, this.root_.height - 1); - this.valueExtent_[key] = extent; + this.valueExtent_[key] = ol.extent.clone(extent); }; @@ -631,6 +631,6 @@ ol.structs.RBush.prototype.update = function(extent, value) { if (!ol.extent.equals(currentExtent, extent)) { this.remove_(currentExtent, value); this.insert_(extent, value, this.root_.height - 1); - this.valueExtent_[key] = extent; + ol.extent.clone(extent, currentExtent); } }; From 5fef6d554ba61a6a2d6fcc1b53bd15d16118e7b7 Mon Sep 17 00:00:00 2001 From: Frederic Junod Date: Wed, 27 Nov 2013 10:05:06 +0100 Subject: [PATCH 66/66] Export ol.extent.buffer --- src/ol/extent.exports | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ol/extent.exports b/src/ol/extent.exports index c18fc7ca7a..387a75529f 100644 --- a/src/ol/extent.exports +++ b/src/ol/extent.exports @@ -1,4 +1,5 @@ @exportSymbol ol.extent.boundingExtent +@exportSymbol ol.extent.buffer @exportSymbol ol.extent.containsCoordinate @exportSymbol ol.extent.containsExtent @exportSymbol ol.extent.createEmpty