Karte von OpenStreetMap mit dem OpenStreetMap-Logo

OpenStreetMap-Karten über einen Caching-Proxy DSGVO-konform einbinden

OpenStreetMap ist für mich das deutlich bessere Pendant zu Google Maps, Bing Maps oder irgendwelchen anderen proprietären und/oder kommerziellen Kartendiensten. Ich selbst nutze bei Karteneinbindungen auf Websites, die ich administriere, nur noch OpenStreetMap. Allerdings hat man auch bei der Einbindung von OpenStreetMap-Karten, genauso wie bei Google Maps etc., das Problem, dass sie (wenn man nicht gerade über einen eigenen Kartenserver verfügt) mit einer 2-Click-Lösung eingebunden werden müssten.

In diesem Artikel erkläre ich meinen kleinen, aber feinen Caching-Proxy für OpenStreetMap auf PHP-Basis, mit dem Du alle OpenStreetMap-Requests über Deinen Webserver direkt laufen lassen kannst, sodass vom Client keinerlei Anfragen an OpenStreetMap gesendet werden und Du so Deine OpenStreetMap-Karte datenschutzkonform einbindest.

Darf man das?

Zunächst zur Frage, ob man einfach Kartenausschnitte, sogenannte „Tiles“, von den OpenStreetMap-Servern zwischenspeichern darf? Ein Blick in die Nutzungsbedingungen klärt auf:

Caching proxies

We generally do not recommend setting up a caching service in front of tile.openstreetmap.org. If you decide to do so it must set a HTTP User-Agent that allows us to contact you in case of issues.

https://operations.osmfoundation.org/policies/tiles/

Also: Es wird nicht empfohlen, aber man darf es. Damit man aber die Tiles überhaupt herunterladen kann, muss bei der Anfrage ein User-Agent angeben. Darüber hinaus muss in dieser Angabe auch eine Kontaktmöglichkeit hinterlegt werden (bei mir ist das die E-Mail-Adresse).

Aber: Wenn Du eine größere Website mit mehreren tausend Zugriffen täglich betreibst, würde ich Dir empfehlen, einen eigenen Kartenserver einzusetzen.

Kleiner Caching-Proxy auf PHP-Basis

Auf Github findest Du das kleine PHP-Script, mit dessen Hilfe Du Deinen Webserver zu einem Caching-Proxy machen kannst.

Das Script prüft zunächst, ob auf dem eigenen Webserver schon das Tile gespeichert ist. Ist dieses Tile noch aktuell (siehe Konfiguration), dann wird es direkt ausgeliefert. Andernfalls wird das Tile vom OpenStreetMap-Server herunterladen, gespeichert und dann ausgeliefert. Durch diese Zwischenspeicherung wird das Tile i.d.R. auch schneller ausgeliefert, als wenn man direkt auf den OpenStreetMap-Server zugreift.

Zur Funktionsweise des Scripts osm-caching-proxy.php:

In der Konfiguration ganz oben werden drei zentrale Werte definiert:
Mit $storage legst Du fest, in welchem Unterverzeichnis die Tiles abgelegt werden sollen; $ttl steht für „time to leave“ legt fest, wie lange ein Tile zwischengespeichert wird, bevor auf dem OpenStreetMap-Server das Tile erneut abgefragt wird. Als $operator wird die E-Mail-Adresse hinterlegt, unter der Dich die Betreiber des OpenStreetMap-Servers notfalls erreichen können. Sie wird dem User-Agent mitgegeben und taucht dann in den Server-Logs auf.

/**
 * Configuration Settings
 */
// Directory for storing files (e.g. 'cache/')
$storage = 'cache/';
// Time in seconds for renewing the tiles
$ttl = 604800;
// Email address of the proxy operator
$operator = 'you@mail.com';

Im nächsten Schritt werden die Variablen $z, $x und $y ausgelesen. Das $z steht dabei für die Zoom-Stufe, $x und $y für die Koordinaten. Ist einer dieser Werte leer oder ungültig, wird das Script gestoppt.

/**
 * Read Input Variables
 */
