Openstreetmap Webservice

osm linux gis

Nachdem wir grundsätzlich gesehen haben, wie man mit Geodaten arbeitet, geht es nun darum, sich einen eigenen Kartendienst aufzubauen.

Renderd und mod_tile installieren

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

Geodaten (erneut) einlesen

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.

Mapnik-Stylesheet erzeugen

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.

Kacheln vorberechnen

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.

PNG-Dateien aus den Metatiles erzeugen

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/

Optional: PostgreSQL Server Performance optimieren

Hierzu kann man in /etc/postgresql/16/main/postgresql.conffolgende 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

Vorheriger Beitrag