fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
221
geo-service/api/tiles.py
Normal file
221
geo-service/api/tiles.py
Normal file
@@ -0,0 +1,221 @@
|
||||
"""
|
||||
Tile Server API Endpoints
|
||||
Serves Vector Tiles from PMTiles or generates on-demand from PostGIS
|
||||
"""
|
||||
from fastapi import APIRouter, HTTPException, Path, Query, Response
|
||||
from fastapi.responses import JSONResponse
|
||||
import structlog
|
||||
|
||||
from config import settings
|
||||
from services.tile_server import TileServerService
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
# Initialize tile server service
|
||||
tile_service = TileServerService()
|
||||
|
||||
|
||||
@router.get("/{z}/{x}/{y}.pbf", response_class=Response)
|
||||
async def get_vector_tile(
|
||||
z: int = Path(..., ge=0, le=22, description="Zoom level"),
|
||||
x: int = Path(..., ge=0, description="Tile X coordinate"),
|
||||
y: int = Path(..., ge=0, description="Tile Y coordinate"),
|
||||
):
|
||||
"""
|
||||
Get a vector tile in Protocol Buffers format.
|
||||
|
||||
Returns OSM data as vector tiles suitable for MapLibre GL JS.
|
||||
Tiles are served from pre-generated PMTiles or cached on-demand.
|
||||
"""
|
||||
try:
|
||||
tile_data = await tile_service.get_tile(z, x, y)
|
||||
|
||||
if tile_data is None:
|
||||
# Return empty tile (204 No Content is standard for empty tiles)
|
||||
return Response(status_code=204)
|
||||
|
||||
return Response(
|
||||
content=tile_data,
|
||||
media_type="application/x-protobuf",
|
||||
headers={
|
||||
"Content-Encoding": "gzip",
|
||||
"Cache-Control": "public, max-age=86400", # 24h cache
|
||||
"X-Tile-Source": "pmtiles",
|
||||
},
|
||||
)
|
||||
|
||||
except FileNotFoundError:
|
||||
logger.warning("PMTiles file not found", z=z, x=x, y=y)
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="Tile data not available. Please run the data download script first.",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Error serving tile", z=z, x=x, y=y, error=str(e))
|
||||
raise HTTPException(status_code=500, detail="Error serving tile")
|
||||
|
||||
|
||||
@router.get("/style.json")
|
||||
async def get_maplibre_style(
|
||||
base_url: str = Query(None, description="Base URL for tile server"),
|
||||
):
|
||||
"""
|
||||
Get MapLibre GL JS style specification.
|
||||
|
||||
Returns a style document configured for the self-hosted tile server.
|
||||
"""
|
||||
# Use provided base_url or construct from settings
|
||||
if base_url is None:
|
||||
base_url = f"http://localhost:{settings.port}"
|
||||
|
||||
style = {
|
||||
"version": 8,
|
||||
"name": "GeoEdu Germany",
|
||||
"metadata": {
|
||||
"description": "Self-hosted OSM tiles for DSGVO-compliant education",
|
||||
"attribution": "© OpenStreetMap contributors",
|
||||
},
|
||||
"sources": {
|
||||
"osm": {
|
||||
"type": "vector",
|
||||
"tiles": [f"{base_url}/api/v1/tiles/{{z}}/{{x}}/{{y}}.pbf"],
|
||||
"minzoom": 0,
|
||||
"maxzoom": 14,
|
||||
"attribution": "© OpenStreetMap contributors (ODbL)",
|
||||
},
|
||||
"terrain": {
|
||||
"type": "raster-dem",
|
||||
"tiles": [f"{base_url}/api/v1/terrain/{{z}}/{{x}}/{{y}}.png"],
|
||||
"tileSize": 256,
|
||||
"maxzoom": 14,
|
||||
"attribution": "© Copernicus DEM GLO-30",
|
||||
},
|
||||
},
|
||||
"sprite": "",
|
||||
"glyphs": "https://fonts.openmaptiles.org/{fontstack}/{range}.pbf",
|
||||
"layers": [
|
||||
# Background
|
||||
{
|
||||
"id": "background",
|
||||
"type": "background",
|
||||
"paint": {"background-color": "#f8f4f0"},
|
||||
},
|
||||
# Water
|
||||
{
|
||||
"id": "water",
|
||||
"type": "fill",
|
||||
"source": "osm",
|
||||
"source-layer": "water",
|
||||
"paint": {"fill-color": "#a0c8f0"},
|
||||
},
|
||||
# Landuse - Parks
|
||||
{
|
||||
"id": "landuse-park",
|
||||
"type": "fill",
|
||||
"source": "osm",
|
||||
"source-layer": "landuse",
|
||||
"filter": ["==", "class", "park"],
|
||||
"paint": {"fill-color": "#c8e6c8", "fill-opacity": 0.5},
|
||||
},
|
||||
# Landuse - Forest
|
||||
{
|
||||
"id": "landuse-forest",
|
||||
"type": "fill",
|
||||
"source": "osm",
|
||||
"source-layer": "landuse",
|
||||
"filter": ["==", "class", "wood"],
|
||||
"paint": {"fill-color": "#94d294", "fill-opacity": 0.5},
|
||||
},
|
||||
# Buildings
|
||||
{
|
||||
"id": "building",
|
||||
"type": "fill",
|
||||
"source": "osm",
|
||||
"source-layer": "building",
|
||||
"minzoom": 13,
|
||||
"paint": {"fill-color": "#d9d0c9", "fill-opacity": 0.8},
|
||||
},
|
||||
# Roads - Minor
|
||||
{
|
||||
"id": "road-minor",
|
||||
"type": "line",
|
||||
"source": "osm",
|
||||
"source-layer": "transportation",
|
||||
"filter": ["all", ["==", "$type", "LineString"], ["in", "class", "minor", "service"]],
|
||||
"paint": {"line-color": "#ffffff", "line-width": 1},
|
||||
},
|
||||
# Roads - Major
|
||||
{
|
||||
"id": "road-major",
|
||||
"type": "line",
|
||||
"source": "osm",
|
||||
"source-layer": "transportation",
|
||||
"filter": ["all", ["==", "$type", "LineString"], ["in", "class", "primary", "secondary", "tertiary"]],
|
||||
"paint": {"line-color": "#ffc107", "line-width": 2},
|
||||
},
|
||||
# Roads - Highway
|
||||
{
|
||||
"id": "road-highway",
|
||||
"type": "line",
|
||||
"source": "osm",
|
||||
"source-layer": "transportation",
|
||||
"filter": ["all", ["==", "$type", "LineString"], ["==", "class", "motorway"]],
|
||||
"paint": {"line-color": "#ff6f00", "line-width": 3},
|
||||
},
|
||||
# Place labels
|
||||
{
|
||||
"id": "place-label",
|
||||
"type": "symbol",
|
||||
"source": "osm",
|
||||
"source-layer": "place",
|
||||
"layout": {
|
||||
"text-field": "{name}",
|
||||
"text-font": ["Open Sans Regular"],
|
||||
"text-size": 12,
|
||||
},
|
||||
"paint": {"text-color": "#333333", "text-halo-color": "#ffffff", "text-halo-width": 1},
|
||||
},
|
||||
],
|
||||
"terrain": {"source": "terrain", "exaggeration": 1.5},
|
||||
}
|
||||
|
||||
return JSONResponse(content=style)
|
||||
|
||||
|
||||
@router.get("/metadata")
|
||||
async def get_tile_metadata():
|
||||
"""
|
||||
Get metadata about available tiles.
|
||||
|
||||
Returns information about data coverage, zoom levels, and update status.
|
||||
"""
|
||||
metadata = await tile_service.get_metadata()
|
||||
|
||||
return {
|
||||
"name": "GeoEdu Germany Tiles",
|
||||
"description": "Self-hosted OSM vector tiles for Germany",
|
||||
"format": "pbf",
|
||||
"scheme": "xyz",
|
||||
"minzoom": metadata.get("minzoom", 0),
|
||||
"maxzoom": metadata.get("maxzoom", 14),
|
||||
"bounds": metadata.get("bounds", [5.87, 47.27, 15.04, 55.06]), # Germany bbox
|
||||
"center": metadata.get("center", [10.45, 51.16, 6]), # Center of Germany
|
||||
"attribution": "© OpenStreetMap contributors (ODbL)",
|
||||
"data_available": metadata.get("data_available", False),
|
||||
"last_updated": metadata.get("last_updated"),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/bounds")
|
||||
async def get_tile_bounds():
|
||||
"""
|
||||
Get the geographic bounds of available tile data.
|
||||
|
||||
Returns bounding box for Germany in [west, south, east, north] format.
|
||||
"""
|
||||
return {
|
||||
"bounds": [5.87, 47.27, 15.04, 55.06], # Germany bounding box
|
||||
"center": [10.45, 51.16],
|
||||
"description": "Germany (Deutschland)",
|
||||
}
|
||||
Reference in New Issue
Block a user