From 6c0952934f0fc0a0dd433cf1c19a56c6fc75258b Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 2 Aug 2010 19:49:52 +0000 Subject: [PATCH] Adding point, line, polygon, text, and raster symbolizer constructors. This paves the way for rendering multiple symbolizers per rule. The SLD parser now successfully round-trips documents with multiple symbolizers and multiple FeatureTypeStyle elements (through the symbolizer zIndex property). The Style2 (yes, ack) constructor is used to represent a collection of rules with multiple symbolizers. Style2 objects are currently only used by the SLD parser if the multipleSymbolizer property is set to true. Future enhancements to the renderers can be made to account for multiple symbolizers. r=ahocevar (closes #2760). git-svn-id: http://svn.openlayers.org/trunk/openlayers@10560 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- examples/sld-parser.html | 65 +++++ lib/OpenLayers.js | 7 + lib/OpenLayers/Format/SLD/v1.js | 215 +++++++++++--- lib/OpenLayers/Rule.js | 51 +++- lib/OpenLayers/Style2.js | 106 +++++++ lib/OpenLayers/Symbolizer.js | 46 +++ lib/OpenLayers/Symbolizer/Line.js | 64 ++++ lib/OpenLayers/Symbolizer/Point.js | 136 +++++++++ lib/OpenLayers/Symbolizer/Polygon.js | 76 +++++ lib/OpenLayers/Symbolizer/Raster.js | 29 ++ lib/OpenLayers/Symbolizer/Text.js | 60 ++++ tests/Format/SLD/v1_0_0.html | 418 +++++++++++++++++++++++++++ tests/Rule.html | 26 +- tests/Style2.html | 56 ++++ tests/Symbolizer.html | 31 ++ tests/Symbolizer/Line.html | 32 ++ tests/Symbolizer/Point.html | 32 ++ tests/Symbolizer/Polygon.html | 32 ++ tests/Symbolizer/Raster.html | 32 ++ tests/Symbolizer/Text.html | 32 ++ tests/list-tests.html | 7 + 21 files changed, 1498 insertions(+), 55 deletions(-) create mode 100644 examples/sld-parser.html create mode 100644 lib/OpenLayers/Style2.js create mode 100644 lib/OpenLayers/Symbolizer.js create mode 100644 lib/OpenLayers/Symbolizer/Line.js create mode 100644 lib/OpenLayers/Symbolizer/Point.js create mode 100644 lib/OpenLayers/Symbolizer/Polygon.js create mode 100644 lib/OpenLayers/Symbolizer/Raster.js create mode 100644 lib/OpenLayers/Symbolizer/Text.js create mode 100644 tests/Style2.html create mode 100644 tests/Symbolizer.html create mode 100644 tests/Symbolizer/Line.html create mode 100644 tests/Symbolizer/Point.html create mode 100644 tests/Symbolizer/Polygon.html create mode 100644 tests/Symbolizer/Raster.html create mode 100644 tests/Symbolizer/Text.html diff --git a/examples/sld-parser.html b/examples/sld-parser.html new file mode 100644 index 0000000000..56a6c83f0a --- /dev/null +++ b/examples/sld-parser.html @@ -0,0 +1,65 @@ + + + + OpenLayers SLD Parser + + + + + + +

SLD Parser

+ +
Parsing Styled Layer Descriptor (SLD) documents with the SLD format.
+ +
+
+
+ + +
+ This example uses the SLD format to parse SLD documents pasted into the textarea above. + A rough representation of the parsed style is shown in the textarea below. +
+ + + + + + + diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index 6e0c962dbc..94ba1d2206 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -227,6 +227,7 @@ "OpenLayers/Layer/PointTrack.js", "OpenLayers/Layer/GML.js", "OpenLayers/Style.js", + "OpenLayers/Style2.js", "OpenLayers/StyleMap.js", "OpenLayers/Rule.js", "OpenLayers/Format.js", @@ -303,6 +304,12 @@ "OpenLayers/Control/ZoomOut.js", "OpenLayers/Control/ZoomPanel.js", "OpenLayers/Control/EditingToolbar.js", + "OpenLayers/Symbolizer.js", + "OpenLayers/Symbolizer/Point.js", + "OpenLayers/Symbolizer/Line.js", + "OpenLayers/Symbolizer/Polygon.js", + "OpenLayers/Symbolizer/Text.js", + "OpenLayers/Symbolizer/Raster.js", "OpenLayers/Lang.js", "OpenLayers/Lang/en.js" ); // etc. diff --git a/lib/OpenLayers/Format/SLD/v1.js b/lib/OpenLayers/Format/SLD/v1.js index 78b8a066e7..08c231a101 100644 --- a/lib/OpenLayers/Format/SLD/v1.js +++ b/lib/OpenLayers/Format/SLD/v1.js @@ -39,6 +39,27 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, { * {String} Schema location for a particular minor version. */ schemaLocation: null, + + /** + * APIProperty: multipleSymbolizers + * {Boolean} Support multiple symbolizers per rule. Default is false. if + * true, an OpenLayers.Style2 instance will be created to represent + * user styles instead of an OpenLayers.Style instace. The + * OpenLayers.Style2 class allows collections of rules with multiple + * symbolizers, but is not currently useful for client side rendering. + * If multiple symbolizers is true, multiple FeatureTypeStyle elements + * are preserved in reading/writing by setting symbolizer zIndex values. + * In addition, the property is ignored if + * multiple symbolizers are supported (defaults should be applied + * when rendering). + */ + multipleSymbolizers: false, + + /** + * Property: featureTypeCounter + * {Number} Private counter for multiple feature type styles. + */ + featureTypeCounter: null, /** * APIProperty: defaultSymbolizer. @@ -138,8 +159,15 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, { }, "UserStyle": function(node, layer) { var obj = {defaultsPerSymbolizer: true, rules: []}; + this.featureTypeCounter = -1; this.readChildNodes(node, obj); - var style = new OpenLayers.Style(this.defaultSymbolizer, obj); + var style; + if (this.multipleSymbolizers) { + delete obj.defaultsPerSymbolizer; + style = new OpenLayers.Style2(obj); + } else { + style = new OpenLayers.Style(this.defaultSymbolizer, obj); + } layer.userStyles.push(style); }, "IsDefault": function(node, style) { @@ -148,18 +176,21 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, { } }, "FeatureTypeStyle": function(node, style) { - // OpenLayers doesn't have a place for FeatureTypeStyle - // Name, Title, Abstract, FeatureTypeName, or - // SemanticTypeIdentifier so, we make a temporary object - // and later just use the Rule(s). + ++this.featureTypeCounter; var obj = { - rules: [] + rules: this.multipleSymbolizers ? style.rules : [] }; this.readChildNodes(node, obj); - style.rules = obj.rules; + if (!this.multipleSymbolizers) { + style.rules = obj.rules; + } }, "Rule": function(node, obj) { - var rule = new OpenLayers.Rule(); + var config; + if (this.multipleSymbolizers) { + config = {symbolizers: []}; + } + var rule = new OpenLayers.Rule(config); this.readChildNodes(node, rule); obj.rules.push(rule); }, @@ -173,11 +204,18 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, { rule.maxScaleDenominator = parseFloat(this.getChildValue(node)); }, "TextSymbolizer": function(node, rule) { - // OpenLayers doens't do painter's order, instead we extend - var symbolizer = rule.symbolizer["Text"] || {}; - this.readChildNodes(node, symbolizer); - // in case it didn't exist before - rule.symbolizer["Text"] = symbolizer; + var config = {}; + this.readChildNodes(node, config); + if (this.multipleSymbolizers) { + config.zIndex = this.featureTypeCounter; + rule.symbolizers.push( + new OpenLayers.Symbolizer.Text(config) + ); + } else { + rule.symbolizer["Text"] = OpenLayers.Util.applyDefaults( + config, rule.symbolizer["Text"] + ); + } }, "Label": function(node, symbolizer) { // only supporting literal or property name @@ -211,11 +249,18 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, { } }, "RasterSymbolizer": function(node, rule) { - // OpenLayers doesn't do painter's order, instead we extend - var symbolizer = rule.symbolizer["Raster"] || {}; - this.readChildNodes(node, symbolizer); - // in case it didn't exist before - rule.symbolizer["Raster"] = symbolizer; + var config = {}; + this.readChildNodes(node, config); + if (this.multipleSymbolizers) { + config.zIndex = this.featureTypeCounter; + rule.symbolizers.push( + new OpenLayers.Symbolizer.Raster(config) + ); + } else { + rule.symbolizer["Raster"] = OpenLayers.Util.applyDefaults( + config, rule.symbolizer["Raster"] + ); + } }, "Geometry": function(node, obj) { obj.geometry = {}; @@ -236,32 +281,55 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, { }); }, "LineSymbolizer": function(node, rule) { - // OpenLayers doesn't do painter's order, instead we extend - var symbolizer = rule.symbolizer["Line"] || {}; - this.readChildNodes(node, symbolizer); - // in case it didn't exist before - rule.symbolizer["Line"] = symbolizer; + var config = {}; + this.readChildNodes(node, config); + if (this.multipleSymbolizers) { + config.zIndex = this.featureTypeCounter; + rule.symbolizers.push( + new OpenLayers.Symbolizer.Line(config) + ); + } else { + rule.symbolizer["Line"] = OpenLayers.Util.applyDefaults( + config, rule.symbolizer["Line"] + ); + } }, "PolygonSymbolizer": function(node, rule) { - // OpenLayers doens't do painter's order, instead we extend - var symbolizer = rule.symbolizer["Polygon"] || { + var config = { fill: false, stroke: false }; - this.readChildNodes(node, symbolizer); - // in case it didn't exist before - rule.symbolizer["Polygon"] = symbolizer; + if (!this.multipleSymbolizers) { + config = rule.symbolizer["Polygon"] || config; + } + this.readChildNodes(node, config); + if (this.multipleSymbolizers) { + config.zIndex = this.featureTypeCounter; + rule.symbolizers.push( + new OpenLayers.Symbolizer.Polygon(config) + ); + } else { + rule.symbolizer["Polygon"] = config; + } }, "PointSymbolizer": function(node, rule) { - // OpenLayers doens't do painter's order, instead we extend - var symbolizer = rule.symbolizer["Point"] || { + var config = { fill: false, stroke: false, graphic: false }; - this.readChildNodes(node, symbolizer); - // in case it didn't exist before - rule.symbolizer["Point"] = symbolizer; + if (!this.multipleSymbolizers) { + config = rule.symbolizer["Point"] || config; + } + this.readChildNodes(node, config); + if (this.multipleSymbolizers) { + config.zIndex = this.featureTypeCounter; + rule.symbolizers.push( + new OpenLayers.Symbolizer.Point(config) + ); + } else { + rule.symbolizer["Point"] = config; + } }, "Stroke": function(node, symbolizer) { symbolizer.stroke = true; @@ -566,7 +634,53 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, { } // add FeatureTypeStyles - this.writeNode("FeatureTypeStyle", style, node); + if (this.multipleSymbolizers && 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 0) { + clone = style.clone(); + clone.rules = rulesByZ[zValues[i]]; + this.writeNode("FeatureTypeStyle", clone, node); + } + } + } else { + this.writeNode("FeatureTypeStyle", style, node); + } return node; }, @@ -626,17 +740,28 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, { ); } - // add in symbolizers (relies on geometry type keys) - var types = OpenLayers.Style.SYMBOLIZER_PREFIXES; var type, symbolizer; - for(var i=0, len=types.length; i} Name of the layer that this style belongs to, usually + * according to the NamedLayer attribute of an SLD document. + */ + layerName: null, + + /** + * APIProperty: isDefault + * {Boolean} + */ + isDefault: false, + + /** + * APIProperty: rules + * {Array()} Collection of rendering rules. + */ + rules: null, + + /** + * Constructor: OpenLayers.Style2 + * Creates a style representing a collection of rendering rules. + * + * Parameters: + * config - {Object} An object containing properties to be set on the + * style. Any documented properties may be set at construction. + * + * Return: + * {} A new style object. + */ + initialize: function(config) { + OpenLayers.Util.extend(this, config); + this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); + }, + + /** + * APIMethod: destroy + * nullify references to prevent circular references and memory leaks + */ + destroy: function() { + for (var i=0, len=this.rules.length; i} Clone of this style. + */ + clone: function() { + var config = OpenLayers.Util.extend({}, this); + // clone rules + if (this.rules) { + config.rules = []; + for (var i=0, len=this.rules.length; i and ). + */ + rotation: null, + + /** + * APIProperty: graphicName + * {String} Named graphic to use when rendering points. Supported values + * include "circle", "square", "star", "x", "cross", and "triangle". + */ + graphicName: null, + + /** + * Constructor: OpenLayers.Symbolizer.Point + * Create a symbolizer for rendering points. + * + * Parameters: + * config - {Object} An object containing properties to be set on the + * symbolizer. Any documented symbolizer property can be set at + * construction. + * + * Returns: + * A new point symbolizer. + */ + initialize: function(config) { + OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments); + }, + + CLASS_NAME: "OpenLayers.Symbolizer.Point" + +}); + diff --git a/lib/OpenLayers/Symbolizer/Polygon.js b/lib/OpenLayers/Symbolizer/Polygon.js new file mode 100644 index 0000000000..b72a892779 --- /dev/null +++ b/lib/OpenLayers/Symbolizer/Polygon.js @@ -0,0 +1,76 @@ +/** + * @requires OpenLayers/Symbolizer.js + */ + +/** + * Class: OpenLayers.Symbolizer.Polygon + * A symbolizer used to render line features. + */ +OpenLayers.Symbolizer.Polygon = OpenLayers.Class(OpenLayers.Symbolizer, { + + /** + * APIProperty: strokeColor + * {String} Color for line stroke. This is a RGB hex value (e.g. "#ff0000" + * for red). + */ + strokeColor: null, + + /** + * APIProperty: strokeOpacity + * {Number} Stroke opacity (0-1). + */ + strokeOpacity: null, + + /** + * APIProperty: strokeWidth + * {Number} Pixel stroke width. + */ + strokeWidth: null, + + /** + * APIProperty: strokeLinecap + * {String} Stroke cap type ("butt", "round", or "square"). + */ + strokeLinecap: null, + + /** + * Property: strokeDashstyle + * {String} Stroke dash style according to the SLD spec. Note that the + * OpenLayers values for strokeDashstyle ("dot", "dash", "dashdot", + * "longdash", "longdashdot", or "solid") will not work in SLD, but + * most SLD patterns will render correctly in OpenLayers. + */ + strokeDashstyle: "solid", + + /** + * APIProperty: fillColor + * {String} RGB hex fill color (e.g. "#ff0000" for red). + */ + fillColor: null, + + /** + * APIProperty: fillOpacity + * {Number} Fill opacity (0-1). + */ + fillOpacity: null, + + /** + * Constructor: OpenLayers.Symbolizer.Polygon + * Create a symbolizer for rendering polygons. + * + * Parameters: + * config - {Object} An object containing properties to be set on the + * symbolizer. Any documented symbolizer property can be set at + * construction. + * + * Returns: + * A new polygon symbolizer. + */ + initialize: function(config) { + OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments); + }, + + CLASS_NAME: "OpenLayers.Symbolizer.Polygon" + +}); + diff --git a/lib/OpenLayers/Symbolizer/Raster.js b/lib/OpenLayers/Symbolizer/Raster.js new file mode 100644 index 0000000000..cdd72a882a --- /dev/null +++ b/lib/OpenLayers/Symbolizer/Raster.js @@ -0,0 +1,29 @@ +/** + * @requires OpenLayers/Symbolizer.js + */ + +/** + * Class: OpenLayers.Symbolizer.Raster + * A symbolizer used to render raster images. + */ +OpenLayers.Symbolizer.Raster = OpenLayers.Class(OpenLayers.Symbolizer, { + + /** + * Constructor: OpenLayers.Symbolizer.Raster + * Create a symbolizer for rendering rasters. + * + * Parameters: + * config - {Object} An object containing properties to be set on the + * symbolizer. Any documented symbolizer property can be set at + * construction. + * + * Returns: + * A new raster symbolizer. + */ + initialize: function(config) { + OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments); + }, + + CLASS_NAME: "OpenLayers.Symbolizer.Raster" + +}); diff --git a/lib/OpenLayers/Symbolizer/Text.js b/lib/OpenLayers/Symbolizer/Text.js new file mode 100644 index 0000000000..3e97fa5b83 --- /dev/null +++ b/lib/OpenLayers/Symbolizer/Text.js @@ -0,0 +1,60 @@ +/** + * @requires OpenLayers/Symbolizer.js + */ + +/** + * Class: OpenLayers.Symbolizer.Text + * A symbolizer used to render text labels for features. + */ +OpenLayers.Symbolizer.Text = OpenLayers.Class(OpenLayers.Symbolizer, { + + /** + * APIProperty: label + * {String} The text for the label. + */ + label: null, + + /** + * APIProperty: fontFamily + * {String} The font family for the label. + */ + fontFamily: null, + + /** + * APIProperty: fontSize + * {String} The font size for the label. + */ + fontSize: null, + + /** + * APIProperty: fontWeight + * {String} The font weight for the label. + */ + fontWeight: null, + + /** + * Property: fontStyle + * {String} The font style for the label. + */ + fontStyle: null, + + /** + * Constructor: OpenLayers.Symbolizer.Text + * Create a symbolizer for rendering text labels. + * + * Parameters: + * config - {Object} An object containing properties to be set on the + * symbolizer. Any documented symbolizer property can be set at + * construction. + * + * Returns: + * A new text symbolizer. + */ + initialize: function(config) { + OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments); + }, + + CLASS_NAME: "OpenLayers.Symbolizer.Text" + +}); + diff --git a/tests/Format/SLD/v1_0_0.html b/tests/Format/SLD/v1_0_0.html index cfee1720f9..6cb604b82f 100644 --- a/tests/Format/SLD/v1_0_0.html +++ b/tests/Format/SLD/v1_0_0.html @@ -2,6 +2,11 @@ +
+
+
+
+
+
diff --git a/tests/Rule.html b/tests/Rule.html index 9c5a46539f..00ef26b61c 100644 --- a/tests/Rule.html +++ b/tests/Rule.html @@ -43,7 +43,7 @@ function test_clone(t) { - t.plan(7); + t.plan(9); var rule = new OpenLayers.Rule({ name: "test rule", @@ -81,6 +81,30 @@ rule.destroy(); clone.destroy(); + + // test multiple symbolizers + rule = new OpenLayers.Rule({ + name: "test rule", + minScaleDenominator: 10, + maxScaleDenominator: 20, + filter: new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.EQUAL_TO, + property: "prop", + value: "value" + }), + symbolizers: [ + new OpenLayers.Symbolizer.Line({ + strokeColor: "black" + }) + ] + }); + clone = rule.clone(); + + t.eq(clone.symbolizers.length, 1, "clone has one symbolizer"); + t.ok(clone.symbolizers[0] !== rule.symbolizers[0], "clone has different symbolizers than original"); + + clone.destroy(); + rule.destroy(); } diff --git a/tests/Style2.html b/tests/Style2.html new file mode 100644 index 0000000000..9127f3364b --- /dev/null +++ b/tests/Style2.html @@ -0,0 +1,56 @@ + + + + + + + diff --git a/tests/Symbolizer.html b/tests/Symbolizer.html new file mode 100644 index 0000000000..5f51e91c8b --- /dev/null +++ b/tests/Symbolizer.html @@ -0,0 +1,31 @@ + + + + + + + diff --git a/tests/Symbolizer/Line.html b/tests/Symbolizer/Line.html new file mode 100644 index 0000000000..ed321f1806 --- /dev/null +++ b/tests/Symbolizer/Line.html @@ -0,0 +1,32 @@ + + + + + + + diff --git a/tests/Symbolizer/Point.html b/tests/Symbolizer/Point.html new file mode 100644 index 0000000000..e308191f49 --- /dev/null +++ b/tests/Symbolizer/Point.html @@ -0,0 +1,32 @@ + + + + + + + diff --git a/tests/Symbolizer/Polygon.html b/tests/Symbolizer/Polygon.html new file mode 100644 index 0000000000..84dc03aa47 --- /dev/null +++ b/tests/Symbolizer/Polygon.html @@ -0,0 +1,32 @@ + + + + + + + diff --git a/tests/Symbolizer/Raster.html b/tests/Symbolizer/Raster.html new file mode 100644 index 0000000000..ff4bb045cc --- /dev/null +++ b/tests/Symbolizer/Raster.html @@ -0,0 +1,32 @@ + + + + + + + diff --git a/tests/Symbolizer/Text.html b/tests/Symbolizer/Text.html new file mode 100644 index 0000000000..482afb5bbf --- /dev/null +++ b/tests/Symbolizer/Text.html @@ -0,0 +1,32 @@ + + + + + + + diff --git a/tests/list-tests.html b/tests/list-tests.html index 1f1dfab57b..6c15644a87 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -182,7 +182,14 @@
  • Strategy/Save.html
  • Strategy/Refresh.html
  • Style.html
  • +
  • Style2.html
  • StyleMap.html
  • +
  • Symbolizer.html
  • +
  • Symbolizer/Line.html
  • +
  • Symbolizer/Point.html
  • +
  • Symbolizer/Polygon.html
  • +
  • Symbolizer/Raster.html
  • +
  • Symbolizer/Text.html
  • Tile.html
  • Tile/Image.html
  • Tile/Image/IFrame.html