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