diff --git a/src/components/Map/MapView.jsx b/src/components/Map/MapView.jsx index 2e772a8..2b0d323 100644 --- a/src/components/Map/MapView.jsx +++ b/src/components/Map/MapView.jsx @@ -608,7 +608,7 @@ export default function MapView({ Preview diff --git a/src/components/Map/markerManager.js b/src/components/Map/markerManager.js index 66b46c6..0c53710 100644 --- a/src/components/Map/markerManager.js +++ b/src/components/Map/markerManager.js @@ -294,6 +294,18 @@ function openCenteredOverlay({ map, post, theme }) { }; } +function hashPostId(id) { + // Simple hash function for stable offset calculation + const str = String(id); + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Convert to 32bit integer + } + return Math.abs(hash); +} + function calculateSmartOffset(post, markersRef, map) { if (!map || !markersRef.current) return { x: 0, y: 0 }; @@ -302,35 +314,31 @@ function calculateSmartOffset(post, markersRef, map) { if (lat == null || lon == null) return { x: 0, y: 0 }; const currentPoint = map.project([lon, lat]); - const PROXIMITY_THRESHOLD = 80; // pixels - const MAX_OFFSET = 60; // max offset in pixels + const PROXIMITY_THRESHOLD = 100; // pixels - larger to detect more overlaps - const nearbyMarkers = []; - - for (const marker of markersRef.current) { - if (!marker.post || marker.post.id === post.id) continue; + // Check distance in geographic coordinates (more stable across zoom levels) + const nearbyCount = markersRef.current.filter(marker => { + if (!marker.post || marker.post.id === post.id) return false; const mLat = typeof marker.post.lat === "number" ? marker.post.lat : marker.post.latitude; const mLon = typeof marker.post.lon === "number" ? marker.post.lon : marker.post.lng; - if (mLat == null || mLon == null) continue; + if (mLat == null || mLon == null) return false; - const mPoint = map.project([mLon, mLat]); - const dist = Math.sqrt( - Math.pow(currentPoint.x - mPoint.x, 2) + - Math.pow(currentPoint.y - mPoint.y, 2) - ); + // Geographic distance (rough approximation) + const latDiff = Math.abs(lat - mLat); + const lonDiff = Math.abs(lon - mLon); + const geoDist = Math.sqrt(latDiff * latDiff + lonDiff * lonDiff); - if (dist < PROXIMITY_THRESHOLD) { - nearbyMarkers.push({ marker, distance: dist, point: mPoint }); - } - } + // Small threshold for geographic clustering (~100m at equator) + return geoDist < 0.001; + }).length; - if (nearbyMarkers.length === 0) return { x: 0, y: 0 }; + if (nearbyCount === 0) return { x: 0, y: 0 }; - // Create a cascade effect: offset markers in a circular pattern - const index = nearbyMarkers.length; - const angle = (index * 45) * (Math.PI / 180); // 45 degrees apart - const offsetMagnitude = Math.min(20 + (index * 12), MAX_OFFSET); + // Use deterministic hash-based offset so it's stable across zoom/pan + const hash = hashPostId(post.id); + const angle = (hash % 8) * 45 * (Math.PI / 180); // 8 positions, 45° apart + const offsetMagnitude = 25 + ((hash % 3) * 15); // 25, 40, or 55 pixels return { x: Math.cos(angle) * offsetMagnitude,