Skip to main content

Web Viewer Raster Symbology

We decided to simplify the work necessary to bring our QML files from QRave into the web so we can have symbolized rasters in WebRAVE.

The raster .txt format we are using is based on the r.colors spec and we use it when we run gdalDem to "bake-in" the symbology. The spec for this file is compatible with both r.colors and ESRI HDR color table files (.clr).

The right approach depends on which QGIS renderer your layer uses — read on to find yours.


File placement

The filename must follow the pattern somename.txt, where somename matches the symbology attribute in your business logic XML — the same naming convention used for .qml files.

For example, given this business logic entry:

<Node label="Detrended DEM (HAND - Height Above Nearest Drainage)" xpath="Raster[@id='HAND']" type="raster" symbology="hand" transparency="40" />

the file should be named hand.txt.

Case sensitivity

The .txt extension must always be lowercase, and the filename must match the business logic XML exactly — including capitalization.

These files should live alongside the .json files for the WebRAVE vectors:

# Shared across all projects:
RiverscapesXML/Symbology/web/Shared/whatever.txt

# Project-specific:
RiverscapesXML/Symbology/web/ProjectTypeName/whatever.txt

The .txt file format

Every .txt color ramp file has the same basic structure: a header line declaring the interpolation mode, followed by one comma-separated row per color stop.

# QGIS Generated Color Map Export File
INTERPOLATION:INTERPOLATED
1259.34,255,235,176,255,My Label

The columns are:

ValueRedGreenBlueAlphaLabel
FLOATInt (0–255)Int (0–255)Int (0–255)Int (0–255)TEXT

The label column is always treated as text, so you can use descriptive names like Open Water or numeric stand-ins like 1259 — your choice.


Understanding the interpolation modes

The INTERPOLATION: header controls how QGIS (and subsequently gdaldem) fills in colors between your defined stops. There are three modes:

QGIS Renderer (UI name).txt headerBehaviorBest for
Singleband pseudocolorINTERPOLATION:INTERPOLATEDSmooth linear gradient between stopsContinuous data (DEMs, temperature, precipitation)
Paletted / Unique valuesINTERPOLATION:DISCRETEStepped color blocks, no blendingGrouped ranges (elevation bands, risk levels, statistical bins)
Paletted / Unique values (exact)INTERPOLATION:EXACTStrict per-value matching; everything else is NoDataCategorical data (land-cover codes, classified integers)

1. INTERPOLATION:INTERPOLATED (Linear / Smooth)

QGIS creates a smooth gradient between your color stops, linearly blending colors for any value that falls between them.

  • How it works: If you set 0 to Blue and 100 to Red, a pixel with value 50 will render as Purple — a perfect 50/50 blend.
  • Best used for: Continuous data where smooth transitions represent reality, such as Digital Elevation Models (DEMs), temperature maps, or precipitation data.

2. INTERPOLATION:DISCRETE (Stepped / Banded)

QGIS creates color bands or blocks. The color at each stop applies to the entire range of values up to the next stop, with no blending between them — producing sharp, distinct boundaries.

  • How it works: If you assign 0 to Blue, 50 to White, and 100 to Red, all pixels from 0 up to 49.99 will be solid Blue. At exactly 50 the color snaps to solid White, and stays White until it reaches 100.
  • Best used for: Grouping continuous data into classified ranges — contour-style elevation bands, heat maps grouped by risk level (Low/Medium/High), or statistical bins.

3. INTERPOLATION:EXACT (Strict Matching)

There is no blending and no grouping. A color is applied only if a pixel's value matches a defined stop exactly; everything else renders as transparent (NoData).

  • How it works: If you assign 1 to Green and 2 to Yellow, only pixels that are exactly 1.000 or 2.000 get those colors. A pixel with value 1.5 will be transparent.
  • Best used for: Categorical or classified data where in-between values are meaningless — for example, a land-cover map where 1 = Forest, 2 = Water, 3 = Urban.

Exporting from QGIS

Singleband pseudocolor → INTERPOLATION:INTERPOLATED

Use this for continuous, smoothly varying data like DEMs or temperature surfaces.

  1. Load your raster layer in QGIS using QViewer (assuming you already have a .qml for this layer).
  2. Open the Layer Styling panel, confirm the renderer is set to Singleband pseudocolor.
  3. Click the small save icon to export the color map.

