Polygons

Next we look at how CSS styling can be used to represent polygons.

Polygon Geometry

Review of polygon symbology:

  • Polygons offer a direct representation of physical extent or the output of analysis.

  • The visual appearance of polygons reflects the current scale.

  • Polygons are recorded as a LinearRing describing the polygon boundary. Further LinearRings can be used to describe any holes in the polygon if present.

    The Simple Feature for SQL Geometry model (used by GeoJSON) represents these areas as Polygons, the ISO 19107 geometry model (used by GML3) represents these areas as Surfaces.

  • SLD uses a PolygonSymbolizer to describe how the shape of a polygon is drawn. The primary characteristic documented is the Fill used to shade the polygon interior. The use of a Stroke to describe the polygon boundary is optional.

  • Labeling of a polygon is anchored to the centroid of the polygon. GeoServer provides a vendor-option to allow labels to line wrap to remain within the polygon boundaries.

For our Polygon exercises we will try and limit our CSS documents to a single rule, in order to showcase the properties used for rendering.

Reference:

This exercise makes use of the ne:states_provinces_shp layer.

  1. Navigate to CSS Styles.

  2. Set ne:states_provinces_shp as the preview layer.

    ../../../_images/polygon_01_preview.png
  3. Create a new CSS style polygon_example.

    Workspace for new layer: No workspace
    New style name: polygon_example
    ../../../_images/polygon_02_create.png
  4. An initial style is provided:

    * { fill: lightgrey; }
    
  5. Click on the tab Map to preview.

    ../../../_images/polygon_04_preview.png

Stroke and Fill

The key property for polygon data is fill.

The fill property is used to provide the color, or pattern, used to draw the interior of a polygon.

  1. Replace the contents of polygon_example with the following fill example:

    * {
       fill: gray;
    }
    
  2. The Map tab can be used preview the change:

    ../../../_images/polygon_fill_1.png
  3. To draw the boundary of the polygon the stroke property is used:

    The stroke property is used to provide the color, or pattern, for the polygon boundary. It is effected by the same parameters (and vendor specific parameters) as used for LineStrings.

    * {
       fill: gray;
       stroke: black;
       stroke-width: 2;
    }
    

    Note

    Technically the boundary of a polygon is a specific case of a LineString where the first and last vertex are the same, forming a closed LinearRing.

  4. The effect of adding stroke is shown in the map preview:

    ../../../_images/polygon_fill_2.png
  5. An interesting technique when styling polygons in conjunction with background information is to control the fill opacity.

    The fill-opacity property is used to adjust transparency (provided as range from 0.0 to 1.0, or between 0% and 100%). Use of fill-opacity to render polygons works well in conjunction with a raster base map. This approach allows details of the base map to shine through.

    The stroke-opacity property is used in a similar fashion, as a range from 0.0 to 1.0 or 0% to 100%.

    * {
       fill: white;
       fill-opacity: 50%;
       stroke: light-gray;
       stroke-width: 0.25;
       stroke-opacity: 50%;
    }
    
  6. As shown in the map preview:

    ../../../_images/polygon_fill_3.png
  7. This effect can be better appreciated using a layer group,

    ../../../_images/polygon_fill_4.png

    where the transparent polygons are used to lighten the landscape provided by the base map.

    ../../../_images/polygon_fill_5.png

Pattern

In addition to color, the fill property can also be used to provide a pattern.

