feat(karte): nur Deutschland anzeigen (Invers-Maske) + lokale pmtiles-Basemap verdrahten
- map.js: Invers-Maske aus Bundeslaender-GeoJSON blendet Nachbarlaender/Meer aus (Even-Odd, alle Ringe -> Enklaven Berlin/Bremen bleiben sichtbar) - map.js: Ausschnitt via setMaxBounds fest auf Deutschland begrenzt - map.js: Grenz-GeoJSON immer laden; gefuellter Umriss als Fallback ohne Tiles - scripts.html/karte.html/hugo.toml: protomaps-leaflet + pmtiles-Basemap - .gitignore: /static/tiles/ (6,7-GB-pmtiles liegt ausserhalb des Repos)
This commit is contained in:
@@ -9,3 +9,8 @@ Thumbs.db
|
|||||||
|
|
||||||
# Persönliche Notizen – nie pushen
|
# Persönliche Notizen – nie pushen
|
||||||
/Todo.md
|
/Todo.md
|
||||||
|
|
||||||
|
# Große Vektor-Basemap (6,7 GB) – außerhalb des Repos, lokal per Symlink
|
||||||
|
# nach static/tiles/ eingehängt (ln -s /home/maik/op-pmtiles/germany.pmtiles).
|
||||||
|
# Nie ins Git, auf dem Server separat unter /tiles/ bereitstellen.
|
||||||
|
/static/tiles/
|
||||||
|
|||||||
@@ -22,9 +22,12 @@ removePathAccents = true # ä/ö/ü → a/o/u in URLs
|
|||||||
email = "info@offener-prozess.de"
|
email = "info@offener-prozess.de"
|
||||||
|
|
||||||
[params.map]
|
[params.map]
|
||||||
# Keine externen Kacheln/CDN – Basemap ist die lokale Bundesländer-GeoJSON
|
# Kein externes CDN: Basemap = selbst gehostete Vektor-Tiles (Protomaps/OSM)
|
||||||
# unter static/geo/. Attribution nur als Text (kein Request).
|
# als .pmtiles auf dem eigenen Server unter /tiles/, gerendert im Browser via
|
||||||
attribution = "Geodaten: © GeoBasis-DE / BKG (Bundesländergrenzen)"
|
# protomaps-leaflet. Fallback ohne pmtiles = lokaler Umriss (static/geo/).
|
||||||
|
# pmtilesURL leer lassen → Default /tiles/germany.pmtiles (relativ zur baseURL).
|
||||||
|
flavor = "light"
|
||||||
|
attribution = "© OpenStreetMap-Mitwirkende"
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# SPRACHEN
|
# SPRACHEN
|
||||||
|
|||||||
@@ -21,41 +21,91 @@
|
|||||||
var defaultColor = payload.defaultColor || "#1e2758";
|
var defaultColor = payload.defaultColor || "#1e2758";
|
||||||
var klein = !!payload.klein;
|
var klein = !!payload.klein;
|
||||||
var geojsonUrl = payload.geojsonUrl || "";
|
var geojsonUrl = payload.geojsonUrl || "";
|
||||||
var attribution = payload.attribution || "Geodaten: © GeoBasis-DE / BKG";
|
var pmtilesUrl = payload.pmtilesUrl || "";
|
||||||
var germanyBounds = null;
|
var flavor = payload.flavor || "light";
|
||||||
|
var attribution = payload.attribution || "© OpenStreetMap-Mitwirkende";
|
||||||
|
// Statischer Deutschland-Ausschnitt (für Locator/leere Filterung ohne Marker)
|
||||||
|
var germanyBounds = L.latLngBounds([[47.2, 5.8], [55.1, 15.1]]);
|
||||||
|
|
||||||
var map = L.map(el, {
|
var map = L.map(el, {
|
||||||
scrollWheelZoom: false,
|
scrollWheelZoom: false,
|
||||||
minZoom: 5, maxZoom: 12,
|
minZoom: 5, maxZoom: 18,
|
||||||
attributionControl: true
|
attributionControl: true
|
||||||
});
|
});
|
||||||
map.attributionControl.setPrefix(false).addAttribution(attribution);
|
map.attributionControl.setPrefix(false).addAttribution(attribution);
|
||||||
map.on("focus", function () { map.scrollWheelZoom.enable(); });
|
map.on("focus", function () { map.scrollWheelZoom.enable(); });
|
||||||
map.on("blur", function () { map.scrollWheelZoom.disable(); });
|
map.on("blur", function () { map.scrollWheelZoom.disable(); });
|
||||||
|
|
||||||
// Lokale Deutschland-Karte (Bundesländer) als Vektor-Hintergrund laden.
|
// Ausschnitt fest auf Deutschland begrenzen: kein Wegscrollen ins Ausland.
|
||||||
// Same-Origin-Fetch aus dem Repo – kein externer Request.
|
map.setMaxBounds(germanyBounds.pad(0.08));
|
||||||
|
|
||||||
|
// Basemap: selbst gehostete Vektor-Tiles (Protomaps/OSM, .pmtiles auf dem
|
||||||
|
// eigenen Server – kein externes CDN). Der Browser rendert Strassen/Haeuser.
|
||||||
|
var tilesActive = false;
|
||||||
|
if (pmtilesUrl && typeof protomapsL !== "undefined" && protomapsL.leafletLayer) {
|
||||||
|
try {
|
||||||
|
protomapsL.leafletLayer({ url: pmtilesUrl, flavor: flavor, lang: "de" }).addTo(map);
|
||||||
|
tilesActive = true;
|
||||||
|
} catch (e) { tilesActive = false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grenz-GeoJSON (Bundeslaender) immer laden:
|
||||||
|
// - ohne Tiles: gefuellter Umriss als Ersatz-Hintergrund
|
||||||
|
// - IMMER: Invers-Maske, die alles ausserhalb Deutschlands abdeckt, damit
|
||||||
|
// nur Deutschland sichtbar ist (Nachbarlaender/Meer ausgeblendet)
|
||||||
if (geojsonUrl) {
|
if (geojsonUrl) {
|
||||||
fetch(geojsonUrl)
|
fetch(geojsonUrl)
|
||||||
.then(function (r) { return r.ok ? r.json() : null; })
|
.then(function (r) { return r.ok ? r.json() : null; })
|
||||||
.then(function (geo) {
|
.then(function (geo) {
|
||||||
if (!geo) return;
|
if (!geo) { fitGermanyIfNeeded(); return; }
|
||||||
var land = L.geoJSON(geo, {
|
if (!tilesActive) {
|
||||||
style: {
|
L.geoJSON(geo, {
|
||||||
color: "#1e2758", weight: 1, opacity: .85,
|
style: { color: "#1e2758", weight: 1, opacity: .85, fillColor: "#f4f1e6", fillOpacity: 1 },
|
||||||
fillColor: "#f4f1e6", fillOpacity: 1
|
interactive: false
|
||||||
},
|
}).addTo(map).bringToBack();
|
||||||
interactive: false
|
|
||||||
}).addTo(map);
|
|
||||||
land.bringToBack();
|
|
||||||
germanyBounds = land.getBounds();
|
|
||||||
// Detail-Minikarte = Verortung: ganz Deutschland mit dem einen Pin zeigen.
|
|
||||||
// Grosse Karte: nur wenn (noch) keine Marker den Ausschnitt bestimmen.
|
|
||||||
if (klein || !records.length) {
|
|
||||||
try { map.fitBounds(germanyBounds.pad(0.02)); } catch (e) {}
|
|
||||||
}
|
}
|
||||||
|
addGermanyMask(geo);
|
||||||
|
fitGermanyIfNeeded();
|
||||||
})
|
})
|
||||||
.catch(function () {});
|
.catch(function () { fitGermanyIfNeeded(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitGermanyIfNeeded() {
|
||||||
|
if (klein || !records.length) {
|
||||||
|
try { map.fitBounds(germanyBounds.pad(0.02)); } catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invers-Maske: weltgrosses Rechteck mit Deutschland als "Loechern".
|
||||||
|
// Fuellung (Papierfarbe) deckt alles ausserhalb der Landesgrenze ab.
|
||||||
|
// Even-Odd-Fuellregel: es MUESSEN alle Ringe rein (auch innere), sonst
|
||||||
|
// werden Enklaven wie Berlin/Bremen faelschlich ueberdeckt.
|
||||||
|
// Liegt im overlayPane – ueber den Tiles, aber unter den Markern.
|
||||||
|
function addGermanyMask(geo) {
|
||||||
|
var world = [[-89, -180], [-89, 180], [89, 180], [89, -180]];
|
||||||
|
var holes = [];
|
||||||
|
function toLatLng(ring) {
|
||||||
|
return ring.map(function (c) { return [c[1], c[0]]; });
|
||||||
|
}
|
||||||
|
function collect(geom) {
|
||||||
|
if (!geom) return;
|
||||||
|
if (geom.type === "Polygon") {
|
||||||
|
geom.coordinates.forEach(function (ring) { holes.push(toLatLng(ring)); });
|
||||||
|
} else if (geom.type === "MultiPolygon") {
|
||||||
|
geom.coordinates.forEach(function (poly) {
|
||||||
|
poly.forEach(function (ring) { holes.push(toLatLng(ring)); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(geo.features || []).forEach(function (f) { collect(f.geometry); });
|
||||||
|
if (geo.type === "Polygon" || geo.type === "MultiPolygon") collect(geo);
|
||||||
|
if (!holes.length) return;
|
||||||
|
L.polygon([world].concat(holes), {
|
||||||
|
stroke: false,
|
||||||
|
fill: true, fillColor: "#f6f6f1", fillOpacity: 1,
|
||||||
|
fillRule: "evenodd",
|
||||||
|
interactive: false
|
||||||
|
}).addTo(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
var useCluster = places.length > 1 && typeof L.markerClusterGroup === "function";
|
var useCluster = places.length > 1 && typeof L.markerClusterGroup === "function";
|
||||||
@@ -125,11 +175,11 @@
|
|||||||
var countCards = cards.filter(function (c) { return !c.classList.contains("is--hidden"); }).length;
|
var countCards = cards.filter(function (c) { return !c.classList.contains("is--hidden"); }).length;
|
||||||
if (countEl) countEl.textContent = countCards;
|
if (countEl) countEl.textContent = countCards;
|
||||||
if (bounds.isValid()) {
|
if (bounds.isValid()) {
|
||||||
// Auf der Detail-Minikarte nicht ins Leere zoomen: Umkreis statt Punkt
|
// Detail-Minikarte: nah an Strasse/Haus heran (Zoom 17)
|
||||||
if (klein) map.fitBounds(bounds.pad(0.15), { maxZoom: 8 });
|
if (klein) map.fitBounds(bounds.pad(0.15), { maxZoom: 17 });
|
||||||
else map.fitBounds(bounds.pad(0.15));
|
else map.fitBounds(bounds.pad(0.15));
|
||||||
}
|
}
|
||||||
else if (allBounds) map.fitBounds(allBounds.pad(0.15), klein ? { maxZoom: 8 } : undefined);
|
else if (allBounds) map.fitBounds(allBounds.pad(0.15), klein ? { maxZoom: 17 } : undefined);
|
||||||
else if (germanyBounds) map.fitBounds(germanyBounds.pad(0.02));
|
else if (germanyBounds) map.fitBounds(germanyBounds.pad(0.02));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,10 @@
|
|||||||
"colors" $colorMap
|
"colors" $colorMap
|
||||||
"defaultColor" $default
|
"defaultColor" $default
|
||||||
"klein" ($klein | default false)
|
"klein" ($klein | default false)
|
||||||
|
"pmtilesUrl" (site.Params.map.pmtilesURL | default ("tiles/germany.pmtiles" | relURL))
|
||||||
|
"flavor" (site.Params.map.flavor | default "light")
|
||||||
"geojsonUrl" ("geo/deutschland-bundeslaender.geojson" | relURL)
|
"geojsonUrl" ("geo/deutschland-bundeslaender.geojson" | relURL)
|
||||||
"attribution" (site.Params.map.attribution | default "Geodaten: © GeoBasis-DE / BKG (Bundesländergrenzen)")
|
"attribution" (site.Params.map.attribution | default "© OpenStreetMap-Mitwirkende")
|
||||||
}}
|
}}
|
||||||
|
|
||||||
<div id="op-map" class="op-map{{ if $klein }} op-map--klein{{ end }}"
|
<div id="op-map" class="op-map{{ if $klein }} op-map--klein{{ end }}"
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
{{ if or .IsHome (eq .Type "schauplaetze") (eq .Type "kategorien") (eq .Type "erinnerungsorte") }}
|
{{ if or .IsHome (eq .Type "schauplaetze") (eq .Type "kategorien") (eq .Type "erinnerungsorte") }}
|
||||||
<script src="{{ "vendor/leaflet/leaflet.js" | relURL }}"></script>
|
<script src="{{ "vendor/leaflet/leaflet.js" | relURL }}"></script>
|
||||||
<script src="{{ "vendor/markercluster/leaflet.markercluster.js" | relURL }}"></script>
|
<script src="{{ "vendor/markercluster/leaflet.markercluster.js" | relURL }}"></script>
|
||||||
|
{{/* Selbst gehostete Vektor-Basemap (Protomaps/OSM, .pmtiles) – kein externes CDN */}}
|
||||||
|
<script src="{{ "vendor/protomaps/protomaps-leaflet.js" | relURL }}"></script>
|
||||||
{{ $map := resources.Get "js/map.js" }}
|
{{ $map := resources.Get "js/map.js" }}
|
||||||
{{ if hugo.IsProduction }}{{ $map = $map | minify | fingerprint }}{{ end }}
|
{{ if hugo.IsProduction }}{{ $map = $map | minify | fingerprint }}{{ end }}
|
||||||
<script src="{{ $map.RelPermalink }}"{{ with $map.Data.Integrity }} integrity="{{ . }}"{{ end }}></script>
|
<script src="{{ $map.RelPermalink }}"{{ with $map.Data.Integrity }} integrity="{{ . }}"{{ end }}></script>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user