feat: initial tile server setup

- TileServer-GL + nginx reverse proxy (docker-compose)
- CORS-Konfiguration für MediaWiki, Nextcloud & weitere Dienste
- Rate-Limiting & Tile-Caching-Header via nginx
- config/config.json: OSM + Satellitenkacheln
- scripts/download-data.sh: Planetiler (OSM), Sentinel-2 Hinweise
- docs: API-Endpoints, MediaWiki Kartographer, Nextcloud Maps
- .gitignore: MBTiles/PMTiles und .env ausgeschlossen
This commit is contained in:
Conrad Schulz 2026-05-30 15:57:07 +00:00
commit bfd8dfdf96
11 changed files with 383 additions and 0 deletions

2
.env.example Normal file
View file

@ -0,0 +1,2 @@
# Beispiel in .env kopieren und anpassen
TILE_PORT=8080

12
.gitignore vendored Normal file
View file

@ -0,0 +1,12 @@
# Datendateien nicht im Repo (können mehrere GB groß sein)
data/*.mbtiles
data/*.pmtiles
data/fonts/
data/sprites/
data/styles/
# Laufzeit
.env
# Docker
*.log

29
config/config.json Normal file
View file

@ -0,0 +1,29 @@
{
"options": {
"paths": {
"root": "",
"fonts": "fonts",
"sprites": "sprites",
"styles": "styles",
"mbtiles": ""
},
"serveAllFonts": true,
"serveAllStyles": true
},
"data": {
"osm-openmaptiles": {
"mbtiles": "osm.mbtiles"
},
"satellite": {
"mbtiles": "satellite.mbtiles"
}
},
"styles": {
"osm-bright": {
"style": "styles/osm-bright/style.json"
},
"satellite-hybrid": {
"style": "styles/satellite-hybrid/style.json"
}
}
}

7
data/.gitkeep Normal file
View file

@ -0,0 +1,7 @@
# MBTiles-Dateien hier ablegen
#
# Benötigte Dateien (nicht im Git, da zu groß):
# osm.mbtiles OpenStreetMap Vektorkacheln (via Planetiler)
# satellite.mbtiles Satellitenkacheln
#
# Download-Skript: ../scripts/download-data.sh

30
docker-compose.yml Normal file
View file

@ -0,0 +1,30 @@
services:
tileserver:
image: maptiler/tileserver-gl:latest
container_name: rd13_tileserver
expose:
- "8080"
volumes:
- ./data:/data
- ./config/config.json:/data/config.json:ro
command: ["--config", "/data/config.json", "--port", "8080", "--verbose"]
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
nginx:
image: nginx:alpine
container_name: rd13_tileserver_proxy
ports:
- "${TILE_PORT:-8080}:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/cors.conf:/etc/nginx/cors.conf:ro
depends_on:
tileserver:
condition: service_healthy
restart: unless-stopped

34
docs/api-endpoints.md Normal file
View file

@ -0,0 +1,34 @@
# Allgemeine Tile-Server Endpunkte
#
# Sobald der Server läuft, sind folgende Endpoints verfügbar:
#
# Web-Interface:
# http://SERVER:PORT/
#
# Styles (Raster-Tiles, PNG):
# http://SERVER:PORT/styles/osm-bright/{z}/{x}/{y}.png ← OSM
# http://SERVER:PORT/styles/satellite-hybrid/{z}/{x}/{y}.png ← Satellit
# http://SERVER:PORT/styles/osm-bright/{z}/{x}/{y}@2x.png ← Retina
#
# Daten (Vektor-Tiles, PBF):
# http://SERVER:PORT/data/osm-openmaptiles/{z}/{x}/{y}.pbf
# http://SERVER:PORT/data/satellite/{z}/{x}/{y}.png
#
# TileJSON (Metadaten für Leaflet, MapLibre, etc.):
# http://SERVER:PORT/styles/osm-bright.json
# http://SERVER:PORT/data/osm-openmaptiles.json
#
# WMTS (für QGIS, ArcGIS, etc.):
# http://SERVER:PORT/styles/osm-bright/wmts
#
# Leaflet.js Beispiel:
# L.tileLayer('http://SERVER:PORT/styles/osm-bright/{z}/{x}/{y}.png', {
# attribution: '© OpenMapTiles © OpenStreetMap contributors',
# maxZoom: 20
# }).addTo(map);
#
# MapLibre GL Beispiel:
# new maplibregl.Map({
# style: 'http://SERVER:PORT/styles/osm-bright.json',
# container: 'map'
# });

35
docs/mediawiki-config.php Normal file
View file

@ -0,0 +1,35 @@
# MediaWiki Kartographer Konfiguration für LocalSettings.php
#
# Füge diese Zeilen in deine LocalSettings.php ein.
# Ersetze TILE_SERVER_URL mit der tatsächlichen URL deines Servers.
#
# Dokumentation: https://www.mediawiki.org/wiki/Extension:Kartographer
# Kartographer aktivieren
wfLoadExtension( 'Kartographer' );
# Tile-Server URL (dein selbst gehosteter Server)
$wgKartographerMapServer = 'http://TILE_SERVER_URL:8080';
# Welche Kartenstile stehen zur Verfügung?
# Die Namen entsprechen den Keys in config/config.json → "styles"
$wgKartographerStyles = [
'osm-bright', # OpenStreetMap Vektorkarte
'satellite-hybrid', # Satellitenkarte
];
# Standard-Stil
$wgKartographerDfltStyle = 'osm-bright';
# Tile-URL-Muster für Raster-Tiles (XYZ)
# TileServer-GL stellt Tiles unter /styles/{style}/{z}/{x}/{y}.png bereit
$wgKartographerSrcsetScales = [ 1, 1.5, 2 ];
# Optional: statische Kartenbilder für Vorschau (Mapshot)
# $wgKartographerStaticFullWidth = true;
# Beispiel-Verwendung in einem Wiki-Artikel:
# <maplink zoom="12" latitude="48.137" longitude="11.576">
# {"type":"Feature","geometry":{"type":"Point","coordinates":[11.576,48.137]},
# "properties":{"title":"München","description":"Landeshauptstadt"}}
# </maplink>

27
docs/nextcloud-config.md Normal file
View file

@ -0,0 +1,27 @@
# Nextcloud Maps eigenen Tile-Server verwenden
#
# Nextcloud Maps App: https://apps.nextcloud.com/apps/maps
#
# Konfiguration in Nextcloud Admin-Panel:
# Einstellungen → Verwaltung → Maps → Tile server URL
#
# Tile-Server-URLs (TileServer-GL XYZ-Format):
#
# OSM Vektorkarte (als Rasterbild gerendert):
# http://TILE_SERVER_URL:8080/styles/osm-bright/{z}/{x}/{y}.png
#
# Satellitenkarte:
# http://TILE_SERVER_URL:8080/styles/satellite-hybrid/{z}/{x}/{y}.png
#
# Rohe Vektorkacheln (OpenMapTiles Schema):
# http://TILE_SERVER_URL:8080/data/osm-openmaptiles/{z}/{x}/{y}.pbf
#
# Über occ (Kommandozeile) setzen:
# php occ config:app:set maps tileserverUrl \
# --value="http://TILE_SERVER_URL:8080/styles/osm-bright/{z}/{x}/{y}.png"
# Beispiel für docker-compose override wenn Nextcloud im selben Stack läuft:
#
# nextcloud:
# environment:
# MAPS_TILE_SERVER: "http://rd13_tileserver_proxy/styles/osm-bright/{z}/{x}/{y}.png"

4
nginx/cors.conf Normal file
View file

@ -0,0 +1,4 @@
# Shared CORS headers included by nginx.conf
# Wird automatisch auf alle Responses angewendet.
# Für produktiven Betrieb kann Origin auf deine Domains eingeschränkt werden:
# add_header Access-Control-Allow-Origin "https://wiki.example.com" always;

53
nginx/nginx.conf Normal file
View file

@ -0,0 +1,53 @@
worker_processes auto;
events { worker_connections 1024; }
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# Rate limiting schützt vor Missbrauch durch externe Dienste
limit_req_zone $binary_remote_addr zone=tiles:10m rate=100r/s;
server {
listen 80;
server_name _;
# CORS für alle Clients (MediaWiki, Nextcloud, etc.)
include /etc/nginx/cors.conf;
location / {
limit_req zone=tiles burst=200 nodelay;
proxy_pass http://tileserver:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Tile-Caching-Header
proxy_hide_header Cache-Control;
add_header Cache-Control "public, max-age=86400, stale-while-revalidate=3600";
# CORS-Header (aus cors.conf)
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept" always;
}
# CORS Preflight
location ~* \.(pbf|png|jpg|json)$ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Methods "GET, OPTIONS";
add_header Access-Control-Max-Age 86400;
return 204;
}
proxy_pass http://tileserver:8080;
add_header Access-Control-Allow-Origin "*" always;
add_header Cache-Control "public, max-age=86400";
}
}
}

150
scripts/download-data.sh Normal file
View file

@ -0,0 +1,150 @@
#!/usr/bin/env bash
# =============================================================================
# download-data.sh Kartendaten für den rd13 Tile Server herunterladen
#
# Verwendung:
# ./scripts/download-data.sh [region]
#
# Regionen:
# europe-dach Deutschland, Österreich, Schweiz (Standard)
# germany nur Deutschland
# planet gesamter Planet (groß!)
# satellite Sentinel-2 cloudless Satellitenkacheln (niedrig/mittel Zoom)
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DATA_DIR="$SCRIPT_DIR/../data"
mkdir -p "$DATA_DIR"
REGION="${1:-europe-dach}"
# ---- OSM-Vektorkacheln via Planetiler ------------------------------------
download_osm() {
local region="$1"
local jar="$DATA_DIR/planetiler.jar"
# Planetiler herunterladen falls nicht vorhanden
if [[ ! -f "$jar" ]]; then
echo "[OSM] Lade Planetiler herunter..."
curl -L -o "$jar" \
"https://github.com/onthegomap/planetiler/releases/latest/download/planetiler.jar"
fi
case "$region" in
germany)
AREA="germany"
DOWNLOAD="https://download.geofabrik.de/europe/germany-latest.osm.pbf"
;;
europe-dach)
# DACH als Extrakt
AREA="dach"
DOWNLOAD="https://download.geofabrik.de/europe/dach-latest.osm.pbf"
;;
planet)
AREA="planet"
DOWNLOAD="" # Planetiler lädt selbst herunter
;;
*)
echo "Unbekannte Region: $region"; exit 1 ;;
esac
echo "[OSM] Erzeuge osm.mbtiles für Region: $region"
if [[ "$region" == "planet" ]]; then
java -Xmx8g -jar "$jar" \
--download \
--output="$DATA_DIR/osm.mbtiles"
else
# OSM-PBF herunterladen
local pbf="$DATA_DIR/${AREA}.osm.pbf"
if [[ ! -f "$pbf" ]]; then
echo "[OSM] Lade PBF-Datei: $DOWNLOAD"
curl -L -o "$pbf" "$DOWNLOAD"
fi
java -Xmx4g -jar "$jar" \
--area="$AREA" \
--osm-path="$pbf" \
--output="$DATA_DIR/osm.mbtiles"
fi
echo "[OSM] Fertig: $DATA_DIR/osm.mbtiles"
}
# ---- Satellit: Sentinel-2 cloudless (OpenMapTiles / maptiler) ------------
download_satellite() {
echo ""
echo "[Satellit] Hinweis:"
echo " Kostenlose Satelliten-MBTiles sind nur für niedrige Zoomstufen (0-10)"
echo " verfügbar. Für höhere Auflösung gibt es zwei Optionen:"
echo ""
echo " 1) NASA GIBS (kostenlos, TMS/WMTS, kein Download nötig):"
echo " https://gibs.earthdata.nasa.gov/wmts/"
echo " → Einfach in config.json als 'tilejson'-Source eintragen."
echo ""
echo " 2) Sentinel-2 cloudless (maptiler.com, kostenloser Account für self-hosted):"
echo " https://www.maptiler.com/data/satellite-mediumres/"
echo " → MBTiles manuell herunterladen und als data/satellite.mbtiles ablegen."
echo ""
echo " 3) Eigene GeoTIFFs → MBTiles mit gdal2tiles:"
echo " gdal2tiles.py --zoom=0-14 input.tif data/satellite/"
echo " mb-util data/satellite/ data/satellite.mbtiles"
echo ""
}
# ---- Fonts & Sprites für OSM Bright Style --------------------------------
download_assets() {
local fonts_dir="$DATA_DIR/fonts"
local sprites_dir="$DATA_DIR/sprites"
local styles_dir="$DATA_DIR/styles"
mkdir -p "$fonts_dir" "$sprites_dir" "$styles_dir"
if [[ ! -d "$fonts_dir/Open Sans Regular" ]]; then
echo "[Assets] Lade OpenMapTiles Fonts herunter..."
TMP=$(mktemp -d)
curl -L -o "$TMP/fonts.zip" \
"https://github.com/openmaptiles/fonts/releases/latest/download/v3.0.zip" \
|| { echo "[Assets] Font-Download fehlgeschlagen bitte manuell von"; \
echo " https://github.com/openmaptiles/fonts/releases herunterladen"; \
rm -rf "$TMP"; }
if [[ -f "$TMP/fonts.zip" ]]; then
unzip -q "$TMP/fonts.zip" -d "$fonts_dir"
rm -rf "$TMP"
echo "[Assets] Fonts installiert."
fi
else
echo "[Assets] Fonts bereits vorhanden."
fi
if [[ ! -d "$styles_dir/osm-bright" ]]; then
echo "[Assets] Lade OSM Bright GL Style herunter..."
TMP=$(mktemp -d)
curl -L -o "$TMP/style.zip" \
"https://github.com/openmaptiles/osm-bright-gl-style/releases/latest/download/v1.9.zip" \
|| { echo "[Assets] Style-Download fehlgeschlagen."; rm -rf "$TMP"; }
if [[ -f "$TMP/style.zip" ]]; then
unzip -q "$TMP/style.zip" -d "$TMP/extracted"
mv "$TMP/extracted/"*/ "$styles_dir/osm-bright"
rm -rf "$TMP"
echo "[Assets] OSM Bright Style installiert."
fi
else
echo "[Assets] OSM Bright Style bereits vorhanden."
fi
}
# ---- Hauptlogik ----------------------------------------------------------
echo "=== rd13 Tile Server Daten-Download ==="
echo "Region: $REGION"
echo ""
if [[ "$REGION" == "satellite" ]]; then
download_satellite
else
download_osm "$REGION"
download_assets
download_satellite
fi
echo ""
echo "=== Abgeschlossen ==="
echo "Starte den Server mit: docker compose up -d"