Introduction
Traditionally when building an SLD the best approach was to build a set of several different filtered rules. Whilst this works fine, if the only thing that changes between rules is the colour it creates a lot of unnecessary duplicate work. Typically, the traditional multi-rule SLD can be very large and become difficult to maintain.
Imagine you are styling a road layer that contains 25 different rules and you need to change the font on the labels, this would require you to update all 25 rules.
Symbology Encoding Functions
The OGC Filter encoding specification contains a generic concept of Symbology Encoding or Filter functions.
Symbology Encoding Functions or Filter Functions are a function, with arguments, that can be called inside of a filter or, more generically, an expression, to perform specific calculations. Using filter functions we can compress our 25 filtered rules into one easy to maintain rule.
Example
For this example we will use the lines shapefile provided by the GeoServer Cookbook.
To keep this example simple we will shrink our 25 different filtered rules into 3 but the concept can be scaled up to as large as required.
Multiple Rule Approach
Our original SLD uses a typical SLD 1.0 multiple rule and filter style where we make a rule for every style alteration we wish to create. In this case we are making three rules one for each road type: Local road, Secondary and Highway.
<sld:FeatureTypeStyle> <sld:Name>name</sld:Name> <sld:FeatureTypeName>Feature</sld:FeatureTypeName> <sld:Rule> <sld:Name>local-road</sld:Name> <ogc:Filter> <ogc:PropertyIsEqualTo> <ogc:PropertyName>type</ogc:PropertyName> <ogc:Literal>local-road</ogc:Literal> </ogc:PropertyIsEqualTo> </ogc:Filter> <sld:LineSymbolizer> <sld:Stroke> <sld:CssParameter name="stroke">#009933</sld:CssParameter> <sld:CssParameter name="stroke-width">2</sld:CssParameter> </sld:Stroke> </sld:LineSymbolizer> <sld:TextSymbolizer> <sld:Label> <ogc:PropertyName>name</ogc:PropertyName> </sld:Label> <sld:Font> <sld:CssParameter name="font-family">Arial</sld:CssParameter> <sld:CssParameter name="font-size">10</sld:CssParameter> <sld:CssParameter name="font-style">normal</sld:CssParameter> <sld:CssParameter name="font-weight">bold</sld:CssParameter> </sld:Font> <sld:LabelPlacement> <sld:LinePlacement> <sld:PerpendicularOffset>0.0</sld:PerpendicularOffset> </sld:LinePlacement> </sld:LabelPlacement> <sld:Halo> <sld:Radius>1</sld:Radius> <sld:Fill> <sld:CssParameter name="fill">#FFFFFF</sld:CssParameter> </sld:Fill> </sld:Halo> <sld:Fill> <sld:CssParameter name="fill">#000000</sld:CssParameter> </sld:Fill> <sld:VendorOption name="maxAngleDelta">90</sld:VendorOption> <sld:VendorOption name="followLine">true</sld:VendorOption> <sld:VendorOption name="repeat">200</sld:VendorOption> <sld:VendorOption name="maxDisplacement">400</sld:VendorOption> </sld:TextSymbolizer> </sld:Rule> </sld:FeatureTypeStyle> <sld:FeatureTypeStyle> <sld:Name>name</sld:Name> <sld:FeatureTypeName>Feature</sld:FeatureTypeName> <sld:Rule> <sld:Name>secondary</sld:Name> <ogc:Filter> <ogc:PropertyIsEqualTo> <ogc:PropertyName>type</ogc:PropertyName> <ogc:Literal>secondary</ogc:Literal> </ogc:PropertyIsEqualTo> </ogc:Filter> <sld:LineSymbolizer> <sld:Stroke> <sld:CssParameter name="stroke">#0055CC</sld:CssParameter> <sld:CssParameter name="stroke-width">3</sld:CssParameter> </sld:Stroke> </sld:LineSymbolizer> <sld:TextSymbolizer> <sld:Label> <ogc:PropertyName>name</ogc:PropertyName> </sld:Label> <sld:Font> <sld:CssParameter name="font-family">Arial</sld:CssParameter> <sld:CssParameter name="font-size">10</sld:CssParameter> <sld:CssParameter name="font-style">normal</sld:CssParameter> <sld:CssParameter name="font-weight">bold</sld:CssParameter> </sld:Font> <sld:LabelPlacement> <sld:LinePlacement> <sld:PerpendicularOffset>0.0</sld:PerpendicularOffset> </sld:LinePlacement> </sld:LabelPlacement> <sld:Halo> <sld:Radius>1</sld:Radius> <sld:Fill> <sld:CssParameter name="fill">#FFFFFF</sld:CssParameter> </sld:Fill> </sld:Halo> <sld:Fill> <sld:CssParameter name="fill">#000000</sld:CssParameter> </sld:Fill> <sld:VendorOption name="maxAngleDelta">90</sld:VendorOption> <sld:VendorOption name="followLine">true</sld:VendorOption> <sld:VendorOption name="repeat">200</sld:VendorOption> <sld:VendorOption name="maxDisplacement">400</sld:VendorOption> </sld:TextSymbolizer> </sld:Rule> </sld:FeatureTypeStyle> <sld:FeatureTypeStyle> <sld:Name>name</sld:Name> <sld:FeatureTypeName>Feature</sld:FeatureTypeName> <sld:Rule> <sld:Name>highway</sld:Name> <ogc:Filter> <ogc:PropertyIsEqualTo> <ogc:PropertyName>type</ogc:PropertyName> <ogc:Literal>highway</ogc:Literal> </ogc:PropertyIsEqualTo> </ogc:Filter> <sld:LineSymbolizer> <sld:Stroke> <sld:CssParameter name="stroke">#FF0000</sld:CssParameter> <sld:CssParameter name="stroke-width">6</sld:CssParameter> </sld:Stroke> </sld:LineSymbolizer> <sld:TextSymbolizer> <sld:Label> <ogc:PropertyName>name</ogc:PropertyName> </sld:Label> <sld:Font> <sld:CssParameter name="font-family">Arial</sld:CssParameter> <sld:CssParameter name="font-size">10</sld:CssParameter> <sld:CssParameter name="font-style">normal</sld:CssParameter> <sld:CssParameter name="font-weight">bold</sld:CssParameter> </sld:Font> <sld:LabelPlacement> <sld:LinePlacement> <sld:PerpendicularOffset>0.0</sld:PerpendicularOffset> </sld:LinePlacement> </sld:LabelPlacement> <sld:Halo> <sld:Radius>1</sld:Radius> <sld:Fill> <sld:CssParameter name="fill">#FFFFFF</sld:CssParameter> </sld:Fill> </sld:Halo> <sld:Fill> <sld:CssParameter name="fill">#000000</sld:CssParameter> </sld:Fill> <sld:VendorOption name="maxAngleDelta">90</sld:VendorOption> <sld:VendorOption name="followLine">true</sld:VendorOption> <sld:VendorOption name="repeat">200</sld:VendorOption> <sld:VendorOption name="maxDisplacement">400</sld:VendorOption> </sld:TextSymbolizer> </sld:Rule> </sld:FeatureTypeStyle>
Function Approach
Our new compact SLD will make use of the Recode function to select different stroke based on the value of the “type” property.
<sld:FeatureTypeStyle> <sld:Name>name</sld:Name> <sld:FeatureTypeName>Feature</sld:FeatureTypeName> <sld:Rule> <sld:Name>local-road</sld:Name> <sld:LineSymbolizer> <sld:Stroke> <sld:CssParameter name="stroke"> <ogc:Function name="Recode"> <ogc:PropertyName>type</ogc:PropertyName> <ogc:Literal>local-road</ogc:Literal> <ogc:Literal>#009933</ogc:Literal> <ogc:Literal>secondary</ogc:Literal> <ogc:Literal>#0055CC</ogc:Literal> <ogc:Literal>highway</ogc:Literal> <ogc:Literal>#FF0000</ogc:Literal> </ogc:Function> </sld:CssParameter> <sld:CssParameter name="stroke-width"> <ogc:Function name="Recode"> <ogc:PropertyName>type</ogc:PropertyName> <ogc:Literal>local-road</ogc:Literal> <ogc:Literal>2</ogc:Literal> <ogc:Literal>secondary</ogc:Literal> <ogc:Literal>3</ogc:Literal> <ogc:Literal>highway</ogc:Literal> <ogc:Literal>6</ogc:Literal> </ogc:Function> </sld:CssParameter> </sld:Stroke> </sld:LineSymbolizer> <sld:TextSymbolizer> <sld:Label> <ogc:PropertyName>name</ogc:PropertyName> </sld:Label> <sld:Font> <sld:CssParameter name="font-family">Arial</sld:CssParameter> <sld:CssParameter name="font-size">10</sld:CssParameter> <sld:CssParameter name="font-style">normal</sld:CssParameter> <sld:CssParameter name="font-weight">bold</sld:CssParameter> </sld:Font> <sld:LabelPlacement> <sld:LinePlacement> <sld:PerpendicularOffset>0.0</sld:PerpendicularOffset> </sld:LinePlacement> </sld:LabelPlacement> <sld:Halo> <sld:Radius>1</sld:Radius> <sld:Fill> <sld:CssParameter name="fill">#FFFFFF</sld:CssParameter> </sld:Fill> </sld:Halo> <sld:Fill> <sld:CssParameter name="fill">#000000</sld:CssParameter> </sld:Fill> <sld:VendorOption name="maxAngleDelta">90</sld:VendorOption> <sld:VendorOption name="followLine">true</sld:VendorOption> <sld:VendorOption name="repeat">200</sld:VendorOption> <sld:VendorOption name="maxDisplacement">400</sld:VendorOption> </sld:TextSymbolizer> </sld:Rule> </sld:FeatureTypeStyle>
Performance implications
Assuming we have 45 lines per rule in our original SLD, now if we are styling a layer with 200 style variations we would end up with an SLD with roughly 9,000 lines of XML. Alternatively, if we use the function approach we are only adding 4 extra lines per style variation. Therefore, we end up with an SLD with roughly 845 lines of XML. Now say we want to change the font type across all our styles, in the one rule per variation approach we need to update 200 lines, one per rule. In the function approach we only need to update 1 line.
Finally, to render a map using the multiple rule approach all 200 rules must be evaluated for each feature you render, whereas the function approach requires only 1 rule be evaluated per feature. This can make a significantly noticeable difference to performance and render time.
References
- Andrea Aime – Developer corner: Compact choropleth map styles with the Categorize function
- OpenGIS – OpenGIS Symbology Encoding Implementation Specification



One Comment
There is one problem with the Function method that I haven’t found a solution for is controlling the draw order.
If anyone knows a fix for this please let me know.