Nachdem wir grundsätzlich gesehen haben, wie man mit Geodaten arbeitet, geht es nun darum, sich einen eigenen Kartendienst aufzubauen.
Renderd läuft als Daemon, um die OpenStreetMap-Kacheln aus der PostgreSQL-Datenbank zu erzeugen. mod_tile ist ein Apache-Modul, das die Kacheln an den Webbrowser ausliefert. Bei Ubuntu sollten beide aus den Standardpaketquellen installiert werden können. Beispieldateien finden sich im Unterverzeichnis von utils/example-map von mod_tile.
$ sudo apt install mapnik-utils libapache2-mod-tile apache2`
Hierzu die Beispieldateien kopieren:
$ sudo mkdir -p /usr/share/renderd
$ sudo cp -av utils/example-map /usr/share/renderd/
$ sudo cp -av etc/apache2/renderd-example-map.conf /etc/apache2/sites-available/renderd-example-map.conf
Um herauszufinden, wo mapnik auf dem System seine Plugins hat, folgendes Kommando aufrufen:
$ mapnik-plugin-base
/usr/lib/x86_64-linux-gnu/mapnik/4.0.1/input
Und die Konfiguration in /etc/renderd.conf
ergänzen / anpassen (z.B. die anzahl der Renderthreads in num_threads):
[renderd]
pid_file=/run/renderd/renderd.pid
stats_file=/run/renderd/renderd.stats
socketname=/run/renderd/renderd.sock
num_threads=5
tile_dir=/var/cache/renderd/tiles
[mapnik]
plugins_dir=/usr/lib/x86_64-linux-gnu/mapnik/4.0.1/input
font_dir=/usr/share/fonts/truetype
font_dir_recurse=true
[example-map]
URI=/tiles/renderd-example
XML=/usr/share/renderd/example-map/mapnik.xml
[example-map-jpg]
TYPE=jpg image/jpeg jpeg
URI=/tiles/renderd-example-jpg
XML=/usr/share/renderd/example-map/mapnik.xml
[example-map-png256]
TYPE=png image/png png256
URI=/tiles/renderd-example-png256
XML=/usr/share/renderd/example-map/mapnik.xml
[example-map-png32]
TYPE=png image/png png32
URI=/tiles/renderd-example-png32
XML=/usr/share/renderd/example-map/mapnik.xml
[example-map-webp]
TYPE=webp image/webp webp
URI=/tiles/renderd-example-webp
XML=/usr/share/renderd/example-map/mapnik.xml
Dann die Konfiguration im Apache aktivieren und apache und renderd neustarten
$ sudo a2enmod tile
$ sudo a2ensite renderd-example-map
$ sudo service apache2 restart
$ sudo service renderd restart
Mit journalctl -eu renderd
kann geprüft werden, ob alles korrekt geladen wurde oder ggf. noch zu behebende Fehler bestehen.
Anschließend kann die Beispielkarte im Browser angeschaut werden: http://localhost/renderd-example-map
Als nächstes wird die Beispielkonfiguration so angepasst, dass die eigenen OpenStreetMap-Daten aus der PostgreSQL-Datenbank genutzt werden
Dazu brauchen wir, um den OpenStreetMap-Kartenstil zu erhalten, zuerst openstreetmap-carto - hier darauf achten, nicht den master herunterzuladen, sondern das jeweils neueste Release (sonst fehlen die korrekten lua uns style Dateien). Da wir hierfür die Daten etwas anders laden müssen, die Datenbank erstmal wieder leeren (beim der ersten pbf-Datei das -a weglassen, dann wird automatisch gelöscht, bei den folgenden dann wieder -a hinzufügen. Prozessanzahl und Cache an die individuellen Verhältnisse anpassen).
osm2pgsql -W -C 10240 -U osm -s -H localhost -d gisdb -G -K --hstore-add-index --hstore --multi-geometry --number-processes 10 --flat-nodes=flatnode/flatnodes --tag-transform-script openstreetmap-carto-5.9.0/openstreetmap-carto.lua --style openstreetmap-carto-5.9.0/openstreetmap-carto.style europe-latest.osm.pbf
[...]
2025-04-25 11:14:49 [00] osm2pgsql took 59509s (16h 31m 49s) overall.
Bei großen Importen (wie europe) ist es nützlich, den --flat-nodes Parameter zu nutzen. Wenn man mehrere pbf-Dateien per skript (for-Schleife) einlesen will, dann mittels export PGPASSWORD=strenggeheim
das Passwort setzen (oder eine .pgpass-Datei nutzen) und den -W Parameter im o.g. Kommando entfernen.
Zuerst benötigen wir wieder einige Pakete
$ sudo apt install -y curl unzip gdal-bin libmapnik-dev nodejs npm python3-psycopg2
Als nächstes das carto-Paket mit npm installieren:
$ sudo npm install -g carto
Als nächstes im Verzeichnis openstreetmap-carto-5.9.0 (oder was auch immer eure aktuelle Releasenummer ist) die Datei external-data.yml
anpassen und den korrekten Datenbanknamen hinterlegen (in unserem Beispiel gisdb)
settings:
database: gisdb
Als nächstes, die Geodaten auslesen und das Stylesheet vorbereiten
$ sudo -u postgres -i
$ cd openstreetmap-carto/
$ scripts/get-external-data.py
In der nun erzeugen project.mml auch nochmals die Datenbankverbindung anpassen (dbname: gisdb)
Anschließend kann das Mapnik XML Stylesheet mit carto project.mml > style.xml
kompiliert werden.
Meldungen der Form "Warning: style/admin.mss:64:6 Styles do not match layer selector #admin-low-zoom." können ignoriert werden.
Zuletzt benötigt noch der renderd-User Zugriff auf die PostgreSQL-Datenbank und wir benötigen noch ein paar spezielle Funktionen in unserer Datenbank für die Darstellung. Hierzu also (immer noch als user postgres):
$ psql -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO _renderd;" -d gisdb
$ psql -c "GRANT pg_read_all_data TO _renderd;" -d gisdb
$ psql -d gisdb < functions.sql
$ exit
Den Postgres-User brauchen wir nun nicht mehr, alledings noch die Pattern und Symbole aus dem carto-Verzeichnis in unserer Beispielkarte, sowie unser neues Style:
$ cp -ax patterns symbols /usr/share/renderd/example-map/
$ cp style.xml /usr/share/renderd/example-map/mapnik.xml
Jetzt am besten den Browser-Cache einmal leeren und dann erneut http://localhost/renderd-example-map aufrufen. Nach ein wenig Ladezeit, sollten nun die OpenStreetMap-Daten sichtbar werden. Im Zweifel nochmals apache2 und renderd neustarten und die Ausgabe von journalctl prüfen.
Da das dynamische Erzeugen über den Webbrowser sehr lange dauert, kann man auch die Kacheln im Hintergrund vorberechnen lassen. Hierzu gibt es das render_list
-Kommando:
$ render_list -m example-map -a -z 0 -Z 19 --num-threads=5
Damit würden nun alle Kacheln weltweit von Zoomstufe 0 - 19 erzeugt, was entsprechend lange dauert. Wenn man allerdings nur einen kleinen Bereich an OpenStreetMap-Daten importiert hat (z.B. Teile von Europa), kann man auch nur die Kacheln im entsrechenden Teilgebiet erzeugen. Hierzu benötigt man allerdings die Minimum- und Maximumwerte von x bzw. y der Kacheln, um diese mit -x N -X N -y N -Y N
entsprechend übergeben zu können. Nützlich ist an dieser Stelle whatthequad wo man sich entweder die konkreten Kachelwerte für die entsprechende Zoomstufe besorgt, oder aber sich die Längen- und Breitengrade der linken unteren und rechten oberen Ecke notiert, um diese dann in einem Skript weiterzuverwenden. Siehe hierzu das OpenStreetMap-Wiki
Basierend auf dem dort gelisteten Bash-Skript die folgende Anpassung (z.B. als tilecalc.sh speichern):
#!/bin/bash
xtile2long()
{
xtile=$1
zoom=$2
echo "${xtile} ${zoom}" | awk '{printf("%.9f", $1 / 2.0^$2 * 360.0 - 180)}'
}
long2xtile()
{
long=$1
zoom=$2
echo "${long} ${zoom}" | awk '{ xtile = ($1 + 180.0) / 360 * 2.0^$2;
xtile+=xtile<0?-0.5:0.5;
printf("%d", xtile ) }'
}
ytile2lat()
{
ytile=$1;
zoom=$2;
tms=$3;
if [ ! -z "${tms}" ]
then
# from tms_numbering into osm_numbering
ytile=`echo "${ytile}" ${zoom} | awk '{printf("%d\n",((2.0^$2)-1)-$1)}'`;
fi
lat=`echo "${ytile} ${zoom}" | awk -v PI=3.14159265358979323846 '{
num_tiles = PI - 2.0 * PI * $1 / 2.0^$2;
printf("%.9f", 180.0 / PI * atan2(0.5 * (exp(num_tiles) - exp(-num_tiles)),1)); }'`;
echo "${lat}";
}
lat2ytile()
{
lat=$1;
zoom=$2;
tms=$3;
ytile=`echo "${lat} ${zoom}" | awk -v PI=3.14159265358979323846 '{
tan_x=sin($1 * PI / 180.0)/cos($1 * PI / 180.0);
ytile = (1 - log(tan_x + 1/cos($1 * PI/ 180))/PI)/2 * 2.0^$2;
ytile+=ytile<0?-0.5:0.5;
printf("%d", ytile ) }'`;
if [ ! -z "${tms}" ]
then
# from oms_numbering into tms_numbering
ytile=`echo "${ytile}" ${zoom} | awk '{printf("%d\n",((2.0^$2)-1)-$1)}'`;
fi
echo "${ytile}";
}
# ------------------------------------
# Sample of use:
# ------------------------------------
LatMin=34.309259;
LonMin=-32.570581;
LatMax=71.701968;
LonMax=41.138412;
TMS=""; # when NOT empty: tms format assumed
# ------------------------------------
for ZOOM in `seq 0 19` ; do
TILE_X_MIN=$( long2xtile ${LonMin} ${ZOOM} );
TILE_Y_MIN=$( lat2ytile ${LatMin} ${ZOOM} ${TMS} );
TILE_X_MAX=$( long2xtile ${LonMax} ${ZOOM} );
TILE_Y_MAX=$( lat2ytile ${LatMax} ${ZOOM} ${TMS} );
CMD="render_list --num-threads=5 -a -x ${TILE_X_MIN} -X ${TILE_X_MAX} -y ${TILE_Y_MIN} -Y ${TILE_Y_MAX} -z ${ZOOM} -Z ${ZOOM} -f -m example-map "
echo $CMD
$CMD
done
Standardmäßig wird renderd die Tiles nach einer gewissen Verfallsdauer neu berechnen. Wenn sich nun aber an den Quelldaten nichts geändert hat ist das unnötige Rechenzeit, vor allem wenn man die Tiles schon vorberechnet hat. Dazu kann man in
'/var/cache/renderd/tiles' eine Datei mit dem Namen planet-import-complete
anlegen, deren Datum allerdings älter sein muss, als das älteste Kachelverzeichnis.
Das ist z.B. nützlich, wenn man das Kartenmaterial für andere Anwendungen (z.B. meshtastic) verwenden möchte. Unter meta2tile gibt es hierzu ein nützliches kleines tool.
Mit sudo apt install libzip-dev libgd-dev
die benötigten Abhängigkeiten installieren und anschließend im entpackten Paketverzeichnis von meta2tile mittels make
das Tool erzeugen.
Anschließend können alle oder bestimmte Kacheln konvertiert werden (für mehr Optionen die Hilfe benutzen)
$ ./meta2tile /var/cache/renderd/tiles/example-map/ /tmp/tiles/
Hierzu kann man in /etc/postgresql/16/main/postgresql.conf
folgende Parameter anpassen:
shared_buffers = 6GB # 25% des Gesamtrams setzen
work_mem = 1GB
maintenance_work_mem = 8GB
effective_cache_size = 8GB
PostgreSQL anschließend neustarten: sudo systemctl restart postgresql
Quelle und weitere Optimierungen unter: https://www.linuxbabe.com/linux-server/osm-openstreetmap-tile-server-ubuntu-22-04