From bfd8dfdf968edc291593713ddd06b8f64b067d0a Mon Sep 17 00:00:00 2001 From: Conrad Schulz Date: Sat, 30 May 2026 15:57:07 +0000 Subject: [PATCH] feat: initial tile server setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .env.example | 2 + .gitignore | 12 +++ config/config.json | 29 ++++++++ data/.gitkeep | 7 ++ docker-compose.yml | 30 ++++++++ docs/api-endpoints.md | 34 +++++++++ docs/mediawiki-config.php | 35 +++++++++ docs/nextcloud-config.md | 27 +++++++ nginx/cors.conf | 4 + nginx/nginx.conf | 53 ++++++++++++++ scripts/download-data.sh | 150 ++++++++++++++++++++++++++++++++++++++ 11 files changed, 383 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 config/config.json create mode 100644 data/.gitkeep create mode 100644 docker-compose.yml create mode 100644 docs/api-endpoints.md create mode 100644 docs/mediawiki-config.php create mode 100644 docs/nextcloud-config.md create mode 100644 nginx/cors.conf create mode 100644 nginx/nginx.conf create mode 100644 scripts/download-data.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..688d241 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +# Beispiel – in .env kopieren und anpassen +TILE_PORT=8080 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c76196d --- /dev/null +++ b/.gitignore @@ -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 diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..b848d4b --- /dev/null +++ b/config/config.json @@ -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" + } + } +} diff --git a/data/.gitkeep b/data/.gitkeep new file mode 100644 index 0000000..20bba9e --- /dev/null +++ b/data/.gitkeep @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3095b30 --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/docs/api-endpoints.md b/docs/api-endpoints.md new file mode 100644 index 0000000..40853a2 --- /dev/null +++ b/docs/api-endpoints.md @@ -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' +# }); diff --git a/docs/mediawiki-config.php b/docs/mediawiki-config.php new file mode 100644 index 0000000..bd24ecd --- /dev/null +++ b/docs/mediawiki-config.php @@ -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: +# +# {"type":"Feature","geometry":{"type":"Point","coordinates":[11.576,48.137]}, +# "properties":{"title":"München","description":"Landeshauptstadt"}} +# diff --git a/docs/nextcloud-config.md b/docs/nextcloud-config.md new file mode 100644 index 0000000..a065f0f --- /dev/null +++ b/docs/nextcloud-config.md @@ -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" diff --git a/nginx/cors.conf b/nginx/cors.conf new file mode 100644 index 0000000..a9e7fb3 --- /dev/null +++ b/nginx/cors.conf @@ -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; diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..e1deec3 --- /dev/null +++ b/nginx/nginx.conf @@ -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"; + } + } +} diff --git a/scripts/download-data.sh b/scripts/download-data.sh new file mode 100644 index 0000000..409da66 --- /dev/null +++ b/scripts/download-data.sh @@ -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"