# ADR-001: Tile Rendering Stack – Martin + tileserver-gl **Date:** 2026-06-12 **Status:** Accepted ## Context The tile server must serve map tiles to three external systems: - **MediaWiki** (Kartographer extension) – requires raster PNG tiles for client-side rendering - **Home Assistant** – requires raster PNG tiles for map visualization - **Nextcloud** – may require vector or raster tiles in future Current state: - Martin tile server distributes vector tiles (PBF format) from `osm.mbtiles` - `osm.mbtiles` contains static OSM snapshot (not live-updated data) - No client-side rendering capability for Kartographer (requires PNG or pre-rendered output) - Map rendering fails silently in MediaWiki without server-side PNG delivery Key requirements: - Maps must display in MediaWiki without external tile server dependency - Styles and layer definitions must be customizable by team - Map tile updates are planned 2–4 times per year (not live) - Low operational overhead (single-server, docker-compose deployment) - Horizontal scaling is not a near-term requirement ## Decision Adopt a **two-tier tile architecture:** 1. **Martin** (stays) - Role: Vector tile distribution API - Input: `osm.mbtiles` (static snapshot, updated manually 2–4×/year) - Output: PBF tiles via REST API (`/{layer}/{z}/{x}/{y}.pbf`) - Purpose: Foundation for future MapLibre GL integration, internal vector APIs 2. **tileserver-gl** (new) - Role: PNG raster tile renderer + server - Input: Vector tiles from Martin + Style definitions (stored in `/data/styles/`) - Output: Raster PNG tiles via REST API (`/{style}/{z}/{x}/{y}.png`) - Purpose: Serve Kartographer and other raster-only clients **URL routing in Reverse Proxy (NPM):** - `/osm/{z}/{x}/{y}.pbf` → Martin (internal/API use) - `/osm-intl/{z}/{x}/{y}.png` → tileserver-gl (Kartographer) - `/osm-intl/{z}/{x}/{y}@2x.png` → tileserver-gl (retina displays) **MediaWiki Config:** - `$wgKartographerMapServer = 'https://tiles.rd13server.de'` - `$wgKartographerDfltStyle = 'osm-intl'` (raster style) - No external tile dependency ## Consequences ### Benefits - ✅ **Minimal operational complexity** – Both Martin and tileserver-gl fit in docker-compose, combined ~200MB RAM - ✅ **No external dependencies** – Kartographer renders entirely from local server; no upstream tile requests - ✅ **Style customization** – tileserver-gl uses standard MapLibre GL styles (JSON); team can adjust colors, fonts, layer visibility without code changes - ✅ **Flexible upgrade path** – If live OSM updates become required (2027+), OpenMapTiles Server can be added without disrupting Martin or tileserver-gl - ✅ **Fast iteration** – Update cycle: download new `osm.mbtiles` → docker-compose restart → ~30 seconds downtime - ✅ **Vector + Raster simultaneously** – Future clients (MapLibre GL, Leaflet vector) can coexist with raster-only systems (Kartographer) ### Drawbacks - ⚠️ **Manual tile refresh cycle** – No live OSM updates; requires download and swap of `osm.mbtiles` - ⚠️ **PNG caching complexity** – Raster tiles are pre-rendered, so cached images may lag behind style changes by one tile-generation cycle - ⚠️ **CPU spike during rendering** – tileserver-gl pre-warms cache on startup; brief CPU spike when new zoom level/area is requested for first time - ⚠️ **Storage** – Raster caching will eventually consume disk space (~1GB per zoom level for Europe at full coverage); mitigation: define tile cache TTL ## Alternatives Considered ### Alternative A: OpenMapTiles Server (full-stack replacement) **Pros:** - Live OSM data import pipeline - Central rendering engine with parametrized styling - Full control over data freshness **Cons:** - **Overengineered for current needs** – Requires PostGIS, Lua, full data pipeline; 3–5 GB footprint - **High setup cost** – 3–5 days to operationalize + ongoing maintenance burden - **Not needed yet** – static 2–4×/year updates don't justify infrastructure investment - **Risk** – Complex system = more failure modes; Martin is proven production-grade today **Why not chosen:** Defers to future decision (ADR-002?) when live update requirement becomes real. ### Alternative B: External tile server proxy (OSM.org, Mapbox) **Pros:** - Zero infrastructure investment - Instant map display **Cons:** - **Violates ToS** – OSM.org tile server prohibits automated/bulk access (Kartographer = repeated requests) - **No resilience** – Dependent on external uptime; tiles fail if upstream is down - **Violates NFR-001** – REQ-001 requires "maps must be independently hostable" **Why not chosen:** Does not meet architectural requirements. ### Alternative C: Martin + client-side rendering (MapLibre GL only) **Pros:** - Simpler stack (one service) - Full styling flexibility at runtime **Cons:** - **Breaks MediaWiki** – Kartographer does not support MapLibre GL client-side rendering - **Blocks requirement** – MediaWiki maps would remain broken; unacceptable to stakeholders **Why not chosen:** Fails functional requirements. ## Implementation Plan 1. **Week 1** - Add tileserver-gl to docker-compose.yml - Mount `/data/styles/` and `/data/fonts/` for style/font config - Create baseline osm-intl style (JSON) for raster rendering - Configure Nginx reverse proxy routing in NPM 2. **Week 2** - Test end-to-end: MediaWiki mapframe → PNG delivery - Verify cache headers and CDN-friendliness - Document in ADMIN.md: tile update workflow 3. **Week 3** - Performance testing (p95 response times, cache hit rates) - Rollback procedure documentation - Team training on style customization ## Future Decisions - **ADR-002:** Live OSM update pipeline (if 2027 requirement arises) - **ADR-003:** Horizontal tile server scaling (if traffic exceeds single-server capacity) - **ADR-004:** Sprites and fonts CDN distribution ## References - [Martin Tile Server Docs](https://maplibre.org/martin/) - [tileserver-gl Docs](https://tileserver.readthedocs.io/) - [REQ-001: Tile Server öffentlich erreichbar](../requirements/REQUIREMENTS.md#req-001) - [REQ-004: MediaWiki Integration](../requirements/REQUIREMENTS.md#req-004)