From 628632ff5092f1e3cf6c968d9bdfbd9a24f59541 Mon Sep 17 00:00:00 2001 From: Marcin Zelent Date: Fri, 8 Jan 2021 19:47:37 +0100 Subject: Initial commit --- components/Map/Map.module.css | 28 ++++++++++ components/Map/Map.tsx | 121 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 components/Map/Map.module.css create mode 100644 components/Map/Map.tsx (limited to 'components/Map') diff --git a/components/Map/Map.module.css b/components/Map/Map.module.css new file mode 100644 index 0000000..b13f5d2 --- /dev/null +++ b/components/Map/Map.module.css @@ -0,0 +1,28 @@ +.popup { + width: auto; + min-height: 90%; +} + +.popup .leaflet-popup-content { + margin: 0; +} + +.markerIcon { + border: 2px solid #fff; + border-radius: 2px; + overflow: hidden; +} + +.markerItemCount { + width: 18px; + height: 18px; + display: block; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: 10px; + background: #fff; + text-align: center; + font-weight: bold; +} diff --git a/components/Map/Map.tsx b/components/Map/Map.tsx new file mode 100644 index 0000000..44f8ea6 --- /dev/null +++ b/components/Map/Map.tsx @@ -0,0 +1,121 @@ +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)} + + ); +} -- cgit v1.2.3