Styling features for display¶
GeoTools Rendering process is controlled styling information that you provide. The data structures we use to describe styling are based on the Style Layer Descriptor (SLD) and Symbology Encoding (SE) specifications provided by the OGC. The SLD specification defines an XML document you can use to save and load your styles.
This page is devoted to examples, to review the concepts consult the references below.
References:
gt-api style layer descriptor (interfaces)
gt-api symbology encoding (interfaces)
http://www.opengeospatial.org/standards/sld (style layer descriptor)
http://www.opengeospatial.org/standards/symbol (symbology encoding)
StyleFactory¶
We have two approaches offering various levels of standards compliance vs pragmatism.
Module |
Class |
Capability |
Scope |
Description |
---|---|---|---|---|
|
|
get/set |
SE / SLD |
SE, SLD, and vendor extensions |
|
|
get/set/defaults |
SE / SLD |
Shorter methods, does not do everything |
|
|
fluent |
SE / SLD |
Fluent API for SE, SLD and vendor extensions |
Here are some examples of these classes in action:
StyleFactory
StyleFactory allows creation using the parameters defined by the SLD standard.
Here is a quick example showing the creation of a
PointSymbolizer
:// org.geotools.api.style.StyleFactory sf = CommonFactoryFinder.getStyleFactory(); FilterFactory ff = CommonFactoryFinder.getFilterFactory(); // // create the graphical mark used to represent a city Stroke stroke = sf.stroke(ff.literal("#000000"), null, null, null, null, null, null); Fill fill = sf.fill(null, ff.literal(Color.BLUE), ff.literal(1.0)); // OnLineResource implemented by gt-metadata - so no factory! OnLineResourceImpl svg = new OnLineResourceImpl(new URI("file:city.svg")); svg.freeze(); // freeze to prevent modification at runtime OnLineResourceImpl png = new OnLineResourceImpl(new URI("file:city.png")); png.freeze(); // freeze to prevent modification at runtime // // List of symbols is considered in order with the rendering engine choosing // the first one it can handle. Allowing for svg, png, mark order List<GraphicalSymbol> symbols = new ArrayList<>(); symbols.add(sf.externalGraphic(svg, "svg", null)); // svg preferred symbols.add(sf.externalGraphic(png, "png", null)); // png preferred symbols.add(sf.mark(ff.literal("circle"), fill, stroke)); // simple circle backup plan Expression opacity = null; // use default Expression size = ff.literal(10); Expression rotation = null; // use default AnchorPoint anchor = null; // use default Displacement displacement = null; // use default // define a point symbolizer of a small circle Graphic circle = sf.graphic(symbols, opacity, size, rotation, anchor, displacement); PointSymbolizer pointSymbolizer = sf.pointSymbolizer("point", ff.property("the_geom"), null, null, circle);
To work with GeoTools vendor specific options, a slightly different style of programming is needed taking advantage of the mutable instances are creating allowing you to call both get and set methods.
Note
These classes are not thread-safe, do not update a style while it is being used to draw.
// // We are using the GeoTools styleFactory that allows access to get/set methods org.geotools.api.style.StyleFactory sf = CommonFactoryFinder.getStyleFactory(); FilterFactory ff = CommonFactoryFinder.getFilterFactory(); StyledLayerDescriptor sld = sf.createStyledLayerDescriptor(); sld.setName("sld"); sld.setTitle("Example"); sld.setAbstract("Example Style Layer Descriptor"); UserLayer layer = sf.createUserLayer(); layer.setName("layer"); // // define constraint limited what features the sld applies to FeatureTypeConstraint constraint = sf.createFeatureTypeConstraint("Feature", Filter.INCLUDE, null); layer.layerFeatureConstraints().add(constraint); // // create a "user defined" style Style style = sf.createStyle(); style.setName("style"); style.getDescription().setTitle("User Style"); style.getDescription().setAbstract("Definition of Style"); // // define feature type styles used to actually define how features are rendered FeatureTypeStyle featureTypeStyle = sf.createFeatureTypeStyle(); // RULE 1 // first rule to draw cities Rule rule1 = sf.createRule(); rule1.setName("rule1"); rule1.getDescription().setTitle("City"); rule1.getDescription().setAbstract("Rule for drawing cities"); rule1.setFilter(ff.less(ff.property("POPULATION"), ff.literal(50000))); // // create the graphical mark used to represent a city Stroke stroke = sf.stroke(ff.literal("#000000"), null, null, null, null, null, null); Fill fill = sf.fill(null, ff.literal(Color.BLUE), ff.literal(1.0)); // OnLineResource implemented by gt-metadata - so no factory! OnLineResourceImpl svg = new OnLineResourceImpl(new URI("file:city.svg")); svg.freeze(); // freeze to prevent modification at runtime OnLineResourceImpl png = new OnLineResourceImpl(new URI("file:city.png")); png.freeze(); // freeze to prevent modification at runtime // // List of symbols is considered in order with the rendering engine choosing // the first one it can handle. Allowing for svg, png, mark order List<GraphicalSymbol> symbols = new ArrayList<>(); symbols.add(sf.externalGraphic(svg, "svg", null)); // svg preferred symbols.add(sf.externalGraphic(png, "png", null)); // png preferred symbols.add(sf.mark(ff.literal("circle"), fill, stroke)); // simple circle backup plan Expression opacity = null; // use default Expression size = ff.literal(10); Expression rotation = null; // use default AnchorPoint anchor = null; // use default Displacement displacement = null; // use default // define a point symbolizer of a small circle Graphic city = sf.graphic(symbols, opacity, size, rotation, anchor, displacement); PointSymbolizer pointSymbolizer = sf.pointSymbolizer("point", ff.property("the_geom"), null, null, city); rule1.symbolizers().add(pointSymbolizer); featureTypeStyle.rules().add(rule1); // // RULE 2 Default List<GraphicalSymbol> dotSymbols = new ArrayList<>(); dotSymbols.add(sf.mark(ff.literal("circle"), null, null)); Graphic dotGraphic = sf.graphic(dotSymbols, null, ff.literal(3), null, null, null); PointSymbolizer dotSymbolizer = sf.pointSymbolizer("dot", null, null, null, dotGraphic); List<org.geotools.api.style.Symbolizer> symbolizers = new ArrayList<>(); symbolizers.add(dotSymbolizer); Filter other = null; // null will mark this rule as "other" accepting all remaining features Rule rule2 = sf.rule( "default", null, null, Double.MIN_VALUE, Double.MAX_VALUE, symbolizers, other); featureTypeStyle.rules().add(rule2); style.featureTypeStyles().add(featureTypeStyle); layer.userStyles().add(style); sld.layers().add(layer);
StyleBuilder
fromgt-main
:Since a
Style
is composed of a complex set of objects, aStyleBuilder
object is provided to build simple styles without the need to build all of the style elements by hand.For example, you can create a
PolygonSymbolizer
and then create aStyle
out of it with a single method call: the builder will generate a defaultFeatureTypeStyle
and theRule
for you.// // We are using the GeoTools StyleBuilder that is helpful for quickly making things StyleBuilder builder = new StyleBuilder(); FilterFactory ff = builder.getFilterFactory(); // RULE 1 // first rule to draw cities // define a point symbolizer representing a city Graphic city = builder.createGraphic(); city.setSize(ff.literal(10)); city.graphicalSymbols().add(builder.createExternalGraphic("file:city.svg", "svg")); // svg // preferred city.graphicalSymbols() .add(builder.createExternalGraphic("file:city.png", "png")); // png next city.graphicalSymbols() .add(builder.createMark(StyleBuilder.MARK_CIRCLE, Color.BLUE, Color.BLACK, 1)); PointSymbolizer pointSymbolizer = builder.createPointSymbolizer(city, "the_geom"); Rule rule1 = builder.createRule(pointSymbolizer); rule1.setName("rule1"); rule1.getDescription().setTitle("City"); rule1.getDescription().setAbstract("Rule for drawing cities"); rule1.setFilter(ff.less(ff.property("POPULATION"), ff.literal(50000))); // // RULE 2 Default Graphic dotGraphic = builder.createGraphic(null, builder.createMark(StyleBuilder.MARK_CIRCLE), null); PointSymbolizer dotSymbolize = builder.createPointSymbolizer(dotGraphic); Rule rule2 = builder.createRule(dotSymbolize); rule2.setElseFilter(true); // // define feature type styles used to actually define how features are rendered Rule[] rules = {rule1, rule2}; FeatureTypeStyle featureTypeStyle = builder.createFeatureTypeStyle("Feature", rules); // // create a "user defined" style Style style = builder.createStyle(); style.setName("style"); style.getDescription().setTitle("User Style"); style.getDescription().setAbstract("Definition of Style"); style.featureTypeStyles().add(featureTypeStyle);
StyleBuilder
also helps by filling in many defaults values. The use of defaults is less of an issue now as the rendering system is able to correctly handle null as a default for many cases such as default symbol size.StyleBuilder
fromgt-brewer
:Style style = new StrokeBuilder().color(Color.BLACK).width(3).buildStyle();
What to use
For working with symbology encoding StyleFactory
is recommended as it defines a small
number of easy to use methods. There are however no helpful methods and shortcuts
(but you have the advantage of less methods to trip over). Since everything is in plain
sight you may discover some tricky advanced abilities that may not obvious using
StyleBuilder
.
For working with style layer descriptor use StyleBuilder
to quickly create objects
with their default values filled in; and then configure them as needed using setters.
Internally we have:
StyleFactoryImpl2
that creates the raw objectsStyleFactoryImpl
makes use of a delegate to create the objects; and then allows for a wider range of create methods defined bygt-api
StyleFactory
StyleBuilder
uses aFilterFactory
and aStyleFactory
in order build up a complicated data structure
Style Layer Descriptor¶
GeoTools styling is built on the style layer descriptor data model shown below (from gt-api).
GeoTools rendering tends to focus the “User Style” which we represent Style
to let you control how
your Map is rendered.
Style
The
Style
interface matches up with the “Style Layer Descriptor” 1.0 specification (so if you need explanations or examples please review the OGC documentation for more information).
Create¶
To create a StyleLayerDescriptor
object using a StyleFactory
:
StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory();
StyledLayerDescriptor sld = styleFactory.createStyledLayerDescriptor();
sld.setName("example");
sld.setAbstract("Example Style Layer Descriptor");
UserLayer layer = styleFactory.createUserLayer();
layer.setName("layer");
FeatureTypeConstraint constraint =
styleFactory.createFeatureTypeConstraint("Feature", Filter.INCLUDE, null);
layer.layerFeatureConstraints().add(constraint);
Style style = styleFactory.createStyle();
style.getDescription().setTitle("Style");
style.getDescription().setAbstract("Definition of Style");
// define feature type styles used to actually
// define how features are rendered
//
layer.userStyles().add(style);
sld.layers().add(layer);
This is the last time we will talk about StyleLayerDescriptor
object - it is not really that useful in
controlling the rendering process.
Access¶
To go from a StyleLayerDescriptor
object to something useful:
FeatureTypeStyle useful[] = SLD.featureTypeStyles( sld );
Or find a style that is compatible with your feature type:
FeatureTypeStyle applicable = SLD.featureTypeStyle( sld, schema );
Document¶
The Styled Layer Descriptor Reference Document OpenGIS standard defines an XML document we use to persist our GeoTools Style objects. This standard is the authoritative definition as far as functionally goes, if you find any place where we are out of line please send us a bug report.
How to parse an SLD file¶
You can create a
Style
using an SLD document (an XML file format defined by the Style Layer Descriptor 1.0 specification):// create the parser with the sld configuration Configuration configuration = new org.geotools.sld.SLDConfiguration(); Parser parser = new Parser(configuration); // the xml instance document above InputStream xml = new FileInputStream("markTest.sld"); // parse StyledLayerDescriptor sld = (StyledLayerDescriptor) parser.parse(xml);
SAX
StyleReader
A SAX based
StyleReader
is also available for GeoTools 2.2 code:private Style loadStyleFromXml() throws Exception { java.net.URL base = getClass().getResource("rs-testData"); StyleFactory factory = StyleFactory.createStyleFactory(); java.net.URL surl = new java.net.URL(base + "/markTest.sld"); //A class to read and parse an SLD file based on verion 0.7.2 of the OGC SLDStyle stylereader = new SLDStyle(factory, surl); Style[] style = stylereader.readXML(); return style[0]; }
How to write a SLD file¶
GeoTools has an XML Transfer written up allowing you to generate an SLD file:
SLDTransformer styleTransform = new SLDTransformer(); String xml = styleTransform.transform(sld);
How to write an SLD file using only a Style
The above code example requires a complete
StyleLayerDescriptor
document in order to make a valid SLD file.Here is how you can wrap up your
Style
object for output:StyledLayerDescriptor sld = styleFactory.createStyledLayerDescriptor(); UserLayer layer = styleFactory.createUserLayer(); layer.setLayerFeatureConstraints(new FeatureTypeConstraint[] {null}); sld.addStyledLayer(layer); layer.addUserStyle(style); SLDTransformer styleTransform = new SLDTransformer(); String xml = styleTransform.transform(sld);
XML:
The file
markTest.sld
contains the following XML:<StyledLayerDescriptor version="0.7.2"> <!-- a named layer is the basic building block of an sld document --> <NamedLayer> <Name>A Random Layer</Name> <title>The title of the layer</title> <abstract> A longer and some would say less random peice of text that allows you to describe the latyer in more detail </abstract> <!-- with in a layer you have Named Styles --> <UserStyle> <!-- again they have names, titles and abstracts --> <Name>MyStyle</Name> <!-- FeatureTypeStyles describe how to render different features --> <FeatureTypeStyle> <FeatureTypeName>testPoint</FeatureTypeName> <rule> <PointSymbolizer> <graphic> <size><PropertyName>size</PropertyName></size> <rotation><PropertyName>rotation</PropertyName></rotation> <mark> <wellknownname><PropertyName>name</PropertyName></wellknownname> <Fill> <!-- CssParameters allowed are fill (the color) and fill-opacity --> <CssParameter name="fill">#FF0000</CssParameter> <CssParameter name="fill-opacity">0.5</CssParameter> </Fill> </mark> </graphic> </PointSymbolizer> </rule> </FeatureTypeStyle> <FeatureTypeStyle> <FeatureTypeName>labelPoint</FeatureTypeName> <rule> <TextSymbolizer> <Label><PropertyName>name</PropertyName></Label> <Font> <CssParameter name="font-family">SansSerif</CssParameter> <CssParameter name="font-Size"> <literal>10</literal> </CssParameter> </Font> <LabelPlacement> <PointPlacement> <AnchorPoint> <AnchorPointX><PropertyName>X</PropertyName> </AnchorPointX> <AnchorPointY><PropertyName>Y</PropertyName> </AnchorPointY> </AnchorPoint> </PointPlacement> </LabelPlacement> <Fill> <CssParameter name="fill">#000000</CssParameter> </Fill> <Halo/> </TextSymbolizer> <PointSymbolizer> <graphic> <size>4</size> <mark> <wellknownname>circle</wellknownname> <Fill> <!-- CssParameters allowed are fill (the color) and fill-opacity --> <CssParameter name="fill">#FF0000</CssParameter> </Fill> </mark> </graphic> </PointSymbolizer> </rule> </FeatureTypeStyle> </UserStyle> </NamedLayer> </StyledLayerDescriptor>
The same style can be created using the
StyleBuilder
:StyleBuilder sb = new StyleBuilder(); FilterFactory ff = sb.getFilterFactory(); Style style = sb.createStyle(); style.setName("MyStyle"); // "testPoint" feature type style Mark testMark = sb.createMark(sb.attributeExpression("name"), sb.createFill(Color.RED, 0.5), null); Graphic graph = sb.createGraphic( null, new Mark[] {testMark}, null, sb.literalExpression(1), sb.attributeExpression("size"), sb.attributeExpression("rotation")); style.featureTypeStyles() .add(sb.createFeatureTypeStyle("testPoint", sb.createPointSymbolizer(graph))); // "labelPoint" feature type style AnchorPoint anchorPoint = sb.createAnchorPoint(sb.attributeExpression("X"), sb.attributeExpression("Y")); PointPlacement pointPlacement = sb.createPointPlacement(anchorPoint, null, sb.literalExpression(0)); TextSymbolizer textSymbolizer = sb.createTextSymbolizer( sb.createFill(Color.BLACK), new Font[] {sb.createFont("Lucida Sans", 10), sb.createFont("Arial", 10)}, sb.createHalo(), sb.attributeExpression("name"), pointPlacement, null); Mark circle = sb.createMark(StyleBuilder.MARK_CIRCLE, Color.RED); Graphic graph2 = sb.createGraphic(null, circle, null, 1, 4, 0); PointSymbolizer pointSymbolizer = sb.createPointSymbolizer(graph2); style.featureTypeStyles() .add(sb.createFeatureTypeStyle("labelPoint", textSymbolizer, pointSymbolizer));
As an extension GeoTools supports defining a Style Background
object that will be used
to fill the canvas before the style rendering directives are applied to features and coverages.
Here is how to setup a style with background:
// create a "user defined" style StyleFactory sf = CommonFactoryFinder.getStyleFactory(); Style style = sf.createStyle(); style.setName("style"); // ... // // set a background FilterFactory ff = CommonFactoryFinder.getFilterFactory(); Fill background = sf.fill(null, ff.literal(Color.RED), ff.literal(1.0)); style.setBackground(background); // ... set the feature type styles and symbolizers
and a similar setup as a SLD
:
<?xml version="1.0" encoding="ISO-8859-1"?> <StyledLayerDescriptor version="1.0.0" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd" xmlns="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <NamedLayer> <Name>Test</Name> <UserStyle> <Name>Default Styler</Name> <Background> <CssParameter name="fill">#FF0000</CssParameter> <CssParameter name="fill-opacity">1.0</CssParameter> </Background> <FeatureTypeStyle> <Name>name</Name> <Rule> <LineSymbolizer> <Stroke> <CssParameter name="stroke">#000000</CssParameter> <CssParameter name="stroke-linecap">butt</CssParameter> <CssParameter name="stroke-linejoin">miter</CssParameter> <CssParameter name="stroke-opacity">1</CssParameter> <CssParameter name="stroke-width">1</CssParameter> <CssParameter name="stroke-dashoffset">0</CssParameter> </Stroke> </LineSymbolizer> </Rule> </FeatureTypeStyle> </UserStyle> </NamedLayer> </StyledLayerDescriptor>
Symbology Encoding¶
The feature type style data model captures the symbology encoding information describing how a feature should be drawn on the screen and will represent the bulk of our examples.
FeatureTypeStyle¶
A FeatureTypeStyle
declares a part of a style that is specifically geared toward a FeatureType
,
that is, features will be rendered according to this FeatureTypeStyle
only if their FeatureType
is the same as the FeatureType
declared in the FeatureTypeStyle
or a descendant.
When defining a Style you will spend the majority of time working with FeatureTypeStyle
. A FeatureTypeStyle
is specifically geared toward drawing features.
The level of detail is similar to CSS in that you need to define some Rules saying when to draw, and some symbolizers saying how to draw it. Individual symbolizers will use expressions to access feature content (as an example TextSymbolizer
will use an expression you provide to construct the text to display).
FeatureTypeStyle.getName()
: machine readable nameFeatureTypeStyle.getDescriptor()
: human readable title and descriptionFeatureTypeStyle.featureTypeNames()
: the Name here is important; it must match the Features you want to draw.Features
will be rendered according aFeatureTypeStyle
only if theirFeatureType
name matches what is recorded in theFeatureTypeStyle
or a descendant.For most practical purposes you will set
featureTypeName
to be “Feature” to act as a wild card.FeatureTypeStyle.semanticTypeIdentifiers()
: used to quickly limit based on the kind of vector data (point, line or polygon)
Here is a quick example that will draw any “Feature” using a PointSymbolizer
:
StyleBuilder styleBuilder = new StyleBuilder();
Style style = styleBuilder.createStyle();
PointSymbolizer pointSymbolizer = styleBuilder.createPointSymbolizer();
Graphic graphic = styleBuilder.createGraphic();
ExternalGraphic external =
styleBuilder.createExternalGraphic("file:///C:/images/house.gif", "image/gif");
graphic.graphicalSymbols().add(external);
graphic.graphicalSymbols().add(styleBuilder.createMark("circle"));
pointSymbolizer.setGraphic(graphic);
Rule rule = styleBuilder.createRule(pointSymbolizer);
FeatureTypeStyle featureTypeStyle = styleBuilder.createFeatureTypeStyle("Feature", rule);
style.featureTypeStyles().add(featureTypeStyle);
Note
how the PointSymbolizer
will first try and use C:\images\house.gif
(i.e. an external graphic) and if that fails it will use a circle (i.e. a mark).
Notes on handling of features:
Each
FeatureTypeStyle
that applies is used. That means, the layer will be drawn multiple times if the styles contain more than oneFeatureTypeStyle
that matches theFeatureType
of the features in the layer.FeatureTypeStyles
are painted in order: aFeatureTypeStyle
is painted only once the previous one in the list has been completely paintedEach feature is then passed to the rules and their list of symbolizers
This means that a single feature can be painted more than once, if more than one rule matches it, or if the rules contain more than one symbolizer.
The full set of rules and symbolizers in the current
FeatureTypeStyle
is applied to the current Feature before considering the next one.The last consideration is important when you need to draw, for example, roads with a double line such as a wide black line below a thin white line. This is possible using two
FeatureTypeStyles
, since using a Rule with a couple of symbolizers will generate a map that doesn’t look good at road intersections.Example of style with two
FeatureTypeStyles
:StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory(); FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory(); Style style = styleFactory.getDefaultStyle(); // Feature type style 1 FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle(); fts.featureTypeNames().add(new NameImpl("feature-type-1")); style.featureTypeStyles().add(fts); // Feature type style 2 FeatureTypeStyle fts2 = styleFactory.createFeatureTypeStyle(); fts2.featureTypeNames().add(new NameImpl("feature-type-2")); // creating the rule 1 Rule rule1 = styleFactory.createRule(); rule1.setName("rule1"); Filter aFilter = filterFactory.id(Collections.singleton(filterFactory.featureId("FID"))); rule1.setFilter(aFilter); fts2.rules().add(rule1); // creating the rule 2 Rule rule2 = styleFactory.createRule(); rule2.setElseFilter(true); rule2.setName("rule2"); fts2.rules().add(rule2); style.featureTypeStyles().add(fts2);
For an in depth discussion of the rendering process please refer to * style (tutorial)
FeatureTypeStyle
includes vendor options specific to the GeoTools rendering engine:
composite
(source-over): allows control of color blending (using copy destination, source-over, destination-over, source-in, destination-in, source-out, destination-out source-atop, destination-atop, xor, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion).composite
-base (false): definition of composition groups// multiply buffer from feature type style 0, onto composite-base provided by feature type style 1 style.featureTypeStyles().get(0).getOptions().put("composite","multiply, 0.5"); style.featureTypeStyles().get(1).getOptions().put("composite-base", "true");
firstMatch
: stops rule evaluation after the first match (making it easier to work with data sets where content is classified by distinct attribute values)// exit rules on first match, like a switch statement fts.getOptions().put("ruleEvaluation", "first");
sortBy
: Control order features are retrieved, controlling drawing order.The syntax is Attribute1 {A|D},Attribute2 {A|D}… where A is ascending, D is descending. The sorting direction is optional and defaults to ascending if not specified.
// sort newer cities first, than by name fts.getOptions().put( "sortBy", "year D,name A");
Rule¶
A FeatureTypeStyle
contains one or more rules, these rules are considered in order with the possibility of an “else” Rule being
used to render any remaining features.
A rule is based on the following:
minimum/maximum scale: if set and the current scale is outside the specified range, the rule won’t apply and thus its symbolizers won’t be used
Filter: that is applied to the features, only the features matching the filter will be painted according to the Rule symbolizers.
As an alternative, the rule can have an “else filter”. This special kind of filter catches all of the features that still haven’t been symbolized by previous rules with a regular filter.
FeatureTypeStyle
used featureTypename
to sort out what kind of features we are dealing with. Rules
are used to refine this contents, possibly filtering according to feature attributes or scale, to determine specifically what we are going to draw.
Pay Attention to:
minimum and maximum map scale, if set and the current scale is outside the specified range, the rule won’t apply and thus its symbolizers won’t be used
Filter that is used to select features to draw, only the features matching the filter will be painted
A rule can have an “else filter”. This special kind of filter catches all of the features that still haven’t been symbolized by previous rules with a regular filter).
Once FeatureTypeStyle
and Rules
have determined that a Feature
is going to be drawn; the Rule
makes use of a list of of Symbolizers
to define how the content is painted:
A
Symbolizer
describes how to represent a feature on the screen based on the feature contents (geometry and attributes).Each
Rule
can have a list ofSymbolizer
attached to it.Symbolizer
s are used like a display language to produce pixels on the display device.
Symbolizer¶
A Symbolizer
describes how a Feature
should appear on a map. Each Rule
has a list of Symbolizer
s which it applies in order.
As you can see, there are many kind of symbolizers, for points, lines, polygons, labels and raster data.
You don’t need to match the symbolizer with the specific geometry contained in the feature, the renderer will try to do the most appropriate thing on a case by case basis. For example, TextSymbolizer
applies to all kinds of geometries, and will generate labels on the map. If you apply a PolygonSymbolizer
to a line, the line will be closed to form a polygon, and then the polygon symbolizer will be applied.
The GeoTools Symbolizer
interface offers a couple of advantages over the base standard:
getGeometry()
setGeometry( Expression )
The ability to define a geometry using an expression allows the use of a function to pre-process your geometry prior to it being considered for rendering.
This is a little bit tricky (as functions like buffer will make your geometry bigger) but the result is worthwhile in the amount of flexibility it offers.
Notes on the use of symbolizers:
The symbolizer describes not just the shape that should appear but also such graphical properties as color and opacity
Symbolizers do have a default behavior, after creating a Symbolizer you should supplying parameters to override the default settings
The original details of this object are taken from the OGC Styled-Layer Descriptor Report (OGC 01-077) version 0.7.2.
Renderers can use this information when displaying styled features. Though it must be remembered that not all renderers will be able to fully represent strokes as set out by this interface. For example, opacity may not be supported.
The graphical parameters and their values are derived from SVG/CSS2 standards with names and semantics which are as close as possible.
The most important thing to note here is that symbolizer component objects are composed of Expression objects, which means that they may be made dependent on Feature attributes.
For example, you can create a mathematical expression that links some Feature attribute to the line width.
Thus, you have two ways to symbolize different features with different styles:
By using more than one rule with different filters, and then building symbolizers with literal expressions. This is a good way to create a classified map, in which colors, line styles and so on depend on the range the attribute value falls into.
By directly linking a symbolizer property to an attribute value;
Vendor options suitable for use with any symbolizer:
composite (source-over): allows control of color blending (using copy destination, source-over, destination-over, source-in, destination-in, source-out, destination-out source-atop, destination-atop, xor, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion).
Point Symbolizer¶
Used to draw a point location, the actual graphic drawn is referred to as a Mark with the option to use some well known marks (circle, square etc..) or your own external graphics such as PNG icons.
Examples:
GeoServer SLD cookbook points <styling/sld/cookbook/points.html>
Quick example creating a
PointSymbolizer
usingStyleBuilder
:// "testPoint" feature type style StyleBuilder sb = new StyleBuilder(); FilterFactory ff = sb.getFilterFactory(); Mark testMark = sb.createMark(sb.attributeExpression("name"), sb.createFill(Color.RED, 0.5), null); Graphic graph = sb.createGraphic( null, // An external graphics if needed new Mark[] {testMark}, // a Mark if not an external graphics null, // aSymbol ff.literal(1), // opacity ff.property("size"), // read from feature "size" attribute ff.property("rotation")); // rotation, here read into the feature PointSymbolizer aPointSymbolizer = sb.createPointSymbolizer(graph); // creation of the style Style style = sb.createStyle(aPointSymbolizer);
Here is the same style as an xml fragments:
<PointSymbolizer> <graphic> <size><PropertyName>size</PropertyName></size> <rotation><PropertyName>rotation</PropertyName></rotation> <mark> <wellknownname><PropertyName>name</PropertyName></wellknownname> <Fill> <!-- CssParameters allowed are fill (the color) and fill-opacity --> <CssParameter name="fill">#FF0000</CssParameter> <CssParameter name="fill-opacity">0.5</CssParameter> </Fill> </mark> </graphic> </PointSymbolizer>
Point symbolizers support VendorOptions
:
labelObstacle(true/false)
: No labels should overlap this feature, used to ensure point graphics are clearly visible and not obscured by text.fallbackOnDefaultMark(true/false)
: If the graphics used in the symbolizer cannot be found, fallback on a default gray square (true
, default value) or skip the symbolizer without painting anything (false
).
LineSymbolizer¶
Used to control how lines (or edges) are drawn.
Examples:
GeoServer SLD cookbook Lines <styling/sld/cookbook/lines.html>
PolygonSymbolizer¶
Used to control how solid shapes are drawn.
Examples:
GeoServer SLD cookbook Polygons <styling/sld/cookbook/polygons.html>
Quick example using
StyleBuilder
to create aPolygonSymbolizer
:
StyleBuilder styleBuilder = new StyleBuilder();
FilterFactory ff = CommonFactoryFinder.getFilterFactory();
PolygonSymbolizer polygonSymbolizer = styleBuilder.createPolygonSymbolizer(Color.BLUE);
polygonSymbolizer.getFill().setOpacity(ff.literal(0.5)); // 50% blue
polygonSymbolizer.setStroke(styleBuilder.createStroke(Color.BLACK, 2.0));
// will create a default feature type style and rule etc...
Style style = styleBuilder.createStyle(polygonSymbolizer);
TextSymbolizer¶
Used to control the labeling system; labels are generated by TextSymbolizers
and
thrown into the rendering engine which detect overlaps, sorts things out according
to priorities you have defined and decides on a final label placement.
This makes TextSymbolizer
a little bit odd in that it does not always get the final say on how labels are rendered on a pixel by pixel basis.
Examples:
GeoServer SLD cookbook
Here is a quick example of creating a
TextSymbolizer
withStyleBuilder
:// "labelPoint" feature type style StyleBuilder sb = new StyleBuilder(); FilterFactory ff = sb.getFilterFactory(); // creation of the TextSymbolizer AnchorPoint anchorPoint = sb.createAnchorPoint(sb.attributeExpression("X"), sb.attributeExpression("Y")); PointPlacement pointPlacement = sb.createPointPlacement(anchorPoint, null, sb.literalExpression(0)); TextSymbolizer textSymbolizer = sb.createTextSymbolizer( sb.createFill(Color.BLACK), new Font[] {sb.createFont("Lucida Sans", 10), sb.createFont("Arial", 10)}, sb.createHalo(), sb.attributeExpression("name"), pointPlacement, null); // creation of the Point symbolizer Mark circle = sb.createMark(StyleBuilder.MARK_CIRCLE, Color.RED); Graphic graph2 = sb.createGraphic(null, circle, null, 1, 4, 0); PointSymbolizer pointSymbolizer = sb.createPointSymbolizer(graph2); // creation of the style Style style = sb.createStyle(); FeatureTypeStyle featureTypeStyle = sb.createFeatureTypeStyle("labelPoint", textSymbolizer, pointSymbolizer); style.featureTypeStyles().add(featureTypeStyle); // creation of the style
Here is the same example as an xml fragment:
<TextSymbolizer> <Label><PropertyName>name</PropertyName></Label> <Font> <CssParameter name="font-family">Lucida Sans</CssParameter> <CssParameter name="font-Size"> <literal>10</literal> </CssParameter> </Font> <LabelPlacement> <PointPlacement> <AnchorPoint> <AnchorPointX><PropertyName>X</PropertyName> </AnchorPointX> <AnchorPointY><PropertyName>Y</PropertyName> </AnchorPointY> </AnchorPoint> </PointPlacement> </LabelPlacement> <Fill> <CssParameter name="fill">#000000</CssParameter> </Fill> <Halo/> </TextSymbolizer> <PointSymbolizer> <graphic> <size>4</size> <mark> <wellknownname>circle</wellknownname> <Fill> <!-- CssParameters allowed are fill (the color) and fill-opacity --> <CssParameter name="fill">#FF0000</CssParameter> </Fill> </mark> </graphic> </PointSymbolizer>
Considerable vendor options are provided for working with TextSymbolizers
:
allowOverruns(false)
: When false does not allow labels on lines to get beyond the beginning/end of the line. By default a partial overrun is tolerated, set to false to disallow it.autoWrap(400)
: Number of pixels are which a long label should be split into multiple lines. Works on all geometries, on lines it is mutually exclusive with thefollowLine
optionconflictResolution(true)
: Enables conflict resolution (default, true) meaning no two labels will be allowed to overlap. Symbolizers with conflict resolution off are considered outside of the conflict resolution game, they don’t reserve area and can overlap with other labels.followLine(true)
: When true activates curved labels on linear geometries. The label will follow the shape of the current line, as opposed to being drawn a tangent straight lineforceLeftToRight(true)
: When true forces labels to a readable orientation, when false they make follow the line orientation even if that means the label will look upside down (useful when using TTF symbol fonts to add direction markers along a line)goodnessOfFit(0.5)
: Sets the ratio of the label that must sit inside the geometry to allow drawing the label. Works only on polygons. Provided values should span from 0 .. 1graphic-margin(10)
: Pixels between the stretched graphic and the text, applies when graphic stretching is in usegraphic-resize(true)
: Stretches the graphic below a label to fit the label size. Possible values are ‘stretch’, ‘proportional’.group(false)
: If true, geometries with the same labels are grouped and considered a single entity to be labeled. This allows to avoid or control repeated labelslabelAllGroup(false)
: When false, only the biggest geometry in a group is labeled (the biggest is obtained by merging, when possible, the original geometries). When true, also the smaller items in the group are labeled. Works only on lines at the moment.repeat(0)
: When positive it’s the desired distance between two subsequent labels on a “big” geometry. Works only on lines at the moment. If zero only one label is drawn no matter how big the geometry ismaxAngleDelta(90)
: When drawing curved labels, max allowed angle between two subsequent characters. Higher angles may cause disconnected words or overlapping charactersmaxDisplacement(0)
: The distance, in pixel, a label can be displaced from its natural position in an attempt to find a position that does not conflict with already drawn labels.minGroupDistance(3)
: Minimum distance between two labels in the same label group. To be used when both displacement and repeat are used to avoid having two labels too close to each otherpartials(true)
: Option to truncate labels placed on the border of thedisplayArea
(display partial labels)polygonAlign(true)
: Option overriding manual rotation to align label rotation automatically for polygons.spaceAround(50)
: The minimum distance between two labels, in pixelscharSpacing(0)
: The extra space between characters, in pixels. Can be negative.wordSpacing(0)
: The extra space between words, in pixels. Must be zero or positive.underlineText(true)
: When true instructs the renderer to underline labelsstrikethroughText(true)
: When true instructs the renderer tostrikethrough
labelskerning(true)
: When true enables text kerning (adjustment of space between characters to get a more compact and readable layout)displacementMode
Comma separated list of label displacement directions for point/polygon labels (used along withmaxDisplacement
). The indicated directions will be tried in turn.Valid values are cardinal directions abbreviations, in particular, N, W, E, S, NW, NE, SW, SE.
fontShrinkSizeMin(0)
: lower font size limit that could be used when rendering the label. When set (to a positive value) the rendering process will be applied iteratively by decreasing the initial font size by 1 unit until an acceptable placement location is found or the specified value is reached. Should be set to a value greater than 0 and lower than the symbolizer’s font size otherwise it is ignored.graphicPlacement(label)
: placement of the graphic found in the text symbolizer, compared to the associated label. If usinglabel
(default value) the graphic is centered with the label. If usingindependet
the graphic is placed relative to label point instead, and the graphic anchor/offset are used to position it, while the label retains its own anchor/offset related to the label point.
Raster Symbolizer¶
Used to control the rendering of raster data with full “color map” control.
GeoServer SLD cookbook Rasters
StyleVisitor¶
Just like with the FilterVisitor
interface we are going to use these implementation to navigate over a nested data structure and either copy what we see, or modify it as we go.
While the StyleVisitor
interface will let modify a style in place we have never found that to be a good idea (at best opening your code up to magic threading issues with what is probably a very active rendering thread).
The generic StyleVisitor
interface is everything you would expect from the Gang of Four Visitor
pattern, it has a visit methods one for each significant interface in a Style
object.
To use a StyleVisitor
pass it to a Style
(or any style object) using the accepts method:
style.accepts( styleVisitor );
You will find that not all Style
objects accept a StyleVisitor
; as an example Font
does not. This is not really a problem - but it is something to keep in mind when writing your own visitor.
Ready to Use Implementations
There are a number of ready to use implementations; while we have provided some examples on this page please explore what is available in the library - you can do this quickly by checking the javadocs.
StyleAttributeExtractor
- return all the attributes mentioned by this style; used by the renderer when constructing a QueryDuplicatingStyleVisitor
- return a copy of the styleRescaleStyleVisitor
- return a copy of the style modified for display at a different scale.
Implementation Tips
If you are used to simple visitors on list like data structures you are in for a surprise - StyleVisitor
does not navigate the Style
object structure on its own you are going to have to do the work.:
class YourStyleVisitor implements StyleVisitor {
...
public void visit(Halo halo) {
// do your work here
// make sure you visit the "child" objects
if( halo.getFill() != null ) halo.getFill().accepts( this );
if( halo.getRadius() != null ) halo.getRadius().accepts( this );
}
...
}
We should have an AbstractStyleVisitor
for you to start from; perhaps you would like to write it for us?
DuplicatingStyleVisitor¶
DuplicatingStyleVisitor
will copy any style object; it keeps track of what is copied using an internal stack (this means it is not thread safe!).:
DuplicatingStyleVisitor xerox = new DuplicatingStyleVisitor();
style.accepts( xerox );
Style copy = (Style) xerox.getCopy();
Please note this works for everything:
DuplicatingStyleVisitor xerox = new DuplicatingStyleVisitor();
lineSymbolizer.accepts( xerox );
LineSymbolizer copy = (LineSymbolizer) xerox.getCopy();
RescaleStyleVisitor¶
RescaleStyleVisitor
can be used to scale up a provided style; something that is useful when printing. The SLD specification is pretty careful about working with pixels at all times (this is annoying when you switch to 300 DPI).:
RescaleStyleVisitor scale = new RescaleStyleVisitor(5.0);
style.accepts( scale );
Style bigger = (Style) scale.getCopy();
Please note that this also returns a copy; while you could modify a style in place using a visitor we find that life is too short for threading issues.:
RescaleStyleVisitor scale = new RescaleStyleVisitor(5.0);
lineSymbolizer.accepts( scale );
LineSymbolizer bigger = (LineSymbolizer) scale.getCopy();