This API cuts out tiles (e.g., for Leaflet) from
Cloud Optimized GeoTIFFs (COG) or other datasets using
GDAL. It is used on https://tiles.codefor.de
.
It is meant to replace TileStache (which seems unmaintained), but only
it's TileStache.Goodies.Providers.GDAL:Provider
. A similar workflow could be implemented using
TiTiler or Terracotta, but both
tools have a different focus, and a lot of additional features.
The core functionality of the API is provided by a call to
gdal.Warp
(which is apearantly faster than gdal.ReprojectImage
used by TileStache):
gdal.Warp(
'',
dataset_path,
format='MEM',
dstSRS="EPSG:4326",
outputBounds=(west, south, east, north),
width=width,
height=height,
resampleAlg='cubic'
)
For optimal performance, the input dataset should be a valid COG or a VRT composed of COG. Such datasets can be created with GDAL, e.g.:
gdal_translate INPUT OUTPUT \
-of COG -b 1 -b 2 -b 3 -a_srs EPSG:25833 \
-co COMPRESS=JPEG -co QUALITY=75 -co BLOCKSIZE=512 \
-co OVERVIEWS=IGNORE_EXISTING -co NUM_THREADS=4 \
Like TiTiler, GDAL-Tiles-API is build using FastAPI.
Instal GDAL, e.g. for Debian 12:
sudo apt-get install gdal-bin libgdal-dev
Install the matching GDAL python bindings (in a virtual environment):
pip install GDAL==3.6.2
Install the package
pip install gdal-tiles-api
pip install git+https://github.com/codeforberlin/gdal-tiles-api # directly from GitHub
The API expects a config.toml
in the current directory and/or a set of .toml
files in a config.d
directory
(which are all merged into one config object).
The config file(s) look like this:
[tiles]
host = 'https://tiles.codefor.de'
path = "/data"
[[maps]]
name = "Digitale farbige TrueOrthophotos 2024 (DOP20RGBI)"
path = "berlin/geoportal/luftbilder/2024-dop20rgbi/tiles.vrt"
attribution.text = "Senatsverwaltung für Stadtentwicklung, Bauen und Wohnen Berlin / Digitale farbige Orthophotos 2024 (DOP20RGBI)"
attribution.href = "https://gdi.berlin.de/geonetwork/srv/ger/catalog.search#/metadata/aff8a8a5-2b48-44e8-949b-ea5f7d382a4f"
[[maps]]
path = "berlin/geoportal/luftbilder/2023-dop20rgbi"
[[maps]]
path = "berlin/geoportal/historische-karten/1750"
dataset = "cog/Berlin_1750_2.tif"
The actual path to the COG of the map build from the tiles path, the map path, and the dataset
(which defaults to tiles.vrt
). For the config above, we get:
/data/berlin/geoportal/luftbilder/2024-dop20rgbi/tiles.vrt
/data/berlin/geoportal/luftbilder/2023-dop20rgbi/tiles.vrt
/data/berlin/geoportal/historische-karten/1750/cog/Berlin_1750_2.tif
The other entries are only displayed in the root url.
The local developtment server can be started using:
fastapi dev gdal_tiles_api/main.py
The API is then available at http://localhost:8000.
In production, gunicorn with the uvicorn should be used. In order
to run with Systemd, the a service similar to the following should be placed in /etc/systemd/system/tiles.service
:
[Unit]
Description=GDAL-Tiles-API gunicorn daemon
After=network.target
[Service]
User=tiles
Group=tiles
WorkingDirectory=/srv/tiles/tiles-config
LogsDirectory=gunicorn
RuntimeDirectory=gunicorn
Environment="PATH=/srv/tiles/tiles-config/env/"
Environment="PYTHONUNBUFFERED=1"
ExecStart=gunicorn
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--timeout 5 \
--bind unix:/run/gunicorn/tiles.sock \
--pid /run/gunicorn/tiles.pid \
--access-logfile /var/log/gunicorn/access.log \
--error-logfile /var/log/gunicorn/error.log \
gdal_tiles_api.main:app
ExecReload=/bin/sh -c '/usr/bin/pkill -HUP -F ${GUNICORN_PID_FILE}'
ExecStop=/bin/sh -c '/usr/bin/pkill -TERM -F ${GUNICORN_PID_FILE}'
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
The gunicorn
should be reverse proxied using NGINX, with the create tiles cached by the
build in Content Caching. The following
configuration can be used for this (in /etc/nginx/sites-enabled/default
):
proxy_cache_path /tmp/nginx_cache keys_zone=tiles:10m max_size=10g use_temp_path=off;
proxy_connect_timeout 5;
proxy_send_timeout 5;
proxy_read_timeout 5;
send_timeout 5;
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html;
server_name _;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://unix:/run/gunicorn/tiles.sock;
proxy_cache tiles;
proxy_cache_valid 200 302 404 7d;
proxy_cache_use_stale error timeout updating;
add_header X-Cache-Status $upstream_cache_status;
}
}