Tile-Server-Spike: MapLibre-Testseite /maplibre-test (vendored maplibre-gl+pmtiles)
- /maplibre-test rendert bayern.pmtiles per pmtiles-Protokoll, minimaler Geometrie-Style (OpenMapTiles-Layer, keine Glyphs), Touren-Demo als GeoJSON-Line - maplibre-gl 4.7.1 + pmtiles 3.2.1 lokal vendored (CSP script-src 'self') - CSP: worker-src blob: (MapLibre-Worker)
This commit is contained in:
parent
e5a2953a80
commit
a561759034
6 changed files with 1928 additions and 0 deletions
|
|
@ -111,6 +111,7 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
|||
response.headers["Content-Security-Policy"] = (
|
||||
"default-src 'self'; "
|
||||
"script-src 'self' https://umami.motocamp.de; " # ohne unsafe-inline/eval — alle Inline-Scripts extrahiert
|
||||
"worker-src blob:; " # MapLibre GL spawnt Web-Worker aus blob-URLs (Tile-Server)
|
||||
"style-src 'self' 'unsafe-inline'; " # Inline-Styles bleiben (zu viele Fundstellen für jetzt)
|
||||
"img-src 'self' data: blob: https:; "
|
||||
"connect-src 'self' https:; "
|
||||
|
|
@ -422,6 +423,12 @@ async def serve_tile(filename: str, request: Request):
|
|||
# Kein Range → ganze Datei streamen (pmtiles macht das normalerweise nicht).
|
||||
return FileResponse(path, media_type="application/octet-stream", headers=base_headers)
|
||||
|
||||
|
||||
@app.get("/maplibre-test")
|
||||
async def maplibre_test():
|
||||
# Spike-Testseite: MapLibre rendert /tiles/*.pmtiles (Geometrie-Style, kein Glyph).
|
||||
return FileResponse(os.path.join(STATIC_DIR, "maplibre-test.html"), media_type="text/html")
|
||||
|
||||
# User-generierte Medien (Fotos aus Tagebuch, Giftköder-Alarm, etc.)
|
||||
MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")
|
||||
os.makedirs(MEDIA_DIR, exist_ok=True)
|
||||
|
|
|
|||
86
backend/static/js/maplibre-test.js
Normal file
86
backend/static/js/maplibre-test.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// Tile-Server-Spike: MapLibre rendert unsere eigene bayern.pmtiles.
|
||||
// Minimaler Geometrie-Style (OpenMapTiles-Schema von planetiler), KEINE Labels →
|
||||
// keine Glyphs/Fonts nötig. Touren-Overlay als GeoJSON-Line obendrauf (basemap-unabhängig).
|
||||
(function () {
|
||||
'use strict';
|
||||
var statusEl = document.getElementById('status');
|
||||
function setStatus(t) { if (statusEl) statusEl.textContent = t; }
|
||||
|
||||
// pmtiles-Protokoll registrieren (liest Tiles per HTTP-Range aus dem Single-File).
|
||||
var protocol = new pmtiles.Protocol();
|
||||
maplibregl.addProtocol('pmtiles', protocol.tile);
|
||||
|
||||
var TILES = 'pmtiles://' + window.location.origin + '/tiles/bayern.pmtiles';
|
||||
|
||||
var style = {
|
||||
version: 8,
|
||||
// Kein 'glyphs'/'sprite' — minimaler Style ohne Text-/Icon-Layer.
|
||||
sources: {
|
||||
by: { type: 'vector', url: TILES }
|
||||
},
|
||||
layers: [
|
||||
{ id: 'bg', type: 'background', paint: { 'background-color': '#f4f1ec' } },
|
||||
{ id: 'landcover', type: 'fill', source: 'by', 'source-layer': 'landcover',
|
||||
paint: { 'fill-color': '#d6e6c3', 'fill-opacity': 0.6 } },
|
||||
{ id: 'park', type: 'fill', source: 'by', 'source-layer': 'park',
|
||||
paint: { 'fill-color': '#c8e6b0', 'fill-opacity': 0.5 } },
|
||||
{ id: 'water', type: 'fill', source: 'by', 'source-layer': 'water',
|
||||
paint: { 'fill-color': '#a0c8f0' } },
|
||||
{ id: 'waterway', type: 'line', source: 'by', 'source-layer': 'waterway',
|
||||
paint: { 'line-color': '#a0c8f0', 'line-width': 1 } },
|
||||
{ id: 'roads', type: 'line', source: 'by', 'source-layer': 'transportation',
|
||||
paint: {
|
||||
'line-color': '#ffffff',
|
||||
'line-width': ['interpolate', ['linear'], ['zoom'], 6, 0.5, 12, 1.5, 16, 4]
|
||||
} },
|
||||
{ id: 'road-casing', type: 'line', source: 'by', 'source-layer': 'transportation',
|
||||
minzoom: 11,
|
||||
paint: { 'line-color': '#d9cfc2', 'line-gap-width': 1,
|
||||
'line-width': ['interpolate', ['linear'], ['zoom'], 11, 0.5, 16, 2] } },
|
||||
{ id: 'buildings', type: 'fill', source: 'by', 'source-layer': 'building',
|
||||
minzoom: 13,
|
||||
paint: { 'fill-color': '#e3dccf', 'fill-outline-color': '#d0c8ba' } },
|
||||
{ id: 'boundary', type: 'line', source: 'by', 'source-layer': 'boundary',
|
||||
paint: { 'line-color': '#b08ac0', 'line-dasharray': [2, 2], 'line-width': 1 } }
|
||||
]
|
||||
};
|
||||
|
||||
var map = new maplibregl.Map({
|
||||
container: 'map',
|
||||
style: style,
|
||||
center: [11.576, 48.137], // München
|
||||
zoom: 11,
|
||||
hash: true
|
||||
});
|
||||
map.addControl(new maplibregl.NavigationControl(), 'top-right');
|
||||
map.addControl(new maplibregl.ScaleControl());
|
||||
|
||||
map.on('load', function () {
|
||||
setStatus('✅ Tiles geladen — Range-Requests laufen');
|
||||
|
||||
// Touren-Overlay: GeoJSON-Linie (Demo, München-Innenstadt) — Basemap-unabhängig.
|
||||
map.addSource('tour', {
|
||||
type: 'geojson',
|
||||
data: {
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: [
|
||||
[11.5755, 48.1374], [11.5780, 48.1390], [11.5820, 48.1402],
|
||||
[11.5860, 48.1395], [11.5895, 48.1378], [11.5910, 48.1350]
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
map.addLayer({
|
||||
id: 'tour-line', type: 'line', source: 'tour',
|
||||
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
||||
paint: { 'line-color': '#e8590c', 'line-width': 5, 'line-opacity': 0.9 }
|
||||
});
|
||||
});
|
||||
|
||||
map.on('error', function (e) {
|
||||
setStatus('⚠️ Fehler: ' + (e && e.error ? e.error.message : 'unbekannt'));
|
||||
if (e && e.error) console.error('MapLibre error:', e.error);
|
||||
});
|
||||
})();
|
||||
1
backend/static/js/vendor/maplibre-gl.css
vendored
Normal file
1
backend/static/js/vendor/maplibre-gl.css
vendored
Normal file
File diff suppressed because one or more lines are too long
59
backend/static/js/vendor/maplibre-gl.js
vendored
Normal file
59
backend/static/js/vendor/maplibre-gl.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1738
backend/static/js/vendor/pmtiles.js
vendored
Normal file
1738
backend/static/js/vendor/pmtiles.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
37
backend/static/maplibre-test.html
Normal file
37
backend/static/maplibre-test.html
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title>Ban Yaro — MapLibre Tile-Spike</title>
|
||||
<link rel="stylesheet" href="/js/vendor/maplibre-gl.css">
|
||||
<style>
|
||||
html, body { margin: 0; height: 100%; }
|
||||
#map { position: absolute; inset: 0; }
|
||||
#hud {
|
||||
position: absolute; top: 10px; left: 10px; z-index: 5;
|
||||
background: rgba(255,255,255,.9); padding: 8px 12px; border-radius: 8px;
|
||||
font: 13px/1.4 system-ui, sans-serif; box-shadow: 0 1px 6px rgba(0,0,0,.2);
|
||||
max-width: 260px;
|
||||
}
|
||||
#hud b { color: #2e7d32; }
|
||||
.attr {
|
||||
position: absolute; bottom: 0; right: 0; z-index: 5;
|
||||
background: rgba(255,255,255,.7); padding: 2px 6px;
|
||||
font: 11px system-ui, sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
<div id="hud">
|
||||
<b>Tile-Spike</b> — eigene PMTiles (Bayern)<br>
|
||||
Quelle: <code>/tiles/bayern.pmtiles</code><br>
|
||||
<span id="status">lädt…</span>
|
||||
</div>
|
||||
<div class="attr">© OpenStreetMap contributors</div>
|
||||
<script src="/js/vendor/maplibre-gl.js"></script>
|
||||
<script src="/js/vendor/pmtiles.js"></script>
|
||||
<script src="/js/maplibre-test.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue