Skip to content

Commit

Permalink
Merge pull request #6 from Hanka8/hm/interactive-map
Browse files Browse the repository at this point in the history
Hm/interactive map
  • Loading branch information
Hanka8 committed Nov 20, 2024
2 parents d65ed6a + c9b6c93 commit eb21dc1
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 12 deletions.
35 changes: 35 additions & 0 deletions src/assets/bird-ico.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/FormGeolocation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const LocationForm: React.FC<LocationFormProps> = ({
/>
<div className="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-green-600 flex-shrink-0"></div>
<span className="ms-3 text-sm font-medium text-gray-700">
Insert manually
{geolocationManually ? "Get your location" : "Set adress manually" }
</span>
</label>
<div className="m-2 flex flex-col relative">
Expand Down
5 changes: 3 additions & 2 deletions src/components/Index.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -72,8 +73,8 @@ const Index: React.FC = () => {

return (
<div className="text-gray-800 bg-green-50 flex flex-col items-center min-h-screen">
<InteractiveMap latitude={latitude} longitude={longitude} />
<h3 className="text-5xl m-8 text-green-700">Birds around you</h3>
<InteractiveMap latitude={latitude} longitude={longitude} setLatitude={setLatitude} setLongitude={setLongitude} data={data}/>
<div className="m-2 p-4 pb-8 bg-green-100">
<FormGeolocation
latitude={latitude}
Expand Down
122 changes: 115 additions & 7 deletions src/components/InteractiveMap.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
import { useEffect, useRef } from "react";
import { useState, useEffect, useRef } from "react";
import MapModal from "./MapModal";
import Map from "ol/Map.js";
import OSM from "ol/source/OSM.js";
import TileLayer from "ol/layer/Tile.js";
import View from "ol/View.js";
import { fromLonLat } from "ol/proj";
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import Feature from "ol/Feature";
import Point from "ol/geom/Point";
import Circle from "ol/geom/Circle";
import { Icon, Style, Stroke, Fill } from "ol/style";
import { toLonLat } from "ol/proj";
import { InteractiveMapProps } from "../types";
import { radius } from "../constants";
import birdIcon from "../assets/bird-ico.svg";

const InteractiveMap: React.FC<InteractiveMapProps> = ({
latitude,
longitude,
setLatitude,
setLongitude,
data,
}) => {

const [modalOpen, setModalOpen] = useState<boolean>(false);
const [modalContent, setModalContent] = useState<string>("");

const InteractiveMap: React.FC<InteractiveMapProps> = ({ latitude, longitude }) => {
const mapRef = useRef<HTMLDivElement | null>(null);

// create map when latitude and longitude are set
useEffect(() => {
if (!latitude || !longitude) return;

Expand All @@ -26,17 +45,106 @@ const InteractiveMap: React.FC<InteractiveMapProps> = ({ 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 <div id="map" ref={mapRef} className="w-full h-32rem"></div>;
return (
<div className="w-full h-32rem">
<div ref={mapRef} className="w-full h-32rem"></div>
<MapModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
content={modalContent}
/>
</div>
);
};

export default InteractiveMap;
24 changes: 24 additions & 0 deletions src/components/MapModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@


import { MapModalProps } from "../types";

const MapModal: React.FC<MapModalProps> = ({ isOpen, onClose, content }) => {
if (!isOpen) return null;

return (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white p-4 rounded">
<h2 className="text-lg font-bold">Info</h2>
<p>{content}</p>
<button
onClick={onClose}
className="mt-2 p-2 bg-blue-500 text-white rounded"
>
Close
</button>
</div>
</div>
);
};

export default MapModal;
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const radius = 5;
2 changes: 1 addition & 1 deletion src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
@tailwind utilities;

.h-32rem {
height: 20rem;
height: 32rem;
}
13 changes: 12 additions & 1 deletion src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export type Bird = {
howMany: number;
locName: string;
obsDt: string;
lat: number;
lng: number;
};

export type BirdCardProps = {
Expand Down Expand Up @@ -91,4 +93,13 @@ export type ErrorProps = {
export type InteractiveMapProps = {
latitude: string;
longitude: string;
};
setLatitude: (value: string) => void;
setLongitude: (value: string) => void;
data: Bird[] | undefined;
};

export type MapModalProps = {
isOpen: boolean;
onClose: () => void;
content: string;
}

0 comments on commit eb21dc1

Please sign in to comment.