Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
370 lines
9.3 KiB
TypeScript
370 lines
9.3 KiB
TypeScript
/**
|
|
* MapLibre Style Configurations for GeoEdu Service
|
|
* Styles for displaying OSM data with terrain
|
|
*/
|
|
|
|
import { MapStyle, MapLayer } from '@/app/geo-lernwelt/types'
|
|
|
|
// Germany bounds
|
|
export const GERMANY_BOUNDS: [[number, number], [number, number]] = [
|
|
[5.87, 47.27],
|
|
[15.04, 55.06],
|
|
]
|
|
|
|
// Default center (Germany)
|
|
export const GERMANY_CENTER: [number, number] = [10.45, 51.16]
|
|
|
|
// Mainau island (demo location)
|
|
export const MAINAU_CENTER: [number, number] = [9.1925, 47.7085]
|
|
export const MAINAU_BOUNDS: [[number, number], [number, number]] = [
|
|
[9.185, 47.705],
|
|
[9.200, 47.712],
|
|
]
|
|
|
|
/**
|
|
* Create a MapLibre style for the self-hosted tile server
|
|
*/
|
|
export function createMapStyle(geoServiceUrl: string): MapStyle {
|
|
return {
|
|
version: 8,
|
|
name: 'GeoEdu Germany',
|
|
metadata: {
|
|
description: 'Self-hosted OSM tiles for DSGVO-compliant education',
|
|
attribution: '© OpenStreetMap contributors',
|
|
},
|
|
sources: {
|
|
osm: {
|
|
type: 'vector',
|
|
tiles: [`${geoServiceUrl}/api/v1/tiles/{z}/{x}/{y}.pbf`],
|
|
minzoom: 0,
|
|
maxzoom: 14,
|
|
attribution: '© OpenStreetMap contributors (ODbL)',
|
|
},
|
|
terrain: {
|
|
type: 'raster-dem',
|
|
tiles: [`${geoServiceUrl}/api/v1/terrain/{z}/{x}/{y}.png`],
|
|
tileSize: 256,
|
|
attribution: '© Copernicus DEM GLO-30',
|
|
},
|
|
hillshade: {
|
|
type: 'raster',
|
|
tiles: [`${geoServiceUrl}/api/v1/terrain/hillshade/{z}/{x}/{y}.png`],
|
|
tileSize: 256,
|
|
},
|
|
},
|
|
layers: [
|
|
// Background
|
|
{
|
|
id: 'background',
|
|
type: 'background',
|
|
paint: { 'background-color': '#f8f4f0' },
|
|
},
|
|
// Hillshade
|
|
{
|
|
id: 'hillshade',
|
|
type: 'raster',
|
|
source: 'hillshade',
|
|
paint: { 'raster-opacity': 0.3 },
|
|
},
|
|
// Water areas
|
|
{
|
|
id: 'water',
|
|
type: 'fill',
|
|
source: 'osm',
|
|
'source-layer': 'water',
|
|
paint: { 'fill-color': '#a0c8f0' },
|
|
},
|
|
// Parks
|
|
{
|
|
id: 'landuse-park',
|
|
type: 'fill',
|
|
source: 'osm',
|
|
'source-layer': 'landuse',
|
|
filter: ['==', 'class', 'park'],
|
|
paint: { 'fill-color': '#c8e6c8', 'fill-opacity': 0.5 },
|
|
},
|
|
// 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,
|
|
},
|
|
},
|
|
// Building outlines
|
|
{
|
|
id: 'building-outline',
|
|
type: 'line',
|
|
source: 'osm',
|
|
'source-layer': 'building',
|
|
minzoom: 13,
|
|
paint: {
|
|
'line-color': '#b8a89a',
|
|
'line-width': 1,
|
|
},
|
|
},
|
|
// Minor roads
|
|
{
|
|
id: 'road-minor',
|
|
type: 'line',
|
|
source: 'osm',
|
|
'source-layer': 'transportation',
|
|
filter: ['all', ['==', '$type', 'LineString'], ['in', 'class', 'minor', 'service']],
|
|
paint: {
|
|
'line-color': '#ffffff',
|
|
'line-width': ['interpolate', ['linear'], ['zoom'], 10, 0.5, 14, 2],
|
|
},
|
|
},
|
|
// Secondary roads
|
|
{
|
|
id: 'road-secondary',
|
|
type: 'line',
|
|
source: 'osm',
|
|
'source-layer': 'transportation',
|
|
filter: ['all', ['==', '$type', 'LineString'], ['in', 'class', 'secondary', 'tertiary']],
|
|
paint: {
|
|
'line-color': '#ffc107',
|
|
'line-width': ['interpolate', ['linear'], ['zoom'], 8, 1, 14, 4],
|
|
},
|
|
},
|
|
// Primary roads
|
|
{
|
|
id: 'road-primary',
|
|
type: 'line',
|
|
source: 'osm',
|
|
'source-layer': 'transportation',
|
|
filter: ['all', ['==', '$type', 'LineString'], ['==', 'class', 'primary']],
|
|
paint: {
|
|
'line-color': '#ff9800',
|
|
'line-width': ['interpolate', ['linear'], ['zoom'], 6, 1, 14, 6],
|
|
},
|
|
},
|
|
// Highways
|
|
{
|
|
id: 'road-highway',
|
|
type: 'line',
|
|
source: 'osm',
|
|
'source-layer': 'transportation',
|
|
filter: ['all', ['==', '$type', 'LineString'], ['==', 'class', 'motorway']],
|
|
paint: {
|
|
'line-color': '#ff6f00',
|
|
'line-width': ['interpolate', ['linear'], ['zoom'], 4, 1, 14, 8],
|
|
},
|
|
},
|
|
// Railways
|
|
{
|
|
id: 'railway',
|
|
type: 'line',
|
|
source: 'osm',
|
|
'source-layer': 'transportation',
|
|
filter: ['==', 'class', 'rail'],
|
|
paint: {
|
|
'line-color': '#555555',
|
|
'line-width': 2,
|
|
'line-dasharray': [3, 3],
|
|
},
|
|
},
|
|
// Water lines (rivers, streams)
|
|
{
|
|
id: 'waterway',
|
|
type: 'line',
|
|
source: 'osm',
|
|
'source-layer': 'waterway',
|
|
paint: {
|
|
'line-color': '#a0c8f0',
|
|
'line-width': ['interpolate', ['linear'], ['zoom'], 10, 1, 14, 3],
|
|
},
|
|
},
|
|
// Place labels
|
|
{
|
|
id: 'place-label-city',
|
|
type: 'symbol',
|
|
source: 'osm',
|
|
'source-layer': 'place',
|
|
filter: ['==', 'class', 'city'],
|
|
layout: {
|
|
'text-field': '{name}',
|
|
'text-font': ['Open Sans Bold'],
|
|
'text-size': 16,
|
|
},
|
|
paint: {
|
|
'text-color': '#333333',
|
|
'text-halo-color': '#ffffff',
|
|
'text-halo-width': 2,
|
|
},
|
|
},
|
|
{
|
|
id: 'place-label-town',
|
|
type: 'symbol',
|
|
source: 'osm',
|
|
'source-layer': 'place',
|
|
filter: ['==', 'class', 'town'],
|
|
minzoom: 8,
|
|
layout: {
|
|
'text-field': '{name}',
|
|
'text-font': ['Open Sans Semibold'],
|
|
'text-size': 14,
|
|
},
|
|
paint: {
|
|
'text-color': '#444444',
|
|
'text-halo-color': '#ffffff',
|
|
'text-halo-width': 1.5,
|
|
},
|
|
},
|
|
{
|
|
id: 'place-label-village',
|
|
type: 'symbol',
|
|
source: 'osm',
|
|
'source-layer': 'place',
|
|
filter: ['==', 'class', 'village'],
|
|
minzoom: 10,
|
|
layout: {
|
|
'text-field': '{name}',
|
|
'text-font': ['Open Sans Regular'],
|
|
'text-size': 12,
|
|
},
|
|
paint: {
|
|
'text-color': '#555555',
|
|
'text-halo-color': '#ffffff',
|
|
'text-halo-width': 1,
|
|
},
|
|
},
|
|
],
|
|
terrain: {
|
|
source: 'terrain',
|
|
exaggeration: 1.5,
|
|
},
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fallback style using OSM raster tiles (when self-hosted tiles not available)
|
|
*/
|
|
export function createFallbackStyle(): MapStyle {
|
|
return {
|
|
version: 8,
|
|
name: 'OSM Fallback',
|
|
metadata: {
|
|
description: 'Fallback style using public OSM raster tiles',
|
|
attribution: '© OpenStreetMap contributors',
|
|
},
|
|
sources: {
|
|
osm: {
|
|
type: 'raster',
|
|
tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
|
|
tileSize: 256,
|
|
attribution: '© OpenStreetMap contributors',
|
|
},
|
|
},
|
|
layers: [
|
|
{
|
|
id: 'osm-tiles',
|
|
type: 'raster',
|
|
source: 'osm',
|
|
minzoom: 0,
|
|
maxzoom: 19,
|
|
},
|
|
],
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dark mode variant of the map style
|
|
*/
|
|
export function createDarkStyle(geoServiceUrl: string): MapStyle {
|
|
const baseStyle = createMapStyle(geoServiceUrl)
|
|
|
|
return {
|
|
...baseStyle,
|
|
name: 'GeoEdu Germany (Dark)',
|
|
layers: baseStyle.layers.map((layer: MapLayer) => {
|
|
// Adjust colors for dark mode
|
|
if (layer.id === 'background') {
|
|
return { ...layer, paint: { 'background-color': '#1a1a2e' } }
|
|
}
|
|
if (layer.id === 'water') {
|
|
return { ...layer, paint: { 'fill-color': '#1e3a5f' } }
|
|
}
|
|
if (layer.type === 'symbol') {
|
|
return {
|
|
...layer,
|
|
paint: {
|
|
...layer.paint,
|
|
'text-color': '#e0e0e0',
|
|
'text-halo-color': '#1a1a2e',
|
|
},
|
|
}
|
|
}
|
|
return layer
|
|
}),
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Terrain-focused style (for topography theme)
|
|
*/
|
|
export function createTerrainStyle(geoServiceUrl: string): MapStyle {
|
|
const baseStyle = createMapStyle(geoServiceUrl)
|
|
|
|
// Add contour lines source and layer
|
|
return {
|
|
...baseStyle,
|
|
name: 'GeoEdu Terrain',
|
|
sources: {
|
|
...baseStyle.sources,
|
|
contours: {
|
|
type: 'vector',
|
|
tiles: [`${geoServiceUrl}/api/v1/terrain/contours/{z}/{x}/{y}.pbf`],
|
|
maxzoom: 14,
|
|
},
|
|
},
|
|
layers: [
|
|
...baseStyle.layers,
|
|
{
|
|
id: 'contour-lines',
|
|
type: 'line',
|
|
source: 'contours',
|
|
'source-layer': 'contour',
|
|
minzoom: 10,
|
|
paint: {
|
|
'line-color': '#8b4513',
|
|
'line-width': ['match', ['get', 'index'], 5, 1.5, 0.5],
|
|
'line-opacity': 0.5,
|
|
},
|
|
},
|
|
{
|
|
id: 'contour-labels',
|
|
type: 'symbol',
|
|
source: 'contours',
|
|
'source-layer': 'contour',
|
|
minzoom: 12,
|
|
filter: ['==', ['%', ['get', 'ele'], 50], 0],
|
|
layout: {
|
|
'text-field': '{ele}m',
|
|
'text-font': ['Open Sans Regular'],
|
|
'text-size': 10,
|
|
'symbol-placement': 'line',
|
|
},
|
|
paint: {
|
|
'text-color': '#8b4513',
|
|
'text-halo-color': '#ffffff',
|
|
'text-halo-width': 1,
|
|
},
|
|
},
|
|
],
|
|
}
|
|
}
|