Share
Creating Maps in GeoServer using CSS and SLD (FOSS4G 2022 Edition)
The presentation aims to provide attendees with enough information to master GeoServer styling documents and most of GeoServer extensions to generate appealing, informative, readable maps that can be quickly rendered on screen. Examples will be provided from GeoSolutions training material, as well as from the OSM data directory we shared with the community.
Several topics will be covered, providing examples in CSS and SLD, including:
- Mastering common symbolization, filtering, multi-scale styling.
- Using GeoServer extensions to build common hatch patterns, line styling beyond the basics, cased lines, controlling symbols along a line and the way they repeat.
- Leveraging TTF symbol fonts and SVGs to generate good looking point thematic maps.
- Using the full power of GeoServer label lay-outing tools to build pleasant, informative maps on both point, polygon and line layers, including adding road plates around labels, leverage the labeling subsystem conflict resolution engine to avoid overlaps in stand alone point symbology.
- Dynamically transform data during rendering to get more explicative maps without the need to pre-process a large amount of views.
- Generating styles with external tools.
Simone Giannecchini PRO
Transcript
47 slides
-
Creating stunning maps with GeoServer With SLD and CSS Ing.
Andrea Aime GeoSolutions 1 -
GeoSolutions Enterprise Support Services Deployment Subscription Professional Training Customized Solutions
GeoNode • Offices in Italy & US, Global Clients/Team • 40+ collaborators, 30+ Engineers • Our products • Our Offer -
Affiliations We strongly support Open Source, it Is in our
core We actively participate in OGC working groups and get funded to advance new open standards We support standards critical to GEOINT -
Languages SLD 1.0 core SLD 1.1 core YSLD extension MBStyles
community GeoCSS extension SLD 1.0 inspired object model, with extensions parse parse parse parse and translate parse and translate 5 Map rendering -
Shared concepts • Layers (the styles applies to) • Rules
• Filters/selectors (what should be painted) • Scale dependencies (zoomed in, zoomed out?) • Symbolizers (how should it be painted) – Point – Line – Polygon – Text 6 -
Styled Layer Descriptor 1.0 and 11 • OGC styling standard,
XML based • Was meant for machine export but ended up being edited a lot by hand • Can be generated by external tools and imported (with some hand tweaking) 7 -
SLD 1.0 example <!–- Boilerplate beginning ommitted --> … <sld:Rule>
<ogc:Filter> <ogc:PropertyIsEqualTo> <ogc:PropertyName>type</ogc:PropertyName> <ogc:Literal>alpine_hut</ogc:Literal> </ogc:PropertyIsEqualTo> </ogc:Filter> <sld:MaxScaleDenominator>100000.0</sld:MaxScaleDenominator> <sld:PointSymbolizer> <sld:Graphic> <sld:ExternalGraphic> <sld:OnlineResource xlink:type="simple" xlink:href="symbols/alpinehut.p.16.png"/> <sld:Format>image/png</sld:Format> </sld:ExternalGraphic> </sld:Graphic> </sld:PointSymbolizer> </sld:Rule> <!–- Boilerplate end ommitted --> Filter Scale dep. Symbolizer -
YSLD • SLD in YAML syntax • Filtering by CQL
• Can define reusable variables and blocks • Verbosity it’s between SLD and CSS • Has a notion of zoom levels, if needed feature-styles: - rules: - filter: type = 'alpine_hut' scale: (,100000.0) symbolizers: - point: symbols: - external: url: symbols/alpinehut.p.16.png format: image/png 9 -
GeoCSS • Compact syntax, familiar for web developers • CQL
based filtering, rule nesting and cascading keeps styling compact • Does not get any more compact than this: • Cons: some are confused by “rule cascading” 🡪 can now be turned off [type = 'alpine_hut'][@sd < 100k] { mark: url('symbols/alpinehut.p.16.png'); } 10 -
MBStyles (aka MapBox GL) • Symbols as “sprites” (symbol collections)
• Can be applied both on the client side and the server side • Online editors available, complex expressions still require writing JSON • JSON based, designed for GUI editing (like SLD) instead of hand editing • Scale control based only on Web Mercator zoom levels • -
MBStyles (aka MapBox GL) { "version": 8, … boilerplate omitted
here "name": "mountain huts" , "sprite": "https://my.sever/sprite" , "layers": [ { "id": "huts", "type": "symbol", "minzoom": 9, "filter": [ "==", "type", "alpine_hut" ], "layout": { "icon-image" : "alpinehut", "icon-size": 1, "icon-allow-overlap" : true } } ] } Filter (in postfix notation!) Scale dep. Symbolizer The sprint contains various images, like in videogames -
Scale dependency Filters • SLD: • <MinScaleDenominator> 1000 </MinScaleDenominator> •
<MaxScaleDenominator> 1000000 </MaxScaleDenominator> • CSS • [@sd > 1k][@sd < 1M] Compact expression of large numbers makes them readable at a glance, e.g., 100k, 1M , 10M Show buildings as user zooms in -
Unit of Measure • Real world measures, sizes grow as
one zooms in … <LineSymbolizer uom="http://www.opengeospatial.org/se/units/metre"> <Stroke> <CssParameter name="stroke">#0000FF</CssParameter> <CssParameter name="stroke-width">5</CssParameter> </Stroke> </LineSymbolizer> ... … stroke: blue; stroke-width: 5m; SLD CSS -
Transformation functions • Use a different value depending on the
current scale/zoom level • For non linear scaling [class = 'highway’ and type in ('motorway’, 'motorway_link’)] [@sd < 25M] { stroke: #e66e89; stroke-width: categorize(@sd, 2, 400k, 1.9, 800k, 1.4, 1.5M, 1, 3M, 0.8, 6M, 0.5); • Less than 400k 🡪 2px • [400k, 800k] 🡪 1.9px • [800k, 1.5M] 🡪 1.4px • [1.5M, 3M] 🡪 1 • [3M, 6M] 🡪 0.8 • Above 6M -> 0.5 CSS -
Transformation functions in SLD <sld:LineSymbolizer> <sld:Stroke> <sld:CssParameter name="stroke">#e66e89</sld:CssParameter> <sld:CssParameter name="stroke-width">
<ogc:Function name="Categorize"> <ogc:Function name="env"> <ogc:Literal>wms_scale_denominator</ogc:Literal> </ogc:Function> <ogc:Literal>2</ogc:Literal> <ogc:Literal>400000</ogc:Literal> <ogc:Literal>1.9</ogc:Literal> <ogc:Literal>800000</ogc:Literal> <ogc:Literal>1.4</ogc:Literal> <ogc:Literal>1500000</ogc:Literal> <ogc:Literal>1</ogc:Literal> <ogc:Literal>3000000</ogc:Literal> <ogc:Literal>0.8</ogc:Literal> <ogc:Literal>6000000</ogc:Literal> <ogc:Literal>0.5</ogc:Literal> </ogc:Function> </sld:CssParameter> </sld:Stroke> </sld:LineSymbolizer> SLD -
Simple symbol [type = 'alpine_hut'][@sd < 100k] { mark: url('symbols/alpinehut.p.16.png');
} <sld:Rule> <ogc:Filter> <ogc:PropertyIsEqualTo> <ogc:PropertyName>type</ogc:PropertyName> <ogc:Literal>alpine_hut</ogc:Literal> </ogc:PropertyIsEqualTo> </ogc:Filter> <sld:MaxScaleDenominator>100000.0</sld:MaxScaleDenominator> <sld:PointSymbolizer> <sld:Graphic> <sld:ExternalGraphic> <sld:OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="symbols/alpinehut.p.16.png"/> <sld:Format>image/png</sld:Format> </sld:ExternalGraphic> </sld:Graphic> </sld:PointSymbolizer> </sld:Rule> SLD CSS -
SVG as a scalable, fillable shape [type = 'bank'][@sd <
6k] { mark: symbol('file://symbols/bank.svg'); :mark { fill: #734a08 }; mark-size: 14; } <sld:Rule> <ogc:Filter> <ogc:PropertyIsEqualTo> <ogc:PropertyName>type</ogc:PropertyName> <ogc:Literal>bank</ogc:Literal> </ogc:PropertyIsEqualTo> </ogc:Filter> <sld:MaxScaleDenominator>6000</sld:MaxScaleDenominator> <sld:PointSymbolizer> <sld:Graphic> <sld:Mark> <sld:WellKnownName>file://symbols/bank.svg</sld:WellKnownName> <sld:Fill> <sld:CssParameter name="fill">#734a08</sld:CssParameter> </sld:Fill> </sld:Mark> <sld:Size>14</sld:Size> </sld:Graphic> </sld:PointSymbolizer> </sld:Rule> CSS bank.svg -
Marks composition and override [type = 'fountain’] { [@sd <
6k] { mark: symbol(circle), symbol(circle); :nth-mark(1) { fill: #b5d0d0 }; :nth-mark(2) { fill: #576ddf }; mark-size: 10, 3; }; [@sd < 3k] { mark: symbol('file://symbols/fountain.svg'); :mark { fill: #576ddf; }; } } CSS -
• Here are some examples: • From TTF fonts using
the name ttf://<fontname>#charcode • Windbarbs, e.g.: windbarbs://default(15)[kts] • WKT specification, e.g. wkt://MULTIPOLYGON(((-0.25 -0.25, -0.125 -0.25), (0.125 -0.25, 0.25 -0.25)), (-0.25 0.25, -0.125 0.25), (0.125 0.25, 0.25 0.25) …… ) • Build your own, pluggable system • Full docs at: http://docs.geoserver.org/latest/en/user/styling/sld/exte nsions/pointsymbols.html Other mark options -
Solid filling * { fill: lightgrey; stroke-width: 0.5; } <sld:PolygonSymbolizer>
<sld:Fill> <sld:CssParameter name="fill">#d3d3d3</sld:CssParameter> </sld:Fill> <sld:Stroke> <sld:CssParameter name="stroke-width">0.5</sld:CssParameter> </sld:Stroke> </sld:PolygonSymbolizer> SLD CSS -
Overlap solid fills with repeated icons fill [@sd < 800k][type
in ('cemetery', 'grave_yard')] { fill: #aacbaf; [religion = 'jewish'] { fill: #aacbaf, url('symbols/grave_yard_jewish.png'); }; [religion = 'christian'] { fill: #aacbaf, url('symbols/grave_yard_christian.png'); }; [religion = 'INT-generic'] { fill: #aacbaf, url('symbols/grave_yard_generic.png'); }; } CSS -
Hatching <sld:PolygonSymbolizer> <sld:Fill> <sld:GraphicFill> <sld:Graphic> <sld:Mark> <sld:WellKnownName>shape://times</sld:WellKnownName> <sld:Stroke> <sld:CssParameter name="stroke">#ADD8E6</sld:CssParameter>
</sld:Stroke> </sld:Mark> <sld:Size>8</sld:Size> </sld:Graphic> </sld:GraphicFill> </sld:Fill> </sld:PolygonSymbolizer> </sld:Rule> [@scale < 10000] { fill: symbol('shape://times'); fill-size: 8; :fill { stroke: #ADD8E6; } } SLD CSS -
Solid lines (OSM admin borders) [type = 'administrative'] { [admin_level
<= 4], [admin_level = 5 or admin_level = 6][@sd <= 400k], [admin_level = 7 or admin_level = 8][@sd <= 200k], [admin_level = 9 or admin_level = 10][@sd <= 100k] { stroke: #ac46ac; stroke-opacity: 0.4; } } CSS -
Dashes/Marks/images along a line * { stroke: darkRed, symbol('circle'); stroke-dasharray:
10 14, 6 18; stroke-dashoffset: 14, 0; :stroke { fill: darkRed; size: 6; } } • Two coordinated dashed lines • One made with lines • One made with circles • The offset shifts them to have one appear in the empty spaces of the other CSS -
Vendor options • Priority labelling, static or attribute based •
Deluge of vendor options to control in detail labelling: • group • labelAllGroup • spaceAround • followLine • maxDisplacement • repeat • maxAngleDelta • autoWrap • forceLeftToRight • conflictResolution • goodnessOfFit • polygonAlign • graphic-resize • graphic-margin • partials • underlineText • strikethroughText • charSpacing • wordSpacing -
Polygon labels <sld:TextSymbolizer> <sld:Label> <ogc:PropertyName>FULLNAME</ogc:PropertyName> </sld:Label> <sld:Font> <sld:CssParameter name="font-family">Arial</sld:CssParameter> <sld:CssParameter
name="font-size">14.0</sld:CssParameter> <sld:CssParameter name="font-weight">bold</sld:CssParameter> </sld:Font> <sld:LabelPlacement> <sld:PointPlacement> <sld:AnchorPoint> <sld:AnchorPointX>0.5</sld:AnchorPointX> <sld:AnchorPointY>0.5</sld:AnchorPointY> </sld:AnchorPoint> </sld:PointPlacement> </sld:LabelPlacement> <sld:Fill> <sld:CssParameter name="fill">#000000</sld:CssParameter> </sld:Fill> <sld:Priority>50000</sld:Priority> <sld:VendorOption name="autoWrap">100</sld:VendorOption> <sld:VendorOption name="maxDisplacement">200</sld:VendorOption> <sld:VendorOption name="goodnessOfFit">0.9</sld:VendorOption> </sld:TextSymbolizer> SLD -
Point labels and obstacles [@sd < 200k] { label: [FULLNAME];
label-anchor: 0.5 1.0; label-offset: 0.0 -14.0; font-fill: #000033; font-family: Arial; font-size: 12; halo-color: white; halo-radius: 1.5; label-priority: 200000; label-auto-wrap: 100; mark: url('./img/landmarks/${IMAGE}’); mark-label-obstacle: true; } «FULLNAME» attribute Auto wrapping label with halo. Data driven symbol URL Labels won’t overlap the symbol CSS -
Line labels [@sd < 200k] { label: [LABEL_NAME]; font-fill: #000000;
font-family: Arial; font-size: 13; font-style: normal; font-weight: bold; halo-color: #FFFFFF; halo-radius: 1; label-follow-line: true; label-repeat: 400; label-group: true; label-max-displacement: 200; } Draw «LABEL_NAME», black, with white halo Draw them along lines, fuse segments with same label, repeat CSS -
A DEM and a color map • SRTM from USGS
• Standard color map • Also shaded relief (not shown in the css below) [@sd > 75000] { raster-channels: auto; raster-color-map: color-map-entry(#00BFBF, -100.0, 0) color-map-entry(#00FF00, 920.0, 0) color-map-entry(#00FF00, 920.0, 1.0) color-map-entry(#FFFF00, 1940.0, 1.0) color-map-entry(#FFFF00, 1940.0, 1.0) color-map-entry(#FF7F00, 2960.0, 1.0) color-map-entry(#FF7F00, 2960.0, 1.0) color-map-entry(#BF7F3F, 3980.0, 1.0) color-map-entry(#BF7F3F, 3980.0, 1.0) color-map-entry(#141514, 5000.0, 1.0); } CSS -
Contrast enhancement http://docs.geoserver.org/latest/en/user/styling/sld-reference/rastersymbolizer. html#contrastenhancement <sld:RasterSymbolizer> <sld:ContrastEnhancement> <sld:Normalize> <sld:VendorOption name="algorithm"> StretchToMinimumMaximum
</sld:VendorOption> <sld:VendorOption name="minValue">50</sld:VendorOption> <sld:VendorOption name="maxValue">800</sld:VendorOption> </sld:Normalize> </sld:ContrastEnhancement> </sld:RasterSymbolizer> GeoServer vendor extension SLD -
Color blending and alpha compositing multiply color-burn difference Source Destination
https://docs.geoserver.org/latest/en/user/styling/sld/extensions/composite-blend/index.html More info at: Mask with alpha compositing <VendorOption name="composite"> multiply, 0.5 </VendorOption> -
Z ordering https://docs.geoserver.org/latest/en/user/styling/sl d/extensions/z-order/syntax.html#z-ordering-acros s-layers [class = 'motorways'] { stroke:
#990000, #ff6666; stroke-width: 8, 6; stroke-linecap: round; z-index: 0, 2; } [class = 'railways'] { stroke: #333333; stroke-width: 3; z-index: 1; } [class = 'railways'] { stroke: #ffffff; stroke-width: 1.5; stroke-dasharray: 5 5; z-index: 2; } * { sort-by: "z_order"; sort-by-group: "roadsGroup"; } CSS 40 -
Rendering transformations • WPS + WMS + rendering optimizations •
In this slide, on the fly contour extraction * { transform: ras:Contour(levels: 1100 1200 1300 1400 1500 1600 1700 1800); stroke: black; label: [GRAY-INDEX]; font-fill: black; font-family: Sans; font-size: 12; halo-radius: 2; halo-color: white; label-follow-line: true } CSS 41 -
Jiffle map algebra * { transform: ras:Jiffle(script: ‘ nir =
src[7]; vir = src[3]; dest = (nir-vir)/(nir+vir);’); raster-channels: auto; raster-color-map: color-map-entry(#00BFBF, -100.0, 0) color-map-entry(#00FF00, 920.0, 0) … } CSS On the fly NDVI computation from Sentinel 2 images -
QGIS SLD export • Basic vector and raster styling covered
• Much advanced bits missing though • Needs sponsoring on both ends (QGIS and GeoServer)