The fill pattern is defined by repeating one of the built-in symbols, or making use of an external image.

  1. We have two options for configuring a fill with a repeating graphic:

    Using url to reference to an external graphic. Used in conjunction with fill-mime property.

    Use of symbol to access a predefined shape. SLD provides several well-known shapes (circle, square, triangle, arrow, cross, star, and x). GeoServer provides additional shapes specifically for use as fill patterns.

    Update polygon_example with the following built-in symbol as a repeating fill pattern:

    * {
       fill: symbol(square);
    }
    
  2. The map preview (and legend) will show the result:

    ../../../_images/polygon_pattern_0.png
  3. Add a black stroke:

    * {
       fill: symbol(square);
       stroke: black;
    }
    
  4. To outline the individual shapes:

    ../../../_images/polygon_pattern_1.png
  5. Additional fill properties allow control over the orientation and size of the symbol.

    The fill-size property is used to adjust the size of the symbol prior to use.

    The fill-rotation property is used to adjust the orientation of the symbol.

    Adjust the size and rotation as shown:

    * {
       fill: symbol(square);
       fill-size: 22px;
       fill-rotation: 45;
       stroke: black;
    }
    
  6. The size of each symbol is increased, and each symbol rotated by 45 degrees.

    ../../../_images/polygon_pattern_2.png

    Note

    Does the above look correct? There is an open request :geot:`4642` to rotate the entire pattern, rather than each individual symbol.

  7. The size and rotation properties just affect the size and placement of the symbol, but do not alter the symbol’s design. In order to control the color we need to make use of a pseudo-selector. We have two options for referencing to our symbol above:

    :symbol provides styling for all the symbols in the CSS document.

    :fill provides styling for all the fill symbols in the CSS document.

  8. Replace the contents of polygon_example with the following:

    * {
       fill: symbol(square);
    }
    :fill {
       fill: green;
       stroke: darkgreen;
    }
    
  9. This change adjusts the appearance of our grid of squares.

    ../../../_images/polygon_pattern_3.png
  10. If you have more than one symbol:

    :nth-symbol(1) is used to specify which symbol in the document we wish to modify.

    :nth-fill(1) provides styling for the indicated fill symbol

    To rewrite our example to use this approach:

    * {
       fill: symbol(square);
    }
    :nth-fill(1) {
       fill: green;
       stroke: darkgreen;
    }
    
  11. Since we only have one fill in our CSS document the map preview looks identical.

    ../../../_images/polygon_pattern_3.png
  12. The well-known symbols are more suited for marking individual points. Now that we understand how a pattern can be controlled it is time to look at the patterns GeoServer provides.

    shape://horizline horizontal hatching
    shape://vertline vertical hatching
    shape://backslash right hatching pattern
    shape://slash left hatching pattern
    shape://plus vertical and horizontal hatching pattern
    shape://times cross hatch pattern

    Update the example to use shape://slash for a pattern of left hatching.

    * {
       fill: symbol('shape://slash');
       stroke: black;
    }
    :fill {
      stroke: gray;
    }
    
  13. This approach is well suited to printed output or low color devices.

    ../../../_images/polygon_pattern_4.png
  14. To control the size of the symbol produced use the fill-size property.

    * {
       fill: symbol('shape://slash');
       fill-size: 8;
       stroke: black;
    }
    :fill {
       stroke: green;
    }
    
  15. This results in a tighter pattern shown:

    ../../../_images/polygon_pattern_5.png
  16. Another approach (producing the same result is to use the size property on the appropriate pseudo-selector.

    * {
       fill: symbol('shape://slash');
       stroke: black;
    }
    :fill {
       stroke: green;
       size: 8;
    }
    
  17. This produces the same visual result:

    ../../../_images/polygon_pattern_5.png
  18. Multiple fills can be combined by supplying more than one fill as part of the same rule.

    Note the use of a comma to separate fill-size values. This was the same approach used when combining strokes.

    * {
       fill: #DDDDFF, symbol('shape://slash');
       fill-size: 0,8;
       stroke: black;
    }
    :fill {
       stroke: black;
       stroke-width: 0.5;
    }
    
  19. The resulting image has a solid fill, with a pattern drawn overtop.

    ../../../_images/polygon_pattern_6.png

Label

Labeling polygons follows the same approach used for LineStrings.

The key properties fill and label are used to enable Polygon label generation.

  1. By default labels are drawn starting at the centroid of each polygon.

  2. Try out label and fill together by replacing our polygon_example with the following:

    * {
      stroke: blue;
      fill: #7EB5D3;
      label: [name];
      font-fill: black;
    }
    
  3. Each label is drawn from the lower-left corner as shown in the Map preview.

    ../../../_images/polygon_label_0.png
  4. We can adjust how the label is drawn at the polygon centroid.

    The property label-anchor provides two numbers expressing how a label is aligned with respect to the centroid. The first value controls the horizontal alignment, while the second value controls the vertical alignment. Alignment is expressed between 0.0 and 1.0 as shown in the following table.

      Left Center Right
    Top 0.0 1.0 0.5 1.0 1.0 1.0
    Middle 0.0 0.5 0.5 0.5 1.0 0.5
    Bottom 0.0 0.0 0.5 0.0 1.0 0.0

    Adjusting the label-anchor is the recommended approach to positioning your labels.

  5. Using the label-anchor property we can center our labels with respect to geometry centroid.

    To align the center of our label we select 50% horizontally and 50% vertically, by filling in 0.5 and 0.5 below:

    * {  stroke: blue;
         fill: #7EB5D3;
         label: [name];
         font-fill: black;
         label-anchor: 0.5 0.5;
    }
    
  6. The labeling position remains at the polygon centroid. We adjust alignment by controlling which part of the label we are “snapping” into position.

    ../../../_images/polygon_label_1.png
  7. The property label-offset can be used to provide an initial displacement using and x and y offset.

  8. This offset is used to adjust the label position relative to the geometry centroid resulting in the starting label position.

    * {  stroke: blue;
         fill: #7EB5D3;
         label: [name];
         font-fill: black;
         label-offset: 0 7;
    }
    
  9. Confirm this result in the map preview.

    ../../../_images/polygon_label_2.png
  10. These two settings can be used together.

    The rendering engine starts by determining the label position generated from the geometry centroid and the label-offset displacement. The bounding box of the label is used with the label-anchor setting align the label to this location.

    Step 1: starting label position = centroid + displacement

    Step 2: snap the label anchor to the starting label position

  11. To move our labels down (allowing readers to focus on each shape) we can use displacement combined with followed by horizontal alignment.

    * {  stroke: blue;
         fill: #7EB5D3;
         label: [name];
         font-fill: black;
         label-anchor: 0.5 1;
         label-offset: 0 -7;
     }
    
  12. As shown in the map preview.

    ../../../_images/polygon_label_3.png

Legibility

When working with labels a map can become busy very quickly, and difficult to read.

  1. GeoServer provides extensive vendor parameters directly controlling the labeling process.

    Many of these parameters focus on controlling conflict resolution (when labels would otherwise overlap).

  2. Two common properties for controlling labeling are:

    -gt-label-max-displacement indicates the maximum distance GeoServer should displace a label during conflict resolution.

    -gt-label-auto-wrap allows any labels extending past the provided width will be wrapped into multiple lines.

  3. Using these together we can make a small improvement in our example:

    * {  stroke: blue;
         fill: #7EB5D3;
         label: [name];
         font-fill: black;
         label-anchor: 0.5 0.5;
    
         -gt-label-max-displacement: 40;
         -gt-label-auto-wrap: 70;
       }
    
  4. As shown in the following preview.

    ../../../_images/polygon_label_4.png
  5. Even with this improved spacing between labels, it is difficult to read the result against the complicated line work.

    Use of a halo to outline labels allows the text to stand out from an otherwise busy background. In this case we will make use of the fill color, to provide some space around our labels.

    * {  stroke: blue;
         fill: #7EB5D3;
         label: [name];
         label-anchor: 0.5 0.5;
         font-fill: black;
         font-family: "Arial";
         font-size: 14;
         halo-radius: 2;
         halo-color: #7EB5D3;
         halo-opacity:0.8;
    
         -gt-label-max-displacement: 40;
         -gt-label-auto-wrap: 70;
       }
    
  6. By making use of halo-opacity we we still allow stroke information to show through, but prevent the stroke information from making the text hard to read.

    ../../../_images/polygon_label_5.png
  7. And advanced technique for manually taking control of conflict resolution is the use of the -gt-label-priority.

    This property takes an expression which is used in the event of a conflict. The label with the highest priority “wins.”

  8. The Natural Earth dataset we are using includes a labelrank intended to control what labels are displayed based on zoom level.

    The values for labelrank go from 0 (for zoomed out) to 20 (for zoomed in). To use this value for -gt-label-priority we need to swap the values around so a scalerank of 1 is given the highest priority.

    * {  stroke: blue;
         fill: #7EB5D3;
         label: [name];
         label-anchor: 0.5 0.5;
         font-fill: black;
         font-family: "Arial";
         font-size: 14;
         halo-radius: 2;
         halo-color: #7EB5D3;
         halo-opacity:0.8;
    
         -gt-label-max-displacement: 40;
         -gt-label-auto-wrap: 70;
         -gt-label-priority: [20-labelrank];
       }
    
  9. In the following map East Flanders will take priority over Zeeland when the two labels overlap.

    ../../../_images/polygon_label_6.png

Theme

A thematic map (rather than focusing on representing the shape of the world) uses elements of style to illustrate differences in the data under study. This section is a little more advanced and we will take the time to look at the generated SLD file.

  1. We can use a site like ColorBrewer to explore the use of color theming for polygon symbology. In this approach the the fill color of the polygon is determined by the value of the attribute under study.

    ../../../_images/polygon_06_brewer.png

    This presentation of a dataset is known as “theming” by an attribute.

  2. For our ne:states_provinces_shp dataset, a mapcolor9 attribute has been provided for this purpose. Theming by mapcolor9 results in a map where neighbouring countries are visually distinct.

    Qualitative 9-class Set3
    #8dd3c7 #fb8072 #b3de69
    #ffffb3 #80b1d3 #fccde5
    #bebada #fdb462 #d9d9d9

    If you are unfamiliar with theming you may wish to visit http://colorbrewer2.org/js/ to learn more. The i icons provide an adequate background on theming approaches for qualitative, sequential and diverging datasets.

  3. The first approach we will take is to directly select content based on colormap, providing a color based on the 9-class Set3 palette above:

    [mapcolor9=1] {
       fill: #8dd3c7;
    }
    [mapcolor9=2] {
       fill: #ffffb3;
    }
    [mapcolor9=3] {
       fill: #bebada;
    }
    [mapcolor9=4] {
       fill: #fb8072;
    }
    [mapcolor9=5] {
       fill: #80b1d3;
    }
    [mapcolor9=6] {
       fill: #fdb462;
    }
    [mapcolor9=7] {
       fill: #b3de69;
    }
    [mapcolor9=8] {
       fill: #fccde5;
    }
    [mapcolor9=9] {
       fill: #d9d9d9;
    }
    * {
      stroke: gray;
      stroke-width: 0.5;
    }
    
  4. The Map tab can be used to preview this result.

    ../../../_images/polygon_09_selector_theme.png
  5. This CSS makes use of cascading to avoid repeating the stroke and stroke-width information multiple times.

    As an example the mapcolor9=2 rule, combined with the * rule results in the following collection of properties:

    [mapcolor9=2] {
      fill: #ffffb3;
      stroke: gray;
      stroke-width: 0.5;
    }
    
  6. Reviewing the generated SLD shows us this representation:

    <sld:Rule>
       <ogc:Filter>
          <ogc:PropertyIsEqualTo>
             <ogc:PropertyName>mapcolor9</ogc:PropertyName>
             <ogc:Literal>2</ogc:Literal>
          </ogc:PropertyIsEqualTo>
       </ogc:Filter>
       <sld:PolygonSymbolizer>
          <sld:Fill>
             <sld:CssParameter name="fill">#ffffb3</sld:CssParameter>
          </sld:Fill>
       </sld:PolygonSymbolizer>
       <sld:LineSymbolizer>
          <sld:Stroke>
             <sld:CssParameter name="stroke">#808080</sld:CssParameter>
             <sld:CssParameter name="stroke-width">0.5</sld:CssParameter>
          </sld:Stroke>
       </sld:LineSymbolizer>
    </sld:Rule>
    
  7. There are three important functions, defined by the Symbology Encoding specification, that are often easier to use for theming than using rules.

    • Recode: Used the theme qualitative data. Attribute values are directly mapped to styling property such as fill or stroke-width.
    • Categorize: Used the theme quantitative data. Categories are defined using min and max ranges, and values are sorted into the appropriate category.
    • Interpolate: Used to smoothly theme quantitative data by calculating a styling property based on an attribute value.

    Theming is an activity, producing a visual result allow map readers to learn more about how an attribute is distributed spatially. We are free to produce this visual in the most efficient way possible.

  8. Swap out mapcolor9 theme to use the Recode function:

    * {
      fill:[
        recode(mapcolor9,
          1,'#8dd3c7', 2,'#ffffb3', 3,'#bebada',
          4,'#fb8072', 5,'#80b1d3', 6,'#fdb462',
          7,'#b3de69', 8,'#fccde5', 9,'#d9d9d9')
      ];
      stroke: gray;
      stroke-width: 0.5;
    }
    
  9. The Map tab provides the same preview.

    ../../../_images/polygon_10_recode_theme.png
  10. The Generated SLD tab shows where things get interesting. Our generated style now consists of a single Rule:

    <sld:Rule>
       <sld:PolygonSymbolizer>
          <sld:Fill>
             <sld:CssParameter name="fill">
                <ogc:Function name="Recode">
                   <ogc:PropertyName>mapcolor9</ogc:PropertyName>
                   <ogc:Literal>1</ogc:Literal>
                      <ogc:Literal>#8dd3c7</ogc:Literal>
                   <ogc:Literal>2</ogc:Literal>
                      <ogc:Literal>#ffffb3</ogc:Literal>
                   <ogc:Literal>3</ogc:Literal>
                      <ogc:Literal>#bebada</ogc:Literal>
                   <ogc:Literal>4</ogc:Literal>
                      <ogc:Literal>#fb8072</ogc:Literal>
                   <ogc:Literal>5</ogc:Literal>
                      <ogc:Literal>#80b1d3</ogc:Literal>
                   <ogc:Literal>6</ogc:Literal>
                      <ogc:Literal>#fdb462</ogc:Literal>
                   <ogc:Literal>7</ogc:Literal>
                      <ogc:Literal>#b3de69</ogc:Literal>
                   <ogc:Literal>8</ogc:Literal>
                      <ogc:Literal>#fccde5</ogc:Literal>
                   <ogc:Literal>9</ogc:Literal>
                      <ogc:Literal>#d9d9d9</ogc:Literal>
             </ogc:Function>
             </sld:CssParameter>
          </sld:Fill>
       </sld:PolygonSymbolizer>
       <sld:LineSymbolizer>
          <sld:Stroke>
             <sld:CssParameter name="stroke">#808080</sld:CssParameter>
             <sld:CssParameter name="stroke-width">0.5</sld:CssParameter>
          </sld:Stroke>
       </sld:LineSymbolizer>
    </sld:Rule>
    

Additional Considerations

Note

This section will contain some extra information related to polygons. If you’re already feeling comfortable, feel free to move on to the next section.

Fixing the Gaps

  1. When we rendered our initial preview, without a stroke, thin white gaps (or slivers) are visible between our polygons.

    ../../../_images/polygon_04_preview.png

    This effect is made more pronounced by the rendering engine making use of the Java 2D sub-pixel accuracy. This technique is primarily used to prevent an aliased (stair-stepped) appearance on diagonal lines.

  2. Clients can turn this feature off using a GetMap format option:

    format_options=antialiasing=off;
    

    The LayerPreview provides access to this setting from the Open Layers Options Toolbar:

    ../../../_images/polygon_antialias.png

    To eliminate the slivers between polygons, we can use the following css:

    * {
      fill: lightgrey;
      stroke: lightgrey;
    }
    

Categorize

  1. The Categorize function can be used to generate property values based on quantitative information. Here is an example using Categorize to color states according to size.

    * {
       fill: [
          Categorize(Shape_Area,
             '#08519c', 0.5,
             '#3182bd', 1,
             '#6baed6', 5,
             '#9ecae1', 60,
             '#c6dbef', 80,
             '#eff3ff')
       ];
    }
    
    ../../../_images/polygon_area.png
  2. An exciting use of the GeoServer shape symbols is the theming by changing the fill-size used for pattern density.

  3. We can use the Categorize function to theme by datarank. The following css block will do it for us:

    * {
      fill: symbol('shape://slash');
      fill-size: [
         Categorize(datarank,
          4, 4,
          5, 6,
          8, 10,
         10)
      ];
      stroke: black;
    }
    :fill {
      stroke: darkgray;
    }
    

    The result is as follows:

    ../../../_images/polygon_categorize.png

Vendor Options

  1. In addition to the vendor parameter for max displacement you can experiment with different values for “goodness of fit”. These settings control how far GeoServer is willing to move a label to avoid conflict, and under what terms it simply gives up:

    -gt-label-fit-goodness: 0.3;
    -gt-label-max-displacement: 130;
    
  2. You can also experiment with turning off this facility completely:

    -gt-label-conflict-resolution: false;
    

Improved Halo

  1. The halo example used the fill color and opacity for a muted halo, while this improved readability it did not bring attention to our labels.

    A common design choice for emphasis is to outline the text in a contrasting color. One possibility is to use a white halo around black text.

    * {  stroke: gray;
         fill: #7EB5D3;
         label: [name];
         label-anchor: 0.5 0.5;
         font-fill: black;
         font-family: "Arial";
         font-size: 14;
         halo-radius: 1;
         halo-color: white;
       }
    

Multiple Attribute Theming

  1. A powerful tool is theming using multiple attributes. This is an important concept allowing map readers to perform “integration by eyeball” (detecting correlations between attribute values information).

    ../../../_images/polygon_multitheme.png

    We can create this map with the following css:

    * {
       fill: [
        recode(mapcolor9,
          1,'#8dd3c7', 2,'#ffffb3', 3,'#bebada',
          4,'#fb8072', 5,'#80b1d3', 6,'#fdb462',
          7,'#b3de69', 8,'#fccde5', 9,'#d9d9d9')
       ], symbol('shape://slash');
    
       fill-size: 0,[
          Categorize(datarank,
           6, 4,
           8, 6,
          10, 10,
          12)
       ];
       stroke: black;
    }
    :fill {
       stroke: black;
    }
    

Z-Order Stroke and Fill Order

  1. Earlier we looked at using z-index to simulate line string casing. The line work was drawn twice, once with a thick line, and then a second time with a thinner line. The resulting effect is similar to text halos - providing breathing space around complex line work allowing it to stand out.

    ../../../_images/polygon_zorder.png

    To reproduce this map is a tricky challenge. While it is easy enough to introduce z-index to control stroke what is not immediately obvious is that z-order also controls fill order. The following css will do the trick:

    * {
      fill: lightgray, symbol('shape://slash');
      fill-size: 8px;
      stroke: 0,0,lightgray, black;
      stroke-width: 0,0,6,1.5;
      z-index: 1,2,3,4;
    }
    :fill {
      stroke: black;
      stroke-width: 0.75;
    }