From 0c837f5de26365cfd72f5ffb586c8cb6ff6d59c3 Mon Sep 17 00:00:00 2001 From: MoritzWeber0 Date: Mon, 25 May 2026 11:40:21 +0200 Subject: [PATCH] feat: Add border point map to switzerland --- assets/js/borderPointsMap.js | 170 ++++++++++++++++++++++ assets/js/expander.js | 13 ++ assets/js/main.js | 1 + assets/sass/borderPointsMap.scss | 85 +++++++++++ assets/sass/main.scss | 1 + content/country/switzerland/index.de.md | 66 +++++++++ content/country/switzerland/index.en.md | 66 +++++++++ content/country/switzerland/index.fr.md | 66 +++++++++ i18n/de.yaml | 8 + i18n/en.yaml | 8 + i18n/fr.yaml | 8 + layouts/shortcodes/border-points-map.html | 37 +++++ 12 files changed, 529 insertions(+) create mode 100644 assets/js/borderPointsMap.js create mode 100644 assets/sass/borderPointsMap.scss create mode 100644 layouts/shortcodes/border-points-map.html diff --git a/assets/js/borderPointsMap.js b/assets/js/borderPointsMap.js new file mode 100644 index 000000000..332520696 --- /dev/null +++ b/assets/js/borderPointsMap.js @@ -0,0 +1,170 @@ +const CONSENT_KEY = "borderPointsMapConsent"; +const createMap = (container, points) => { + const map = L.map(container, { + scrollWheelZoom: false, + minZoom: 5, + }); + + const osmBase = new L.TileLayer( + "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + { + attribution: + '© OpenStreetMap contributors', + maxZoom: 19, + }, + ).addTo(map); + + const openrailwaymap = new L.TileLayer( + "https://tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png", + { + attribution: + '© OpenStreetMap contributors, Style: CC-BY-SA 2.0 OpenRailwayMap and OpenStreetMap', + minZoom: 5, + maxZoom: 19, + tileSize: 256, + }, + ).addTo(map); + + const bounds = []; + const markerIcon = L.divIcon({ + className: "o-border-points-map__marker", + html: '', + iconSize: [24, 24], + iconAnchor: [12, 24], + popupAnchor: [0, -24], + }); + points.forEach((point) => { + const marker = L.marker([point.lat, point.lng], { icon: markerIcon }).addTo( + map, + ); + marker.bindPopup(point.name); + bounds.push([point.lat, point.lng]); + }); + + if (bounds.length) { + const pointsBounds = L.latLngBounds(bounds).pad(0.5); + map.fitBounds(pointsBounds, { padding: [20, 20] }); + } + + return map; +}; + +const loadLeafletAssets = () => + new Promise((resolve, reject) => { + if (window.L) { + resolve(); + return; + } + + const existingCss = document.querySelector("link[data-leaflet]"); + if (!existingCss) { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"; + link.integrity = "sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="; + link.crossOrigin = ""; + link.setAttribute("data-leaflet", "true"); + document.head.appendChild(link); + } + + const existingScript = document.querySelector("script[data-leaflet]"); + if (existingScript) { + if (window.L) { + resolve(); + } else { + existingScript.addEventListener("load", () => resolve()); + } + return; + } + + const script = document.createElement("script"); + script.src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"; + script.integrity = "sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="; + script.crossOrigin = ""; + script.setAttribute("data-leaflet", "true"); + script.onload = () => resolve(); + script.onerror = () => reject(); + document.body.appendChild(script); + }); + +const initBorderPointsMap = (wrapper) => { + const pointsData = wrapper.dataset.points; + if (!pointsData) { + return; + } + + let points = []; + try { + points = JSON.parse(pointsData); + } catch (error) { + return; + } + + const mapContainer = wrapper.querySelector( + "[data-border-points-map-container]", + ); + const consentLayer = wrapper.querySelector( + "[data-border-points-map-consent]", + ); + const acceptButton = wrapper.querySelector("[data-border-points-map-accept]"); + const optOutButton = wrapper.querySelector("[data-border-points-map-optout]"); + + if (!mapContainer || !consentLayer || !acceptButton || !optOutButton) { + return; + } + + const storedConsent = localStorage.getItem(CONSENT_KEY) === "true"; + let mapInstance = null; + let resizeObserver = null; + + const showMap = async () => { + consentLayer.hidden = true; + optOutButton.hidden = false; + if (!mapInstance) { + await loadLeafletAssets(); + mapInstance = createMap(mapContainer, points); + resizeObserver = new ResizeObserver(() => { + mapInstance.invalidateSize(); + }); + resizeObserver.observe(mapContainer); + } + }; + + const showConsent = () => { + consentLayer.hidden = false; + optOutButton.hidden = true; + }; + + if (storedConsent) { + showMap(); + } else { + showConsent(); + } + + acceptButton.addEventListener("click", async () => { + localStorage.setItem(CONSENT_KEY, "true"); + await showMap(); + }); + + optOutButton.addEventListener("click", () => { + localStorage.removeItem(CONSENT_KEY); + showConsent(); + if (mapInstance) { + mapInstance.off(); + mapInstance.remove(); + mapInstance = null; + mapContainer.innerHTML = ""; + if (resizeObserver) { + resizeObserver.disconnect(); + resizeObserver = null; + } + } + }); +}; + +const initializeBorderPointsMaps = () => { + const wrappers = document.querySelectorAll("[data-border-points-map]"); + wrappers.forEach((wrapper) => initBorderPointsMap(wrapper)); +}; + +window.initializeBorderPointsMaps = initializeBorderPointsMaps; diff --git a/assets/js/expander.js b/assets/js/expander.js index 007a8e967..1619dddc1 100644 --- a/assets/js/expander.js +++ b/assets/js/expander.js @@ -9,5 +9,18 @@ function expandTargetedExpander() { } } +function initializeExpanderToggles() { + const expanders = document.querySelectorAll(".o-expander"); + expanders.forEach((expander) => { + expander.addEventListener("toggle", (event) => { + const currentTarget = event.currentTarget; + if (currentTarget.open && window.initializeBorderPointsMaps) { + window.initializeBorderPointsMaps(); + } + }); + }); +} + document.addEventListener("DOMContentLoaded", expandTargetedExpander); +document.addEventListener("DOMContentLoaded", initializeExpanderToggles); window.addEventListener("hashchange", expandTargetedExpander); diff --git a/assets/js/main.js b/assets/js/main.js index 250f2f335..59585dd36 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -11,3 +11,4 @@ import "./expander.js"; import "./dialog.js"; import "./fipValidityComparison.js"; import "./search.js"; +import "./borderPointsMap.js"; diff --git a/assets/sass/borderPointsMap.scss b/assets/sass/borderPointsMap.scss new file mode 100644 index 000000000..60b7dba24 --- /dev/null +++ b/assets/sass/borderPointsMap.scss @@ -0,0 +1,85 @@ +.o-border-points-map { + margin: 1.5rem 0 2rem; + + &__frame { + position: relative; + border-radius: var(--border-radius-m); + overflow: hidden; + box-shadow: var(--box-shadow); + } + + &__map { + width: 100%; + min-height: 32rem; + background: linear-gradient(160deg, #111827 0%, #1f2937 55%, #0b1120 100%); + } + + &__consent { + position: absolute; + inset: 0; + background: linear-gradient( + 135deg, + rgba(16, 18, 24, 0.82), + rgba(7, 11, 24, 0.92) + ); + color: #fff; + opacity: 1; + visibility: visible; + transition: + opacity 0.2s ease, + visibility 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; + } + + &__consent[hidden] { + opacity: 0; + visibility: hidden; + } + + &__consent-content { + max-width: 42rem; + text-align: center; + display: grid; + gap: 1rem; + } + + &__consent-title { + font-size: 1.8rem; + font-weight: 600; + margin: 0; + } + + &__opt-out { + margin-top: 1rem; + } + + .a-button { + background-color: rgba(255, 255, 255, 0.12); + color: #fff; + border: 0.1rem solid rgba(255, 255, 255, 0.4); + } + + .a-button:hover, + .a-button:focus { + background-color: rgba(255, 255, 255, 0.2); + } + + &__marker { + color: #b91c1c; + } + + &__marker .a-icon { + width: 2.4rem; + height: 2.4rem; + display: block; + } + + @media (max-width: 600px) { + &__map { + min-height: 24rem; + } + } +} diff --git a/assets/sass/main.scss b/assets/sass/main.scss index 3aed6e2f5..eff4cc0e7 100644 --- a/assets/sass/main.scss +++ b/assets/sass/main.scss @@ -18,6 +18,7 @@ @import "button.scss"; @import "startpage.scss"; @import "interactiveMap.scss"; +@import "borderPointsMap.scss"; @import "dropdown.scss"; @import "trainCategory.scss"; @import "tag.scss"; diff --git a/content/country/switzerland/index.de.md b/content/country/switzerland/index.de.md index b5c310fae..77aa53ad1 100644 --- a/content/country/switzerland/index.de.md +++ b/content/country/switzerland/index.de.md @@ -10,6 +10,70 @@ params: - Gondelbahn Grindelwald – Männlichen (GGM) - Seilbahn Mürren – Allmendhubel (SMA) - Luftseilbahn Stechelberg (Mürren – Schilthorn) (LSMS) + border_points_map: + - name: Basel Bad Bf + lat: 47.5674616 + lng: 7.6073096 + - name: Konstanz + lat: 47.6588689 + lng: 9.1772175 + - name: Schaffhausen + lat: 47.6981052 + lng: 8.6326837 + - name: Waldshut + lat: 47.6212145 + lng: 8.2195188 + - name: Buchs (SG) + lat: 47.1687116 + lng: 9.4787656 + - name: St. Margrethen + lat: 47.4531468 + lng: 9.6382298 + - name: Chiasso + lat: 45.8322299 + lng: 9.0314153 + - name: Domodossola + lat: 46.1154217 + lng: 8.2964016 + - name: Iselle di Trasquera [> Domodossola] + lat: 46.2070874 + lng: 8.2070479 + - name: Locarno [> Domodossola] + lat: 46.1723153 + lng: 8.8013509 + - name: Lugano [> Porto Ceresio] + lat: 46.0054989 + lng: 8.9468486 + - name: Pino transito + lat: 46.0986592 + lng: 8.7370481 + - name: Tirano + lat: 46.215738 + lng: 10.1666093 + - name: Basel SBB + lat: 47.5476225 + lng: 7.5896417 + - name: Delle + lat: 47.5055161 + lng: 7.0099855 + - name: Genève [> Bellegarde] + lat: 46.2102288 + lng: 6.1426218 + - name: Annemasse + lat: 46.1995889 + lng: 6.2371108 + - name: Le Châtelard-Frontière (fr) + lat: 46.0532873 + lng: 6.9502321 + - name: Le Locle-Col-des-Roches + lat: 47.0499315 + lng: 6.7261101 + - name: Pontarlier + lat: 46.9008879 + lng: 6.35338 + - name: Vallorbe + lat: 46.7122861 + lng: 6.3706406 --- ## FIP Nutzung @@ -34,6 +98,8 @@ Die Schweiz verfügt über eines der dichtesten Bahnnetze der Welt. Trotz der to {{% expander "Grenzpunkte" border %}} +{{< border-points-map >}} + | Land | Grenzpunkte | | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | [Deutschland](/country/germany) ([DB AG](/operator/db)) | Basel Bad Bf, Konstanz, Schaffhausen, Waldshut | diff --git a/content/country/switzerland/index.en.md b/content/country/switzerland/index.en.md index a083c7f07..166f9c6fe 100644 --- a/content/country/switzerland/index.en.md +++ b/content/country/switzerland/index.en.md @@ -10,6 +10,70 @@ params: - Gondelbahn Grindelwald – Männlichen (GGM) - Seilbahn Mürren – Allmendhubel (SMA) - Luftseilbahn Stechelberg (Mürren – Schilthorn) (LSMS) + border_points_map: + - name: Basel Bad Bf + lat: 47.5674616 + lng: 7.6073096 + - name: Konstanz + lat: 47.6588689 + lng: 9.1772175 + - name: Schaffhausen + lat: 47.6981052 + lng: 8.6326837 + - name: Waldshut + lat: 47.6212145 + lng: 8.2195188 + - name: Buchs (SG) + lat: 47.1687116 + lng: 9.4787656 + - name: St. Margrethen + lat: 47.4531468 + lng: 9.6382298 + - name: Chiasso + lat: 45.8322299 + lng: 9.0314153 + - name: Domodossola + lat: 46.1154217 + lng: 8.2964016 + - name: Iselle di Trasquera [> Domodossola] + lat: 46.2070874 + lng: 8.2070479 + - name: Locarno [> Domodossola] + lat: 46.1723153 + lng: 8.8013509 + - name: Lugano [> Porto Ceresio] + lat: 46.0054989 + lng: 8.9468486 + - name: Pino transito + lat: 46.0986592 + lng: 8.7370481 + - name: Tirano + lat: 46.215738 + lng: 10.1666093 + - name: Basel SBB + lat: 47.5476225 + lng: 7.5896417 + - name: Delle + lat: 47.5055161 + lng: 7.0099855 + - name: Genève [> Bellegarde] + lat: 46.2102288 + lng: 6.1426218 + - name: Annemasse + lat: 46.1995889 + lng: 6.2371108 + - name: Le Châtelard-Frontière (fr) + lat: 46.0532873 + lng: 6.9502321 + - name: Le Locle-Col-des-Roches + lat: 47.0499315 + lng: 6.7261101 + - name: Pontarlier + lat: 46.9008879 + lng: 6.35338 + - name: Vallorbe + lat: 46.7122861 + lng: 6.3706406 --- ## FIP Information @@ -34,6 +98,8 @@ Switzerland has one of the densest rail networks in the world. Despite challengi {{% expander "Border Points" border %}} +{{< border-points-map >}} + | Country | Border Points | | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | [Germany](/country/germany) ([DB AG](/operator/db)) | Basel Bad Bf, Konstanz, Schaffhausen, Waldshut | diff --git a/content/country/switzerland/index.fr.md b/content/country/switzerland/index.fr.md index bd56dbca5..ffbdf20a0 100644 --- a/content/country/switzerland/index.fr.md +++ b/content/country/switzerland/index.fr.md @@ -10,6 +10,70 @@ params: - Gondelbahn Grindelwald – Männlichen (GGM) - Seilbahn Mürren – Allmendhubel (SMA) - Luftseilbahn Stechelberg (Mürren – Schilthorn) (LSMS) + border_points_map: + - name: Basel Bad Bf + lat: 47.5674616 + lng: 7.6073096 + - name: Konstanz + lat: 47.6588689 + lng: 9.1772175 + - name: Schaffhausen + lat: 47.6981052 + lng: 8.6326837 + - name: Waldshut + lat: 47.6212145 + lng: 8.2195188 + - name: Buchs (SG) + lat: 47.1687116 + lng: 9.4787656 + - name: St. Margrethen + lat: 47.4531468 + lng: 9.6382298 + - name: Chiasso + lat: 45.8322299 + lng: 9.0314153 + - name: Domodossola + lat: 46.1154217 + lng: 8.2964016 + - name: Iselle di Trasquera [> Domodossola] + lat: 46.2070874 + lng: 8.2070479 + - name: Locarno [> Domodossola] + lat: 46.1723153 + lng: 8.8013509 + - name: Lugano [> Porto Ceresio] + lat: 46.0054989 + lng: 8.9468486 + - name: Pino transito + lat: 46.0986592 + lng: 8.7370481 + - name: Tirano + lat: 46.215738 + lng: 10.1666093 + - name: Basel SBB + lat: 47.5476225 + lng: 7.5896417 + - name: Delle + lat: 47.5055161 + lng: 7.0099855 + - name: Genève [> Bellegarde] + lat: 46.2102288 + lng: 6.1426218 + - name: Annemasse + lat: 46.1995889 + lng: 6.2371108 + - name: Le Châtelard-Frontière (fr) + lat: 46.0532873 + lng: 6.9502321 + - name: Le Locle-Col-des-Roches + lat: 47.0499315 + lng: 6.7261101 + - name: Pontarlier + lat: 46.9008879 + lng: 6.35338 + - name: Vallorbe + lat: 46.7122861 + lng: 6.3706406 --- ## Informations FIP @@ -34,6 +98,8 @@ La Suisse possède l’un des réseaux ferroviaires les plus denses au monde. Ma {{% expander "Points frontières" border %}} +{{< border-points-map >}} + | Pays | Points frontières | | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | [Allemagne](/country/germany) ([DB AG](/operator/db)) | Basel Bad Bf, Konstanz, Schaffhausen, Waldshut | diff --git a/i18n/de.yaml b/i18n/de.yaml index 4d34eba30..c2d56c332 100644 --- a/i18n/de.yaml +++ b/i18n/de.yaml @@ -15,6 +15,14 @@ booking: reservation-costs: Reservierungskosten visit-additional-information-website: Weitere Informationen visit-booking-website: Zur Buchungsseite +borderPointsMap: + consent: + accept: Karte laden + text: >- + Diese Karte wird von OpenRailwayMap und OpenStreetMap geladen. Beim Laden + wird deine IP-Adresse an deren Server übertragen. + title: Grenzpunktekarte laden + optOut: Karte deaktivieren contact: accessibility: label: 'Feedback zur Website: Barrierefreiheit' diff --git a/i18n/en.yaml b/i18n/en.yaml index 976af2d84..ab9c1f3e9 100644 --- a/i18n/en.yaml +++ b/i18n/en.yaml @@ -15,6 +15,14 @@ booking: reservation-costs: Reservation Costs visit-additional-information-website: Additional Information visit-booking-website: To Booking Website +borderPointsMap: + consent: + accept: Load map + text: >- + This map is loaded from OpenRailwayMap and OpenStreetMap. Loading it will + send your IP address to their servers. + title: Load border points map + optOut: Disable map contact: accessibility: label: 'Website Feedback: Accessibility' diff --git a/i18n/fr.yaml b/i18n/fr.yaml index 2139514c5..02213ec88 100644 --- a/i18n/fr.yaml +++ b/i18n/fr.yaml @@ -15,6 +15,14 @@ booking: reservation-costs: Frais de réservation visit-additional-information-website: Informations complémentaires visit-booking-website: Aller sur le site de réservation +borderPointsMap: + consent: + accept: Charger la carte + text: >- + Cette carte est chargée depuis OpenRailwayMap et OpenStreetMap. Son + chargement envoie votre adresse IP à leurs serveurs. + title: Charger la carte des points frontières + optOut: Désactiver la carte contact: accessibility: label: 'Retour sur le site Web : Accessibilité' diff --git a/layouts/shortcodes/border-points-map.html b/layouts/shortcodes/border-points-map.html new file mode 100644 index 000000000..14a3e7559 --- /dev/null +++ b/layouts/shortcodes/border-points-map.html @@ -0,0 +1,37 @@ +{{ with .Page.Params.border_points_map }} +
+
+
+ +
+ +
+{{ end }}