diff --git a/examples/sld.html b/examples/sld.html index 0a23159f2d..f57b9804bd 100644 --- a/examples/sld.html +++ b/examples/sld.html @@ -6,89 +6,114 @@ width: 800px; height: 475px; border: 1px solid black; + background: #ccddff; } - +

This example uses a SLD - file to style the vector features. The style to be used is either - determined by the NamedLayer and IsDefault properties in the sld file, or - can directly be applied by addressing a style from the styles - hash with the UserStyle name from the sld file as key. Select a new style for the WaterBodies layer below:

+ file to style the vector features. To construct layers that use styles + from SLD, create a StyleMap for the layer that uses one of the userStyles in the + namedLayers object of the return from format.read().

+

Select a new style for the WaterBodies layer below:

- Default Styler (zoom in to see more features)
- Styler Test PropertyIsEqualTo
- Styler Test Not FeatureId
- Styler Test WATER_TYPE
- Styler Test PropertyIsGreaterThanOrEqualTo
- Styler Test PropertyIsLessThanOrEqualTo
- Styler Test PropertyIsGreaterThan
- Styler Test PropertyIsLessThan
- Styler Test PropertyIsLike
+ Default Styler (zoom in to see more features)
+ Styler Test PropertyIsEqualTo
+ Styler Test WATER_TYPE
+ Styler Test PropertyIsGreaterThanOrEqualTo
+ Styler Test PropertyIsLessThanOrEqualTo
+ Styler Test PropertyIsGreaterThan
+ Styler Test PropertyIsLessThan
+ Styler Test PropertyIsLike
+ Styler Test PropertyIsBetween
+ Styler Test FeatureId
diff --git a/examples/tasmania/sld-tasmania.xml b/examples/tasmania/sld-tasmania.xml index 1ff20afa1c..ce3e3e5538 100644 --- a/examples/tasmania/sld-tasmania.xml +++ b/examples/tasmania/sld-tasmania.xml @@ -1,5 +1,11 @@ - + WaterBodies @@ -55,7 +61,26 @@ testRuleNameElse title Abstract - + + + + #aaaaff + + 0.5 + + + + + #C0C0C0 + + + 1 + + + 1 + + + @@ -123,44 +148,35 @@ testRuleNameHoverElse title Abstract - - - - - - - - - Attribute Filter Styler - Attribute Filter Styler - - attribute filter type - attribute filter type - Feature - generic:geometry - - - rulePropertyIsEqualTo - rulePropertyIsEqualTo - rulePropertyIsEqualTo - - - name - My simple Polygon - - + - #000033 + black + + + 0.5 + + + fuchsia + + + 0.5 + + + 5 + + + 0 + + - Attribute Filter Styler Attribute Filter Styler @@ -220,39 +236,7 @@ - - - Styler Test Not FeatureId - Styler Test Not FeatureId - - attribute filter type - attribute filter type - Feature - generic:geometry - - - ruleNotFeatureId - ruleNotFeatureId - ruleNotFeatureId - - - - - - - - - red - - - - - - - - - - + Styler Test WATER_TYPE Styler Test WATER_TYPE @@ -429,7 +413,7 @@ - + Styler Test PropertyIsLike Styler Test PropertyIsLike @@ -460,7 +444,6 @@ - Styler Test PropertyIsBetween Styler Test PropertyIsBetween @@ -478,10 +461,10 @@ AREA - 1060000000 + 1064866676 - 1070000000 + 1065512599 @@ -496,6 +479,23 @@ + + FeatureId + Styler Test FeatureId + + + + + + + + blue + + + + + + @@ -503,19 +503,21 @@ RoadsDefault 1 - - justAStyler - - - - red - - - 2 - - - - + + + justAStyler + + + + red + + + 2 + + + + + @@ -524,19 +526,52 @@ DefaultCities 1 - - - - - - image/png - - 10 - 0.5 - - - + + + + + + + image/png + + 0.5 + 10 + + + + + + Land + + Land Style + 1 + + + + + #ccffaa + + 0.5 + + + + + #C0C0C0 + + + 1 + + + 1 + + + + + + + + diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index 7de7d4bcd7..c29021b67c 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -193,6 +193,8 @@ "OpenLayers/Format/WKT.js", "OpenLayers/Format/OSM.js", "OpenLayers/Format/SLD.js", + "OpenLayers/Format/SLD/v1.js", + "OpenLayers/Format/SLD/v1_0_0.js", "OpenLayers/Format/Text.js", "OpenLayers/Format/JSON.js", "OpenLayers/Format/GeoJSON.js", diff --git a/lib/OpenLayers/Format/SLD.js b/lib/OpenLayers/Format/SLD.js index db12e335be..eff1adff34 100644 --- a/lib/OpenLayers/Format/SLD.js +++ b/lib/OpenLayers/Format/SLD.js @@ -21,59 +21,23 @@ OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML, { /** - * APIProperty: sldns - * Namespace used for sld. + * APIProperty: defaultVersion + * {String} Version number to assume if none found. Default is "1.0.0". */ - sldns: "http://www.opengis.net/sld", + defaultVersion: "1.0.0", /** - * APIProperty: ogcns - * Namespace used for ogc. + * APIProperty: version + * {String} Specify a version string if one is known. */ - ogcns: "http://www.opengis.net/ogc", + version: null, /** - * APIProperty: gmlns - * Namespace used for gml. + * Property: parser + * {Object} Instance of the versioned parser. Cached for multiple read and + * write calls of the same version. */ - gmlns: "http://www.opengis.net/gml", - - /** - * APIProperty: defaultStyle. - * {Object} - * A simple style, preset with the SLD defaults. - */ - defaultStyle: { - fillColor: "#808080", - fillOpacity: 1, - strokeColor: "#000000", - strokeOpacity: 1, - strokeWidth: 1, - pointRadius: 6 - }, - - /** - * Property: withNamedLayer - * {Boolean} Option set during . Default is false. If true, the - * return from will be a two item array ([styles, namedLayer]): - * - styles - {Array()} - * - namedLayer - {Object} hash of userStyles, keyed by - * sld:NamedLayer/Name, each again keyed by - * sld:UserStyle/Name. Each entry of namedLayer is a - * StyleMap for a layer, with the userStyle names as style - * keys. - */ - withNamedLayer: false, - - /** - * APIProperty: overrideDefaultStyleKey - * {Boolean} Store styles with key of "default" instead of user style name. - * If true, userStyles with sld:IsDefault==1 will be stored with - * key "default" instead of the sld:UserStyle/Name in the style map. - * Default is true. - */ - overrideDefaultStyleKey: true, - + parser: null, /** * Constructor: OpenLayers.Format.SLD @@ -88,517 +52,68 @@ OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML, { }, /** - * APIMethod: read - * Read data from a string, and return a list of features. - * + * APIMethod: write + * Write a SLD document given a list of styles. + * * Parameters: - * data - {String} or {XMLNode} data to read/parse. - * options - {Object} Object that sets optional read configuration values. - * These include , and . + * sld - {Object} An object representing the SLD. + * options - {Object} Optional configuration object. * * Returns: - * {Array()} List of styles. If is - * true, return will be a two item array where the first item is - * a list of styles and the second is the namedLayer object. + * {String} An SLD document string. */ - read: function(data, options) { - if (typeof data == "string") { + write: function(sld, options) { + var version = (options && options.version) || + this.version || this.defaultVersion; + if(!this.parser || this.parser.VERSION != version) { + var format = OpenLayers.Format.SLD[ + "v" + version.replace(/\./g, "_") + ]; + if(!format) { + throw "Can't find a SLD parser for version " + + version; + } + this.parser = new format(this.options); + } + var root = this.parser.write(sld); + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * APIMethod: read + * Read and SLD doc and return an object representing the SLD. + * + * Parameters: + * data - {String | DOMElement} Data to read. + * + * Returns: + * {Object} An object representing the SLD. + */ + read: function(data) { + if(typeof data == "string") { data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); } - - options = options || {}; - OpenLayers.Util.applyDefaults(options, { - withNamedLayer: false, - overrideDefaultStyleKey: true - }); - - var userStyles = this.getElementsByTagNameNS( - data, this.sldns, "UserStyle" - ); - var result = {}; - if (userStyles.length > 0) { - var namedLayer = {}; - var styles = new Array(userStyles.length); - var styleName, userStyle, style; - for (var i=0; i} - */ - parseUserStyle: function(xmlNode, name) { - var userStyle = new OpenLayers.Style(this.defaultStyle, {name: name}); - - userStyle.isDefault = ( - this.parseProperty(xmlNode, this.sldns, "IsDefault") == 1 - ); - - // get the name of the layer if we have a NamedLayer - var namedLayerNode = xmlNode.parentNode; - var nameNodes = this.getElementsByTagNameNS( - namedLayerNode, this.sldns, "Name" - ); - if (namedLayerNode.nodeName.indexOf("NamedLayer") != -1 && - nameNodes && - nameNodes.length > 0 && - nameNodes[0].parentNode == namedLayerNode) { - userStyle.layerName = this.getChildValue(nameNodes[0]); - } - - var ruleNodes = this.getElementsByTagNameNS( - xmlNode, this.sldns, "Rule" - ); - - if (ruleNodes.length > 0) { - var rules = userStyle.rules; - var ruleName; - for (var i=0; i} - * - * Returns: - * {Object} Hash of rule properties - */ - parseRule: function(xmlNode, name) { - - // FILTERS - - var filter = this.getElementsByTagNameNS(xmlNode, this.ogcns, "Filter"); - if (filter && filter.length > 0) { - var rule = this.parseFilter(filter[0]); - } else { - // start with an empty rule that always applies - var rule = new OpenLayers.Rule(); - // and check if the rule is an ElseFilter - var elseFilter = this.getElementsByTagNameNS(xmlNode, this.ogcns, - "ElseFilter"); - if (elseFilter && elseFilter.length > 0) { - rule.elseFilter = true; - } - } - - rule.name = name; - - // SCALE DENOMINATORS - - // MinScaleDenominator - var minScale = this.getElementsByTagNameNS( - xmlNode, this.sldns, "MinScaleDenominator" - ); - if (minScale && minScale.length > 0) { - rule.minScaleDenominator = - parseFloat(this.getChildValue(minScale[0])); - } - - // MaxScaleDenominator - var maxScale = this.getElementsByTagNameNS( - xmlNode, this.sldns, "MaxScaleDenominator" - ); - if (maxScale && maxScale.length > 0) { - rule.maxScaleDenominator = - parseFloat(this.getChildValue(maxScale[0])); - } - - // STYLES - - // walk through all symbolizers - var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES; - for (var s=0; s 0) { - - var style = {}; - - // externalGraphic - var graphic = this.getElementsByTagNameNS( - symbolizer[0], this.sldns, "Graphic" - ); - if (graphic && graphic.length > 0) { - style.externalGraphic = this.parseProperty( - graphic[0], this.sldns, "OnlineResource", "xlink:href" - ); - style.pointRadius = this.parseProperty( - graphic[0], this.sldns, "Size" - ); - style.graphicOpacity = this.parseProperty( - graphic[0], this.sldns, "Opacity" - ); - } - - // fill - var fill = this.getElementsByTagNameNS( - symbolizer[0], this.sldns, "Fill" - ); - if (fill && fill.length > 0) { - style.fillColor = this.parseProperty( - fill[0], this.sldns, "CssParameter", "name", "fill" - ); - style.fillOpacity = this.parseProperty( - fill[0], this.sldns, "CssParameter", - "name", "fill-opacity" - ) || 1; - } - - // stroke - var stroke = this.getElementsByTagNameNS( - symbolizer[0], this.sldns, "Stroke" - ); - if (stroke && stroke.length > 0) { - style.strokeColor = this.parseProperty( - stroke[0], this.sldns, "CssParameter", "name", "stroke" - ); - style.strokeOpacity = this.parseProperty( - stroke[0], this.sldns, "CssParameter", - "name", "stroke-opacity" - ) || 1; - style.strokeWidth = this.parseProperty( - stroke[0], this.sldns, "CssParameter", - "name", "stroke-width" - ); - style.strokeLinecap = this.parseProperty( - stroke[0], this.sldns, "CssParameter", - "name", "stroke-linecap" - ); - } - - // set the [point|line|polygon]Symbolizer property of the rule - rule.symbolizer[prefixes[s]] = style; - } - } - - return rule; - }, - - /** - * Method: parseFilter - * Parses ogc fiters. - * - * Parameters: - * xmlNode - {} - * - * Returns: - * {} rule representing the filter - */ - parseFilter: function(xmlNode) { - // ogc:FeatureId filter - var filter = this.getNodeOrChildrenByTagName(xmlNode, "FeatureId"); - if (filter) { - var rule = new OpenLayers.Rule.FeatureId(); - for (var i=0; i)} or null if no matching content is found - */ - getNodeOrChildrenByTagName: function(xmlNode, tagName) { - var nodeName = (xmlNode.prefix) ? - xmlNode.nodeName.split(":")[1] : - xmlNode.nodeName; - - if (nodeName == tagName) { - return [xmlNode]; - } else { - var nodelist = this.getElementsByTagNameNS( - xmlNode, this.ogcns, tagName); - } - - // make a new list which only contains matching child nodes - if (nodelist.length > 0) { - var node; - var list = []; - for (var i=0; i 0 ? list : null; - } - - return null; - }, - - /** - * Method: parseProperty - * Convenience method to parse the different kinds of properties - * found in the sld and ogc namespace. - * - * Parses an ogc node that can either contain a value directly, - * or inside a property. The parsing can also be limited - * to nodes with certain attribute names and/or values. - * - * Parameters: - * xmlNode - {} - * namespace - {String} namespace of the node to find - * propertyName - {String} name of the property to parse - * attributeName - {String} optional name of the property to match - * attributeValue - {String} optional value of the specified attribute - * - * Returns: - * {String} The value for the requested property. - */ - parseProperty: function(xmlNode, namespace, propertyName, attributeName, - attributeValue) { - var result = null; - var propertyNodeList = this.getElementsByTagNameNS( - xmlNode, namespace, propertyName); - - if (propertyNodeList && propertyNodeList.length > 0) { - var propertyNode = attributeName ? - this.getNodeWithAttribute(propertyNodeList, - attributeName) : - propertyNodeList[0]; - - // strip namespace from attribute name for Opera browsers - if (window.opera && attributeName) { - var nsDelimiterPos = attributeName.indexOf(":"); - if (nsDelimiterPos != -1) { - attributeName = attributeName.substring(++nsDelimiterPos); - } - } - - // get the property value from the node matching attributeName - // and attributeValue, eg.: - // - // red - // - // or: - // red - if (attributeName && attributeValue) { - propertyNode = this.getNodeWithAttribute(propertyNodeList, - attributeName, attributeValue); - result = this.parseParameter(propertyNode); - } - - // get the attribute value and use it as result, eg.: - // - if (attributeName && !attributeValue) { - var propertyNode = this.getNodeWithAttribute(propertyNodeList, - attributeName); - result = propertyNode.getAttribute(attributeName); - } - - // get the property value directly or from an ogc:propertyName, - // ogc:Literal or any other property at the level of the property - // node, eg.: - // 0.5 - if (!attributeName) { - var result = this.parseParameter(propertyNode); - } - } - - // adjust the result to be a trimmed string or a number - if (result) { - result = OpenLayers.String.trim(result); - if (!isNaN(result)) { - result = parseFloat(result); - } - } - - return result; - }, - - /** - * Method: parseParameter - * parses a property for propertyNames, Literals and textContent and - * creates the according value string. - * - * Parameters: - * xmlNode - {} - * - * Returns: - * {String} a string holding a value suitable for OpenLayers.Style.value - */ - parseParameter: function(xmlNode) { - if (!xmlNode) { - return null; - } - var childNodes = xmlNode.childNodes; - if (!childNodes) { - return null; - } - - var value = new Array(childNodes.length); - for (var i=0; i)} list to search - * attributeName - {String} name of the attribute to match - * attributeValue - {String} optional value of the attribute - */ - getNodeWithAttribute: function(xmlNodeList, attributeName, attributeValue) { - for (var i=0; i + */ +OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + sld: "http://www.opengis.net/sld", + ogc: "http://www.opengis.net/ogc", + xlink: "http://www.w3.org/1999/xlink", + xsi: "http://www.w3.org/2001/XMLSchema-instance" + }, + + /** + * Property: defaultPrefix + */ + defaultPrefix: "sld", + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. + */ + schemaLocation: null, + + /** + * APIProperty: defaultSymbolizer. + * {Object} A symbolizer with the SLD defaults. + */ + defaultSymbolizer: { + fillColor: "#808080", + fillOpacity: 1, + strokeColor: "#000000", + strokeOpacity: 1, + strokeWidth: 1, + pointRadius: 6 + }, + + /** + * Constructor: OpenLayers.Format.SLD.v1 + * Instances of this class are not created directly. Use the + * constructor instead. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * Method: read + * + * Parameters: + * data - {DOMElement} An SLD document element. + * + * Returns: + * {Object} An object representing the SLD. + */ + read: function(data) { + var sld = { + namedLayers: {} + }; + this.readChildNodes(data, sld); + return sld; + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "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); + // give each of the user styles this layer name + for(var i=0; i 0) { + rule.rules.push(new OpenLayers.Rule.FeatureId({ + fids: filter.fids + })); + } + if(filter.rules.length > 0) { + rule.rules = rule.rules.concat(filter.rules); + } + }, + "FeatureId": function(node, filter) { + var fid = node.getAttribute("fid"); + if(fid) { + filter.fids.push(fid); + } + }, + "And": function(node, filter) { + var rule = new OpenLayers.Rule.Logical({ + type: OpenLayers.Rule.Logical.AND + }); + // since FeatureId rules may be nested here, make room for them + rule.fids = []; + this.readChildNodes(node, rule); + if(rule.fids.length > 0) { + rule.rules.push(new OpenLayers.Rule.FeatureId({ + fids: rule.fids + })); + } + delete rule.fids; + filter.rules.push(rule); + }, + "Or": function(node, filter) { + var rule = new OpenLayers.Rule.Logical({ + type: OpenLayers.Rule.Logical.OR + }); + // since FeatureId rules may be nested here, make room for them + rule.fids = []; + this.readChildNodes(node, rule); + if(rule.fids.length > 0) { + rule.rules.push(new OpenLayers.Rule.FeatureId({ + fids: rule.fids + })); + } + delete rule.fids; + filter.rules.push(rule); + }, + "Not": function(node, filter) { + var rule = new OpenLayers.Rule.Logical({ + type: OpenLayers.Rule.Logical.NOT + }); + // since FeatureId rules may be nested here, make room for them + rule.fids = []; + this.readChildNodes(node, rule); + if(rule.fids.length > 0) { + rule.rules.push(new OpenLayers.Rule.FeatureId({ + fids: rule.fids + })); + } + delete rule.fids; + filter.rules.push(rule); + }, + "PropertyIsEqualTo": function(node, filter) { + var rule = new OpenLayers.Rule.Comparison({ + type: OpenLayers.Rule.Comparison.EQUAL_TO + }); + this.readChildNodes(node, rule); + filter.rules.push(rule); + }, + "PropertyIsNotEqualTo": function(node, filter) { + var rule = new OpenLayers.Rule.Comparison({ + type: OpenLayers.Rule.Comparison.NOT_EQUAL_TO + }); + this.readChildNodes(node, rule); + filter.rules.push(rule); + }, + "PropertyIsLessThan": function(node, filter) { + var rule = new OpenLayers.Rule.Comparison({ + type: OpenLayers.Rule.Comparison.LESS_THAN + }); + this.readChildNodes(node, rule); + filter.rules.push(rule); + }, + "PropertyIsGreaterThan": function(node, filter) { + var rule = new OpenLayers.Rule.Comparison({ + type: OpenLayers.Rule.Comparison.GREATER_THAN + }); + this.readChildNodes(node, rule); + filter.rules.push(rule); + }, + "PropertyIsLessThanOrEqualTo": function(node, filter) { + var rule = new OpenLayers.Rule.Comparison({ + type: OpenLayers.Rule.Comparison.LESS_THAN_OR_EQUAL_TO + }); + this.readChildNodes(node, rule); + filter.rules.push(rule); + }, + "PropertyIsGreaterThanOrEqualTo": function(node, filter) { + var rule = new OpenLayers.Rule.Comparison({ + type: OpenLayers.Rule.Comparison.GREATER_THAN_OR_EQUAL_TO + }); + this.readChildNodes(node, rule); + filter.rules.push(rule); + }, + "PropertyIsBetween": function(node, filter) { + var rule = new OpenLayers.Rule.Comparison({ + type: OpenLayers.Rule.Comparison.BETWEEN + }); + this.readChildNodes(node, rule); + filter.rules.push(rule); + }, + "PropertyIsLike": function(node, filter) { + var rule = new OpenLayers.Rule.Comparison({ + type: OpenLayers.Rule.Comparison.LIKE + }); + this.readChildNodes(node, rule); + var wildCard = node.getAttribute("wildCard"); + var singleChar = node.getAttribute("singleChar"); + var esc = node.getAttribute("escape"); + rule.value2regex(wildCard, singleChar, esc); + filter.rules.push(rule); + }, + "Literal": function(node, obj) { + obj.value = this.getChildValue(node); + }, + "PropertyName": function(node, rule) { + rule.property = this.getChildValue(node); + }, + "LowerBoundary": function(node, rule) { + rule.lowerBoundary = this.readOgcExpression(node); + }, + "UpperBoundary": function(node, rule) { + rule.upperBoundary = this.readOgcExpression(node); + } + } + }, + + /** + * Method: readOgcExpression + * Limited support for OGC expressions. + * + * Parameters: + * node - {DOMElement} A DOM element that contains an ogc:expression. + * + * Returns: + * {String} A value to be used in a symbolizer. + */ + readOgcExpression: function(node) { + var obj = {}; + this.readChildNodes(node, obj); + var value = obj.value; + if(!value) { + value = this.getChildValue(node); + } + return value; + }, + + /** + * Property: cssMap + * {Object} Object mapping supported css property names to OpenLayers + * symbolizer property names. + */ + cssMap: { + "stroke": "strokeColor", + "stroke-opacity": "strokeOpacity", + "stroke-width": "strokeWidth", + "stroke-linecap": "strokeLinecap", + "fill": "fillColor", + "fill-opacity": "fillOpacity" + }, + + /** + * Method: getCssProperty + * Given a symbolizer property, get the corresponding CSS property + * from the . + * + * Parameters: + * sym - {String} A symbolizer property name. + * + * Returns: + * {String} A CSS property name or null if none found. + */ + getCssProperty: function(sym) { + var css = null; + for(var prop in this.cssMap) { + if(this.cssMap[prop] == sym) { + css = prop; + break + } + } + return css; + }, + + /** + * Method: getGraphicFormat + * Given a href for an external graphic, try to determine the mime-type. + * This method doesn't try too hard, and will fall back to + * if one of the known is not + * the file extension of the provided href. + * + * Parameters: + * href - {String} + * + * Returns: + * {String} The graphic format. + */ + getGraphicFormat: function(href) { + var format, regex; + for(var key in this.graphicFormats) { + if(this.graphicFormats[key].test(href)) { + format = key; + break + } + } + return format || this.defautlGraphicFormat; + }, + + /** + * Property: defaultGraphicFormat + * {String} If none other can be determined from , this + * default will be returned. + */ + defaultGraphicFormat: "image/png", + + /** + * Property: graphicFormats + * {Object} Mapping of image mime-types to regular extensions matching + * well-known file extensions. + */ + graphicFormats: { + "image/jpeg": /\.jpe?g$/i, + "image/gif": /\.gif$/i, + "image/png": /\.png$/i + }, + + /** + * Method: write + * + * Parameters: + * sld - {Object} An object representing the SLD. + * + * Returns: + * {DOMElement} The root of an SLD document. + */ + write: function(sld) { + return this.writers.sld.StyledLayerDescriptor.apply(this, [sld]) + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "sld": { + "StyledLayerDescriptor": function(sld) { + var root = this.createElementNSPlus( + "StyledLayerDescriptor", + {attributes: { + "version": this.VERSION, + "xsi:schemaLocation": this.schemaLocation + }} + ); + // add in optional name + if(sld.name) { + this.writeNode(root, "Name", sld.name); + } + // add in optional title + if(sld.title) { + this.writeNode(root, "Title", sld.title); + } + // add in optional description + if(sld.description) { + this.writeNode(root, "Abstract", sld.description); + } + // add in named layers + for(var name in sld.namedLayers) { + this.writeNode(root, "NamedLayer", sld.namedLayers[name]); + } + return root; + }, + "Name": function(name) { + return this.createElementNSPlus("Name", {value: name}); + }, + "Title": function(title) { + return this.createElementNSPlus("Title", {value: title}); + }, + "Abstract": function(description) { + return this.createElementNSPlus( + "Abstract", {value: description} + ); + }, + "NamedLayer": function(layer) { + var node = this.createElementNSPlus("NamedLayer"); + + // add in required name + this.writeNode(node, "Name", layer.name); + + // optional sld:LayerFeatureConstraints here + + // add in named styles + if(layer.namedStyles) { + for(var i=0; i": "PropertyIsGreaterThan", + "<=": "PropertyIsLessThanOrEqualTo", + ">=": "PropertyIsGreaterThanOrEqualTo", + "..": "PropertyIsBetween", + "~": "PropertyIsLike" + }, + + + /** + * Methods below this point are of general use for versioned XML parsers. + * These are candidates for an abstract class. + */ + + /** + * Method: getNamespacePrefix + * Get the namespace prefix for a given uri from the object. + * + * Returns: + * {String} A namespace prefix or null if none found. + */ + getNamespacePrefix: function(uri) { + var prefix = null; + if(uri == null) { + prefix = this.namespaces[this.defaultPrefix]; + } else { + var gotPrefix = false; + for(prefix in this.namespaces) { + if(this.namespaces[prefix] == uri) { + gotPrefix = true; + break; + } + } + if(!gotPrefix) { + prefix = null; + } + } + return prefix; + }, + + + /** + * Method: readChildNodes + */ + readChildNodes: function(node, obj) { + var children = node.childNodes; + var child, group, reader, prefix, local; + for(var i=0; i group. If a local name is used (e.g. "Name") then + * the namespace of the parent is assumed. + * obj - {Object} Structure containing data for the writer. + * + * Returns: + * {DOMElement} The child node. + */ + writeNode: function(parent, name, obj) { + var prefix, local; + var split = name.indexOf(":"); + if(split > 0) { + prefix = name.substring(0, split); + local = name.substring(split + 1); + } else { + prefix = this.getNamespacePrefix(parent.namespaceURI); + local = name; + } + var child = this.writers[prefix][local].apply(this, [obj]); + parent.appendChild(child); + return child; + }, + + /** + * Method: createElementNSPlus + * Shorthand for creating namespaced elements with optional attributes and + * child text nodes. + * + * Parameters: + * name - {String} The qualified node name. + * options - {Object} Optional object for node configuration. + * + * Returns: + * {Element} An element node. + */ + createElementNSPlus: function(name, options) { + options = options || {}; + var loc = name.indexOf(":"); + // order of prefix preference + // 1. in the uri option + // 2. in the prefix option + // 3. in the qualified name + // 4. from the defaultPrefix + var uri = options.uri || this.namespaces[options.prefix] + if(!uri) { + loc = name.indexOf(":"); + uri = this.namespaces[name.substring(0, loc)]; + } + if(!uri) { + uri = this.namespaces[this.defaultPrefix]; + } + var node = this.createElementNS(uri, name); + if(options.attributes) { + this.setAttributes(node, options.attributes); + } + if(options.value) { + node.appendChild(this.createTextNode(options.value)); + } + return node; + }, + + /** + * Method: setAttributes + * Set multiple attributes given key value pairs from an object. + * + * Parameters: + * node - {Element} An element node. + * obj - {Object || Array} An object whose properties represent attribute + * names and values represent attribute values. If an attribute name + * is a qualified name ("prefix:local"), the prefix will be looked up + * in the parsers {namespaces} object. If the prefix is found, + * setAttributeNS will be used instead of setAttribute. + */ + setAttributes: function(node, obj) { + var value, loc, alias, uri; + for(var name in obj) { + value = obj[name].toString(); + // check for qualified attribute name ("prefix:local") + uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null; + this.setAttributeNS(node, uri, name, value); + } + }, + + CLASS_NAME: "OpenLayers.Format.SLD.v1" + +}); diff --git a/lib/OpenLayers/Format/SLD/v1_0_0.js b/lib/OpenLayers/Format/SLD/v1_0_0.js new file mode 100644 index 0000000000..cba89936af --- /dev/null +++ b/lib/OpenLayers/Format/SLD/v1_0_0.js @@ -0,0 +1,49 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/SLD/v1.js + */ + +/** + * Class: OpenLayers.Format.SLD.v1_0_0 + * Write SLD version 1.0.0. + * + * Inherits from: + * - + */ +OpenLayers.Format.SLD.v1_0_0 = OpenLayers.Class( + OpenLayers.Format.SLD.v1, { + + /** + * Constant: VERSION + * {String} 1.0.0 + */ + VERSION: "1.0.0", + + /** + * Property: schemaLocation + * {String} http://www.opengis.net/sld + * http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd + */ + schemaLocation: "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd", + + /** + * Constructor: OpenLayers.Format.SLD.v1_0_0 + * Instances of this class are not created directly. Use the + * constructor instead. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.SLD.v1.prototype.initialize.apply( + this, [options] + ); + }, + + CLASS_NAME: "OpenLayers.Format.SLD.v1_0_0" + +}); \ No newline at end of file diff --git a/lib/OpenLayers/Rule.js b/lib/OpenLayers/Rule.js index 439de1be16..26ab152d9a 100644 --- a/lib/OpenLayers/Rule.js +++ b/lib/OpenLayers/Rule.js @@ -14,12 +14,30 @@ */ OpenLayers.Rule = OpenLayers.Class({ + /** + * Property: id + * {String} A unique id for this session. + */ + id: null, + /** * APIProperty: name * {String} name of this rule */ name: 'default', + /** + * Property: title + * {String} Title of this rule (set if included in SLD) + */ + title: null, + + /** + * Property: description + * {String} Description of this rule (set if abstract is included in SLD) + */ + description: null, + /** * Property: context * {Object} An optional object with properties that the rule should be @@ -73,6 +91,7 @@ OpenLayers.Rule = OpenLayers.Class({ * {} */ initialize: function(options) { + this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); this.symbolizer = {}; OpenLayers.Util.extend(this, options); diff --git a/lib/OpenLayers/Rule/Comparison.js b/lib/OpenLayers/Rule/Comparison.js index 431010f2aa..334fe3b4b3 100644 --- a/lib/OpenLayers/Rule/Comparison.js +++ b/lib/OpenLayers/Rule/Comparison.js @@ -107,9 +107,9 @@ OpenLayers.Rule.Comparison = OpenLayers.Class(OpenLayers.Rule, { case OpenLayers.Rule.Comparison.BETWEEN: var result = - context[this.property] > this.lowerBoundary; + context[this.property] >= this.lowerBoundary; result = result && - context[this.property] < this.upperBoundary; + context[this.property] <= this.upperBoundary; return result; case OpenLayers.Rule.Comparison.LIKE: var regexp = new RegExp(this.value, @@ -163,6 +163,41 @@ OpenLayers.Rule.Comparison = OpenLayers.Class(OpenLayers.Rule, { return this.value; }, + /** + * Method: regex2value + * Convert the value of this rule from a regular expression string into an + * ogc literal string using a wildCard of *, a singleChar of ., and an + * escape of !. Leaves the property unmodified. + * + * Returns: + * {String} A string value. + */ + regex2value: function() { + + var value = this.value; + + // replace ! with !! + value = value.replace(/!/g, "!!"); + + // replace \. with !. (watching out for \\.) + value = value.replace(/(\\)?\\\./g, function($0, $1) { + return $1 ? $0 : "!."; + }); + + // replace \* with #* (watching out for \\*) + value = value.replace(/(\\)?\\\*/g, function($0, $1) { + return $1 ? $0 : "!*"; + }); + + // replace \\ with \ + value = value.replace(/\\\\/g, "\\"); + + // convert .* to * (the sequence #.* is not allowed) + value = value.replace(/\.\*/g, "*"); + + return value; + }, + /** * Function: binaryCompare * Compares a feature property to a rule value diff --git a/lib/OpenLayers/Style.js b/lib/OpenLayers/Style.js index 6750ec693d..28b9bb6fc9 100644 --- a/lib/OpenLayers/Style.js +++ b/lib/OpenLayers/Style.js @@ -21,6 +21,18 @@ OpenLayers.Style = OpenLayers.Class({ */ name: null, + /** + * Property: title + * {String} Title of this style (set if included in SLD) + */ + title: null, + + /** + * Property: description + * {String} Description of this style (set if abstract is included in SLD) + */ + description: null, + /** * APIProperty: layerName * {} name of the layer that this style belongs to, usually diff --git a/tests/Format/test_SLD.html b/tests/Format/test_SLD.html index f6b94d8d70..d63798b31d 100644 --- a/tests/Format/test_SLD.html +++ b/tests/Format/test_SLD.html @@ -17,17 +17,16 @@ } function test_Format_SLD_read(t) { - t.plan(5); - var styles = new OpenLayers.Format.SLD().read(this.test_content, - {withNamedLayer: true}); + t.plan(4); + var sld = new OpenLayers.Format.SLD().read(this.test_content); - var testLayer = styles[1].TestLayer; + var testLayer = sld.namedLayers["TestLayer"]; + var userStyles = testLayer.userStyles; - t.ok(testLayer.foo != undefined, "SLD correctly reads a UserStyle named \"foo\""); - t.eq(testLayer.foo.rules.length, 1, "The number of rules for the UserStyle is correct"); - t.eq(testLayer.foo.rules[0].name, "bar", "The first rule's name is \"bar\""); - t.eq(testLayer.foo.rules[0].symbolizer.Polygon.fillColor, "blue", "The fillColor for the Polygon symbolizer is correct"); - t.eq(testLayer.foo.name, styles[0][0].name, "The content hash of the Format contains the correct rules."); + t.eq(userStyles[0].name, "foo", "SLD correctly reads a UserStyle named 'foo'"); + t.eq(userStyles[0].rules.length, 1, "The number of rules for the UserStyle is correct"); + t.eq(userStyles[0].rules[0].name, "bar", "The first rule's name is 'bar'"); + t.eq(userStyles[0].rules[0].symbolizer.Polygon.fillColor, "blue", "The fillColor for the Polygon symbolizer is correct"); } diff --git a/tests/Rule/test_Comparison.html b/tests/Rule/test_Comparison.html index 018f281c4c..62af049673 100644 --- a/tests/Rule/test_Comparison.html +++ b/tests/Rule/test_Comparison.html @@ -37,26 +37,50 @@ t.eq(rule.value, ".*b.r\\%\\..*", "Regular expression with different wildcard and escape chars generated correctly."); } + function test_regex2value(t) { + t.plan(8); + + function r2v(regex) { + return OpenLayers.Rule.Comparison.prototype.regex2value.call( + {value: regex} + ); + } + + t.eq(r2v("foo"), "foo", "doesn't change string without special chars"); + t.eq(r2v("foo.*foo"), "foo*foo", "wildCard replaced"); + t.eq(r2v("foo.foo"), "foo.foo", "singleChar replaced"); + t.eq(r2v("foo\\\\foo"), "foo\\foo", "escape removed"); + t.eq(r2v("foo!foo"), "foo!!foo", "escapes !"); + t.eq(r2v("foo\\*foo"), "foo!*foo", "replaces escape on *"); + t.eq(r2v("foo\\.foo"), "foo!.foo", "replaces escape on ."); + t.eq(r2v("foo\\\\.foo"), "foo\\.foo", "unescapes only \\ before ."); + + } + function test_Comparison_evaluate(t) { - t.plan(4); + t.plan(5); var rule = new OpenLayers.Rule.Comparison({ property: "area", lowerBoundary: 1000, - upperBoundary: 5000, + upperBoundary: 4999, type: OpenLayers.Rule.Comparison.BETWEEN}); var features = [ new OpenLayers.Feature.Vector(null, { - area: 2000}), + area: 999}), new OpenLayers.Feature.Vector(null, { - area: 6000}), + area: 1000}), new OpenLayers.Feature.Vector(null, { - area: 4999})]; + area: 4999}), + new OpenLayers.Feature.Vector(null, { + area: 5000})]; + // PropertyIsBetween filter: lower and upper boundary are inclusive var ruleResults = { - 0: true, - 1: false, - 2: true}; + 0: false, + 1: true, + 2: true, + 3: false}; for (var i in ruleResults) { var result = rule.evaluate(features[i]); t.eq(result, ruleResults[i], "feature "+i+