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