python-sld is a simple python library that enables some basic manipulation of StyledLayerDescriptor (SLD) documents.
The OpenGIS® Styled Layer Descriptor (SLD) Profile of the OpenGIS® Web Map Service (WMS) Encoding Standard defines an extends the WMS standard to allow user-defined symbolization and coloring of geographic feature and coverage data.
In layman’s terms, SLD is a common way to style your own maps that come from any map server that speaks WMS (another standard by OGC). Of all the GIS tools available, the WMS server ecosystem is exceptionally rich and diverse. There are many proprietary choices, as well as a plethora of open source options.
State of the Art
Recently in the course of developing new features for DistrictBuilder, we arrived at a point where we needed to generate SLDs dynamically. Looking around at the existing python libraries, we examined:
What we were looking for was a pure object model access to components in the SLD, as well as XML validation, with very few dependencies. None of the above projects really fit the bill, so we started working on our own.
python-sld in an open source (Apache 2.0) library for dynamic SLD creation and manipulation. The project is hosted over on github, and the packages are in pypi (including generated inline documentation).
Width python-sld, creating new SLD documents is as easy as creating a new instance of a StyledLayerDescriptor object:
>>> from sld import * >>> sld_doc = StyledLayerDescriptor()
With this SLD document, all descendants are accessed as properties, and most child objects are created off the parent with “create_xxx()” methods:
>>> sld_doc.NamedLayer is None True >>> nl = sld_doc.create_namedlayer('My Layer') >>> nl.Name 'My Layer'
For most complex types, the parent’s property is an instance of the class. In our example:
>>> isinstance(nl, NamedLayer) True >>> us = nl.create_userstyle() >>> us.Title = 'Style Title' >>> us.Title 'Style Title' >>> isinstance(us, UserStyle) True
A couple pythonic classes break up the monotony, too. For elements that contain collections of items (a FeatureTypeStyle element may contain many Rule elements, and Fill, Stroke, and Font elements may contain many CssParameter elements), they behave as pythonic lists.
>>> fts = us.create_featuretypestyle() >>> len(fts.Rules) 0 >>> r1 = fts.create_rule('Criteria 1') >>> len(fts.Rules) 1 >>> fts.Rules.Title == r1.Title True
Another bit of pythonic syntactic sugar is the combination of Filters. By constructing filters (with the Rule as a parent) and combining them with “+” or “|”, they create logical “AND” and “OR” filters, respectively.
>>> f1 = Filter(r1) >>> f1.PropertyIsGreaterThan = PropertyCriterion(f1, 'PropertyIsGreaterThan') >>> f1.PropertyIsGreaterThan.PropertyName = 'number' >>> f1.PropertyIsGreaterThan.Literal = '-10' >>> >>> f2 = Filter(r1) >>> f2.PropertyIsLessThanOrEqualTo = PropertyCriterion(f2, 'PropertyIsLessThanOrEqualTo') >>> f2.PropertyIsLessThanOrEqualTo.PropertyName = 'number' >>> f2.PropertyIsLessThanOrEqualTo.Literal = '10' >>> >>> r1.Filter = f1 + f2
When the SLD object is serialized, it will render an “ogc:And” element that contains both property comparisons. You may have noticed that both the “PropertyIsGreaterThan” and “PropertyIsLessThanOrEqualTo” properties are assigned an instance of a PropertyCriterion class. This is the common class for all property comparitors. The name of the comparitor determines it’s logical comparison (less than, greater than, equal to, etc.), and the class has a PropertyName and Literal property, to control which property gets compared, and which value it is compared against.
Finally, serialization is performed on the main StyledLayerDescriptor object, with options to ‘prettify’ the output:
>>> content = sld_doc.as_sld(pretty_print=True)
The lxml library is required by python-sld. This is the library that provides the underlying parsing and serializing of the XML document, as well as the validation steps against the canonical SLD schema.
At the current time, only a subset of the entire SLD specification is implemented. All SLD elements are parsed and stored, but only the following elements may be manipulated as objects in python-sld:
- Name (of NamedLayer)
- Title (of UserStyle and Rule)
- ogc:Filter (implicit ogc:And and ogc:Or)
All other SLD elements cannot be directly manipulated in python-sld, but are accessible (from a parsed SLD that is perhaps more complex) via the parent object’s _node property. This is the lxml.Element that the python-sld class represents.
django-sld builds upon the capabilities in python-sld by enabling quick SLD generation from geographic models. This library is separate from the python-sld library because of the dependencies on django and pysal, the Python Spatial Analysis Library.
Primer on Geographic Models
I gave a quick background to geographic models in django to the Boston django meetup last week, and the slides of my presentation are available online as a presentation in Google Docs. The slides are embedded here for your convenience:
django-sld is an open source (Apache 2.0) library for generating SLD documents from geographic querysets. The project is hosted over on github, and the packages are in pypi (including generated inline documentation).
django-sld enables quick classification of geographic querysets by passing the data distribution of an individual model field into the classification algorithms built into pysal. Not all classification methods in pysal are available, however. At the current version (1.0.3), the following classification algorithms are supported:
- Equal Interval
- Fisher Jenks
- Jenks Caspall
- Jenks Caspall Forced
- Jenks Caspall Sampled
- Max P Classifier
- Maximum Breaks
- Natural Breaks
To classify a django queryset, use any of the as_xxx() methods in the djsld.generator module.
>>> from djsld import generator >>> qs = MySpatialModel.objects.all() >>> sld = generator.as_quantiles(qs, 'population', 10)
The above example assumes that you have a model named “MySpatialModel” in django’s models.py file. The result is a sld.StyledLayerDescriptor object, which may be serialized to a string with “as_sld()”
>>> sld_content = sld.as_sld(pretty_print=True)
The “pretty_print” option is available to format the SLD in a fashion that is more readable by us humans.
In addition to simple models, django’s support for related fields really shines, as it’s possible to classify the distribution on any related field, using the “__” (double underscore) format preferred by django:
>>> sld = generator.as_quantiles(qs, 'city__population', 10)
The one caveat is that the PropertyName in the criteria will be set to this field name (which is not the way most mapping packages refer to related fields). To accommodate this difference, you may use the ‘propertyname’ keyword to control the output PropertyName:
>>> sld = generator.as_quantiles(qs, 'city__population', 10, ... propertyname='population')
django-sld requires python-sld and the pysal library.