Digital elevation models (DEM) represent the shape of the terrain. One of the most widely used representations in cartography is the hillshade, which simulates the shadows cast by the relief to give a sense of volume.
In this article, we will see how to generate a global or regional hillshade from SRTM data, then convert it into WebP raster tiles that a web map server can serve.
The method is inspired by the following project:
https://github.com/nst-guide/terrain
Today, much of the world topography used in web applications is generated from SRTM30 data. The same elevation data is also used to generate contour lines.
1. Downloading the topographic data
Terrain data mainly comes from:
-
SRTM (Shuttle Radar Topography Mission)
-
EU-DEM for Europe
In this guide, we will use SRTM GL1 (30 m resolution).
The data used to be available directly from NASA, but that service is no longer reachable. It is now available through OpenTopography.
Metadata:
https://portal.opentopography.org/datasetMetadata
To download a specific area only:
Available resolutions
-
SRTM GL3: 90 m resolution
-
SRTM GL1: 30 m resolution (about 126 GB in total)
The data is stored on OpenTopography’s S3-compatible storage.
Installing the AWS CLI
To download the data from the S3 bucket:
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
Then go to the destination directory and run:
aws s3 cp s3://raster/SRTM_GL1/ . --recursive --endpoint-url https://opentopography.s3.sdsc.edu --no-sign-request
This command downloads:
-
all the SRTM TIF files
-
the global VRT file
The VRT file uses -32768 for areas without data (NoData).
Plan your own route
MapPlanner helps you calculate distances, elevation, duration and plan your hikes, runs and bike rides on an interactive map.
- Automatic distance & elevation calculation
- Topographic, satellite maps
- GPX/KML export for your GPS
- Real-time GPS tracking
2. Installing the required tools
SRTM data comes as GeoTIFF.
To use it in a web map application, you need to:
-
reproject it
-
turn it into raster tiles
-
encode them as WebP
Several tools are needed.
Installing GDAL
GDAL is the reference library to handle raster and vector data.
sudo apt-get install gdal-bin
Installing rio-rgbify
rio-rgbify (developed by Mapbox) converts elevation data into raster tiles that map rendering engines can use.
git clone https://github.com/mapbox/rio-rgbify.git
cd rio-rgbify
One file needs a fix:
nano ./rio_rgbify/mbtiler.py
Replace the line that calls transform_bounds with:
w, s, e, n = transform_bounds(*[src_crs, "epsg:4326"] + bbox, densify_pts=2)
Then install the package globally.
sudo apt install python3-pip python3-setuptools
sudo python3 setup.py install
3. Building a global VRT
When the data is split across many GeoTIFF files, it is convenient to create a VRT (Virtual Raster).
A VRT acts as a virtual mosaic of all the raster files.
gdalbuildvrt -vrtnodata -32768 SRTM_GL1_srtm.vrt SRTM_GL1_srtm/*.tif
Here:
-
-vrtnodata -32768sets the NoData value -
all the .tif files are combined into a single virtual raster
4. Reprojecting the data
For web use (with Mapbox GL or MapLibre in particular), the data must be projected to EPSG:3857 (Web Mercator).
SRTM data is in WGS84 (EPSG:4326):
gdalwarp -of VRT -r cubicspline -s_srs EPSG:4326 -t_srs EPSG:3857 -dstnodata 0 SRTM_GL1_srtm.vrt SRTM_GL1_srtm_3857.vrt
Explanation:
-
-of VRT: the output stays a virtual raster (no data is copied) -
-r cubicspline: interpolation for a smoother result -
-s_srs: source coordinate system -
-t_srs: target projection -
-dstnodata 0: value for empty areas
5. Generating the MBTiles raster tiles
The data must now be turned into pyramidal raster tiles.
rio rgbify generates an MBTiles file holding the tiles directly.
rio rgbify -b -10000 -i 0.1 --min-z 6 --max-z 12 -j 22 --format webp SRTM_GL1_srtm_3857.vrt data_hillshade_12.mbtiles
Main parameters:
-
--min-z 6: minimum zoom -
--max-z 12: maximum zoom -
-j 22: parallelism -
--format webp: tile compression -
-band-i: elevation encoding parameters
6. Generating by region
To avoid overly heavy runs, the data can be generated region by region.
Each region produces an MBTiles file, and all the files can then be merged with tile-join (shipped with tippecanoe):
tile-join -pk -o europe.mbtiles *.mbtiles
Option:
-
-pk: stops the tool from dropping tiles larger than 500 kB
7. Performance and required resources
Generation time
-
North America:
-
40 GB of RAM
-
15 h of generation
-
11 processes
-
-
Whole world:
-
37 h
-
12 processes
-
up to 200 GB of RAM
-
Disk space
For a global run:
-
final MBTiles: 182 GB
-
unpacked WebP: 208 GB
-
number of tiles: 8,940,823
8. Tileserver limits
A global MBTiles file is too large for tileserver to serve efficiently.
The solution is to extract the raster tiles into a file tree.
9. Unpacking the MBTiles into WebP tiles
Install mb-util.
git clone https://github.com/mapbox/mbutil.git
cd mbutil
sudo python3 setup.py install
Unpack the tiles:
mb-util data_hillshade_12.mbtiles tiles --image_format=webp
Resulting structure:
tiles/{z}/{x}/{y}.webp
10. Deploying the tiles to the server
Copy the tiles into:
/home/www/hillshade/data
Then create a TileJSON file.
nano /home/www/hillshade/data/data_hillshade_12.json
Content:
{
"tiles": ["https://hillshade.example.com/data/{z}/{x}/{y}.webp"],
"name": "SRTM Hillshade 30m WEBP",
"format": "webp",
"basename": "data_hillshade_12",
"id": "data_hillshade_12",
"description": "SRTM Hillshade 30m WEBP",
"version": "1",
"type": "baselayer",
"minzoom": 6,
"maxzoom": 12,
"center": [2.8125, 46.779279304705625, 9],
"attribution": "SRTM",
"tilejson": "2.0.0"
}
This file describes:
-
the tile URL
-
the zoom levels
-
the metadata
11. Configuring the web server (Caddy)
Example Caddy configuration:
sudo nano /etc/caddy/Caddyfile
Configuration:
hillshade.example.com {
root * /home/www/hillshade
file_server
header {
Access-Control-Allow-Origin "*"
Access-Control-Allow-Methods "POST, GET, OPTIONS, PUT, DELETE"
Access-Control-Allow-Headers "*"
X-Content-Type-Options nosniff
X-XSS-Protection "1; mode=block;"
X-Robots-Tag none
X-Frame-Options SAMEORIGIN
Cache-Control "public, max-age=5184000"
}
@blocked {
path *.mbtiles
}
respond @blocked 403
# encode gzip
log {
output file /var/log/caddy/maps_access.log
}
}
This configuration:
-
serves the static tiles
-
enables browser caching
-
blocks direct access to .mbtiles files
12. Adding the source in Maputnik to check
In Maputnik, add a source of type:
Raster DEM
with the TileJSON URL, served by the domain declared in the Caddyfile:
https://hillshade.example.com/data/data_hillshade_12.json
The tiles can then be used for:
-
dynamic hillshading
-
relief rendering
-
terrain analysis