$z = $_GET['z'] ?? '';
$x = $_GET['x'] ?? '';
$y = $_GET['y'] ?? '';
// If any of the variables are empty or not numeric, terminate the script
if(empty($z) || empty($x) || empty($y) || !is_numeric($z) || !is_numeric($x) || !is_numeric($y) || $z < 0 || $x < 0 || $y < 0) {
    die;
}

Danach wird die download-Funktion definiert: Mittels einer einfachen cURL-Abfrage wird das Tile vom OpenStreetMap-Server geladen.

/**
 * Function to Download Tiles
 */
function download($storage, $z, $x, $y, $operator) {
    // Set maximum execution time to unlimited
    set_time_limit(0);
    // Define the source URL for the tile
    $source = 'https://tile.openstreetmap.org/' . $z . '/' . $x . '/' . $y . '.png';
    // Set timeout for the download
    $timeout = 30;
    // Open file handler for writing
    $fh = fopen($storage . $z . '/' . $x . '/' . $y . '.png', 'w');
    // Initialize cURL session
    $ch = curl_init();
    // Set cURL options
    curl_setopt($ch, CURLOPT_URL, $source);
    curl_setopt($ch, CURLOPT_FILE, $fh);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_USERAGENT, 'osm-caching-proxy, Operator: ' . $operator);
    // Execute cURL session
    curl_exec($ch);
    // Close cURL session
    curl_close($ch);
    // Close file handler
    fclose($fh);
}

Jetzt beginnt das Script erst richtig: Es wird geprüft, ob alle notwendigen Verzeichnisse schon angelegt sind. Diese werden angelegt, wenn Sie nicht vorhanden sind.

Anschließend wird geprüft, ob das Tile schon vorhanden ist. Wenn es vorhanden, wird zusätzlich geprüft, ob es älter als die TTL ist – wenn ja, dann wird das Tile heruntergeladen und das alte überschrieben. Ist das Tile nicht vorhanden, wird es auch heruntergeladen.

// Check if directories exist and download tile
$tileDirectory = $storage . $z . '/' . $x;
if (!is_dir($tileDirectory)) {
    mkdir($tileDirectory, 0750, true);
}

$tilePath = $tileDirectory . '/' . $y . '.png';
if (file_exists($tilePath)) {
    // If the tile exists, check its age
    $age = filemtime($tilePath);
    // If the tile is older than the defined TTL, download a new one
    if (time() >= ($age + $ttl)) {
        download($storage, $z, $x, $y, $operator);
    }
} else {    
    // If the tile doesn't exist, download it
    download($storage, $z, $x, $y, $operator);
}

Ganz zum Schluss wird die Tile-Grafik geladen und ausgegeben:

// Output the tile image
header('Content-Type: image/png');
readfile($storage . $z . '/' . $x . '/' . $y . '.png');

Die in meinem GitHub-Repository hinterlegte .htaccess sorgt dafür, dass Du die Anfrage im Format https://osm.example.com/{z}/{x}/{y}.png stellen kannst.

Systemvorrausetzungen

Das Script wurde auf einem eigenen Apache-vHost wie auch auf einem Shared-Hosting-Webspace getestet. Vermutlich sollte es auch mit einem NGINX-Webserver einwandfrei funktionieren.

2 Kommentare

  1. Hi Erik,

    herzlichen Dank, dass du dieses Wissen teilst! Das hilft mir sehr, die Karten rechtssicher einzubetten.

    Aber ich habe vermutet, dass die gecachten Dateien nicht richtig genutzt werden, weil die Performance nicht ganz so flott wie erhofft war. Ich glaube, dabei einen Fehler entdeckt zu haben.

    Im Code lautet es:
    // If the tile is older than the defined TTL, download a new one
    if ((time() + $ttl) >= $age) {

    Die If-Clause müsste entsprechend des Kommentars aber andersrum formuliert sein, meine ich, also $age >= (time() + $ttl).

    Herzliche Grüße und schönes Wochenende
    Marvin

    1. Hey Marvin,

      danke für den Hinweis, ich habs korrigiert; korrekt als if-Clause ist diese hier:
      if (time() >= ($age + $ttl))

      Viele Grüße,
      Erik

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert