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 3c0611a50..2d0ee01fd 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"; @@ -43,13 +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 { usePolylineTooltipLayer } from "../hooks/usePolylineTooltipLayer"; -import { useFetchLineStatusData } from "../hooks/useFetchLineStatusData"; -import { useFetchPriorityConfig } from "../hooks/useFetchPriorityConfig"; -import { useUpdateGmaData } from "../hooks/useUpdateGmaData"; -import { useDynamicMarkerLayers } from "../hooks/useDynamicMarkerLayers"; -import { useFetchUserRights } from "../hooks/useFetchUserRights"; -import { useFetchWebServiceMap } from "../hooks/useFetchWebServiceMap"; +import { initGeocoderFeature } from "../components/features/GeocoderFeature"; //-------------------------------------------- import { currentPoiState } from "../redux/slices/currentPoiSlice.js"; import { selectGisStationsStaticDistrict, setGisStationsStaticDistrict } from "../redux/slices/gisStationsStaticDistrictSlice"; @@ -522,24 +517,100 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => { useUpdateGmaData(map, setGisStationsMeasurements, mapGisStationsMeasurementsUrl, gmaMarkers, layers, oms); //--------------------------------- - useDynamicMarkerLayers(map, gisSystemStaticLoaded, GisSystemStatic, priorityConfig, { - setGmaMarkers, - setTalasMarkers, - setEciMarkers, - setGsmModemMarkers, - setCiscoRouterMarkers, - setWagoMarkers, - setSiemensMarkers, - setOtdrMarkers, - setWdmMarkers, - setMessstellenMarkers, - setTalasiclMarkers, - setDauzMarkers, - setSmsfunkmodemMarkers, - setUlafMarkers, - setSonstigeMarkers, - setTkComponentsMarkers, - }); + + const gmaLayerRef = useRef(null); + const talasLayerRef = useRef(null); + const eciMarkersLayerRef = useRef(null); + const gsmModemMarkersLayerRef = useRef(null); + const ciscoRouterMarkersLayerRef = useRef(null); + const wagoMarkersLayerRef = useRef(null); + const siemensMarkersLayerRef = useRef(null); + const otdrMarkersLayerRef = useRef(null); + const wdmMarkersLayerRef = useRef(null); + const messstellenMarkersLayerRef = useRef(null); + const talasiclMarkersLayerRef = useRef(null); + const dauzMarkersLayerRef = useRef(null); + const smsfunkmodemMarkersLayerRef = useRef(null); + const ulafMarkersLayerRef = useRef(null); + const sonstigeMarkersLayerRef = useRef(null); + const tkComponentsMarkersRef = useRef(null); + useEffect(() => { + if (!gisSystemStaticLoaded || !map) return; // Sicherstellen, dass die Karte und Daten geladen sind + + const layerGroups = [ + { ref: gmaLayerRef, id: 11, setState: setGmaMarkers }, + { ref: talasLayerRef, id: 1, setState: setTalasMarkers }, + { ref: eciMarkersLayerRef, id: 2, setState: setEciMarkers }, + { ref: gsmModemMarkersLayerRef, id: 5, setState: setGsmModemMarkers }, + { ref: ciscoRouterMarkersLayerRef, id: 6, setState: setCiscoRouterMarkers }, + { ref: wagoMarkersLayerRef, id: 7, setState: setWagoMarkers }, + { ref: siemensMarkersLayerRef, id: 8, setState: setSiemensMarkers }, + { ref: otdrMarkersLayerRef, id: 9, setState: setOtdrMarkers }, + { ref: wdmMarkersLayerRef, id: 10, setState: setWdmMarkers }, + { ref: messstellenMarkersLayerRef, id: 13, setState: setMessstellenMarkers }, + { ref: talasiclMarkersLayerRef, id: 100, setState: setTalasiclMarkers }, + { ref: dauzMarkersLayerRef, id: 110, setState: setDauzMarkers }, + { ref: smsfunkmodemMarkersLayerRef, id: 111, setState: setSmsfunkmodemMarkers }, + { ref: ulafMarkersLayerRef, id: 0, setState: setUlafMarkers }, + { ref: sonstigeMarkersLayerRef, id: 200, setState: setSonstigeMarkers }, + { ref: tkComponentsMarkersRef, id: 30, setState: setTkComponentsMarkers }, + ]; + + // Initialisiere LayerGroups nur einmal + layerGroups.forEach(({ ref }) => { + if (!ref.current) { + ref.current = new L.LayerGroup().addTo(map); + } + }); + //-------------------------------------------- + + //-------------------------------------------- + + const updateMarkers = ({ ref, id, setState }) => { + if (ref.current) { + ref.current.clearLayers(); // Entferne alte Marker + } + + // Erstelle und füge neue Marker hinzu + createAndSetDevices( + id, + (newMarkers) => { + setState(newMarkers); // Zustand aktualisieren + newMarkers.forEach((marker) => ref.current.addLayer(marker)); // Marker zur LayerGroup hinzufügen + + // Überprüfe auf überlappende Marker und füge das Plus-Icon hinzu + checkOverlappingMarkers(map, newMarkers, plusRoundIcon); + }, + GisSystemStatic, + priorityConfig + ); + }; + + // Aktualisiere alle Marker-Gruppen + const updateAllMarkers = () => { + layerGroups.forEach(updateMarkers); + }; + + // Initiales Update + updateAllMarkers(); + + // Setze ein Intervall für regelmäßige Updates + /* const intervalId = setInterval(() => { + updateAllMarkers(); + }, 60000); // 20 Sekunden + + // Aufräumen bei Komponentenentladung + return () => { + clearInterval(intervalId); // Entferne Intervall + + // LayerGroups leeren + layerGroups.forEach(({ ref }) => { + if (ref.current) { + ref.current.clearLayers(); + } + }); + }; */ + }, [gisSystemStaticLoaded, map, GisSystemStatic, priorityConfig]); //--------------------------------------- // Initialisiere Leaflet-Karte @@ -601,6 +672,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); +} 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/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; +} 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,