From fdac9b6901d4b3d819d9f412f61123731d2a58f4 Mon Sep 17 00:00:00 2001 From: ISA Date: Mon, 23 Dec 2024 12:30:45 +0100 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20Geocoder-Suchfeld=20zur=20Karte=20h?= =?UTF-8?q?inzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Leaflet-Control-Geocoder integriert, um die Suche nach Koordinaten und Adressen zu ermöglichen. - Suchfeld in der oberen linken Ecke platziert. - Automatische Marker-Platzierung und Popup-Anzeige bei erfolgreicher Suche implementiert. - Layer-Gruppen initialisiert und Suchfeld nach Layer-Setup hinzugefügt. --- components/MapComponent.js | 12 ++++++++++++ package-lock.json | 20 +++++++++++++++++++- package.json | 1 + utils/initializeMap.js | 16 +++++++++++++--- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/components/MapComponent.js b/components/MapComponent.js index 5d4cfea87..70ec7a862 100644 --- a/components/MapComponent.js +++ b/components/MapComponent.js @@ -737,6 +737,18 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => { } }); + // Fügt das Suchfeld (Geocoder) zur Karte hinzu + const geocoder = L.Control.geocoder({ + position: "topleft", // Position des Suchfeldes + defaultMarkGeocode: false, // Deaktiviert den automatischen Marker + }) + .on("markgeocode", function (e) { + const latlng = e.geocode.center; + map.setView(latlng, 15); // Zoom zur Position + L.marker(latlng).addTo(map).bindPopup(e.geocode.name).openPopup(); // Marker hinzufügen + }) + .addTo(map); // Suchfeld zur Karte hinzufügen + const updateMarkers = ({ ref, id, setState }) => { if (ref.current) { ref.current.clearLayers(); // Entferne alte Marker diff --git a/package-lock.json b/package-lock.json index 4eec2e617..fc8eb3bef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "13.12.2024 NodeMap V1.0.16.0", + "name": "23.12.2024 NodeMap V1.0.17.0", "lockfileVersion": 3, "requires": true, "packages": { @@ -15,6 +15,7 @@ "http-proxy-middleware": "^3.0.0", "leaflet": "^1.9.4", "leaflet-contextmenu": "^1.4.0", + "leaflet-control-geocoder": "^3.1.0", "leaflet.smooth_marker_bouncing": "^3.1.0", "mysql": "^2.18.1", "mysql2": "^3.11.0", @@ -7435,6 +7436,17 @@ "resolved": "https://registry.npmjs.org/leaflet-contextmenu/-/leaflet-contextmenu-1.4.0.tgz", "integrity": "sha512-BXASCmJ5bLkuJGDCpWmvGqhZi5AzeOY0IbQalfkgBcMAMfAOFSvD4y0gIQxF/XzEyLkjXaRiUpibVj4+Cf3tUA==" }, + "node_modules/leaflet-control-geocoder": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leaflet-control-geocoder/-/leaflet-control-geocoder-3.1.0.tgz", + "integrity": "sha512-0YlcjBmVq6/9vozpjMCNhxReHaZoCfoRwxKNXQXwjdfYr3TMRQi0YFmpN/dWbqOVc0fWbY1EsoV8r11G2SZ9eA==", + "optionalDependencies": { + "open-location-code": "^1.0.3" + }, + "peerDependencies": { + "leaflet": "^1.6.0" + } + }, "node_modules/leaflet.smooth_marker_bouncing": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leaflet.smooth_marker_bouncing/-/leaflet.smooth_marker_bouncing-3.1.0.tgz", @@ -8126,6 +8138,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open-location-code": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/open-location-code/-/open-location-code-1.0.3.tgz", + "integrity": "sha512-DBm14BSn40Ee241n80zIFXIT6+y8Tb0I+jTdosLJ8Sidvr2qONvymwqymVbHV2nS+1gkDZ5eTNpnOIVV0Kn2fw==", + "optional": true + }, "node_modules/ospath": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", diff --git a/package.json b/package.json index 18b4262b8..c7a295121 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "http-proxy-middleware": "^3.0.0", "leaflet": "^1.9.4", "leaflet-contextmenu": "^1.4.0", + "leaflet-control-geocoder": "^3.1.0", "leaflet.smooth_marker_bouncing": "^3.1.0", "mysql": "^2.18.1", "mysql2": "^3.11.0", diff --git a/utils/initializeMap.js b/utils/initializeMap.js index e96188983..5d7c768dc 100644 --- a/utils/initializeMap.js +++ b/utils/initializeMap.js @@ -1,11 +1,10 @@ // utils/initializeMap.js -// TODO: Add a comment -//FIXME: Add a comment -//BUG: Add a comment import L from "leaflet"; import "leaflet-contextmenu"; import "leaflet/dist/leaflet.css"; import "leaflet-contextmenu/dist/leaflet.contextmenu.css"; +import "leaflet-control-geocoder"; + import * as urls from "../config/urls.js"; import * as layers from "../config/layers.js"; import { openInNewTab } from "./openInNewTab.js"; @@ -71,6 +70,17 @@ export const initializeMap = (mapRef, setMap, setOms, setMenuItemAdded, addItems attribution: '© OpenStreetMap contributors', }).addTo(initMap); + // Suchfeld hinzufügen + const geocoder = L.Control.geocoder({ + defaultMarkGeocode: false, + }) + .on("markgeocode", function (e) { + const latlng = e.geocode.center; + initMap.setView(latlng, 15); + L.marker(latlng).addTo(initMap).bindPopup(e.geocode.name).openPopup(); + }) + .addTo(initMap); + // Initialisiere OverlappingMarkerSpiderfier const overlappingMarkerSpiderfier = new OverlappingMarkerSpiderfier(initMap, { nearbyDistance: 20, From 5483ca83a4273bbd1650501d38599719fecf5f25 Mon Sep 17 00:00:00 2001 From: ISA Date: Mon, 23 Dec 2024 14:11:08 +0100 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20Suchfeld=20optimiert=20und=20alle=20?= =?UTF-8?q?unn=C3=B6tigen=20Elemente=20entfernt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Container vereinfacht und auf Volle-Breite-Layout umgestellt. - Überflüssige Elemente im Suchfeld (z.B. Buttons) entfernt. - Eingabefeld auf maximale Breite erweitert. - Platzhaltertext auf Deutsch gesetzt. --- components/MapComponent.js | 92 +++++++++++++++++++++++++++++++++++--- pages/_app.js | 1 + public/css/geocoder.css | 42 +++++++++++++++++ 3 files changed, 128 insertions(+), 7 deletions(-) create mode 100644 public/css/geocoder.css diff --git a/components/MapComponent.js b/components/MapComponent.js index 70ec7a862..d9c7daaa0 100644 --- a/components/MapComponent.js +++ b/components/MapComponent.js @@ -3,6 +3,7 @@ import React, { useEffect, useRef, useState, useCallback } from "react"; import L from "leaflet"; import "leaflet/dist/leaflet.css"; import "leaflet-contextmenu/dist/leaflet.contextmenu.css"; + import "leaflet-contextmenu"; import * as config from "../config/config.js"; import "leaflet.smooth_marker_bouncing"; @@ -736,18 +737,95 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => { ref.current = new L.LayerGroup().addTo(map); } }); - - // Fügt das Suchfeld (Geocoder) zur Karte hinzu + //-------------------------------------------- + // Benutzerdefiniertes Icon erstellen für den Geocoder + const customIcon = L.icon({ + iconUrl: "/img/marker-icon.png", // Pfad zum Icon + iconSize: [32, 32], // Größe des Icons + iconAnchor: [16, 32], // Ankerpunkt (Mitte unten) + popupAnchor: [0, -32], // Popup-Ankerpunkt + }); + // Geocoder mit benutzerdefiniertem Icon verwenden const geocoder = L.Control.geocoder({ - position: "topleft", // Position des Suchfeldes - defaultMarkGeocode: false, // Deaktiviert den automatischen Marker + position: "topleft", + defaultMarkGeocode: false, + geocoder: new L.Control.Geocoder.Nominatim({ + serviceUrl: "https://nominatim.openstreetmap.org/", + geocodingQueryParams: { + "accept-language": "de", // Sprache auf Deutsch setzen + countrycodes: "DE", // Nur Ergebnisse aus Deutschland + }, + }), }) .on("markgeocode", function (e) { const latlng = e.geocode.center; - map.setView(latlng, 15); // Zoom zur Position - L.marker(latlng).addTo(map).bindPopup(e.geocode.name).openPopup(); // Marker hinzufügen + map.setView(latlng, 15); + + // Marker hinzufügen + L.marker(latlng, { icon: customIcon }).addTo(map).bindPopup(e.geocode.name).openPopup(); }) - .addTo(map); // Suchfeld zur Karte hinzufügen + .addTo(map); + + // Tailwind-Klassen dynamisch hinzufügen + setTimeout(() => { + // Geocoder-Container stylen + const geocoderContainer = document.querySelector(".leaflet-control-geocoder"); + if (geocoderContainer) { + // Nur minimale Klassen für Volle Breite und abgerundetes Design + geocoderContainer.classList.add( + "shadow-md", + "border", + "border-gray-300", + "rounded-full", + "bg-white", + "p-2", + "w-96" // Breite des gesamten Suchfeldes + ); + + // Alle anderen Elemente im Container entfernen außer Eingabefeld + Array.from(geocoderContainer.children).forEach((child) => { + if (!child.querySelector("input")) { + child.remove(); // Löscht alle Elemente außer dem Eingabefeld + } + }); + } + + // Eingabefeld stylen und Platzhaltertext setzen + const geocoderInput = document.querySelector(".leaflet-control-geocoder input"); + if (geocoderInput) { + geocoderInput.classList.add( + "border-none", + "outline-none", + "w-full", // Volle Breite nutzen + "text-sm", + "text-gray-700", + "pl-3" + ); + geocoderInput.placeholder = "Ort oder Adresse suchen..."; // Platzhalter auf Deutsch + } + + // Meldung "Nothing found." ausblenden + const noResultsMessage = document.querySelector(".leaflet-control-geocoder-form-no-error"); + if (noResultsMessage) { + noResultsMessage.style.display = "none"; // Verstecke die Meldung + } + + // Alternativen (falls leer) ebenfalls ausblenden + const observer = new MutationObserver(() => { + const alternatives = document.querySelector(".leaflet-control-geocoder-alternatives"); + if (alternatives && alternatives.children.length === 0) { + alternatives.style.display = "none"; // Verstecke die Liste, wenn keine Vorschläge vorhanden sind + } + }); + + // Beobachte Änderungen im Geocoder-Container + const targetNode = document.querySelector(".leaflet-control-geocoder"); + if (targetNode) { + observer.observe(targetNode, { childList: true, subtree: true }); + } + }, 100); + + //-------------------------------------------- const updateMarkers = ({ ref, id, setState }) => { if (ref.current) { diff --git a/pages/_app.js b/pages/_app.js index 1a2213940..6102bc0e5 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -4,6 +4,7 @@ import { RecoilRoot } from "recoil"; import { Provider } from "react-redux"; import store from "../redux/store"; import "../styles/global.css"; +//import "../public/css/geocoder.css"; function MyApp({ Component, pageProps }) { return ( diff --git a/public/css/geocoder.css b/public/css/geocoder.css new file mode 100644 index 000000000..f20fc1ad2 --- /dev/null +++ b/public/css/geocoder.css @@ -0,0 +1,42 @@ +.leaflet-control-geocoder { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); /* Weicher Schatten */ + border: 1px solid #d9d9d9; /* Heller Rand */ + border-radius: 20px; /* Abgerundete Ecken für Google Maps Look */ + padding: 5px 10px; /* Innenabstand */ + font-size: 14px; /* Schriftgröße */ + font-family: Arial, sans-serif; /* Schriftart */ + width: 300px; /* Breite des Suchfeldes */ + background-color: #fff; /* Hintergrundfarbe */ + display: flex; /* Flexbox für Icon-Ausrichtung */ + align-items: center; /* Zentrierte Inhalte */ +} + +.leaflet-control-geocoder input { + border: none; /* Kein Rand für das Eingabefeld */ + outline: none; /* Kein Fokus-Rahmen */ + width: 100%; /* Volle Breite */ + font-size: 14px; /* Schriftgröße */ + color: #333; /* Textfarbe */ + padding-left: 10px; /* Abstand vom Rand */ +} + +.leaflet-control-geocoder input::placeholder { + color: #757575; /* Platzhalter-Farbe */ +} + +.leaflet-control-geocoder-icon { + background-color: transparent; /* Kein Hintergrund */ + border: none; /* Kein Rand */ + cursor: pointer; /* Zeiger beim Überfahren */ + margin-right: 5px; /* Abstand zum Textfeld */ +} + +.leaflet-control-geocoder-icon img { + width: 20px; /* Icon-Größe */ + height: 20px; +} + +/* Alternative Ergebnisse verbergen */ +.leaflet-control-geocoder-alternatives { + display: none; +} From 4276eec85369aace67634c9acc1e588d61d27a12 Mon Sep 17 00:00:00 2001 From: ISA Date: Fri, 27 Dec 2024 08:04:25 +0100 Subject: [PATCH 3/3] feat: GeocoderFeature mit Feature Toggle und Anpassungen integriert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GeocoderFeature als separates Modul implementiert und initialisiert. - Feature Toggle für Geocoder in .env.local hinzugefügt (NEXT_PUBLIC_ENABLE_GEOCODER). - Dynamische Aktivierung des Geocoders über MapComponent.js basierend auf Feature-Flag. - Anpassungen zur Entfernung bzw. Anpassung der Fehlermeldung "Nothing found". - Styling-Verbesserungen für das Suchfeld mit Tailwind CSS. --- .env.local | 2 + components/MapComponent.js | 93 ++------------------------ components/features/GeocoderFeature.js | 49 ++++++++++++++ 3 files changed, 58 insertions(+), 86 deletions(-) create mode 100644 components/features/GeocoderFeature.js diff --git a/.env.local b/.env.local index e37bc0865..21b3fd80a 100644 --- a/.env.local +++ b/.env.local @@ -11,4 +11,6 @@ DB_PORT=3306 ######################### Offline Karte #NEXT_PUBLIC_ONLINE_TILE_LAYER="http://127.0.0.1:3000/mapTiles/{z}/{x}/{y}.png" #NEXT_PUBLIC_ONLINE_TILE_LAYER="http://10.10.0.13:3000/mapTiles/{z}/{x}/{y}.png" +#################### +NEXT_PUBLIC_ENABLE_GEOCODER=true diff --git a/components/MapComponent.js b/components/MapComponent.js index d9c7daaa0..638790fb7 100644 --- a/components/MapComponent.js +++ b/components/MapComponent.js @@ -44,6 +44,7 @@ import useLineData from "../hooks/useLineData.js"; import { useMapComponentState } from "../hooks/useMapComponentState"; import { disablePolylineEvents, enablePolylineEvents } from "../utils/setupPolylines"; import { updateLocation } from "../utils/updateBereichUtil"; +import { initGeocoderFeature } from "../components/features/GeocoderFeature"; //-------------------------------------------- import { currentPoiState } from "../redux/slices/currentPoiSlice.js"; import { selectGisStationsStaticDistrict, setGisStationsStaticDistrict } from "../redux/slices/gisStationsStaticDistrictSlice"; @@ -738,92 +739,6 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => { } }); //-------------------------------------------- - // Benutzerdefiniertes Icon erstellen für den Geocoder - const customIcon = L.icon({ - iconUrl: "/img/marker-icon.png", // Pfad zum Icon - iconSize: [32, 32], // Größe des Icons - iconAnchor: [16, 32], // Ankerpunkt (Mitte unten) - popupAnchor: [0, -32], // Popup-Ankerpunkt - }); - // Geocoder mit benutzerdefiniertem Icon verwenden - const geocoder = L.Control.geocoder({ - position: "topleft", - defaultMarkGeocode: false, - geocoder: new L.Control.Geocoder.Nominatim({ - serviceUrl: "https://nominatim.openstreetmap.org/", - geocodingQueryParams: { - "accept-language": "de", // Sprache auf Deutsch setzen - countrycodes: "DE", // Nur Ergebnisse aus Deutschland - }, - }), - }) - .on("markgeocode", function (e) { - const latlng = e.geocode.center; - map.setView(latlng, 15); - - // Marker hinzufügen - L.marker(latlng, { icon: customIcon }).addTo(map).bindPopup(e.geocode.name).openPopup(); - }) - .addTo(map); - - // Tailwind-Klassen dynamisch hinzufügen - setTimeout(() => { - // Geocoder-Container stylen - const geocoderContainer = document.querySelector(".leaflet-control-geocoder"); - if (geocoderContainer) { - // Nur minimale Klassen für Volle Breite und abgerundetes Design - geocoderContainer.classList.add( - "shadow-md", - "border", - "border-gray-300", - "rounded-full", - "bg-white", - "p-2", - "w-96" // Breite des gesamten Suchfeldes - ); - - // Alle anderen Elemente im Container entfernen außer Eingabefeld - Array.from(geocoderContainer.children).forEach((child) => { - if (!child.querySelector("input")) { - child.remove(); // Löscht alle Elemente außer dem Eingabefeld - } - }); - } - - // Eingabefeld stylen und Platzhaltertext setzen - const geocoderInput = document.querySelector(".leaflet-control-geocoder input"); - if (geocoderInput) { - geocoderInput.classList.add( - "border-none", - "outline-none", - "w-full", // Volle Breite nutzen - "text-sm", - "text-gray-700", - "pl-3" - ); - geocoderInput.placeholder = "Ort oder Adresse suchen..."; // Platzhalter auf Deutsch - } - - // Meldung "Nothing found." ausblenden - const noResultsMessage = document.querySelector(".leaflet-control-geocoder-form-no-error"); - if (noResultsMessage) { - noResultsMessage.style.display = "none"; // Verstecke die Meldung - } - - // Alternativen (falls leer) ebenfalls ausblenden - const observer = new MutationObserver(() => { - const alternatives = document.querySelector(".leaflet-control-geocoder-alternatives"); - if (alternatives && alternatives.children.length === 0) { - alternatives.style.display = "none"; // Verstecke die Liste, wenn keine Vorschläge vorhanden sind - } - }); - - // Beobachte Änderungen im Geocoder-Container - const targetNode = document.querySelector(".leaflet-control-geocoder"); - if (targetNode) { - observer.observe(targetNode, { childList: true, subtree: true }); - } - }, 100); //-------------------------------------------- @@ -933,6 +848,12 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => { }, [map, allMarkers]); //--------------------------------------- + useEffect(() => { + if (map) { + initGeocoderFeature(map); // Geocoder-Feature initialisieren + } + }, [map]); + //-------------------------------------------- return ( <> diff --git a/components/features/GeocoderFeature.js b/components/features/GeocoderFeature.js new file mode 100644 index 000000000..25d0b8a43 --- /dev/null +++ b/components/features/GeocoderFeature.js @@ -0,0 +1,49 @@ +// components/features/GeocoderFeature.js +export function initGeocoderFeature(map) { + // Feature Toggle + const isGeocoderEnabled = process.env.NEXT_PUBLIC_ENABLE_GEOCODER === "true"; + + if (!isGeocoderEnabled) return; // Falls deaktiviert, nichts ausführen + + // Benutzerdefiniertes Icon erstellen + const customIcon = L.icon({ + iconUrl: "/img/marker-icon.png", + iconSize: [32, 32], + iconAnchor: [16, 32], + popupAnchor: [0, -32], + }); + + // Geocoder hinzufügen + const geocoder = L.Control.geocoder({ + position: "topleft", + defaultMarkGeocode: false, + errorMessage: "", // Keine Fehlermeldung anzeigen + geocoder: new L.Control.Geocoder.Nominatim({ + serviceUrl: "https://nominatim.openstreetmap.org/", + geocodingQueryParams: { + "accept-language": "de", + countrycodes: "DE", + }, + }), + }) + .on("markgeocode", function (e) { + const latlng = e.geocode.center; + map.setView(latlng, 15); + L.marker(latlng, { icon: customIcon }).addTo(map).bindPopup(e.geocode.name).openPopup(); + }) + .addTo(map); + + // Styling mit Tailwind CSS + setTimeout(() => { + const geocoderContainer = document.querySelector(".leaflet-control-geocoder"); + if (geocoderContainer) { + geocoderContainer.classList.add("shadow-md", "border", "border-gray-300", "rounded-full", "bg-white", "p-2", "w-96"); + + const geocoderInput = geocoderContainer.querySelector("input"); + if (geocoderInput) { + geocoderInput.classList.add("border-none", "outline-none", "w-full", "text-sm", "text-gray-700", "pl-3"); + geocoderInput.placeholder = "Ort oder Adresse suchen..."; + } + } + }, 100); +}