diff --git a/src/assets/bird-ico.svg b/src/assets/bird-ico.svg new file mode 100644 index 0000000..02cb38d --- /dev/null +++ b/src/assets/bird-ico.svg @@ -0,0 +1,35 @@ + + + + diff --git a/src/components/FormGeolocation.tsx b/src/components/FormGeolocation.tsx index 1ee772c..12459bf 100644 --- a/src/components/FormGeolocation.tsx +++ b/src/components/FormGeolocation.tsx @@ -96,7 +96,7 @@ const LocationForm: React.FC = ({ />
- Insert manually + {geolocationManually ? "Get your location" : "Set adress manually" }
diff --git a/src/components/Index.tsx b/src/components/Index.tsx index 1ebf8e9..ab1c37d 100644 --- a/src/components/Index.tsx +++ b/src/components/Index.tsx @@ -1,5 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { useEffect, useState } from "react"; +import { radius } from "../constants"; import { getLocation } from "../functions/getLocation"; import BirdCard from "./BirdCard"; import InteractiveMap from "./InteractiveMap"; @@ -43,7 +44,7 @@ const Index: React.FC = () => { longitude, }) => { const response = await fetch( - `https://api.ebird.org/v2/data/obs/geo/recent?dist=15&back=3&lat=${Number(latitude)}&lng=${Number(longitude)}`, + `https://api.ebird.org/v2/data/obs/geo/recent?dist=${radius}&back=30&includeProvisional=true&lat=${Number(latitude)}&lng=${Number(longitude)}`, { headers: { "X-eBirdApiToken": import.meta.env.VITE_API_KEY_EBIRD, @@ -72,8 +73,8 @@ const Index: React.FC = () => { return (
-

Birds around you

+
= ({ + latitude, + longitude, + setLatitude, + setLongitude, + data, +}) => { + + const [modalOpen, setModalOpen] = useState(false); + const [modalContent, setModalContent] = useState(""); -const InteractiveMap: React.FC = ({ latitude, longitude }) => { const mapRef = useRef(null); - // create map when latitude and longitude are set useEffect(() => { if (!latitude || !longitude) return; @@ -26,17 +45,106 @@ const InteractiveMap: React.FC = ({ latitude, longitude }) }), ], view: new View({ - center: [0, 0], + center: coordinates, zoom: 12, }), }); - map.getView().setCenter(coordinates); + // Add main marker for the central location + const marker = new Feature({ + geometry: new Point(coordinates), + }); + + marker.setStyle( + new Style({ + image: new Icon({ + src: "https://openlayers.org/en/latest/examples/data/icon.png", + scale: 0.75, + }), + }) + ); + + const circleFeature = new Feature({ + geometry: new Circle(coordinates, radius * 1000), + }); + + circleFeature.setStyle( + new Style({ + stroke: new Stroke({ + color: "rgba(0, 128, 0, 0.8)", + width: 2, + }), + fill: new Fill({ + color: "rgba(0, 255, 0, 0.2)", + }), + }) + ); + + const vectorSource = new VectorSource({ + features: [marker, circleFeature], + }); + + data?.forEach((bird) => { + const birdCoordinates = fromLonLat([bird.lng, bird.lat]); + const birdMarker = new Feature({ + geometry: new Point(birdCoordinates), + }); + + birdMarker.setStyle( + new Style({ + image: new Icon({ + src: birdIcon, + scale: 0.75, + color: "rgba(255, 0, 0, 0.8)", + }), + }) + ); + + birdMarker.set("description", `Bird Name: ${bird.comName}`); + vectorSource.addFeature(birdMarker); + }); + + const markerLayer = new VectorLayer({ + source: vectorSource, + }); + + map.addLayer(markerLayer); + + // Add click event listener to the map + map.on("singleclick", (event) => { + const clickedFeature = map.forEachFeatureAtPixel( + event.pixel, + (feature) => { + return feature; + } + ); + + if (clickedFeature) { + const description = clickedFeature.get("description"); + if (description) { + setModalContent(description); // Set the content for the modal + setModalOpen(true); // Open the modal + } + } else { + const clickedCoordinate = toLonLat(event.coordinate); // Convert to longitude/latitude + setLatitude(clickedCoordinate[1].toFixed(2)); + setLongitude(clickedCoordinate[0].toFixed(2)); + } + }); return () => map.setTarget(undefined); - }, [latitude, longitude]); + }, [latitude, longitude, data, setLatitude, setLongitude]); - return
; + return ( +
+
+ setModalOpen(false)} + content={modalContent} + /> +
+ ); }; export default InteractiveMap; diff --git a/src/components/MapModal.tsx b/src/components/MapModal.tsx new file mode 100644 index 0000000..553dbb9 --- /dev/null +++ b/src/components/MapModal.tsx @@ -0,0 +1,24 @@ + + +import { MapModalProps } from "../types"; + +const MapModal: React.FC = ({ isOpen, onClose, content }) => { + if (!isOpen) return null; + + return ( +
+
+

Info

+

{content}

+ +
+
+ ); +}; + +export default MapModal; diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..439d407 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1 @@ +export const radius = 5; \ No newline at end of file diff --git a/src/index.css b/src/index.css index 2f1e94f..ce1b7f4 100644 --- a/src/index.css +++ b/src/index.css @@ -3,5 +3,5 @@ @tailwind utilities; .h-32rem { - height: 20rem; + height: 32rem; } \ No newline at end of file diff --git a/src/types.tsx b/src/types.tsx index 587f33e..ba3a8fd 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -21,6 +21,8 @@ export type Bird = { howMany: number; locName: string; obsDt: string; + lat: number; + lng: number; }; export type BirdCardProps = { @@ -91,4 +93,13 @@ export type ErrorProps = { export type InteractiveMapProps = { latitude: string; longitude: string; -}; \ No newline at end of file + setLatitude: (value: string) => void; + setLongitude: (value: string) => void; + data: Bird[] | undefined; +}; + +export type MapModalProps = { + isOpen: boolean; + onClose: () => void; + content: string; +} \ No newline at end of file