diff --git a/packages/web/package.json b/packages/web/package.json
index 7084359..6d2d2e3 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -20,6 +20,8 @@
"fuse.js": "^7.0.0",
"ionicons": "^7.1.2",
"leaflet": "^1.9.4",
+ "leaflet-geosearch": "^4.0.0",
+ "lodash": "^4.17.21",
"react": "^18.2.0",
"react-calendar": "^4.6.0",
"react-dom": "^18.2.0",
diff --git a/packages/web/src/components/Map/Map.jsx b/packages/web/src/components/Map/Map.jsx
index 9bd0774..893d5c9 100644
--- a/packages/web/src/components/Map/Map.jsx
+++ b/packages/web/src/components/Map/Map.jsx
@@ -25,7 +25,7 @@ import {
Circle,
useMapEvents,
} from "react-leaflet";
-import { useDisclosure, useColorMode } from "@chakra-ui/react";
+import { useDisclosure, useColorMode, useColorModeValue, useToast } from "@chakra-ui/react";
import InfoModal from "../InfoModal/InfoModal";
import DataContext from "../../context/DataContext";
@@ -35,6 +35,12 @@ import axios from "axios";
import { filterItem } from "../../utils/Utils.js";
+// Add this new import for the geocoding service
+import { OpenStreetMapProvider } from 'leaflet-geosearch';
+import { Input, Button, Box, IconButton, Spinner, VStack, Text } from "@chakra-ui/react"; // Import Chakra UI components
+import { SearchIcon } from "@chakra-ui/icons";
+import debounce from 'lodash/debounce';
+
/**
* Map is uses react-leaflet's API to communicate user actions to map entities and information
*
@@ -94,11 +100,11 @@ export default function Map({
];
const bounds = L.latLngBounds(allowedBounds);
- const mapBoundsCoordinates = [
- [33.625038, -117.875143],
- [33.668298, -117.808742],
- ];
- const mapBounds = L.latLngBounds(mapBoundsCoordinates);
+ // const mapBoundsCoordinates = [
+ // [33.625038, -117.875143],
+ // [33.668298, -117.808742],
+ // ];
+ // const mapBounds = L.latLngBounds(mapBoundsCoordinates);
const handleMarkerSelect = async () => {
setShowDonut(true);
@@ -333,18 +339,32 @@ export default function Map({
) : null;
};
+ const [locationSearch, setLocationSearch] = useState("");
+ const provider = useMemo(() => new OpenStreetMapProvider(), []);
+
+ const handleLocationSearch = useCallback(async () => {
+ if (locationSearch.trim() === "") return;
+
+ try {
+ const results = await provider.search({ query: locationSearch });
+ if (results.length > 0) {
+ const { x, y } = results[0];
+ setFocusLocation([y, x]);
+ }
+ } catch (error) {
+ console.error("Error searching for location:", error);
+ }
+ }, [locationSearch, provider, setFocusLocation]);
+
return (
- {/* Styles applied to MapContainer don't render unless page is reloaded */}
>
)}
-
+
+
{isOpen && (
);
}
+
+function MapControls({ locationSearch, setLocationSearch, handleLocationSearch, focusLocation, setFocusLocation }) {
+ const map = useMap();
+ const bg = useColorModeValue("white", "gray.800");
+ const color = useColorModeValue("gray.800", "white");
+ const placeholderColor = useColorModeValue("gray.500", "gray.400");
+ const [isLoading, setIsLoading] = useState(false);
+ const [suggestions, setSuggestions] = useState([]);
+ const [showSuggestions, setShowSuggestions] = useState(false);
+ const suggestionsRef = useRef(null);
+
+ useEffect(() => {
+ if (focusLocation) {
+ map.flyTo(focusLocation, 18);
+ }
+ }, [focusLocation, map]);
+
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ if (suggestionsRef.current && !suggestionsRef.current.contains(event.target)) {
+ setShowSuggestions(false);
+ }
+ };
+
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, []);
+
+ useMapEvents({
+ click: () => {
+ setShowSuggestions(false);
+ },
+ });
+
+ const fetchSuggestions = async (value) => {
+ if (value.length > 2) {
+ try {
+ const response = await axios.get(
+ `https://nominatim.openstreetmap.org/search?format=json&q=${value}&limit=5`
+ );
+ setSuggestions(response.data);
+ setShowSuggestions(true);
+ } catch (error) {
+ console.error("Error fetching suggestions:", error);
+ }
+ } else {
+ setSuggestions([]);
+ setShowSuggestions(false);
+ }
+ };
+
+ // Debounce the fetchSuggestions function
+ const debouncedFetchSuggestions = useCallback(
+ debounce(fetchSuggestions, 300),
+ []
+ );
+
+ const handleInputChange = (e) => {
+ const value = e.target.value;
+ setLocationSearch(value);
+ debouncedFetchSuggestions(value);
+ };
+
+ const handleKeyDown = (e) => {
+ if (e.key === 'Enter') {
+ performSearch();
+ }
+ };
+
+ const performSearch = async () => {
+ setIsLoading(true);
+ setShowSuggestions(false);
+ try {
+ await handleLocationSearch();
+ } catch (error) {
+ console.error("Error searching for location:", error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleSuggestionClick = (suggestion) => {
+ setLocationSearch(suggestion.display_name);
+ setShowSuggestions(false);
+ setFocusLocation([parseFloat(suggestion.lat), parseFloat(suggestion.lon)]);
+ };
+
+ return (
+
+ {showSuggestions && suggestions.length > 0 && (
+
+ {suggestions.map((suggestion) => (
+ handleSuggestionClick(suggestion)}
+ >
+ {suggestion.display_name}
+
+ ))}
+
+ )}
+
+
+ {isLoading ? (
+
+ ) : (
+ }
+ onClick={performSearch}
+ size="sm"
+ colorScheme="blue"
+ variant="ghost"
+ borderRadius="full"
+ aria-label="Search location"
+ minWidth="40px"
+ />
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6148c5a..755b5cb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -146,6 +146,12 @@ importers:
leaflet:
specifier: ^1.9.4
version: 1.9.4
+ leaflet-geosearch:
+ specifier: ^4.0.0
+ version: 4.0.0
+ lodash:
+ specifier: ^4.17.21
+ version: 4.17.21
react:
specifier: ^18.2.0
version: 18.2.0
@@ -5029,6 +5035,12 @@ packages:
dev: false
optional: true
+ /@googlemaps/js-api-loader@1.16.8:
+ resolution: {integrity: sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ==}
+ requiresBuild: true
+ dev: false
+ optional: true
+
/@graphql-tools/executor@0.0.18(graphql@16.8.1):
resolution: {integrity: sha512-xZC0C+/npXoSHBB5bsJdwxDLgtl1Gu4fL9J2TPQmXoZC3L2N506KJoppf9LgWdHU/xK04luJrhP6WjhfkIN0pQ==}
peerDependencies:
@@ -9441,6 +9453,13 @@ packages:
readable-stream: 2.3.8
dev: true
+ /leaflet-geosearch@4.0.0:
+ resolution: {integrity: sha512-a92VNY9gxyv3oyEDqIWoCNoBllajWRYejztzOSNmpLRtzpA6JtGgy/wwl9tsB8+6Eek1fe+L6+W0MDEOaidbXA==}
+ optionalDependencies:
+ '@googlemaps/js-api-loader': 1.16.8
+ leaflet: 1.9.4
+ dev: false
+
/leaflet@1.9.4:
resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==}
dev: false