1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
6 * @requires OpenLayers/Rule.js
7 * @requires OpenLayers/Format/SLD.js
8 * @requires OpenLayers/Format/Filter/v1_0_0.js
12 * Class: OpenLayers.Format.SLD.v1
13 * Superclass for SLD version 1 parsers.
16 * - <OpenLayers.Format.Filter.v1_0_0>
18 OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, {
21 * Property: namespaces
22 * {Object} Mapping of namespace aliases to namespace URIs.
25 sld: "http://www.opengis.net/sld",
26 ogc: "http://www.opengis.net/ogc",
27 gml: "http://www.opengis.net/gml",
28 xlink: "http://www.w3.org/1999/xlink",
29 xsi: "http://www.w3.org/2001/XMLSchema-instance"
33 * Property: defaultPrefix
38 * Property: schemaLocation
39 * {String} Schema location for a particular minor version.
44 * APIProperty: defaultSymbolizer.
45 * {Object} A symbolizer with the SLD defaults.
50 strokeColor: "#000000",
53 strokeDashstyle: "solid",
59 * Constructor: OpenLayers.Format.SLD.v1
60 * Instances of this class are not created directly. Use the
61 * <OpenLayers.Format.SLD> constructor instead.
64 * options - {Object} An optional object whose properties will be set on
67 initialize: function(options) {
68 OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this, [options]);
75 * data - {DOMElement} An SLD document element.
76 * options - {Object} Options for the reader.
79 * namedLayersAsArray - {Boolean} Generate a namedLayers array. If false,
80 * the namedLayers property value will be an object keyed by layer name.
84 * {Object} An object representing the SLD.
86 read: function(data, options) {
87 options = OpenLayers.Util.applyDefaults(options, this.options);
89 namedLayers: options.namedLayersAsArray === true ? [] : {}
91 this.readChildNodes(data, sld);
97 * Contains public functions, grouped by namespace prefix, that will
98 * be applied when a namespaced node is found matching the function
99 * name. The function will be applied in the scope of this parser
100 * with two arguments: the node being read and a context object passed
103 readers: OpenLayers.Util.applyDefaults({
105 "StyledLayerDescriptor": function(node, sld) {
106 sld.version = node.getAttribute("version");
107 this.readChildNodes(node, sld);
109 "Name": function(node, obj) {
110 obj.name = this.getChildValue(node);
112 "Title": function(node, obj) {
113 obj.title = this.getChildValue(node);
115 "Abstract": function(node, obj) {
116 obj.description = this.getChildValue(node);
118 "NamedLayer": function(node, sld) {
123 this.readChildNodes(node, layer);
124 // give each of the user styles this layer name
125 for(var i=0, len=layer.userStyles.length; i<len; ++i) {
126 layer.userStyles[i].layerName = layer.name;
128 if(sld.namedLayers instanceof Array) {
129 sld.namedLayers.push(layer);
131 sld.namedLayers[layer.name] = layer;
134 "NamedStyle": function(node, layer) {
135 layer.namedStyles.push(
136 this.getChildName(node.firstChild)
139 "UserStyle": function(node, layer) {
140 var obj = {defaultsPerSymbolizer: true, rules: []};
141 this.readChildNodes(node, obj);
142 var style = new OpenLayers.Style(this.defaultSymbolizer, obj);
143 layer.userStyles.push(style);
145 "IsDefault": function(node, style) {
146 if(this.getChildValue(node) == "1") {
147 style.isDefault = true;
150 "FeatureTypeStyle": function(node, style) {
151 // OpenLayers doesn't have a place for FeatureTypeStyle
152 // Name, Title, Abstract, FeatureTypeName, or
153 // SemanticTypeIdentifier so, we make a temporary object
154 // and later just use the Rule(s).
158 this.readChildNodes(node, obj);
159 style.rules = obj.rules;
161 "Rule": function(node, obj) {
162 var rule = new OpenLayers.Rule();
163 this.readChildNodes(node, rule);
164 obj.rules.push(rule);
166 "ElseFilter": function(node, rule) {
167 rule.elseFilter = true;
169 "MinScaleDenominator": function(node, rule) {
170 rule.minScaleDenominator = parseFloat(this.getChildValue(node));
172 "MaxScaleDenominator": function(node, rule) {
173 rule.maxScaleDenominator = parseFloat(this.getChildValue(node));
175 "TextSymbolizer": function(node, rule) {
176 // OpenLayers doens't do painter's order, instead we extend
177 var symbolizer = rule.symbolizer["Text"] || {};
178 this.readChildNodes(node, symbolizer);
179 // in case it didn't exist before
180 rule.symbolizer["Text"] = symbolizer;
182 "Label": function(node, symbolizer) {
183 // only supporting literal or property name
185 this.readChildNodes(node, obj);
187 symbolizer.label = "${" + obj.property + "}";
189 var value = this.readOgcExpression(node);
191 symbolizer.label = value;
195 "Font": function(node, symbolizer) {
196 this.readChildNodes(node, symbolizer);
198 "Halo": function(node, symbolizer) {
199 // halo has a fill, so send fresh object
201 this.readChildNodes(node, obj);
202 symbolizer.haloRadius = obj.haloRadius;
203 symbolizer.haloColor = obj.fillColor;
204 symbolizer.haloOpacity = obj.fillOpacity;
206 "Radius": function(node, symbolizer) {
207 var radius = this.readOgcExpression(node);
209 // radius is only used for halo
210 symbolizer.haloRadius = radius;
213 "LineSymbolizer": function(node, rule) {
214 // OpenLayers doesn't do painter's order, instead we extend
215 var symbolizer = rule.symbolizer["Line"] || {};
216 this.readChildNodes(node, symbolizer);
217 // in case it didn't exist before
218 rule.symbolizer["Line"] = symbolizer;
220 "PolygonSymbolizer": function(node, rule) {
221 // OpenLayers doens't do painter's order, instead we extend
222 var symbolizer = rule.symbolizer["Polygon"] || {};
223 this.readChildNodes(node, symbolizer);
224 // in case it didn't exist before
225 rule.symbolizer["Polygon"] = symbolizer;
227 "PointSymbolizer": function(node, rule) {
228 // OpenLayers doens't do painter's order, instead we extend
229 var symbolizer = rule.symbolizer["Point"] || {};
230 this.readChildNodes(node, symbolizer);
231 // in case it didn't exist before
232 rule.symbolizer["Point"] = symbolizer;
234 "Stroke": function(node, symbolizer) {
235 symbolizer.stroke = true;
236 this.readChildNodes(node, symbolizer);
238 "Fill": function(node, symbolizer) {
239 symbolizer.fill = true;
240 this.readChildNodes(node, symbolizer);
242 "CssParameter": function(node, symbolizer) {
243 var cssProperty = node.getAttribute("name");
244 var symProperty = this.cssMap[cssProperty];
246 // Limited support for parsing of OGC expressions
247 var value = this.readOgcExpression(node);
248 // always string, could be an empty string
250 symbolizer[symProperty] = value;
254 "Graphic": function(node, symbolizer) {
255 symbolizer.graphic = true;
257 // painter's order not respected here, clobber previous with next
258 this.readChildNodes(node, graphic);
259 // directly properties with names that match symbolizer properties
261 "strokeColor", "strokeWidth", "strokeOpacity",
262 "strokeLinecap", "fillColor", "fillOpacity",
263 "graphicName", "rotation", "graphicFormat"
266 for(var i=0, len=properties.length; i<len; ++i) {
267 prop = properties[i];
268 value = graphic[prop];
269 if(value != undefined) {
270 symbolizer[prop] = value;
273 // set other generic properties with specific graphic property names
274 if(graphic.opacity != undefined) {
275 symbolizer.graphicOpacity = graphic.opacity;
277 if(graphic.size != undefined) {
278 symbolizer.pointRadius = graphic.size / 2;
280 if(graphic.href != undefined) {
281 symbolizer.externalGraphic = graphic.href;
283 if(graphic.rotation != undefined) {
284 symbolizer.rotation = graphic.rotation;
287 "ExternalGraphic": function(node, graphic) {
288 this.readChildNodes(node, graphic);
290 "Mark": function(node, graphic) {
291 this.readChildNodes(node, graphic);
293 "WellKnownName": function(node, graphic) {
294 graphic.graphicName = this.getChildValue(node);
296 "Opacity": function(node, obj) {
297 var opacity = this.readOgcExpression(node);
298 // always string, could be empty string
300 obj.opacity = opacity;
303 "Size": function(node, obj) {
304 var size = this.readOgcExpression(node);
305 // always string, could be empty string
310 "Rotation": function(node, obj) {
311 var rotation = this.readOgcExpression(node);
312 // always string, could be empty string
314 obj.rotation = rotation;
317 "OnlineResource": function(node, obj) {
318 obj.href = this.getAttributeNS(
319 node, this.namespaces.xlink, "href"
322 "Format": function(node, graphic) {
323 graphic.graphicFormat = this.getChildValue(node);
326 }, OpenLayers.Format.Filter.v1_0_0.prototype.readers),
330 * {Object} Object mapping supported css property names to OpenLayers
331 * symbolizer property names.
334 "stroke": "strokeColor",
335 "stroke-opacity": "strokeOpacity",
336 "stroke-width": "strokeWidth",
337 "stroke-linecap": "strokeLinecap",
338 "stroke-dasharray": "strokeDashstyle",
340 "fill-opacity": "fillOpacity",
341 "font-family": "fontFamily",
342 "font-size": "fontSize",
343 "font-weight": "fontWeight",
344 "font-style": "fontStyle"
348 * Method: getCssProperty
349 * Given a symbolizer property, get the corresponding CSS property
353 * sym - {String} A symbolizer property name.
356 * {String} A CSS property name or null if none found.
358 getCssProperty: function(sym) {
360 for(var prop in this.cssMap) {
361 if(this.cssMap[prop] == sym) {
370 * Method: getGraphicFormat
371 * Given a href for an external graphic, try to determine the mime-type.
372 * This method doesn't try too hard, and will fall back to
373 * <defautlGraphicFormat> if one of the known <graphicFormats> is not
374 * the file extension of the provided href.
380 * {String} The graphic format.
382 getGraphicFormat: function(href) {
384 for(var key in this.graphicFormats) {
385 if(this.graphicFormats[key].test(href)) {
390 return format || this.defautlGraphicFormat;
394 * Property: defaultGraphicFormat
395 * {String} If none other can be determined from <getGraphicFormat>, this
396 * default will be returned.
398 defaultGraphicFormat: "image/png",
401 * Property: graphicFormats
402 * {Object} Mapping of image mime-types to regular extensions matching
403 * well-known file extensions.
406 "image/jpeg": /\.jpe?g$/i,
407 "image/gif": /\.gif$/i,
408 "image/png": /\.png$/i
415 * sld - {Object} An object representing the SLD.
418 * {DOMElement} The root of an SLD document.
420 write: function(sld) {
421 return this.writers.sld.StyledLayerDescriptor.apply(this, [sld]);
426 * As a compliment to the readers property, this structure contains public
427 * writing functions grouped by namespace alias and named like the
428 * node names they produce.
430 writers: OpenLayers.Util.applyDefaults({
432 "StyledLayerDescriptor": function(sld) {
433 var root = this.createElementNSPlus(
434 "StyledLayerDescriptor",
436 "version": this.VERSION,
437 "xsi:schemaLocation": this.schemaLocation
440 // add in optional name
442 this.writeNode("Name", sld.name, root);
444 // add in optional title
446 this.writeNode("Title", sld.title, root);
448 // add in optional description
449 if(sld.description) {
450 this.writeNode("Abstract", sld.description, root);
452 // add in named layers
453 // allow namedLayers to be an array
454 if(sld.namedLayers instanceof Array) {
455 for(var i=0, len=sld.namedLayers.length; i<len; ++i) {
456 this.writeNode("NamedLayer", sld.namedLayers[i], root);
459 for(var name in sld.namedLayers) {
460 this.writeNode("NamedLayer", sld.namedLayers[name], root);
465 "Name": function(name) {
466 return this.createElementNSPlus("Name", {value: name});
468 "Title": function(title) {
469 return this.createElementNSPlus("Title", {value: title});
471 "Abstract": function(description) {
472 return this.createElementNSPlus(
473 "Abstract", {value: description}
476 "NamedLayer": function(layer) {
477 var node = this.createElementNSPlus("NamedLayer");
479 // add in required name
480 this.writeNode("Name", layer.name, node);
482 // optional sld:LayerFeatureConstraints here
484 // add in named styles
485 if(layer.namedStyles) {
486 for(var i=0, len=layer.namedStyles.length; i<len; ++i) {
488 "NamedStyle", layer.namedStyles[i], node
493 // add in user styles
494 if(layer.userStyles) {
495 for(var i=0, len=layer.userStyles.length; i<len; ++i) {
497 "UserStyle", layer.userStyles[i], node
504 "NamedStyle": function(name) {
505 var node = this.createElementNSPlus("NamedStyle");
506 this.writeNode("Name", name, node);
509 "UserStyle": function(style) {
510 var node = this.createElementNSPlus("UserStyle");
512 // add in optional name
514 this.writeNode("Name", style.name, node);
516 // add in optional title
518 this.writeNode("Title", style.title, node);
520 // add in optional description
521 if(style.description) {
522 this.writeNode("Abstract", style.description, node);
526 if(style.isDefault) {
527 this.writeNode("IsDefault", style.isDefault, node);
530 // add FeatureTypeStyles
531 this.writeNode("FeatureTypeStyle", style, node);
535 "IsDefault": function(bool) {
536 return this.createElementNSPlus(
537 "IsDefault", {value: (bool) ? "1" : "0"}
540 "FeatureTypeStyle": function(style) {
541 var node = this.createElementNSPlus("FeatureTypeStyle");
543 // OpenLayers currently stores no Name, Title, Abstract,
544 // FeatureTypeName, or SemanticTypeIdentifier information
545 // related to FeatureTypeStyle
548 for(var i=0, len=style.rules.length; i<len; ++i) {
549 this.writeNode("Rule", style.rules[i], node);
554 "Rule": function(rule) {
555 var node = this.createElementNSPlus("Rule");
557 // add in optional name
559 this.writeNode("Name", rule.name, node);
561 // add in optional title
563 this.writeNode("Title", rule.title, node);
565 // add in optional description
566 if(rule.description) {
567 this.writeNode("Abstract", rule.description, node);
570 // add in LegendGraphic here
572 // add in optional filters
573 if(rule.elseFilter) {
574 this.writeNode("ElseFilter", null, node);
575 } else if(rule.filter) {
576 this.writeNode("ogc:Filter", rule.filter, node);
579 // add in scale limits
580 if(rule.minScaleDenominator != undefined) {
582 "MinScaleDenominator", rule.minScaleDenominator, node
585 if(rule.maxScaleDenominator != undefined) {
587 "MaxScaleDenominator", rule.maxScaleDenominator, node
591 // add in symbolizers (relies on geometry type keys)
592 var types = OpenLayers.Style.SYMBOLIZER_PREFIXES;
593 var type, symbolizer;
594 for(var i=0, len=types.length; i<len; ++i) {
596 symbolizer = rule.symbolizer[type];
599 type + "Symbolizer", symbolizer, node
606 "ElseFilter": function() {
607 return this.createElementNSPlus("ElseFilter");
609 "MinScaleDenominator": function(scale) {
610 return this.createElementNSPlus(
611 "MinScaleDenominator", {value: scale}
614 "MaxScaleDenominator": function(scale) {
615 return this.createElementNSPlus(
616 "MaxScaleDenominator", {value: scale}
619 "LineSymbolizer": function(symbolizer) {
620 var node = this.createElementNSPlus("LineSymbolizer");
621 this.writeNode("Stroke", symbolizer, node);
624 "Stroke": function(symbolizer) {
625 var node = this.createElementNSPlus("Stroke");
628 // GraphicStroke here
630 // add in CssParameters
631 if(symbolizer.strokeColor != undefined) {
634 {symbolizer: symbolizer, key: "strokeColor"},
638 if(symbolizer.strokeOpacity != undefined) {
641 {symbolizer: symbolizer, key: "strokeOpacity"},
645 if(symbolizer.strokeWidth != undefined) {
648 {symbolizer: symbolizer, key: "strokeWidth"},
654 "CssParameter": function(obj) {
655 // not handling ogc:expressions for now
656 return this.createElementNSPlus("CssParameter", {
657 attributes: {name: this.getCssProperty(obj.key)},
658 value: obj.symbolizer[obj.key]
661 "TextSymbolizer": function(symbolizer) {
662 var node = this.createElementNSPlus("TextSymbolizer");
663 // add in optional Label
664 if(symbolizer.label != null) {
665 this.writeNode("Label", symbolizer.label, node);
667 // add in optional Font
668 if(symbolizer.fontFamily != null ||
669 symbolizer.fontSize != null ||
670 symbolizer.fontWeight != null ||
671 symbolizer.fontStyle != null) {
672 this.writeNode("Font", symbolizer, node);
674 // add in optional Halo
675 if(symbolizer.haloRadius != null ||
676 symbolizer.haloColor != null ||
677 symbolizer.haloOpacity != null) {
678 this.writeNode("Halo", symbolizer, node);
680 // add in optional Fill
681 if(symbolizer.fillColor != null ||
682 symbolizer.fillOpacity != null) {
683 this.writeNode("Fill", symbolizer, node);
687 "Font": function(symbolizer) {
688 var node = this.createElementNSPlus("Font");
689 // add in CssParameters
690 if(symbolizer.fontFamily) {
693 {symbolizer: symbolizer, key: "fontFamily"},
697 if(symbolizer.fontSize) {
700 {symbolizer: symbolizer, key: "fontSize"},
704 if(symbolizer.fontWeight) {
707 {symbolizer: symbolizer, key: "fontWeight"},
711 if(symbolizer.fontStyle) {
714 {symbolizer: symbolizer, key: "fontStyle"},
720 "Label": function(label) {
721 // only the simplest of ogc:expression handled
722 // {label: "some text and a ${propertyName}"}
723 var node = this.createElementNSPlus("Label");
724 var tokens = label.split("${");
725 node.appendChild(this.createTextNode(tokens[0]));
727 for(var i=1, len=tokens.length; i<len; i++) {
729 last = item.indexOf("}");
733 {property: item.substring(0, last)},
737 this.createTextNode(item.substring(++last))
740 // no ending }, so this is a literal ${
742 this.createTextNode("${" + item)
748 "Halo": function(symbolizer) {
749 var node = this.createElementNSPlus("Halo");
750 if(symbolizer.haloRadius) {
751 this.writeNode("Radius", symbolizer.haloRadius, node);
753 if(symbolizer.haloColor || symbolizer.haloOpacity) {
754 this.writeNode("Fill", {
755 fillColor: symbolizer.haloColor,
756 fillOpacity: symbolizer.haloOpacity
761 "Radius": function(value) {
762 return node = this.createElementNSPlus("Radius", {
766 "PolygonSymbolizer": function(symbolizer) {
767 var node = this.createElementNSPlus("PolygonSymbolizer");
768 if(symbolizer.fillColor != undefined ||
769 symbolizer.fillOpacity != undefined) {
770 this.writeNode("Fill", symbolizer, node);
772 if(symbolizer.strokeWidth != undefined ||
773 symbolizer.strokeColor != undefined ||
774 symbolizer.strokeOpacity != undefined ||
775 symbolizer.strokeDashstyle != undefined) {
776 this.writeNode("Stroke", symbolizer, node);
780 "Fill": function(symbolizer) {
781 var node = this.createElementNSPlus("Fill");
785 // add in CssParameters
786 if(symbolizer.fillColor) {
789 {symbolizer: symbolizer, key: "fillColor"},
793 if(symbolizer.fillOpacity != null) {
796 {symbolizer: symbolizer, key: "fillOpacity"},
802 "PointSymbolizer": function(symbolizer) {
803 var node = this.createElementNSPlus("PointSymbolizer");
804 this.writeNode("Graphic", symbolizer, node);
807 "Graphic": function(symbolizer) {
808 var node = this.createElementNSPlus("Graphic");
809 if(symbolizer.externalGraphic != undefined) {
810 this.writeNode("ExternalGraphic", symbolizer, node);
812 this.writeNode("Mark", symbolizer, node);
815 if(symbolizer.graphicOpacity != undefined) {
816 this.writeNode("Opacity", symbolizer.graphicOpacity, node);
818 if(symbolizer.pointRadius != undefined) {
819 this.writeNode("Size", symbolizer.pointRadius * 2, node);
821 if(symbolizer.rotation != undefined) {
822 this.writeNode("Rotation", symbolizer.rotation, node);
826 "ExternalGraphic": function(symbolizer) {
827 var node = this.createElementNSPlus("ExternalGraphic");
829 "OnlineResource", symbolizer.externalGraphic, node
831 var format = symbolizer.graphicFormat ||
832 this.getGraphicFormat(symbolizer.externalGraphic);
833 this.writeNode("Format", format, node);
836 "Mark": function(symbolizer) {
837 var node = this.createElementNSPlus("Mark");
838 if(symbolizer.graphicName) {
839 this.writeNode("WellKnownName", symbolizer.graphicName, node);
841 this.writeNode("Fill", symbolizer, node);
842 this.writeNode("Stroke", symbolizer, node);
845 "WellKnownName": function(name) {
846 return this.createElementNSPlus("WellKnownName", {
850 "Opacity": function(value) {
851 return this.createElementNSPlus("Opacity", {
855 "Size": function(value) {
856 return this.createElementNSPlus("Size", {
860 "Rotation": function(value) {
861 return this.createElementNSPlus("Rotation", {
865 "OnlineResource": function(href) {
866 return this.createElementNSPlus("OnlineResource", {
868 "xlink:type": "simple",
873 "Format": function(format) {
874 return this.createElementNSPlus("Format", {
879 }, OpenLayers.Format.Filter.v1_0_0.prototype.writers),
881 CLASS_NAME: "OpenLayers.Format.SLD.v1"