Home -> Docs -> Styling

Styling guide

How to colour your maps.

TerraServe styles in three places, depending on how the map is delivered. Pick the surface that matches your data and output, drop the style file in, and point the matching flag at it. Every format here is a real, working example.

The three surfaces

Which style you write depends on what the layer is and how it's delivered:

Your layer Delivered as Flag Style format
Raster (--cog) WMS picture / tiles --style A small JSON: RGB or a colour ramp
Vector (--vector) WMS picture (drawn on the server) --vec-style SLD, or a simple marker/label JSON
Vector (--vector) Vector tiles (drawn in the browser) --mvt-style A MapLibre GL style

A vector layer served both ways (like the land-cover demo) carries two styles: one --vec-style for the picture and one --mvt-style for the tiles. Keep their colours in sync (see the last section).

Raster: RGB image

For an ordinary colour photo (an orthophoto, a true-colour satellite image): map three bands to red, green, blue.

{
  "mode": "rgb",
  "bands": [1, 2, 3],     // which bands become R, G, B
  "alpha": "mask"         // transparent where the image has no data
}

Use it with --style rgb.json.

Raster: pseudocolor ramp

For a single-value raster (elevation, temperature, NDVI): map values to colours with a list of stops. Each stop is [value, red, green, blue, alpha]; TerraServe blends smoothly between them. This NDVI ramp runs brown -> yellow -> green:

{
  "mode": "pseudocolor",
  "band": 1,
  "nodata_transparent": true,
  "stops": [
    [-0.2, 165,   0,  38, 255],   // bare / water
    [ 0.0, 215,  48,  39, 255],
    [ 0.2, 254, 224, 139, 255],
    [ 0.4, 166, 217, 106, 255],
    [ 0.6,  26, 152,  80, 255],
    [ 0.8,   0, 104,  55, 255]    // lush vegetation
  ]
}

Use it with --style ndvi.json. This is what colours the live-NDVI demo, and because it's just data, you can retune the ramp without touching the raster.

Vector picture: SLD (the standard)

SLD (Styled Layer Descriptor) is the OGC standard for styling maps, and the right choice for thematic data: colour-by-class, with per-class rules. TerraServe reads SLD 1.0. Each <Rule> is a filter (which features it matches) plus a symbolizer (how to draw them). One rule from the land-cover style:

<Rule>
  <Name>1.1.1.1</Name>
  <Title>Continuous urban fabric</Title>
  <ogc:Filter><ogc:PropertyIsEqualTo>
    <ogc:PropertyName>COS23_n4_C</ogc:PropertyName>
    <ogc:Literal>1.1.1.1</ogc:Literal>
  </ogc:PropertyIsEqualTo></ogc:Filter>
  <PolygonSymbolizer><Fill>
    <CssParameter name="fill">#e6004d</CssParameter>
  </Fill></PolygonSymbolizer>
</Rule>

Repeat one rule per class. Supported symbolizers: PolygonSymbolizer (fill + stroke, incl. road casing), LineSymbolizer, PointSymbolizer (marker), and TextSymbolizer (labels, with priority and placement). Use it with --vec-style cos2023.sld.

Why SLD? It's portable: the same file styles the layer in QGIS, GeoServer and MapServer too. If your data is already published with an SLD (as most official datasets are), TerraServe can often use it as-is.

Vector picture: marker & label JSON

When you just need points with labels (airports, place names) and don't want the ceremony of XML, use the small marker/label JSON instead:

{
  "mode": "vector",
  "point": {
    "radius": 3.0,
    "fill":   [30, 30, 30, 255],
    "stroke": [255, 255, 255, 255],
    "stroke_width": 1.0
  },
  "text": {
    "label": "name",          // which attribute to show
    "priority": "scalerank",  // which labels win when they collide
    "size": 16.0,
    "color": [20, 20, 20, 255],
    "halo": { "color": [255, 255, 255, 230], "radius": 2.0 },
    "offset": 4.0
  }
}

Use it with --vec-style airports.vec.json. Colours are [R, G, B, A], 0 to 255.

Vector tiles: MapLibre GL style

Vector tiles are drawn in the browser, so they're styled with a MapLibre GL style, the same format Mapbox / MapLibre maps use. TerraServe serves it at /mvt/{layer}/style.json and fills in the boilerplate (version, sources) for you; you provide the layers. Colour-by-class uses a match expression on an attribute:

{
  "layers": [
    {
      "id": "cos-fill",
      "type": "fill",
      "filter": ["==", "$type", "Polygon"],
      "paint": {
        "fill-color": [
          "match", ["get", "COS23_n4_C"],
          "1.1.1.1", "#e6004d",
          "2.1.1.1", "#ffffa8",
          "3.1.1.1", "#4dff00",
          /* … one pair per class … */
          "#cccccc"          // fallback colour
        ]
      }
    }
  ],
  "metadata": { "legend": [ /* see below */ ] }
}

Use it with --mvt-style cos2023.mvt-style.json. A client (or QGIS' "Vector Tiles -> Style URL") loads it straight from /mvt/{layer}/style.json.

The legend

Add a metadata.legend array to the MapLibre style and the built-in X-ray viewer (and any legend-aware client) will show a swatch list. Each entry is a colour and a label:

"metadata": {
  "legend": [
    { "color": "#e6004d", "label": "Artificial surfaces" },
    { "color": "#ffffa8", "label": "Agriculture" },
    { "color": "#4dff00", "label": "Forest" }
  ]
}

Example swatches: Artificial   Agriculture   Forest. If you omit the legend, TerraServe falls back to the colours it can read from the style's match expression.

Keeping styles in sync

When a layer is served both as a picture and as tiles, it has an SLD and a MapLibre style: two files describing the same colours. If they drift, the WMS view and the tile view of the same map disagree.

Rule of thumb. Treat the class-to-colour mapping as the single source of truth and generate both files from it, or keep the class codes and hex values identical between the SLD's <CssParameter name="fill"> and the MapLibre match pairs. The land-cover demo does exactly this. Its two files are kept in lockstep, class for class.

Back to the docs -> See styled examples ->