diff --git a/components/MapComponent.js b/components/MapComponent.js index 3e40d43a9..1a8e2421d 100644 --- a/components/MapComponent.js +++ b/components/MapComponent.js @@ -5,6 +5,8 @@ import "leaflet/dist/leaflet.css"; import "leaflet-contextmenu/dist/leaflet.contextmenu.css"; import "leaflet-contextmenu"; import * as config from "../config/config.js"; +import { OverlappingMarkerSpiderfier } from "../lib/OverlappingMarkerSpiderfier.js"; + const MapComponent = ({ locations, onLocationUpdate }) => { const mapRef = useRef(null); // Referenz auf das DIV-Element der Karte const [map, setMap] = useState(null); // Zustand der Karteninstanz @@ -27,6 +29,7 @@ const MapComponent = ({ locations, onLocationUpdate }) => { const mapGisStationsMeasurementsUrl = config.mapGisStationsMeasurementsUrl; const mapGisSystemStaticUrl = config.mapGisSystemStaticUrl; const mapDataIconUrl = config.mapDataIconUrl; + const [oms, setOms] = useState(null); // State für OMS-Instanz // Funktion zum Aktualisieren der Position in der Datenbank const updateLocationInDatabase = async (id, newLatitude, newLongitude) => { @@ -682,7 +685,9 @@ const MapComponent = ({ locations, onLocationUpdate }) => { popupAnchor: [1, -34], shadowSize: [41, 41], }), - }).addTo(map); + }); + //oms.addMarker(marker); // Marker zum OMS hinzufügen + marker.addTo(map); marker.on("mouseover", function (e) { this.openPopup(); diff --git a/lib/OverlappingMarkerSpiderfier.js b/lib/OverlappingMarkerSpiderfier.js new file mode 100644 index 000000000..a953b3303 --- /dev/null +++ b/lib/OverlappingMarkerSpiderfier.js @@ -0,0 +1,113 @@ +import L from "leaflet"; + +export class OverlappingMarkerSpiderfier { + constructor(map, options = {}) { + this.map = map; + this.markers = []; + this.markerListeners = []; + this.listeners = {}; + this.nearbyDistance = options.nearbyDistance || 20; + this.circleSpiralSwitchover = options.circleSpiralSwitchover || 9; + this.circleFootSeparation = options.circleFootSeparation || 25; // pixels + this.circleStartAngle = options.circleStartAngle || Math.PI / 6; + this.spiralFootSeparation = options.spiralFootSeparation || 28; // pixels + this.spiralLengthStart = options.spiralLengthStart || 11; + this.spiralLengthFactor = options.spiralLengthFactor || 5; + this.legWeight = options.legWeight || 1.5; + this.legColors = options.legColors || { + usual: "#222", + highlighted: "#f00", + }; + + this.initMarkerArrays(); + this.map.on("click", this.unspiderfy.bind(this)); + this.map.on("zoomend", this.unspiderfy.bind(this)); + } + + initMarkerArrays() { + this.markers = []; + this.markerListeners = []; + } + + addMarker(marker) { + if (marker._oms) return this; + marker._oms = true; + + const listener = () => { + this.spiderListener(marker); + }; + + marker.on("click", listener); + this.markerListeners.push(listener); + this.markers.push(marker); + + return this; + } + + spiderListener(marker) { + const spidered = !!marker._omsData; + if (!spidered) { + const nearMarkers = this.nearbyMarkers(marker); + if (nearMarkers.length > 1) { + this.spiderfy(nearMarkers); + } + } + } + + nearbyMarkers(marker) { + const nearby = []; + const markerPt = this.map.latLngToLayerPoint(marker.getLatLng()); + this.markers.forEach((m) => { + if (m !== marker) { + const mPt = this.map.latLngToLayerPoint(m.getLatLng()); + const distanceSq = this.ptDistanceSq(mPt, markerPt); + if (distanceSq < this.nearbyDistance * this.nearbyDistance) { + nearby.push(m); + } + } + }); + return nearby; + } + + spiderfy(markers) { + const centerPt = this.map.latLngToLayerPoint(markers[0].getLatLng()); + markers.forEach((marker, index) => { + const angle = + this.circleStartAngle + (index * 2 * Math.PI) / markers.length; + const legLength = + this.circleFootSeparation * (2 + index / markers.length); + const newPt = L.point( + centerPt.x + legLength * Math.cos(angle), + centerPt.y + legLength * Math.sin(angle) + ); + + const newLatLng = this.map.layerPointToLatLng(newPt); + marker.setLatLng(newLatLng); + marker.setZIndexOffset(1000); + + const polyline = L.polyline([marker.getLatLng(), newLatLng], { + color: this.legColors.usual, + weight: this.legWeight, + clickable: false, + }); + this.map.addLayer(polyline); + }); + } + + unspiderfy() { + this.markers.forEach((marker) => { + if (marker._omsData) { + marker.setLatLng(marker._omsData.usualPosition); + marker.setZIndexOffset(0); + this.map.removeLayer(marker._omsData.leg); + delete marker._omsData; + } + }); + } + + ptDistanceSq(pt1, pt2) { + const dx = pt1.x - pt2.x; + const dy = pt1.y - pt2.y; + return dx * dx + dy * dy; + } +} diff --git a/pages/_app.js b/pages/_app.js index c55121b3e..7617273bd 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -1,6 +1,12 @@ import "../styles/global.css"; // Pfad zur globalen CSS-Datei anpassen +import Script from "next/script"; + import React from "react"; export default function MyApp({ Component, pageProps }) { - return ; + return ( + <> + + + ); } diff --git a/public/js/spiderfier-Leaflet-cdn.js b/public/js/spiderfier-Leaflet-cdn.js new file mode 100644 index 000000000..14b38bc46 --- /dev/null +++ b/public/js/spiderfier-Leaflet-cdn.js @@ -0,0 +1,19 @@ +(function(){/* + OverlappingMarkerSpiderfier +https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet +Copyright (c) 2011 - 2012 George MacKerron +Released under the MIT licence: http://opensource.org/licenses/mit-license +Note: The Leaflet maps API must be included *before* this code +*/ +(function(){var q={}.hasOwnProperty,r=[].slice;null!=this.L&&(this.OverlappingMarkerSpiderfier=function(){function n(c,b){var a,e,g,f,d=this;this.map=c;null==b&&(b={});for(a in b)q.call(b,a)&&(e=b[a],this[a]=e);this.initMarkerArrays();this.listeners={};f=["click","zoomend"];e=0;for(g=f.length;eb)return this;a=this.markerListeners.splice(b,1)[0];c.removeEventListener("click",a);delete c._oms;this.markers.splice(b,1);return this};d.clearMarkers=function(){var c,b,a,e,g;this.unspiderfy();g=this.markers;c=a=0;for(e=g.length;aa||this.listeners[c].splice(a,1);return this};d.clearListeners=function(c){this.listeners[c]=[];return this};d.trigger=function(){var c,b,a,e,g,f;b=arguments[0];c=2<=arguments.length?r.call(arguments,1):[];b=null!=(a=this.listeners[b])?a:[];f=[];e=0;for(g=b.length;ec;a=0<=c?++f:--f)a=this.circleStartAngle+a*e,d.push(new L.Point(b.x+g*Math.cos(a),b.y+g*Math.sin(a)));return d};d.generatePtsSpiral=function(c,b){var a,e,g,f,d;g=this.spiralLengthStart;a=0;d=[];for(e=f=0;0<=c?fc;e=0<=c?++f:--f)a+=this.spiralFootSeparation/g+5E-4*e,e=new L.Point(b.x+g*Math.cos(a),b.y+g*Math.sin(a)),g+=k*this.spiralLengthFactor/a,d.push(e);return d};d.spiderListener=function(c){var b,a,e,g,f,d,h,k,l;(b=null!= +c._omsData)&&this.keepSpiderfied||this.unspiderfy();if(b)return this.trigger("click",c);g=[];f=[];d=this.nearbyDistance*this.nearbyDistance;e=this.map.latLngToLayerPoint(c.getLatLng());l=this.markers;h=0;for(k=l.length;h=this.circleSpiralSwitchover?this.generatePtsSpiral(m,a).reverse():this.generatePtsCircle(m,a);a=function(){var a,b,k,m=this;k=[];a=0;for(b=d.length;a