Name the exported file according to the file placement rules above and commit it to git.

Example: dem.txt

# QGIS Generated Color Map Export File
INTERPOLATION:INTERPOLATED
1129.3089599609375,255,235,176,255,1129
1482.00189819335946595,38,115,0,255,1482
1834.6948364257814319,115,77,0,255,1835
2198.075439453125,255,255,255,255,2198

The label column can hold descriptive text instead of numbers — Small, Medium, Large works just as well as the raw float values.

Note that the exact float values exported by QGIS are specific to the raster you were symbolizing. When applied to other rasters, these values are converted to percentages at tile-time so the color ramp stretches correctly across the new data range.

Details
Under the hood: tile-time conversion

When the tiling pipeline processes an INTERPOLATED file, it converts the absolute values to percentages and produces an internal representation like this:

{
"gdalDem": "0%,255,235,176,255\n33%,38,115,0,255\n66%,115,77,0,255\n100%,255,255,255,255\nnv,0,0,0,0",
"legend": [
["rgba(255,235,176,255)", "0%"],
["rgba(38,115,0,255)", "33%"],
["rgba(115,77,0,255)", "66%"],
["rgba(255,255,255,255)", "100%"]
],
"rampType": "INTERPOLATED"
}

The legend array drives the legend displayed in the web viewer. The gdalDem string is written to a temp file and passed directly to the gdaldem command:

0%,255,235,176,255
33%,38,115,0,255
66%,115,77,0,255
100%,255,255,255,255
nv,0,0,0,0

nv is the NoData value — it is set automatically to ensure transparent rendering of NoData pixels.


Paletted / Unique values → INTERPOLATION:DISCRETE or INTERPOLATION:EXACT

Use this for classified or categorical rasters where pixel values represent discrete categories rather than a continuous range.

Exporting from QGIS:

  1. Load your raster layer in QGIS using QViewer (assuming you already have a .qml for this layer).
  2. Next to the Delete All button, click the small menu button and choose Export Color Map to File.

This produces a space-separated .clr file:

1 31 213 223 255 Small
3 98 218 162 255 Medium
4 62 212 79 255 Large
5 217 210 69 255 Huge
6 230 87 51 255 Scientifically Ginormous

You will need to manually convert this to the comma-separated .txt format and choose the appropriate INTERPOLATION: header for your data — see the two options below.

Using INTERPOLATION:DISCRETE

Use this when your categories represent ranges or groups and you want all values between two stops to inherit the lower stop's color.

# QGIS Generated Color Map Export File
INTERPOLATION:DISCRETE
1,31,213,223,255,Small
3,98,218,162,255,Medium
4,62,212,79,255,Large
5,217,210,69,255,Huge
6,230,87,51,255,Scientifically Ginormous

Using INTERPOLATION:EXACT

Use this when your raster uses strict integer codes where in-between values are meaningless (e.g. a land-cover classification). Only pixels whose values match a table entry exactly will be colored; everything else renders as transparent (NoData).

# QGIS Generated Color Map Export File
INTERPOLATION:EXACT
-9999,255,255,255,255,(-9999) No Data
0,0,0,0,255,None
11,0,92,230,255,Open Water
12,255,255,255,255,Perennial Ice/Snow
31,0,0,0,255,Barren-Rock/Sand/Clay
381,255,115,223,255,Inter-Mountain Basins Big Sagebrush Steppe
383,255,255,190,255,Inter-Mountain Basins Sparsely Vegetated Systems
384,255,255,190,255,Rocky Mountain Alpine/Montane Sparsely Vegetated Systems
385,255,255,190,255,North American Warm Desert Sparsely Vegetated Systems
386,205,205,102,255,Western Great Plains Shortgrass Prairie
387,137,205,102,255,Western Great Plains Foothill and Piedmont Grassland
388,132,140,200,255,Wyoming Basins Dwarf Sagebrush Shrubland and Steppe

In this example, a value of 382 has no entry in the table — it won't render at all. This is the key difference from DISCRETE, where 382 would inherit the color of the nearest lower stop (381).