Article publication date: 11th June 2023.
When I was 19, I had my first solo adventure: a 1,001-mile (1,611-km) bike ride around Wales. I planned and navigated with Bartholomew maps, which my father had bought for 30 pence each in the early 1970s. Even though Wales doesn’t change fast, they were a bit out of date: some towns had grown, and a few bridges had vanished. But they had one major advantage: the topography was shown in colour, using filled contours. These hypsometric colours were pioneered by John Bartholomew Junior the 1800s. They made it easy for me to visualise the landscape and plan a route that was not too hilly.
Of course, filled contours are not always the best way to display the topography. Shading the map with range of colours can make it hard for other symbols to show up, so multi-purpose maps often just use contour lines, perhaps enhanced with shaded relief. However, for a single-purpose hiking or biking map, I find that coloured contours are the most helpful base map.
Unfortunately, QGIS does not handle vector filled contours by default. However, they can be approximated with a raster, or generated with a little bit of help from Python.
We’ll start by illustrating what can be done in QGIS, using a digital elevation model (DEM) of Katmai Volcano in Alaska, downloaded with the
SRTM-Downloader plugin. Entering a map extent covering the volcano will download two tiles, which can be merged into a single DEM using Raster > Miscellaneous > Merge
. I clip the raster to the area of interest using Raster > Extraction > Clip
to speed up the contour calculation.
We can generate contour lines with Raster > Extraction > Contour
, which calls an underlying function from the GDAL library. To keep the output simple, I used a 200-metre contour interval.
This is already quite close to a filled-contour map, except that the colours between the contours are not constant. This makes it harder to distinguish between the different contour levels. To approximate filled contours, we can apply a discrete colour ramp in the raster layer’s symbology. By choosing
Interpolation > Discrete
, Min: 0
, Max: 2000
, Mode: Equal interval
and Classes: 10
, the raster will appear as bands of colour which align with our contours.
The raster approximation to filled contours is probably good enough for most purposes. However, in some cases it is better to have true filled contours, represented by vector polygons. These will look better under high magnification, have useful geometric properties, and in some cases lead to smaller file sizes.
The solution is to convert the contour lines into polygons, using Vector > Geometry tools >Lines to polygons
. However, the resulting polygons (shown here in orange) are usually incorrect. The major problem is that the algorithm does not know how to close the contour lines when they end at the edge of the raster. We’ll now see how to deal with this using a bit of Python code.
The solution is to ‘dig a moat’ around the edge of our digital elevation model, so that all of the contours are closed. We taper the outer part of the raster down to some arbitrary low value. I wrote a Python function to do this called
taper_geotiff()
, which is part of my GitHub repository geotiff_helper
. You must have the module osgeo
installed. Although the concept is not very complex, care is required to ensure that there is a smooth transition down to the edge. The terminal command looks like
python3 taper_geotiff.py raster.tif raster_tapered.tif 50 500
where 50 is the width of the taper zone (in pixels), and 500 is the depth of the taper (in the same units as the raster).
We then use the same steps as before to generate contour lines and turn them into polygons (Raster > Extraction > Contour
, Vector > Geometry tools > Lines to polygons
). Since the polygons will be opaque, you need to make sure that the lower contours are rendered first, by going to Properties > Symbology > Layer rendering > Control feature rendering order
and selecting Ascending
sorting of variable ELEV
. You can then style the polygons as you would any QGIS polygon. The easiest way to apply a colour ramp is to set Properties > Symbology > Categorized
. Choose a colour ramp and click Classify
. You may want to delete any lower contours that are not visible, as well as the all other values
style, to make full use of the range of colours (reset the colour ramp to ensure this). The result should be beautiful vector polygons representing filled contours.
For some unusual landscapes, such as volcanic craters, some of the contour lines have holes (enclosed regions of lower elevation). Holes can also be created by the tapering procedure described above. When contours have holes, the filled contours will not render properly, as the inner contour line is displayed simply as another filled polygon.
This problem can also be fixed in Python, by detecting the holes (contour lines completely enclosed by other contour lines of the same height) and ‘subtracting’ the inner contour from the outer one. The algorithm uses the areas of the polygons, so it is best to put the contours into a projected coordinate system (if they are not already) using Vector > Data Management Tools > Reproject Layer
. For Katmai Volcano, UTM Zone 5N is a suitable projection.
The function to do this, find_contour_holes()
can be found in my GitHub repository contour_helper
, and used as follows:
python3 find_contour_holes.py contours_filled.gpkg contours_filled_no_holes.gpkg
If you tapered the raster, don’t forget to clip this region of the contours. You can do this by creating a new layer with a rectangle feature and applying
Vector > Geoprocessing tools > Clip
.
Controlling the symbology of the polygons from the QGIS menus can be tedious and restrictive. I sometimes use Python scripts to achieve a more automatic or fine-tuned result. For example, I like to adjust the brightness of contour lines (polygon outlines) to be similar to the brightness of the adjacent contour colours. This leads to contours that are always visible but never too heavy.
If you’d like to apply this kind of styling to your own maps, or see how it works, you can download and modify the Python scripts from my GitHub repository qgis_style
. Here I used the function make_style_files()
with the following input file:task make_filled_contours
z_min 0.0
d_z 200.0
n_bins 10
colour_ramp_type pyplot_linear
pyplot_ramp_name magma
outline_contrast 0.15
outline_style solid
outline_width 2.0
outline_width_unit Pixel
dir_out styles/
qgis_version 3.22.9-Białowieża
style_file_name filled_contours
In this article, I’ve shown how to make a hypsometric map illustrating the topography with discrete colour-shaded contours, either as a raster layer with vector contour lines, or as a vector layer of filled polygons. This approach is not limited to topography: any raster that can be processed with Raster > Extraction > Contour
could be treated in the same way, for example a map of surface temperature.
In future, as I learn more about the inner workings of QGIS, I hope to add the filled contour functionality to the software, and extend it to global rasters. Please feel free to contribute to these goals!