1 """
2 StyledLayerDescriptor library for generating SLD documents.
3
4 SLD documents are used to style cartographic representations of geometric
5 features in most professional and desktop GIS applications.
6
7 Specification
8 =============
9 The SLD specification is available from the Open Geospatial Consortium,
10 at U{http://www.opengeospatial.org/standards/sld}
11
12 License
13 =======
14 Copyright 2011 David Zwarg <U{dzwarg@azavea.com}>
15
16 Licensed under the Apache License, Version 2.0 (the "License");
17 you may not use this file except in compliance with the License.
18 You may obtain a copy of the License at
19
20 U{http://www.apache.org/licenses/LICENSE-2.0}
21
22 Unless required by applicable law or agreed to in writing, software
23 distributed under the License is distributed on an "AS IS" BASIS,
24 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 See the License for the specific language governing permissions and
26 limitations under the License.
27
28 @author: David Zwarg
29 @contact: dzwarg@azavea.com
30 @copyright: 2011, Azavea
31 @license: Apache 2.0
32 @version: 1.0.6
33 @newfield prop: Property, Properties
34 """
35 from lxml.etree import parse, Element, XMLSchema, XMLSyntaxError, tostring
36 import urllib2
37 from tempfile import NamedTemporaryFile
38 import os, copy, logging
41 """
42 A base class for all python objects that relate directly to SLD elements.
43 An SLDNode contains references to the underlying parent node, underlying
44 element node, and the namespace map.
45
46 The SLDNode base class also contains utility methods to construct properties
47 for child SLDNode objects.
48 """
49
50 _nsmap = {
51 'sld':"http://www.opengis.net/sld",
52 'ogc':"http://www.opengis.net/ogc",
53 'xlink':"http://www.w3.org/1999/xlink",
54 'xsi':"http://www.w3.org/2001/XMLSchema-instance"
55 }
56 """Defined namespaces in SLD documents."""
57
58 - def __init__(self, parent, descendant=True):
59 """
60 Create a new SLDNode. It is not necessary to call this directly, because
61 all child classes should initialize the SLDNode internally.
62
63 @type parent: L{SLDNode}
64 @param parent: The parent class object.
65 @type descendant: boolean
66 @param descendant: Does this element descend from the parent, or is it a sibling?
67 """
68 if parent is None:
69 self._parent = None
70 elif descendant:
71 self._parent = parent._node
72 else:
73 self._parent = parent._parent
74 self._node = None
75
76 @staticmethod
77 - def makeproperty(ns, cls=None, name=None, docstring='', descendant=True):
78 """
79 Make a property on an instance of an SLDNode. If cls is omitted, the
80 property is assumed to be a text node, with no corresponding class
81 object. If name is omitted, the property is assumed to be a complex
82 node, with a corresponding class wrapper.
83
84 @type ns: string
85 @param ns: The namespace of this property's node.
86 @type cls: class
87 @param cls: Optional. The class of the child property.
88 @type name: string
89 @param name: Optional. The name of the child property.
90 @type docstring: string
91 @param docstring: Optional. The docstring to attach to the new property.
92 @type descendant: boolean
93 @param descendant: Does this element descend from the parent, or is it a sibling?
94
95 @rtype: property attribute
96 @return: A property attribute for this named property.
97 """
98 def get_property(self):
99 """
100 A generic property getter.
101 """
102 if cls is None:
103 xpath = '%s:%s' % (ns, name)
104 else:
105 xpath = '%s:%s' % (ns, cls.__name__)
106
107 xpath = self._node.xpath(xpath, namespaces=SLDNode._nsmap)
108 if len(xpath) == 1:
109 if cls is None:
110 return xpath[0].text
111 else:
112 elem = cls.__new__(cls)
113 cls.__init__(elem, self, descendant=descendant)
114 return elem
115 else:
116 return None
117
118 def set_property(self, value):
119 """
120 A generic property setter.
121 """
122 if cls is None:
123 xpath = '%s:%s' % (ns, name)
124 else:
125 xpath = '%s:%s' % (ns, cls.__name__)
126
127 xpath = self._node.xpath(xpath, namespaces=SLDNode._nsmap)
128 if len(xpath) == 1:
129 if cls is None:
130 xpath[0].text = value
131 else:
132 xpath[0] = value._node
133 else:
134 if cls is None:
135 elem = self._node.makeelement('{%s}%s' % (SLDNode._nsmap[ns], name), nsmap=SLDNode._nsmap)
136 elem.text = value
137 self._node.append(elem)
138 else:
139 self._node.append(value._node)
140
141 def del_property(self):
142 """
143 A generic property deleter.
144 """
145 if cls is None:
146 xpath = '%s:%s' % (ns, name)
147 else:
148 xpath = '%s:%s' % (ns, cls.__name__)
149
150 xpath = self._node.xpath(xpath, namespaces=SLDNode._nsmap)
151 if len(xpath) == 1:
152 self._node.remove(xpath[0])
153
154 return property(get_property, set_property, del_property, docstring)
155
156
158 """
159 Attempt to get the only child element from this SLDNode. If the node
160 does not exist, create the element, attach it to the DOM, and return
161 the class object that wraps the node.
162
163 @type ns: string
164 @param ns: The namespace of the new element.
165 @type name: string
166 @param name: The name of the new element.
167 @rtype: L{SLDNode}
168 @return: The wrapped node, in the parent's property class. This will
169 always be a descendent of SLDNode.
170 """
171 if len(self._node.xpath('%s:%s' % (ns, name), namespaces=SLDNode._nsmap)) == 1:
172 return getattr(self, name)
173
174 return self.create_element(ns, name)
175
177 """
178 Create an element as a child of this SLDNode.
179
180 @type ns: string
181 @param ns: The namespace of the new element.
182 @type name: string
183 @param name: The name of the new element.
184 @rtype: L{SLDNode}
185 @return: The wrapped node, in the parent's property class. This will
186 always be a descendent of SLDNode.
187 """
188 elem = self._node.makeelement('{%s}%s' % (SLDNode._nsmap[ns], name), nsmap=SLDNode._nsmap)
189 self._node.append(elem)
190
191 return getattr(self, name)
192
195 """
196 A css styling parameter. May be a child of L{Fill}, L{Font}, and L{Stroke}.
197 """
198 - def __init__(self, parent, index, descendant=True):
199 """
200 Create a new CssParameter from an existing StyleItem.
201
202 @type parent: L{StyleItem}
203 @param parent: The parent class object.
204 @type index: integer
205 @param index: The index of the node in the list of all CssParameters in the parent.
206 @type descendant: boolean
207 @param descendant: Does this element descend from the parent, or is it a sibling?
208 """
209 super(CssParameter, self).__init__(parent, descendant=descendant)
210 self._node = self._parent.xpath('sld:CssParameter', namespaces=SLDNode._nsmap)[index]
211
213 """
214 Get the name attribute.
215
216 @rtype: string
217 @return: The value of the 'name' attribute.
218 """
219 return self._node.attrib['name']
220
222 """
223 Set the name attribute.
224
225 @type value: string
226 @param value: The value of the 'name' attribute.
227 """
228 self._node.attrib['name'] = value
229
231 """
232 Delete the name attribute.
233 """
234 del self._node.attrib['name']
235
236 Name = property(get_name, set_name, del_name, "The value of the 'name' attribute.")
237 """The value of the 'name' attribute."""
238
240 """
241 Get the text content.
242
243 @rtype: string
244 @return: The text content.
245 """
246 return self._node.text
247
249 """
250 Set the text content.
251
252 @type value: string
253 @param value: The text content.
254 """
255 self._node.text = value
256
258 """
259 Delete the text content.
260 """
261 self._node.clear()
262
263 Value = property(get_value, set_value, del_value, "The value of the parameter.")
264 """The value of the parameter."""
265
268 """
269 A collection of L{CssParameter} nodes. This is a pythonic helper (list of
270 nodes) that does not correspond to a true element in the SLD spec.
271 """
273 """
274 Create a new list of CssParameters from the specified parent node.
275
276 @type parent: L{StyleItem}
277 @param parent: The parent class item.
278 """
279 super(CssParameters, self).__init__(parent)
280 self._node = None
281 self._nodes = self._parent.xpath('sld:CssParameter', namespaces=SLDNode._nsmap)
282
284 """
285 Get the number of L{CssParameter} nodes in this list.
286
287 @rtype: integer
288 @return: The number of L{CssParameter} nodes.
289 """
290 return len(self._nodes)
291
293 """
294 Get one of the L{CssParameter} nodes in the list.
295
296 @type key: integer
297 @param key: The index of the child node.
298 @rtype: L{CssParameter}
299 @return: The specific L{CssParameter} node.
300 """
301 return CssParameter(self, key, descendant=False)
302
304 """
305 Set one of the L{CssParameter} nodes in the list with a new value.
306
307 @type key: integer
308 @param key: The index of the child node.
309 @type value: L{CssParameter}, etree.Element
310 @param value: The new value of the specific child node.
311 """
312 if isinstance(value, CssParameter):
313 self._nodes.replace(self._nodes[key], value._node)
314 elif isinstance(value, Element):
315 self._nodes.replace(self._nodes[key], value)
316
318 """
319 Delete one of the L{CssParameter} nodes from the list.
320
321 @type key: integer
322 @param key: The index of the child node.
323 """
324 self._nodes.remove(self._nodes[key])
325
328 """
329 Abstract base class for all nodes that contain a list of L{CssParameter} nodes.
330 """
331 - def __init__(self, parent, name, descendant=True):
332 """
333 Create a new StyleItem.
334
335 @type parent: L{Symbolizer}
336 @param parent: The parent class object.
337 @type name: string
338 @param name: The name of the node.
339 @type descendant: boolean
340 @param descendant: Does this element descend from the parent, or is it a sibling?
341 """
342 super(StyleItem, self).__init__(parent, descendant=descendant)
343 xpath = self._parent.xpath('sld:'+name, namespaces=SLDNode._nsmap)
344 if len(xpath) < 1:
345 self._node = self._parent.makeelement('{%s}%s' % (SLDNode._nsmap['sld'], name), nsmap=SLDNode._nsmap)
346 self._parent.append(self._node)
347 else:
348 self._node = xpath[0]
349
350 @property
352 """
353 Get the list of L{CssParameter} nodes in a friendly L{CssParameters} helper list.
354
355 @rtype: L{CssParameters}
356 @return: A pythonic list of L{CssParameter} children.
357 """
358 return CssParameters(self)
359
361 """
362 Create a new L{CssParameter} node as a child of this element, and attach it to the DOM.
363 Optionally set the name and value of the parameter, if they are both provided.
364
365 @type name: string
366 @param name: Optional. The name of the L{CssParameter}
367 @type value: string
368 @param value: Optional. The value of the L{CssParameter}
369 @rtype: L{CssParameter}
370 @return: A new style parameter, set to the name and value.
371 """
372 elem = self._node.makeelement('{%s}CssParameter' % SLDNode._nsmap['sld'], nsmap=SLDNode._nsmap)
373 self._node.append(elem)
374
375 if not (name is None or value is None):
376 elem.attrib['name'] = name
377 elem.text = value
378
379 return CssParameter(self, len(self._node)-1)
380
381
382 -class Fill(StyleItem):
383 """
384 A style specification for fill types. This class contains a
385 L{CssParameters} list, which can include:
386
387 - fill
388 - fill-opacity
389
390 This class is a property of any L{Symbolizer}.
391 """
392 - def __init__(self, parent, descendant=True):
393 """
394 Create a new Fill node from the specified parent.
395
396 @type parent: L{Symbolizer}
397 @param parent: The parent class object.
398 @type descendant: boolean
399 @param descendant: A flag indicating if this is a descendant node of the parent.
400 """
401 super(Fill, self).__init__(parent, 'Fill', descendant=descendant)
402
403
404 -class Font(StyleItem):
405 """
406 A style specification for font types. This class contains a
407 L{CssParameters} list, which can include:
408
409 - font-family
410 - font-size
411 - font-style
412 - font-weight
413
414 This class is a property of any L{Symbolizer}.
415 """
416 - def __init__(self, parent, descendant=True):
417 """
418 Create a new Font node from the specified parent.
419
420 @type parent: L{Symbolizer}
421 @param parent: The parent class object.
422 @type descendant: boolean
423 @param descendant: A flag indicating if this is a descendant node of the parent.
424 """
425 super(Font, self).__init__(parent, 'Font', descendant=descendant)
426
429 """
430 A style specification for stroke types. This class contains a
431 L{CssParameters} list, which can include:
432
433 - stroke
434 - stroke-dasharray
435 - stroke-dashoffset
436 - stroke-linecap
437 - stroke-linejoin
438 - stroke-opacity
439 - stroke-width
440
441 This class is a property of any L{Symbolizer}.
442 """
443 - def __init__(self, parent, descendant=True):
444 """
445 Create a new Stroke node from the specified parent.
446
447 @type parent: L{Symbolizer}
448 @param parent: The parent class object.
449 @type descendant: boolean
450 @param descendant: A flag indicating if this is a descendant node of the parent.
451 """
452 super(Stroke, self).__init__(parent, 'Stroke', descendant=descendant)
453
456 """
457 Abstract base class for all symbolizer nodes. Symbolizer nodes are those
458 that contain L{Fill}, L{Font}, or L{Stroke} children.
459
460 All derived Symbolizer classes have access to the Fill, Font, and Stroke properties.
461
462 @prop: B{Fill}
463
464 The element that contains the L{CssParameter} nodes for describing the polygon fill styles.
465
466 I{Type}: L{Fill}
467
468 @prop: B{Font}
469
470 The element that contains the L{CssParameter} nodes for describing the font styles.
471
472 I{Type}: L{Font}
473
474 @prop: B{Stroke}
475
476 The element that contains the L{CssParameter} nodes for describing the line styles.
477
478 I{Type}: L{Stroke}
479 """
480 - def __init__(self, parent, name, descendant=True):
481 """
482 Create a new Symbolizer node. If the specified node is not found in the
483 DOM, the node will be created and attached to the parent.
484
485 @type parent: L{Rule}
486 @param parent: The parent class object.
487 @type name: string
488 @param name: The type of symbolizer node. If this parameter ends with
489 the character '*', the '*' will get expanded into 'Symbolizer'.
490 @type descendant: boolean
491 @param descendant: A flag indicating if this is a descendant node of the parent.
492 """
493 super(Symbolizer, self).__init__(parent, descendant=descendant)
494
495 if name[len(name)-1] == '*':
496 name = name[0:-1] + 'Symbolizer'
497
498 xpath = self._parent.xpath('sld:%s' % name, namespaces=SLDNode._nsmap)
499 if len(xpath) < 1:
500 self._node = self._parent.makeelement('{%s}%s' % (SLDNode._nsmap['sld'], name), nsmap=SLDNode._nsmap)
501 self._parent.append(self._node)
502 else:
503 self._node = xpath[0]
504
505 setattr(self.__class__, 'Fill', SLDNode.makeproperty('sld', cls=Fill,
506 docstring="The parameters for describing the fill styling."))
507 setattr(self.__class__, 'Font', SLDNode.makeproperty('sld', cls=Font,
508 docstring="The parameters for describing the font styling."))
509 setattr(self.__class__, 'Stroke', SLDNode.makeproperty('sld', cls=Stroke,
510 docstring="The parameters for describing the stroke styling."))
511
513 """
514 Create a new L{Fill} element on this Symbolizer.
515
516 @rtype: L{Fill}
517 @return: A new fill element, attached to this symbolizer.
518 """
519 return self.create_element('sld', 'Fill')
520
522 """
523 Create a new L{Font} element on this Symbolizer.
524
525 @rtype: L{Font}
526 @return: A new font element, attached to this symbolizer.
527 """
528 return self.create_element('sld', 'Font')
529
531 """
532 Create a new L{Stroke} element on this Symbolizer.
533
534 @rtype: L{Stroke}
535 @return: A new stroke element, attached to this symbolizer.
536 """
537 return self.create_element('sld', 'Stroke')
538
541 """
542 A symbolizer for polygon geometries. A PolygonSymbolizer is a child of a
543 L{Rule} element.
544
545 @prop: Fill
546
547 The element that contains the L{CssParameter} nodes for describing the
548 polygon fill styles.
549
550 I{Type}: L{Fill}
551
552 @prop: Stroke
553
554 The element that contains the L{CssParameter} nodes for describing the line
555 styles.
556
557 I{Type}: L{Stroke}
558 """
559 - def __init__(self, parent, descendant=True):
560 """
561 Create a new PolygonSymbolizer node, as a child of the specified parent.
562
563 @type parent: L{Rule}
564 @param parent: The parent class object.
565 @type descendant: boolean
566 @param descendant: A flag indicating if this is a descendant node of the parent.
567 """
568 super(PolygonSymbolizer, self).__init__(parent, 'Polygon*', descendant)
569
572 """
573 A symbolizer for line geometries. A LineSymbolizer is a child of a
574 L{Rule} element.
575
576 @prop: Stroke
577
578 The element that contains the L{CssParameter} nodes for describing the line
579 styles.
580
581 I{Type}: L{Stroke}
582 """
583 - def __init__(self, parent, descendant=True):
584 """
585 Create a new LineSymbolizer node, as a child of the specified parent.
586
587 @type parent: L{Rule}
588 @param parent: The parent class object.
589 @type descendant: boolean
590 @param descendant: A flag indicating if this is a descendant node of the parent.
591 """
592 super(LineSymbolizer, self).__init__(parent, 'Line*', descendant)
593
594
595 -class TextSymbolizer(Symbolizer):
596 """
597 A symbolizer for text labels. A TextSymbolizer is a child of a L{Rule}
598 element.
599
600 @prop: Fill
601
602 The element that contains the L{CssParameter} nodes for describing the
603 character fill styles.
604
605 I{Type}: L{Fill}
606 """
607 - def __init__(self, parent, descendant=True):
608 """
609 Create a new TextSymbolizer node, as a child of the specified parent.
610
611 @type parent: L{Rule}
612 @param parent: The parent class object.
613 @type descendant: boolean
614 @param descendant: A flag indicating if this is a descendant node of the parent.
615 """
616 super(TextSymbolizer, self).__init__(parent, 'Text*', descendant=descendant)
617
618
619 -class Mark(Symbolizer):
620 """
621 A graphic mark for describing points. A Mark is a child of a L{Graphic}
622 element.
623
624 @prop: Fill
625
626 The element that contains the L{CssParameter} nodes for describing the
627 fill styles.
628
629 I{Type}: L{Fill}
630
631 @prop: Stroke
632
633 The element that contains the L{CssParameter} nodes for describing the
634 line styles.
635
636 I{Type}: L{Stroke}
637
638 @prop: WellKnownName
639
640 A string describing the Mark, which may be one of:
641 - circle
642 - cross
643 - square
644 - star
645 - triangle
646 - x
647
648 I{Type}: string
649 """
650 - def __init__(self, parent, descendant=True):
651 """
652 Create a new Mark node, as a child of the specified parent.
653
654 @type parent: L{Graphic}
655 @param parent: The parent class object.
656 @type descendant: boolean
657 @param descendant: A flag indicating if this is a descendant node of the parent.
658 """
659 super(Mark, self).__init__(parent, 'Mark', descendant=descendant)
660
661 setattr(self.__class__, 'WellKnownName', SLDNode.makeproperty('sld', name='WellKnownName',
662 docstring="The well known name for the mark."))
663
666 """
667 A Graphic node represents a graphical mark for representing points. A
668 Graphic is a child of a L{PointSymbolizer} element.
669
670 @prop: Mark
671
672 The element that contains the L{CssParameter} nodes for describing the point styles.
673
674 I{Type}: L{Mark}
675
676 @prop: Opacity
677
678 Bewteen 0 (completely transparent) and 1 (completely opaque)
679
680 I{Type}: float
681
682 @prop: Size
683
684 The size of the graphic, in pixels.
685
686 I{Type}: integer
687
688 @prop: Rotation
689
690 Clockwise degrees of rotation.
691
692 I{Type}: float
693 """
694 - def __init__(self, parent, descendant=True):
695 """
696 Create a new Graphic node, as a child of the specified parent.
697
698 @type parent: L{PointSymbolizer}
699 @param parent: The parent class object.
700 @type descendant: boolean
701 @param descendant: A flag indicating if this is a descendant node of the parent.
702 """
703 super(Graphic, self).__init__(parent, descendant=descendant)
704 xpath = self._parent.xpath('sld:Graphic', namespaces=SLDNode._nsmap)
705 if len(xpath) < 1:
706 self._node = self._parent.makeelement('{%s}Graphic' % SLDNode._nsmap['sld'], nsmap=SLDNode._nsmap)
707 self._parent.append(self._node)
708 else:
709 self._node = xpath[0]
710
711 setattr(self.__class__, 'Mark', SLDNode.makeproperty('sld', cls=Mark,
712 docstring="The graphic's mark styling."))
713 setattr(self.__class__, 'Opacity', SLDNode.makeproperty('sld', name='Opacity',
714 docstring="The opacity of the graphic."))
715 setattr(self.__class__, 'Size', SLDNode.makeproperty('sld', name='Size',
716 docstring="The size of the graphic, in pixels."))
717 setattr(self.__class__, 'Rotation', SLDNode.makeproperty('sld', name='Rotation',
718 docstring="The rotation of the graphic, in degrees clockwise."))
719
722 """
723 A symbolizer for point geometries. A PointSymbolizer is a child of a
724 L{Rule} element.
725
726 @prop: Graphic
727
728 The configuration of the point graphic.
729
730 I{Type}: L{Graphic}
731 """
732 - def __init__(self, parent, descendant=True):
733 """
734 Create a new PointSymbolizer node, as a child of the specified parent.
735
736 @type parent: L{Rule}
737 @param parent: The parent class object.
738 @type descendant: boolean
739 @param descendant: A flag indicating if this is a descendant node of the parent.
740 """
741 super(PointSymbolizer, self).__init__(parent, descendant=descendant)
742 xpath = self._parent.xpath('sld:PointSymbolizer', namespaces=SLDNode._nsmap)
743 if len(xpath) < 1:
744 self._node = self._parent.makeelement('{%s}PointSymbolizer' % SLDNode._nsmap['sld'], nsmap=SLDNode._nsmap)
745 self._parent.append(self._node)
746 else:
747 self._node = xpath[0]
748
749 setattr(self.__class__, 'Graphic', SLDNode.makeproperty('sld', cls=Graphic,
750 docstring="The graphic settings for this point geometry."))
751
754 """
755 General property criterion class for all property comparitors.
756 A PropertyCriterion is a child of a L{Filter} element.
757
758 Valid property comparitors that are represented by this class are:
759
760 - PropertyIsNotEqual
761 - PropertyIsLessThan
762 - PropertyIsLessThanOrEqual
763 - PropertyIsEqual
764 - PropertyIsGreaterThan
765 - PropertyIsGreaterThanOrEqual
766 - PropertyIsLike
767
768 @prop: PropertyName
769
770 The name of the property to use in the comparison.
771
772 I{Type}: string
773
774 @prop: Literal
775
776 The value of the property.
777
778 I{Type}: string
779 """
780 - def __init__(self, parent, name, descendant=True):
781 """
782 Create a new PropertyCriterion node, as a child of the specified parent.
783 A PropertyCriterion is not represented in the SLD Spec. This class
784 is a generalization of many of the PropertyIs... elements present in
785 the OGC Filter spec.
786
787 @type parent: L{Filter}
788 @param parent: The parent class object.
789 """
790 super(PropertyCriterion, self).__init__(parent, descendant=descendant)
791 xpath = self._parent.xpath('ogc:'+name, namespaces=SLDNode._nsmap)
792 if len(xpath) < 1:
793 self._node = self._parent.makeelement('{%s}%s' % (SLDNode._nsmap['ogc'], name), nsmap=SLDNode._nsmap)
794 self._parent.append(self._node)
795 else:
796 self._node = xpath[0]
797
798 setattr(self.__class__, 'PropertyName', SLDNode.makeproperty('ogc', name='PropertyName',
799 docstring="The name of the property to compare."))
800 setattr(self.__class__, 'Literal', SLDNode.makeproperty('ogc', name='Literal',
801 docstring="The literal value of the property to compare against."))
802
805 """
806 A filter object that stores the property comparitors. A Filter is a child
807 of a L{Rule} element. Filter nodes are pythonic, and have some syntactic
808 sugar that allows the creation of simple logical combinations.
809
810 To create an AND logical filter, use the '+' operator:
811
812 >>> rule.Filter = filter1 + filter2
813
814 To create an OR logical filter, use the '|' operator:
815
816 >>> rule.Filter = filter1 | filter2
817
818 Complex combinations can be created by chaining these operations together:
819
820 >>> rule.Filter = filter1 | (filter2 + filter3)
821
822 @prop: PropertyIsEqualTo
823
824 A specification of property (=) equality.
825
826 I{Type}: L{PropertyCriterion}
827
828 @prop: PropertyIsNotEqualTo
829
830 A specification of property (!=) inequality.
831
832 I{Type}: L{PropertyCriterion}
833
834 @prop: PropertyIsLessThan
835
836 A specification of property less-than (<) comparison.
837
838 I{Type}: L{PropertyCriterion}
839
840 @prop: PropertyIsLessThanOrEqualTo
841
842 A specification of property less-than-or-equal-to (<=) comparison.
843
844 I{Type}: L{PropertyCriterion}
845
846 @prop: PropertyIsGreaterThan
847
848 A specification of property greater-than (>) comparison,
849
850 I{Type}: L{PropertyCriterion}
851
852 @prop: PropertyIsGreaterThanOrEqualTo
853
854 A specification of property greater-than-or-equal-to (>=) comparison.
855
856 I{Type}: L{PropertyCriterion}
857 """
858 - def __init__(self, parent, descendant=True):
859 """
860 Create a new Filter node.
861
862 @type parent: L{Rule}
863 @param parent: The parent class object.
864 @type descendant: boolean
865 @param descendant: A flag indicating if this is a descendant node of the parent.
866 """
867 super(Filter, self).__init__(parent, descendant=descendant)
868 xpath = self._parent.xpath('ogc:Filter', namespaces=SLDNode._nsmap)
869 if len(xpath) == 1:
870 self._node = xpath[0]
871 else:
872 self._node = self._parent.makeelement('{%s}Filter' % SLDNode._nsmap['ogc'], nsmap=SLDNode._nsmap)
873
874
876 """
877 Add two filters together to create one AND logical filter.
878
879 @type other: L{Filter}
880 @param other: A filter to AND with this one.
881 @rtype: L{Filter}
882 @return: A new filter with an ogc:And element as its child.
883 """
884 if not self._node.getparent() is None:
885 self._node.getparent().remove(self._node)
886 elem = self._node.makeelement('{%s}And' % SLDNode._nsmap['ogc'])
887 elem.append(copy.copy(self._node[0]))
888 elem.append(copy.copy(other._node[0]))
889
890 f = Filter(self)
891 f._node.append(elem)
892
893 return f
894
896 """
897 Or two filters together to create on OR logical filter.
898
899 @type other: L{Filter}
900 @param other: A filter to OR with this one.
901 @rtype: L{Filter}
902 @return: A new filter with an ogc:Or element as its child.
903 """
904 elem = self._node.makeelement('{%s}Or' % SLDNode._nsmap['ogc'])
905 elem.append(copy.copy(self._node[0]))
906 elem.append(copy.copy(other._node[0]))
907
908 f = Filter(self)
909 f._node.append(elem)
910
911 return f
912
914 """
915 Get a named attribute from this Filter instance. This method allows
916 properties with the prefix of 'PropertyIs' to be set, and raises
917 an AttributeError for all other property names.
918
919 @type name: string
920 @param name: The name of the property.
921 @rtype: L{PropertyCriterion}
922 @return: The property comparitor.
923 """
924 if not name.startswith('PropertyIs'):
925 raise AttributeError('Property name must be one of: PropertyIsEqualTo, PropertyIsNotEqualTo, PropertyIsLessThan, PropertyIsLessThanOrEqualTo, PropertyIsGreaterThan, PropertyIsGreaterThanOrEqualTo, PropertyIsLike.')
926 xpath = self._node.xpath('ogc:'+name, namespaces=SLDNode._nsmap)
927 if len(xpath) == 0:
928 return None
929
930 return PropertyCriterion(self, name)
931
933 """
934 Set a named attribute on this Filter instance. If the property name
935 begins with 'PropertyIs', the node value will be appended to the filter.
936
937 @type name: string
938 @param name: The name of the property.
939 @type value: L{PropertyCriterion}
940 @param value: The new property comparitor.
941 """
942 if not name.startswith('PropertyIs'):
943 object.__setattr__(self, name, value)
944 return
945
946 xpath = self._node.xpath('ogc:'+name, namespaces=SLDNode._nsmap)
947 if len(xpath) > 0:
948 xpath[0] = value
949 else:
950 elem = self._node.makeelement('{%s}%s' % (SLDNode._nsmap['ogc'], name), nsmap=SLDNode._nsmap)
951 self._node.append(elem)
952
954 """
955 Delete the property from the Filter. This removes the child node
956 of this name from the Filter.
957
958 @type name: string
959 @param name: The name of the property.
960 """
961 xpath = self._node.xpath('ogc:'+name, namespaces=SLDNode._nsmap)
962 if len(xpath) > 0:
963 self._node.remove(xpath[0])
964
965
966 -class Rule(SLDNode):
967 """
968 A rule object contains a title, an optional L{Filter}, and one or more
969 L{Symbolizer}s. A Rule is a child of a L{FeatureTypeStyle}.
970
971 @prop: Title
972
973 The title of this rule. This is required for a valid SLD.
974
975 I{Type}: string
976
977 @prop: Filter
978
979 Optional. A filter defines logical comparisons against properties.
980
981 I{Type}: L{Filter}
982
983 @prop: PolygonSymbolizer
984
985 A symbolizer that defines how polygons should be rendered.
986
987 I{Type}: L{PolygonSymbolizer}
988
989 @prop: LineSymbolizer
990
991 A symbolizer that defines how lines should be rendered.
992
993 I{Type}: L{LineSymbolizer}
994
995 @prop: TextSymbolizer
996
997 A symbolizer that defines how text should be rendered.
998
999 I{Type}: L{TextSymbolizer}
1000
1001 @prop: PointSymbolizer
1002
1003 A symbolizer that defines how points should be rendered.
1004
1005 I{Type}: L{PointSymbolizer}
1006 """
1007 - def __init__(self, parent, index, descendant=True):
1008 """
1009 Create a new Rule node.
1010
1011 @type parent: L{FeatureTypeStyle}
1012 @param parent: The parent class object.
1013 @type descendant: boolean
1014 @param descendant: A flag indicating if this is a descendant node of the parent.
1015 """
1016 super(Rule, self).__init__(parent, descendant=descendant)
1017 self._node = self._parent.xpath('sld:Rule', namespaces=SLDNode._nsmap)[index]
1018
1019 setattr(self.__class__, 'Title', SLDNode.makeproperty('sld', name='Title',
1020 docstring="The title of the Rule."))
1021 setattr(self.__class__, 'Filter', SLDNode.makeproperty('ogc', cls=Filter,
1022 docstring="The optional filter object, with property comparitors."))
1023 setattr(self.__class__, 'PolygonSymbolizer', SLDNode.makeproperty('sld', cls=PolygonSymbolizer,
1024 docstring="The optional polygon symbolizer for this rule."))
1025 setattr(self.__class__, 'LineSymbolizer', SLDNode.makeproperty('sld', cls=LineSymbolizer,
1026 docstring="The optional line symbolizer for this rule."))
1027 setattr(self.__class__, 'TextSymbolizer', SLDNode.makeproperty('sld', cls=TextSymbolizer,
1028 docstring="The optional text symbolizer for this rule."))
1029 setattr(self.__class__, 'PointSymbolizer', SLDNode.makeproperty('sld', cls=PointSymbolizer,
1030 docstring="The optional point symbolizer for this rule."))
1031
1033 """
1034 Normalize this node prior to validation. This is required, as the
1035 ogc:Filter node must come before any symbolizer nodes. The SLD
1036 is modified in place.
1037 """
1038 order = ['sld:Title','ogc:Filter','sld:PolygonSymbolizer',
1039 'sld:LineSymbolizer', 'sld:TextSymbolizer', 'sld:PointSymbolizer']
1040 for item in order:
1041 xpath = self._node.xpath(item, namespaces=SLDNode._nsmap)
1042 for xitem in xpath:
1043
1044 self._node.remove(xitem)
1045 self._node.append(xitem)
1046
1047
1048
1049 - def create_filter(self, propname=None, comparitor=None, value=None):
1050 """
1051 Create a L{Filter} for this rule. The property name, comparitor, and value
1052 are required to create a valid Filter.
1053
1054 @type propname: string
1055 @param propname: The name of the property to filter.
1056 @type comparitor: string
1057 @param comparitor: The comparison to perform on the property. One of
1058 "!=", "<", "<=", "=", ">=", ">", and "%" is required.
1059 @type value: string
1060 @param value: The value of the property to compare against.
1061 @rtype: L{Filter}
1062 @return: A new filter attached to this Rule.
1063 """
1064 if propname is None or comparitor is None or value is None:
1065 return None
1066
1067 rfilter = self.create_element('ogc', 'Filter')
1068 ftype = None
1069 if comparitor == '==':
1070 ftype = 'PropertyIsEqualTo'
1071 elif comparitor == '<=':
1072 ftype = 'PropertyIsLessThanOrEqualTo'
1073 elif comparitor == '<':
1074 ftype = 'PropertyIsLessThan'
1075 elif comparitor == '>=':
1076 ftype = 'PropertyIsGreaterThanOrEqualTo'
1077 elif comparitor == '>':
1078 ftype = 'PropertyIsGreaterThan'
1079 elif comparitor == '!=':
1080 ftype = 'PropertyIsNotEqualTo'
1081 elif comparitor == '%':
1082 ftype = 'PropertyIsLike'
1083
1084 if not ftype is None:
1085 prop = PropertyCriterion(rfilter, ftype)
1086 prop.PropertyName = propname
1087 if not value is None:
1088 prop.Literal = value
1089 setattr(rfilter, ftype, prop)
1090
1091 return rfilter
1092
1094 """
1095 Create a L{Symbolizer} of the specified type on this rule.
1096
1097 @type stype: string
1098 @param stype: The type of symbolizer. Allowed types are "Point",
1099 "Line", "Polygon", or "Text".
1100 @rtype: L{Symbolizer}
1101 @return: A newly created symbolizer, attached to this Rule.
1102 """
1103 if stype is None:
1104 return None
1105
1106 return self.create_element('sld', stype + 'Symbolizer')
1107
1108
1109 -class Rules(SLDNode):
1110 """
1111 A collection of L{Rule} nodes. This is a pythonic helper (list of
1112 nodes) that does not correspond to a true element in the SLD spec.
1113 """
1114 - def __init__(self, parent, descendant=True):
1115 """
1116 Create a new list of Rules from the specified parent node.
1117
1118 @type parent: L{FeatureTypeStyle}
1119 @param parent: The parent class object.
1120 @type descendant: boolean
1121 @param descendant: A flag indicating if this is a descendant node of the parent.
1122 """
1123 super(Rules, self).__init__(parent, descendant=descendant)
1124 self._node = None
1125 self._nodes = self._parent.xpath('sld:Rule', namespaces=SLDNode._nsmap)
1126
1128 """
1129 Normalize this node and all rules contained within. The SLD model is
1130 modified in place.
1131 """
1132 for i,rnode in enumerate(self._nodes):
1133 rule = Rule(self, i-1, descendant=False)
1134 rule.normalize()
1135
1137 """
1138 Get the number of L{CssParameter} nodes in this list.
1139
1140 @rtype: integer
1141 @return: The number of L{CssParameter} nodes.
1142 """
1143 return len(self._nodes)
1144
1146 """
1147 Get one of the L{Rule} nodes in the list.
1148
1149 @type key: integer
1150 @param key: The index of the child node.
1151 @rtype: L{Rule}
1152 @return: The specific L{Rule} node.
1153 """
1154 rule = Rule(self, key, descendant=False)
1155 return rule
1156
1158 """
1159 Set one of the L{Rule} nodes in the list with a new value.
1160
1161 @type key: integer
1162 @param key: The index of the child node.
1163 @type value: L{Rule}, etree.Element
1164 @param value: The new value of the specific child node.
1165 """
1166 if isinstance(value, Rule):
1167 self._nodes.replace(self._nodes[key], value._node)
1168 elif isinstance(value, Element):
1169 self._nodes.replace(self._nodes[key], value)
1170
1172 """
1173 Delete one of the L{Rule} nodes from the list.
1174
1175 @type key: integer
1176 @param key: The index of the child node.
1177 """
1178 self._nodes.remove(self._nodes[key])
1179
1182 """
1183 A FeatureTypeStyle node contains all L{Rule} objects applicable to a
1184 specific layer. A FeatureTypeStyle is a child of a L{UserStyle} element.
1185 """
1186 - def __init__(self, parent, descendant=True):
1187 """
1188 Create a new FeatureTypeNode node, as a child of the specified parent.
1189
1190 @type parent: L{UserStyle}
1191 @param parent: The parent class object.
1192 @type descendant: boolean
1193 @param descendant: A flag indicating if this is a descendant node of the parent.
1194 """
1195 super(FeatureTypeStyle, self).__init__(parent, descendant=descendant)
1196 self._node = self._parent.xpath('sld:FeatureTypeStyle', namespaces=SLDNode._nsmap)[0]
1197
1199 """
1200 Normalize this element and all child L{Rule}s. The SLD model is
1201 modified in place.
1202 """
1203 if not self.Rules is None:
1204 self.Rules.normalize()
1205
1206 @property
1208 """
1209 Get the L{sld.Rules} pythonic list helper for all L{Rule} objects in this
1210 style.
1211
1212 @rtype: L{sld.Rules}
1213 @return: A list of all rules applied to this style.
1214 """
1215 return Rules(self)
1216
1218 """
1219 Create a L{Rule} object on this style. A rule requires a title and
1220 symbolizer. If no symbolizer is specified, a PointSymbolizer will be
1221 assigned to the rule.
1222
1223 @type title: string
1224 @param title: The name of the new L{Rule}.
1225 @type symbolizer: L{Symbolizer} I{class}
1226 @param symbolizer: The symbolizer type. This is the class object (as
1227 opposed to a class instance) of the symbolizer to use.
1228 @rtype: L{Rule}
1229 @return: A newly created rule, attached to this FeatureTypeStyle.
1230 """
1231 elem = self._node.makeelement('{%s}Rule' % SLDNode._nsmap['sld'], nsmap=SLDNode._nsmap)
1232 self._node.append(elem)
1233
1234 rule = Rule(self, len(self._node)-1)
1235 rule.Title = title
1236
1237 if symbolizer is None:
1238 symbolizer = PointSymbolizer
1239
1240 sym = symbolizer(rule)
1241 if symbolizer == PointSymbolizer:
1242 gph = Graphic(sym)
1243 mrk = Mark(gph)
1244 mrk.WellKnownName = 'square'
1245 fill = Fill(mrk)
1246 fill.create_cssparameter('fill', '#ff0000')
1247
1248 elif symbolizer == LineSymbolizer:
1249 stroke = Stroke(sym)
1250 stroke.create_cssparameter('stroke', '#0000ff')
1251
1252 elif symbolizer == PolygonSymbolizer:
1253 fill = Fill(sym)
1254 fill.create_cssparameter('fill', '#AAAAAA')
1255 stroke = Stroke(sym)
1256 stroke.create_cssparameter('stroke', '#000000')
1257 stroke.create_cssparameter('stroke-width', '1')
1258
1259 return rule
1260
1263 """
1264 A UserStyle object. A UserStyle is a child of a L{StyledLayerDescriptor}.
1265
1266 @prop: Title
1267
1268 The title of the UserStyle.
1269
1270 I{Type}: string
1271
1272 @prop: Abstract
1273
1274 The abstract describing this UserStyle.
1275
1276 I{Type}: string
1277
1278 @prop: FeatureTypeStyle
1279
1280 The styling for the feature type.
1281
1282 I{Type}: L{FeatureTypeStyle}
1283 """
1284 - def __init__(self, parent, descendant=True):
1285 """
1286 Create a new UserStyle node.
1287
1288 @type parent: L{NamedLayer}
1289 @param parent: The parent class object.
1290 @type descendant: boolean
1291 @param descendant: A flag indicating if this is a descendant node of the parent.
1292 """
1293 super(UserStyle, self).__init__(parent, descendant=descendant)
1294 self._node = self._parent.xpath('sld:UserStyle', namespaces=SLDNode._nsmap)[0]
1295
1296 setattr(self.__class__, 'Title', SLDNode.makeproperty('sld', name='Title',
1297 docstring="The title of the UserStyle."))
1298 setattr(self.__class__, 'Abstract', SLDNode.makeproperty('sld', name='Abstract',
1299 docstring="The abstract of the UserStyle."))
1300 setattr(self.__class__, 'FeatureTypeStyle', SLDNode.makeproperty('sld', cls=FeatureTypeStyle,
1301 docstring="The feature type style of the UserStyle."))
1302
1310
1312 """
1313 Create a L{FeatureTypeStyle} object, and attach it to this UserStyle.
1314
1315 @rtype: L{FeatureTypeStyle}
1316 @return: A newly created feature type style, attached to this node.
1317 """
1318 return self.get_or_create_element('sld', 'FeatureTypeStyle')
1319
1322 """
1323 A named layer contains a name and a user style. A NamedLayer is a child of
1324 a L{StyledLayerDescriptor}.
1325
1326 @prop: Name
1327
1328 The name of the UserStyle.
1329
1330 I{Type}: string
1331
1332 @prop: UserStyle
1333
1334 The custom styling for this named layer.
1335
1336 I{Type}: L{UserStyle}
1337 """
1338 - def __init__(self, parent, descendant=True):
1339 """
1340 Create a new NamedLayer node.
1341
1342 @type parent: L{StyledLayerDescriptor}
1343 @param parent: The parent class object.
1344 @type descendant: boolean
1345 @param descendant: A flag indicating if this is a descendant node of the parent.
1346 """
1347 super(NamedLayer, self).__init__(parent, descendant=descendant)
1348 self._node = self._parent.xpath('sld:NamedLayer', namespaces=SLDNode._nsmap)[0]
1349
1350 setattr(self.__class__, 'UserStyle', SLDNode.makeproperty('sld', cls=UserStyle,
1351 docstring="The UserStyle of the NamedLayer."))
1352 setattr(self.__class__, 'Name', SLDNode.makeproperty('sld', name='Name',
1353 docstring="The name of the layer."))
1354
1356 """
1357 Normalize this node and all child nodes prior to validation. The SLD
1358 is modified in place.
1359 """
1360 if not self.UserStyle is None:
1361 self.UserStyle.normalize()
1362
1364 """
1365 Create a L{UserStyle} for this named layer.
1366
1367 @rtype: L{UserStyle}
1368 @return: A newly created user style, attached to this node.
1369 """
1370 return self.get_or_create_element('sld', 'UserStyle')
1371
1374 """
1375 An object representation of an SLD document.
1376
1377 @prop: NamedLayer
1378
1379 The named layer that this styling applies to.
1380
1381 I{Type}: L{NamedLayer}
1382 """
1383
1384 _cached_schema = None
1385 """A cached schema document, to prevent multiple requests from occurring."""
1386
1388 """
1389 Create a new SLD document. If an sld file is provided, this constructor
1390 will fetch the SLD schema from the internet and validate the file
1391 against that schema.
1392
1393 @type sld_file: string
1394 @param sld_file: The name of a pre-existing SLD file.
1395 """
1396 super(StyledLayerDescriptor, self).__init__(None)
1397
1398 if StyledLayerDescriptor._cached_schema is None:
1399 logging.debug('Storing new schema into cache.')
1400
1401 localschema = NamedTemporaryFile(delete=False)
1402 schema_url = 'http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd'
1403 resp = urllib2.urlopen(schema_url)
1404 localschema.write(resp.read())
1405 resp.close()
1406 localschema.seek(0)
1407
1408 theschema = parse(localschema)
1409 localschema.close()
1410
1411 StyledLayerDescriptor._cached_schema = localschema.name
1412 else:
1413 logging.debug('Fetching schema from cache.')
1414
1415 localschema = open(StyledLayerDescriptor._cached_schema, 'r')
1416 theschema = parse(localschema)
1417 localschema.close()
1418
1419 self._schema = XMLSchema(theschema)
1420
1421 if not sld_file is None:
1422 self._node = parse(sld_file)
1423
1424 if not self._schema.validate(self._node):
1425 logging.warn('SLD File "%s" does not validate against the SLD schema.', sld_file)
1426 else:
1427 self._node = Element("{%s}StyledLayerDescriptor" % SLDNode._nsmap['sld'], version="1.0.0", nsmap=SLDNode._nsmap)
1428
1429 setattr(self.__class__, 'NamedLayer', SLDNode.makeproperty('sld', cls=NamedLayer,
1430 docstring="The named layer of the SLD."))
1431
1441
1443 """
1444 Perform a deep copy. Instead of copying references to the schema
1445 object, create a new SLD, and deepcopy the SLD node.
1446 """
1447 sld = StyledLayerDescriptor()
1448 sld._node = copy.deepcopy(self._node)
1449 return sld
1450
1451
1453 """
1454 Normalize this node and all child nodes prior to validation. The SLD
1455 is modified in place.
1456 """
1457 if not self.NamedLayer is None:
1458 self.NamedLayer.normalize()
1459
1460
1462 """
1463 Validate the current file against the SLD schema. This first normalizes
1464 the SLD document, then validates it. Any schema validation error messages
1465 are logged at the INFO level.
1466
1467 @rtype: boolean
1468 @return: A flag indicating if the SLD is valid.
1469 """
1470 self.normalize()
1471
1472 if self._node is None or self._schema is None:
1473 logging.debug('The node or schema is empty, and cannot be validated.')
1474 return False
1475
1476 is_valid = self._schema.validate(self._node)
1477
1478 for msg in self._schema.error_log:
1479 logging.info('Line:%d, Column:%d -- %s', msg.line, msg.column, msg.message)
1480
1481 return is_valid
1482
1483
1484 @property
1486 """
1487 Get the SLD version.
1488 """
1489 return self._node.getroot().get('version')
1490
1491 @property
1493 """
1494 Get the XML Namespace.
1495 """
1496 return self._node.getroot().nsmap[None]
1497
1499 """
1500 Create a L{NamedLayer} in this SLD.
1501
1502 @type name: string
1503 @param name: The name of the layer.
1504 @rtype: L{NamedLayer}
1505 @return: The named layer, attached to this SLD.
1506 """
1507 namedlayer = self.get_or_create_element('sld', 'NamedLayer')
1508 namedlayer.Name = name
1509 return namedlayer
1510
1511 - def as_sld(self, pretty_print=False):
1512 """
1513 Serialize this SLD model into a string.
1514
1515 @rtype: string
1516 @returns: The content of the SLD.
1517 """
1518 return tostring(self._node, pretty_print=pretty_print)
1519