import { LatLngBounds, DivIcon, Icon } from 'leaflet'; import { useEffect, useState } from 'react'; import ReactDom from 'react-dom/server'; import { MapContainer, Marker, TileLayer, GeoJSON, ZoomControl } from 'react-leaflet'; import MarkerClusterGroup from 'react-leaflet-markercluster'; import { Trip, Photo } from 'models'; import { distanceBetween } from 'lib/util'; import 'leaflet/dist/leaflet.css'; import styles from './Map.module.css'; interface Props { trip: Trip; handleMarkerClick: (photo: string) => void; } export default function Map({ trip, handleMarkerClick }: Props): JSX.Element { const [map, setMap] = useState(null); const bounds = trip ? new LatLngBounds( [trip.track.bbox[1], trip.track.bbox[0]], [trip.track.bbox[3], trip.track.bbox[2]], ) : new LatLngBounds([75, -145], [-52, 145]); function createMarkers(photos: Photo[]): JSX.Element[] { // cluster photos that are close to each other const clusters: Photo[][] = []; for (let i = 0; i < photos.length; i += 1) { if (clusters.filter((c) => c.includes(photos[i])).length === 0) { const cluster = [photos[i]]; for (let j = 0; j < photos.length; j += 1) { if (photos[i] !== photos[j]) { const a = [photos[i].latitude, photos[i].longitude]; const b = [photos[j].latitude, photos[j].longitude]; const distance = distanceBetween(a, b); if (distance < 10) cluster.push(photos[j]); } } clusters.push(cluster); } } // create React elements based on the clusters const markers = clusters.map((cluster) => { let Wrapper = ({ children }) => <>{children}; if (cluster.length > 1) { Wrapper = ({ children }) => ( new DivIcon({ html: ReactDom.renderToString( <> {markerCluster.getChildCount()} , ), iconSize: [36, 36], className: styles.markerIcon, }) } > {children} ); } const children = cluster.map((photo) => ( handleMarkerClick(photo.name) }} /> )); return {children}; }); return markers; } useEffect(() => { if (map) { map.setView(bounds.getCenter()); map.fitBounds(bounds); } }, [trip]); return ( {trip && } {trip && createMarkers(trip.photos)} ); }