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; } }