Compare commits
4
Commits
7d2949bf42
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76d68b013a | ||
|
|
dab258140f | ||
|
|
fb4310666c | ||
|
|
bfcb2ce6c0 |
@@ -9,3 +9,8 @@ Thumbs.db
|
||||
|
||||
# Persönliche Notizen – nie pushen
|
||||
/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/
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
Hugo-Neubau von [map.offener-prozess.de](https://map.offener-prozess.de/)
|
||||
(Ablösung des alten WordPress/Listable-Setups). Interaktive Karte der
|
||||
Schauplätze des NSU-Komplexes mit **Leaflet + OpenStreetMap** (statt der alten
|
||||
Google-Maps-/Mapbox-Einbindung), im Layout ans Original angelehnt (Split-View:
|
||||
Schauplätze des NSU-Komplexes mit **Leaflet** und einer **lokalen Deutschland-Karte**
|
||||
(Bundesländer-GeoJSON im Repo, keine externen Kacheln/CDN – DSGVO-freundlich),
|
||||
im Layout ans Original angelehnt (Split-View:
|
||||
Kartenliste links, Karte rechts), umgesetzt mit dem Theme
|
||||
[`themes/op-dz/`](themes/op-dz/) (UIkit 3, Schrift *ABC Favorit*, alle Assets
|
||||
lokal/DSGVO-konform).
|
||||
@@ -71,11 +72,22 @@ Der Punkt erscheint automatisch auf allen Karten (Start, Kategorie) und bekommt
|
||||
eine Detailseite mit Mini-Karte + „Anfahrt per OpenStreetMap". Ein Bild
|
||||
`cover.jpg` im selben Ordner wird als Card-/Kopfbild genutzt.
|
||||
|
||||
## Karte / Kacheln
|
||||
## Karte (komplett lokal, keine externen CDN)
|
||||
|
||||
Standard: OpenStreetMap. Eigener Tile-Server (OSM-Usage-Policy!) in `hugo.toml`
|
||||
unter `[params.map]` (`tileURL`, `attribution`). 118 Marker werden per
|
||||
Leaflet.markercluster gebündelt; Farbe = Kategorie (`data/kategorien.yaml`).
|
||||
Die Basemap ist **keine** externe Kachel (kein OpenStreetMap/CDN, DSGVO-freundlich),
|
||||
sondern eine lokale **Bundesländer-GeoJSON** unter
|
||||
[`static/geo/deutschland-bundeslaender.geojson`](static/geo/deutschland-bundeslaender.geojson)
|
||||
(amtliche BKG-Geometrie, vereinfacht, ~40 KB). `map.js` lädt sie same-origin per
|
||||
`fetch` und rendert sie vektoriell mit Leaflet (Bundesländer-Grenzen blau, Land
|
||||
cremefarben). 118 Marker werden per Leaflet.markercluster gebündelt; Farbe =
|
||||
Kategorie (`data/kategorien.yaml`). Attribution steht als **Text** in `hugo.toml`
|
||||
unter `[params.map]` (kein Request).
|
||||
|
||||
Tradeoff: keine Straßen-Detailtiefe beim Reinzoomen – die Karte ist eine
|
||||
Übersichts-/Verortungskarte. Die Detailseiten zeigen den Ort als Pin auf der
|
||||
ganzen Deutschland-Silhouette (Locator). Neue amtliche Geometrie:
|
||||
[isellsoap/deutschlandGeoJSON](https://github.com/isellsoap/deutschlandGeoJSON)
|
||||
(`2_bundeslaender/3_mittel.geo.json` für mehr Detail).
|
||||
|
||||
## Externe Quellen
|
||||
|
||||
|
||||
@@ -22,8 +22,12 @@ removePathAccents = true # ä/ö/ü → a/o/u in URLs
|
||||
email = "info@offener-prozess.de"
|
||||
|
||||
[params.map]
|
||||
tileURL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
attribution = '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>-Mitwirkende'
|
||||
# Kein externes CDN: Basemap = selbst gehostete Vektor-Tiles (Protomaps/OSM)
|
||||
# als .pmtiles auf dem eigenen Server unter /tiles/, gerendert im Browser via
|
||||
# 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
|
||||
@@ -47,6 +51,11 @@ removePathAccents = true # ä/ö/ü → a/o/u in URLs
|
||||
name = "Schauplätze"
|
||||
url = "/schauplaetze/"
|
||||
weight = 20
|
||||
[[languages.de.menus.main]]
|
||||
name = "Alle Schauplätze"
|
||||
parent = "Schauplätze"
|
||||
url = "/schauplaetze/"
|
||||
weight = -1
|
||||
[[languages.de.menus.main]]
|
||||
name = "Aufenthaltsorte"
|
||||
parent = "Schauplätze"
|
||||
@@ -113,6 +122,11 @@ removePathAccents = true # ä/ö/ü → a/o/u in URLs
|
||||
name = "Locations"
|
||||
url = "/schauplaetze/"
|
||||
weight = 20
|
||||
[[languages.en.menus.main]]
|
||||
name = "All locations"
|
||||
parent = "Locations"
|
||||
url = "/schauplaetze/"
|
||||
weight = -1
|
||||
[[languages.en.menus.main]]
|
||||
name = "Whereabouts"
|
||||
parent = "Locations"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -474,9 +474,11 @@ a:hover { color: var(--op-blau-dunkel); }
|
||||
width: 100%;
|
||||
height: 560px;
|
||||
border: 2px solid var(--op-blau);
|
||||
background: var(--op-papier);
|
||||
background: #d9e1ea; /* neutrale Fläche „ausserhalb DE" (kein Tile-Grau) */
|
||||
z-index: 0; /* unter der Sticky-Navbar halten */
|
||||
}
|
||||
/* Leaflet-eigener Container-Hintergrund (ohne Tiles) an den Hausstil angleichen */
|
||||
.op-map .leaflet-container { background: #d9e1ea; }
|
||||
.op-map--klein { height: 320px; }
|
||||
|
||||
/* Leaflet-Popup im Hausstil: Bild oben, gelber Body (wie im Original) */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* =========================================================================
|
||||
OP·Karte – Leaflet + OpenStreetMap
|
||||
- farbige Kreis-Marker je Kategorie, Clustering (markercluster)
|
||||
OP·Karte – Leaflet mit LOKALER Deutschland-Karte (keine externen Tiles/CDN)
|
||||
- Basemap = Bundesländer-GeoJSON aus dem Repo (static/geo/…), vektoriell
|
||||
- farbige Pin-Marker je Kategorie, Clustering (markercluster)
|
||||
- Kategorie-Filter (Checkboxen) + Textsuche, synchron mit der Card-Liste
|
||||
- Klick auf Card öffnet den zugehörigen Marker
|
||||
Daten kommen aus <script type="application/json" id="op-map-data">.
|
||||
@@ -18,15 +19,98 @@
|
||||
var places = payload.places || [];
|
||||
var colors = payload.colors || {};
|
||||
var defaultColor = payload.defaultColor || "#1e2758";
|
||||
var tileUrl = payload.tileUrl || "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
|
||||
var attribution = payload.attribution ||
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>-Mitwirkende';
|
||||
var klein = !!payload.klein;
|
||||
var geojsonUrl = payload.geojsonUrl || "";
|
||||
var pmtilesUrl = payload.pmtilesUrl || "";
|
||||
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, { scrollWheelZoom: false });
|
||||
L.tileLayer(tileUrl, { maxZoom: 19, attribution: attribution }).addTo(map);
|
||||
var map = L.map(el, {
|
||||
scrollWheelZoom: false,
|
||||
minZoom: 5, maxZoom: 18,
|
||||
attributionControl: true
|
||||
});
|
||||
map.attributionControl.setPrefix(false).addAttribution(attribution);
|
||||
map.on("focus", function () { map.scrollWheelZoom.enable(); });
|
||||
map.on("blur", function () { map.scrollWheelZoom.disable(); });
|
||||
|
||||
// Ausschnitt fest auf Deutschland begrenzen: kein Wegscrollen ins Ausland.
|
||||
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) {
|
||||
fetch(geojsonUrl)
|
||||
.then(function (r) { return r.ok ? r.json() : null; })
|
||||
.then(function (geo) {
|
||||
if (!geo) { fitGermanyIfNeeded(); return; }
|
||||
if (!tilesActive) {
|
||||
L.geoJSON(geo, {
|
||||
style: { color: "#1e2758", weight: 1, opacity: .85, fillColor: "#f4f1e6", fillOpacity: 1 },
|
||||
interactive: false
|
||||
}).addTo(map).bringToBack();
|
||||
}
|
||||
addGermanyMask(geo);
|
||||
fitGermanyIfNeeded();
|
||||
})
|
||||
.catch(function () { fitGermanyIfNeeded(); });
|
||||
}
|
||||
|
||||
function fitGermanyIfNeeded() {
|
||||
// Nur bei leerem Ergebnis ganz Deutschland zeigen. Die Detail-Minikarte
|
||||
// (klein) bleibt auf dem Strassenzoom aus apply() (maxZoom 17), damit man
|
||||
// Strasse/Haus des Schauplatzes sieht.
|
||||
if (!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 group = useCluster
|
||||
? L.markerClusterGroup({ maxClusterRadius: 45, showCoverageOnHover: false })
|
||||
@@ -93,8 +177,13 @@
|
||||
});
|
||||
var countCards = cards.filter(function (c) { return !c.classList.contains("is--hidden"); }).length;
|
||||
if (countEl) countEl.textContent = countCards;
|
||||
if (bounds.isValid()) map.fitBounds(bounds.pad(0.15));
|
||||
else if (allBounds) map.fitBounds(allBounds.pad(0.15));
|
||||
if (bounds.isValid()) {
|
||||
// Detail-Minikarte: nah an Strasse/Haus heran (Zoom 17)
|
||||
if (klein) map.fitBounds(bounds.pad(0.15), { maxZoom: 17 });
|
||||
else map.fitBounds(bounds.pad(0.15));
|
||||
}
|
||||
else if (allBounds) map.fitBounds(allBounds.pad(0.15), klein ? { maxZoom: 17 } : undefined);
|
||||
else if (germanyBounds) map.fitBounds(germanyBounds.pad(0.02));
|
||||
}
|
||||
|
||||
// --- DOM-Hooks ---
|
||||
|
||||
@@ -35,8 +35,11 @@
|
||||
"places" $places
|
||||
"colors" $colorMap
|
||||
"defaultColor" $default
|
||||
"tileUrl" (site.Params.map.tileURL | default "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png")
|
||||
"attribution" (site.Params.map.attribution | default `© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>-Mitwirkende`)
|
||||
"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)
|
||||
"attribution" (site.Params.map.attribution | default "© OpenStreetMap-Mitwirkende")
|
||||
}}
|
||||
|
||||
<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") }}
|
||||
<script src="{{ "vendor/leaflet/leaflet.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" }}
|
||||
{{ if hugo.IsProduction }}{{ $map = $map | minify | fingerprint }}{{ end }}
|
||||
<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