- {currentMapCache.loadedCoordinates.lat.toFixed(6)},{" "}
- {currentMapCache.loadedCoordinates.lng.toFixed(6)}
+
+
+
+ {currentMapCache.loadedCoordinates.displayName.substring(
+ 0,
+ 40
+ ) +
+ (currentMapCache.loadedCoordinates.displayName.length > 40
+ ? "... "
+ : "")}
+
+
+ {currentMapCache.loadedCoordinates instanceof
+ MarkerSelection && (
+
+ ({currentMapCache.loadedCoordinates.marker.lat.toFixed(6)},{" "}
+ {currentMapCache.loadedCoordinates.marker.lng.toFixed(6)})
+
+ )}
+
= ({ closeDialog }) => {
const [datasets, setDatasets] = useState([]);
- const { currentTabsCache, setCurrentTabsCache } = useContext(TabsContext);
- const { currentAlertCache, setCurrentAlertCache } = useContext(AlertContext);
+ const { openNewTab } = useContext(TabsContext);
useEffect(() => {
const fetchDatasetsData = async () => {
@@ -50,7 +41,10 @@ const DatasetsList: React.FC = ({ closeDialog }) => {
),
metaData: undefined,
- data: emptyFeatureCollection,
+ data: {
+ type: "FeatureCollection",
+ features: [],
+ },
lastDataRequestBounds: L.latLngBounds(
L.latLng(0, 0),
L.latLng(0, 0)
@@ -67,29 +61,12 @@ const DatasetsList: React.FC = ({ closeDialog }) => {
}, []);
// Opens a new tab
- const openNewTab = (dataset: Dataset) => {
- if (
- currentTabsCache.openedTabs.some((tab) => tab.dataset.id === dataset.id)
- ) {
- setCurrentAlertCache({
- ...currentAlertCache,
- isAlertOpened: true,
- text: "This dataset was already added.",
- });
- return;
- }
- const newTabID = currentTabsCache.openedTabs.length + 1;
- const newTab: TabProps = {
- id: newTabID.toString(),
- dataset: dataset,
- ifPinned: false,
- };
- setCurrentTabsCache({
- ...currentTabsCache,
- openedTabs: [...currentTabsCache.openedTabs, newTab],
- });
+ const openNewDataset = (dataset: Dataset) => {
+ const ifOpened = openNewTab(dataset);
// Close the dialog if necessary
- closeDialog();
+ if (ifOpened) {
+ closeDialog();
+ }
};
return (
@@ -102,7 +79,7 @@ const DatasetsList: React.FC = ({ closeDialog }) => {
{
- openNewTab(dataset);
+ openNewDataset(dataset);
}}
>
{dataset.datasetIcon}
diff --git a/frontend/src/components/MapView/BackgroundMaps/AerialMap.tsx b/frontend/src/components/MapView/BackgroundMaps/AerialMap.tsx
new file mode 100644
index 00000000..82b381dd
--- /dev/null
+++ b/frontend/src/components/MapView/BackgroundMaps/AerialMap.tsx
@@ -0,0 +1,23 @@
+import L from "leaflet";
+import { TileLayer, WMSTileLayer } from "react-leaflet";
+
+const AerialMap = () => {
+ return (
+
+
+
+
+ );
+};
+
+export default AerialMap;
diff --git a/frontend/src/components/MapView/BackgroundMaps/NormalMap.tsx b/frontend/src/components/MapView/BackgroundMaps/NormalMap.tsx
new file mode 100644
index 00000000..8a2ae738
--- /dev/null
+++ b/frontend/src/components/MapView/BackgroundMaps/NormalMap.tsx
@@ -0,0 +1,14 @@
+import { TileLayer } from "react-leaflet";
+
+const NormalMap = () => {
+ return (
+
+
+
+ );
+};
+
+export default NormalMap;
diff --git a/frontend/src/components/MapView/BackgroundMaps/ParcelMap.tsx b/frontend/src/components/MapView/BackgroundMaps/ParcelMap.tsx
new file mode 100644
index 00000000..9ab6bd39
--- /dev/null
+++ b/frontend/src/components/MapView/BackgroundMaps/ParcelMap.tsx
@@ -0,0 +1,22 @@
+import { TileLayer, WMSTileLayer } from "react-leaflet";
+
+const ParcelMap = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default ParcelMap;
diff --git a/frontend/src/components/MapView/BackgroundMaps/SatelliteMap.tsx b/frontend/src/components/MapView/BackgroundMaps/SatelliteMap.tsx
new file mode 100644
index 00000000..118cc600
--- /dev/null
+++ b/frontend/src/components/MapView/BackgroundMaps/SatelliteMap.tsx
@@ -0,0 +1,23 @@
+import L from "leaflet";
+import { TileLayer, WMSTileLayer } from "react-leaflet";
+
+const SatelliteMap = () => {
+ return (
+
+
+
+
+ );
+};
+
+export default SatelliteMap;
diff --git a/frontend/src/components/MapView/GeoDataFetcher.tsx b/frontend/src/components/MapView/GeoDataFetcher.tsx
index fd6c6825..9fc37136 100644
--- a/frontend/src/components/MapView/GeoDataFetcher.tsx
+++ b/frontend/src/components/MapView/GeoDataFetcher.tsx
@@ -67,6 +67,7 @@ const GeoDataFetcher = (
isAlertOpened: true,
text: "Fetching data failed.",
});
+ console.error("Error fetching data.");
}
};
@@ -78,6 +79,13 @@ const GeoDataFetcher = (
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bounds, zoom, id]);
+ /**
+ * Fetches the data at first load.
+ */
+ useEffect(() => {
+ fetchMetadataAndData();
+ });
+
return data;
};
diff --git a/frontend/src/components/MapView/LeafletMap.css b/frontend/src/components/MapView/LeafletMap.css
new file mode 100644
index 00000000..e337a3fe
--- /dev/null
+++ b/frontend/src/components/MapView/LeafletMap.css
@@ -0,0 +1,18 @@
+.leaflet-draw-tooltip {
+ display: none;
+}
+
+.leaflet-editing-icon {
+ background-color: #ff0000;
+ border-color: black;
+ margin-top: -3px !important;
+ margin-left: -3px !important;
+ border-radius: 1rem;
+ width: 8px !important;
+ height: 8px !important;
+}
+
+.leaflet-draw-guide-dash {
+ background-color: #ff0000 !important;
+ border-radius: 1rem;
+}
diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx
new file mode 100644
index 00000000..a41dd0a2
--- /dev/null
+++ b/frontend/src/components/MapView/LeafletMap.tsx
@@ -0,0 +1,236 @@
+import React, {
+ Fragment,
+ useContext,
+ useEffect,
+ useRef,
+ useState,
+} from "react";
+import { MapContainer, ZoomControl } from "react-leaflet";
+import { TabProps, TabsContext } from "../../contexts/TabsContext";
+import { MapContext } from "../../contexts/MapContext";
+import MapDatasetVisualizer from "./MapDatasetVisualizer";
+import { Dataset } from "../../types/DatasetTypes";
+import MapEventsHandler from "./MapEventsHandler";
+import ZoomWarningLabel from "../ZoomWarningLabel/ZoomWarningLabel";
+import L, { LeafletEvent } from "leaflet";
+import "leaflet-draw/dist/leaflet.draw.css";
+import "leaflet-draw";
+import SatelliteMap from "./BackgroundMaps/SatelliteMap";
+import AerialMap from "./BackgroundMaps/AerialMap";
+import NormalMap from "./BackgroundMaps/NormalMap";
+import ParcelMap from "./BackgroundMaps/ParcelMap";
+import "./LeafletMap.css";
+import {
+ MarkerSelection,
+ PolygonSelection,
+} from "../../types/MapSelectionTypes";
+import {
+ Feature,
+ GeoJsonProperties,
+ Geometry,
+ MultiPolygon,
+ Position,
+} from "geojson";
+
+interface LeafletMapProps {
+ datasetId: string;
+ mapType: string;
+}
+
+const LeafletMap: React.FC = ({ datasetId, mapType }) => {
+ const { currentTabsCache, getCurrentTab, getOrFetchMetadata } =
+ useContext(TabsContext);
+ const [map, setMap] = useState(null);
+ const { currentMapCache, setCurrentMapCache } = useContext(MapContext);
+ const currentMapCacheRef = useRef(currentMapCache);
+ const [isGrayscale, setIsGrayscale] = useState(false);
+ const [polygonDrawer, setPolygonDrawer] = useState(
+ null
+ );
+ const currentTab = getCurrentTab();
+
+ // Update ref value whenever currentMapCache changes
+ useEffect(() => {
+ currentMapCacheRef.current = currentMapCache;
+ }, [currentMapCache]);
+
+ /**
+ * Toggle polygon drawer
+ */
+ useEffect(() => {
+ if (polygonDrawer) {
+ if (currentMapCache.isDrawing) {
+ if (currentMapCache.drawnItems) {
+ currentMapCache.drawnItems.clearLayers();
+ }
+ setCurrentMapCache({ ...currentMapCache, selectedCoordinates: null });
+ polygonDrawer.enable();
+ } else {
+ polygonDrawer.disable();
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [currentMapCache.isDrawing]);
+
+ /**
+ * Delete the selection if other coordinate was selected
+ */
+ useEffect(() => {
+ if (currentMapCache.selectedCoordinates instanceof MarkerSelection) {
+ polygonDrawer?.disable();
+ if (currentMapCache.drawnItems) {
+ currentMapCache.drawnItems.clearLayers();
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [currentMapCache.selectedCoordinates]);
+
+ /**
+ * Fetches the metadata of the current tab
+ */
+ useEffect(() => {
+ if (currentTab) {
+ getOrFetchMetadata(currentTab.dataset.id);
+ }
+ }, [currentTab, getOrFetchMetadata]);
+
+ /**
+ * Refresh the map bounds on map change
+ */
+ useEffect(() => {
+ if (map) {
+ const initialBounds = map.getBounds();
+ const initialCenter = map.getCenter();
+ const initialZoom = map.getZoom();
+ const drawnItems = new L.FeatureGroup();
+
+ setCurrentMapCache((prevCache) => ({
+ ...prevCache,
+ mapInstance: map,
+ mapCenter: initialCenter,
+ mapBounds: initialBounds,
+ zoom: initialZoom,
+ drawnItems: drawnItems,
+ }));
+ // Allow for drawing polygons
+ map.addLayer(drawnItems);
+ // Define the options for the polygon drawer
+ const polygonOptions = {
+ shapeOptions: {
+ color: "#ff0000",
+ weight: 3,
+ fillOpacity: 0.06,
+ },
+ };
+ setPolygonDrawer(new L.Draw.Polygon(map as L.DrawMap, polygonOptions));
+ // Bind for polygon created
+ map.on(L.Draw.Event.CREATED, (event: LeafletEvent) => {
+ const drawnObject = (event as L.DrawEvents.Created).layer;
+ if (drawnObject instanceof L.Polygon) {
+ if (drawnItems) {
+ drawnItems.addLayer(drawnObject);
+ const geoJsonObject = drawnObject.toGeoJSON() as Feature<
+ Geometry,
+ GeoJsonProperties
+ >;
+ let multiPolygon: MultiPolygon;
+
+ // we will probably always encounter only polygons but in a istant future it may be interesting to have multi polygon selection
+ if (geoJsonObject.geometry.type === "Polygon") {
+ const polygon = geoJsonObject.geometry
+ .coordinates as Position[][];
+ multiPolygon = {
+ type: "MultiPolygon",
+ coordinates: [polygon],
+ };
+ } else if (geoJsonObject.geometry.type === "MultiPolygon") {
+ multiPolygon = geoJsonObject.geometry as MultiPolygon;
+ } else {
+ throw new Error("Unsupported geometry type");
+ }
+ const polygonSelection = new PolygonSelection(
+ multiPolygon,
+ "Custom Polygon",
+ true
+ );
+
+ setCurrentMapCache({
+ ...currentMapCacheRef.current,
+ selectedCoordinates: polygonSelection,
+ isDrawing: false,
+ });
+ }
+ }
+ });
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [map]);
+
+ /**
+ * Check for the zoom level threshold to apply the zoom warning and its effects
+ */
+ useEffect(() => {
+ if (currentTab && currentTab.dataset.metaData) {
+ setIsGrayscale(
+ currentMapCache.zoom <= currentTab.dataset.metaData.minZoomLevel
+ );
+ }
+ }, [currentMapCache.zoom, currentTab]);
+
+ /**
+ * Adds or removes the grayscale css value
+ */
+ useEffect(() => {
+ if (map) {
+ if (isGrayscale) {
+ map.getContainer().classList.add("grayscale-overlay");
+ } else {
+ map.getContainer().classList.remove("grayscale-overlay");
+ }
+ }
+ }, [isGrayscale, map]);
+
+ const pinnedFeatureCollections = currentTabsCache.openedTabs
+ .filter((tab: TabProps) => tab.ifPinned)
+ .map((tab: TabProps) => tab.dataset);
+
+ const isCurrentDataPinned = pinnedFeatureCollections.some(
+ (dataset: Dataset) => dataset.id === datasetId
+ );
+
+ return (
+
+
+
+ {isGrayscale ? (
+
+ ) : (
+
+ {pinnedFeatureCollections.map((dataset: Dataset, index: number) => (
+
+ ))}
+ {!isCurrentDataPinned && currentTab && (
+
+ )}
+
+ )}
+
+ {mapType === "satellite" && }
+ {mapType === "aerial" && }
+ {mapType === "normal" && }
+ {mapType === "parcel" && }
+
+
+
+ );
+};
+
+export default LeafletMap;
diff --git a/frontend/src/components/MapView/MapEventsHandler.tsx b/frontend/src/components/MapView/MapEventsHandler.tsx
index 8048fd17..e59cc526 100644
--- a/frontend/src/components/MapView/MapEventsHandler.tsx
+++ b/frontend/src/components/MapView/MapEventsHandler.tsx
@@ -1,12 +1,12 @@
-import { Fragment, useContext } from "react";
-import { Marker } from "react-leaflet/Marker";
-import { Popup } from "react-leaflet/Popup";
-import { useMap, useMapEvents } from "react-leaflet/hooks";
+import React, { Fragment, useContext } from "react";
+import { Marker } from "react-leaflet";
+import { useMapEvents } from "react-leaflet/hooks";
import { MapContext } from "../../contexts/MapContext";
import L, { DivIcon } from "leaflet";
import { MapPin } from "@phosphor-icons/react";
import { createRoot } from "react-dom/client";
import { flushSync } from "react-dom";
+import { MarkerSelection } from "../../types/MapSelectionTypes";
// Utility function to render a React component to HTML string
const renderToHtml = (Component: React.FC) => {
@@ -25,23 +25,23 @@ const divIconMarker: DivIcon = L.divIcon({
iconAnchor: [18, 36], // Adjust the anchor point as needed
});
-const MapEventsHandler = () => {
+const MapEventsHandler: React.FC = () => {
const { currentMapCache, setCurrentMapCache } = useContext(MapContext);
- const setPosition = (latlng: L.LatLng) => {
- setCurrentMapCache({ ...currentMapCache, selectedCoordinates: latlng });
- };
-
- const map = useMap();
// Add events
useMapEvents({
click: (event) => {
- currentMapCache.polygon?.remove();
- setCurrentMapCache({
- ...currentMapCache,
- selectedCoordinates: event.latlng,
- polygon: null,
- });
+ if (!currentMapCache.isDrawing) {
+ const markerSelection = new MarkerSelection(
+ event.latlng,
+ "Custom Marker",
+ true
+ );
+ setCurrentMapCache({
+ ...currentMapCache,
+ selectedCoordinates: markerSelection,
+ });
+ }
},
moveend: (event) => {
setCurrentMapCache({
@@ -53,34 +53,11 @@ const MapEventsHandler = () => {
},
});
- return currentMapCache.selectedCoordinates !== null ? (
-
-
- {
- map
- .locate({ setView: true })
- .on("locationfound", function (event) {
- //currentMapCache.polygon?.remove();
- setPosition(event.latlng);
- map.flyTo(event.latlng, map.getZoom(), {
- animate: true,
- duration: 50,
- });
- })
- // If access to the location was denied
- .on("locationerror", function (event) {
- console.log(event);
- alert("Location access denied.");
- });
- }}
- >
- {currentMapCache.selectedCoordinates.lat.toFixed(4)},{" "}
- {currentMapCache.selectedCoordinates.lng.toFixed(4)}
-
-
-
+ return currentMapCache.selectedCoordinates instanceof MarkerSelection ? (
+
) : (
);
diff --git a/frontend/src/components/MapView/MapOptions.css b/frontend/src/components/MapView/MapOptions.css
index d2b02e6e..620e3629 100644
--- a/frontend/src/components/MapView/MapOptions.css
+++ b/frontend/src/components/MapView/MapOptions.css
@@ -47,7 +47,7 @@
color: #535bf2;
}
-.layers-map-icon {
+.options-icons {
width: 1.5rem;
height: 1.5rem;
}
@@ -65,5 +65,26 @@
}
.image-hover-effect:hover {
- border-color: blue;
-}
\ No newline at end of file
+ border-color: blue;
+}
+
+.draw-polygon-icon-container {
+ width: 2.1rem;
+ height: 2.1rem;
+ position: absolute;
+ top: 7.5rem;
+ right: 0.6rem;
+ cursor: pointer;
+ z-index: 2;
+ display: flex;
+ flex-direction: column;
+ background-color: white;
+ justify-content: center;
+ align-items: center;
+ border: 2px solid rgba(0, 0, 0, 0.27);
+ box-shadow: none;
+}
+.draw-polygon-icon-container:hover {
+ background-color: #f4f4f4;
+ color: #535bf2;
+}
diff --git a/frontend/src/components/MapView/MapOptions.tsx b/frontend/src/components/MapView/MapOptions.tsx
index b84d5313..8db0839a 100644
--- a/frontend/src/components/MapView/MapOptions.tsx
+++ b/frontend/src/components/MapView/MapOptions.tsx
@@ -1,17 +1,17 @@
-import React, { useState } from "react";
-import { Paper, Popover, Grid, Typography, Box } from "@mui/material";
+import React, { useContext, useState } from "react";
+import { Paper, Popover, Grid, Typography, Box, Tooltip } from "@mui/material";
import "./MapOptions.css";
-import { StackSimple } from "@phosphor-icons/react";
+import { Polygon, StackSimple } from "@phosphor-icons/react";
import SearchBar from "../SearchBar/SearchBar";
+import { MapContext } from "../../contexts/MapContext";
interface MapOptionsProps {
- onMapTypeChange: (
- type: "normal" | "satellite" | "parzellar" | "aerial"
- ) => void;
+ onMapTypeChange: (type: "normal" | "satellite" | "parcel" | "aerial") => void;
}
const MapOptions: React.FC = ({ onMapTypeChange }) => {
const [anchorEl, setAnchorEl] = useState(null);
+ const { currentMapCache, setCurrentMapCache } = useContext(MapContext);
const handleClick = (event: React.MouseEvent) => {
setAnchorEl(event.currentTarget);
@@ -22,7 +22,7 @@ const MapOptions: React.FC = ({ onMapTypeChange }) => {
};
const handleMapTypeChange = (
- type: "normal" | "satellite" | "parzellar" | "aerial"
+ type: "normal" | "satellite" | "parcel" | "aerial"
) => {
onMapTypeChange(type);
handleClose();
@@ -36,12 +36,27 @@ const MapOptions: React.FC = ({ onMapTypeChange }) => {
-
-
-
+
+
+
+
+
+
+ {
+ setCurrentMapCache({
+ ...currentMapCache,
+ isDrawing: !currentMapCache.isDrawing,
+ });
+ }}
+ className="draw-polygon-icon-container leaflet-touch leaflet-bar leaflet-control leaflet-control-custom"
+ >
+
+
+
= ({ onMapTypeChange }) => {
handleClose();
}}
/>
-
Aerial
- {/*
-
- {
- handleMapTypeChange("parzellar");
- handleClose();
- }}
- />
-
- Parzellar
-
-
- */}
diff --git a/frontend/src/components/MapView/MapView.css b/frontend/src/components/MapView/MapView.css
index 32b25569..731cd6c1 100644
--- a/frontend/src/components/MapView/MapView.css
+++ b/frontend/src/components/MapView/MapView.css
@@ -10,5 +10,5 @@
}
.grayscale-overlay {
- filter: grayscale(80%);
+ filter: grayscale(40%);
}
diff --git a/frontend/src/components/MapView/MapView.tsx b/frontend/src/components/MapView/MapView.tsx
index ba0c7569..5f0e1981 100644
--- a/frontend/src/components/MapView/MapView.tsx
+++ b/frontend/src/components/MapView/MapView.tsx
@@ -1,10 +1,4 @@
-import { Fragment, useContext, useEffect, useState } from "react";
-import {
- MapContainer,
- TileLayer,
- WMSTileLayer,
- ZoomControl,
-} from "react-leaflet";
+import { useState } from "react";
import "leaflet/dist/leaflet.css";
import "leaflet.markercluster/dist/MarkerCluster.css";
import "leaflet.markercluster/dist/MarkerCluster.Default.css";
@@ -14,12 +8,7 @@ import L from "leaflet";
import icon from "leaflet/dist/images/marker-icon.png";
import iconShadow from "leaflet/dist/images/marker-shadow.png";
import MapOptions from "./MapOptions";
-import { MapContext } from "../../contexts/MapContext";
-import { TabProps, TabsContext } from "../../contexts/TabsContext";
-import MapDatasetVisualizer from "./MapDatasetVisualizer";
-import MapEventsHandler from "./MapEventsHandler";
-import { Dataset } from "../../types/DatasetTypes";
-import ZoomWarningLabel from "../ZoomWarningLabel/ZoomWarningLabel";
+import LeafletMap from "./LeafletMap";
const DefaultIcon = L.icon({
iconUrl: icon,
@@ -34,156 +23,24 @@ interface MapViewProps {
}
const MapView: React.FC
= ({ datasetId }) => {
- const { currentTabsCache, getCurrentTab, getOrFetchMetadata } =
- useContext(TabsContext);
- const [map, setMap] = useState(null);
- const { currentMapCache, setCurrentMapCache } = useContext(MapContext);
- const [isGrayscale, setIsGrayscale] = useState(false);
const [mapType, setMapType] = useState<
- "normal" | "satellite" | "parzellar" | "aerial"
+ "normal" | "satellite" | "parcel" | "aerial"
>("normal");
+ /**
+ * Changes the layer type
+ * @param type type of the layer
+ */
const handleMapTypeChange = (
- type: "normal" | "satellite" | "parzellar" | "aerial"
+ type: "normal" | "satellite" | "parcel" | "aerial"
) => {
setMapType(type);
};
- const currentTab = getCurrentTab();
-
- useEffect(() => {
- if (currentTab) {
- getOrFetchMetadata(currentTab.dataset.id);
- }
- }, [currentTab, getOrFetchMetadata]);
-
- useEffect(() => {
- if (map) {
- const initialBounds = map.getBounds();
- const initialCenter = map.getCenter();
- const initialZoom = map.getZoom();
-
- setCurrentMapCache((prevCache) => ({
- ...prevCache,
- mapInstance: map,
- mapCenter: initialCenter,
- mapBounds: initialBounds,
- zoom: initialZoom,
- }));
- }
- }, [map, setCurrentMapCache]);
-
- useEffect(() => {
- if (currentTab && currentTab.dataset.metaData) {
- setIsGrayscale(
- currentMapCache.zoom <= currentTab.dataset.metaData.minZoomLevel
- );
- }
- }, [currentMapCache.zoom, currentTab]);
-
- useEffect(() => {
- if (map) {
- if (isGrayscale) {
- map.getContainer().classList.add("grayscale-overlay");
- } else {
- map.getContainer().classList.remove("grayscale-overlay");
- }
- }
- }, [isGrayscale, map]);
-
- const pinnedFeatureCollections = currentTabsCache.openedTabs
- .filter((tab: TabProps) => tab.ifPinned)
- .map((tab: TabProps) => tab.dataset);
-
- const isCurrentDataPinned = pinnedFeatureCollections.some(
- (dataset: Dataset) => dataset.id === datasetId
- );
-
return (
+
-
-
- {isGrayscale ? (
-
- ) : (
-
- {pinnedFeatureCollections.map((dataset: Dataset, index: number) => (
-
- ))}
- {!isCurrentDataPinned && currentTab && (
-
- )}
-
- )}
-
-
- {mapType === "satellite" && (
-
-
-
-
- )}
- {mapType === "aerial" && (
-
-
-
-
- )}
- {mapType === "normal" && (
-
-
-
- )}
- {mapType === "parzellar" && (
-
-
-
-
-
- )}
-
-
);
};
diff --git a/frontend/src/components/MultiMap/MultiMap.css b/frontend/src/components/MultiMap/MultiMap.css
index 98a11f97..08af6286 100644
--- a/frontend/src/components/MultiMap/MultiMap.css
+++ b/frontend/src/components/MultiMap/MultiMap.css
@@ -4,8 +4,8 @@
max-width: 100%;
display: flex;
flex-direction: column;
- padding-left:20px;
- padding-right:10px;
+ padding-left: 20px;
+ padding-right: 10px;
padding-top: 3px;
}
@@ -75,33 +75,6 @@
right: 0.2rem;
}
-.add-tab-button {
- background-color: white;
- border-color: gray;
- color: gray;
- padding: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- margin: 0.4rem;
- min-width: 2rem;
- min-height: 2rem;
- width: 2rem;
- height: 2rem;
- margin-right: 2rem;
-}
-
-.add-tab-button:hover {
- background-color: rgb(245, 245, 245);
- border-color: black;
- color: black;
-}
-
-.add-tab-button:focus {
- color: black;
- border-color: black;
-}
-
.MuiTabs-scrollButtons.Mui-disabled {
opacity: 0.3 !important;
}
diff --git a/frontend/src/components/MultiMap/NewTabButton.css b/frontend/src/components/MultiMap/NewTabButton.css
new file mode 100644
index 00000000..a7b70ade
--- /dev/null
+++ b/frontend/src/components/MultiMap/NewTabButton.css
@@ -0,0 +1,27 @@
+.add-tab-button {
+ background-color: white;
+ border-color: gray;
+ color: gray;
+ padding: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 0.4rem;
+ min-width: 2rem;
+ min-height: 2rem;
+ width: 2rem;
+ height: 2rem;
+ margin-right: 2rem;
+}
+
+.add-tab-button:hover {
+ background-color: rgb(245, 245, 245);
+ border-color: black;
+ color: black;
+}
+
+.add-tab-button:focus {
+ color: none;
+ border-color: none;
+ outline: none;
+}
diff --git a/frontend/src/components/MultiMap/NewTabButton.tsx b/frontend/src/components/MultiMap/NewTabButton.tsx
index 7c4315c7..8e2d2c4e 100644
--- a/frontend/src/components/MultiMap/NewTabButton.tsx
+++ b/frontend/src/components/MultiMap/NewTabButton.tsx
@@ -2,6 +2,7 @@ import { Tooltip } from "@mui/material";
import { Plus } from "@phosphor-icons/react";
import { useState } from "react";
import DatasetsPopUp from "../PopUp/DatasetsPopUp";
+import "./NewTabButton.css";
const NewTabButton = () => {
// Stores the state of if the datasets popup is open
diff --git a/frontend/src/components/SearchBar/SearchBar.tsx b/frontend/src/components/SearchBar/SearchBar.tsx
index 6dd978a2..40ca0c6a 100644
--- a/frontend/src/components/SearchBar/SearchBar.tsx
+++ b/frontend/src/components/SearchBar/SearchBar.tsx
@@ -8,9 +8,13 @@ import { MapSelection, SearchContext } from "../../contexts/SearchContext";
import { OpenStreetMapProvider } from "leaflet-geosearch";
import { LatLng } from "leaflet";
import { MapContext } from "../../contexts/MapContext";
-import L from "leaflet";
import "./SearchBar.css";
-import { GeoJSON } from "geojson";
+import { GeoJSON, MultiPolygon } from "geojson";
+import {
+ MarkerSelection,
+ PolygonSelection,
+} from "../../types/MapSelectionTypes";
+import L from "leaflet";
declare module "leaflet-geosearch/dist/providers/openStreetMapProvider.js" {
interface RawResult {
@@ -111,24 +115,42 @@ const SearchBar: React.FC = () => {
if (mapInstance) {
if (item.area && item.bounds) {
mapInstance.flyToBounds(item.bounds, { animate: true, duration: 5 });
- const drawPolygon = L.geoJSON(item.polygon, {
- style: {
- color: "#ff0000",
- weight: 2,
- fillOpacity: 0,
- },
- });
- drawPolygon.addTo(mapInstance);
- setCurrentMapCache({
- ...currentMapCache,
- polygon: drawPolygon,
- selectedCoordinates: null,
- });
+ if (currentMapCache.drawnItems) {
+ currentMapCache.drawnItems.clearLayers();
+ }
+ if (item.polygon) {
+ const drawPolygon = L.geoJSON(item.polygon, {
+ style: {
+ color: "#ff0000",
+ weight: 2,
+ fillOpacity: 0.06,
+ },
+ });
+ drawPolygon.addTo(currentMapCache.drawnItems!);
+ const polygonSelection = new PolygonSelection(
+ item.polygon as MultiPolygon,
+ item.displayName,
+ false
+ );
+ setCurrentMapCache({
+ ...currentMapCache,
+ selectedCoordinates: polygonSelection,
+ });
+ }
} else {
- currentMapCache.selectedCoordinates = targetPosition;
- mapInstance.flyTo(targetPosition, 13, { animate: true, duration: 5 });
+ // Select a marker on the map
+ const markerSelection = new MarkerSelection(
+ targetPosition,
+ item.displayName,
+ false
+ );
+ currentMapCache.selectedCoordinates = markerSelection;
+ mapInstance.flyTo(targetPosition, currentMapCache.zoom, {
+ animate: true,
+ duration: 5,
+ });
}
- } else console.log("no map instance");
+ } else console.log("No map instance");
};
const getUniqueOptions = (options: MapSelection[]) => {
@@ -156,7 +178,7 @@ const SearchBar: React.FC = () => {
getOptionLabel={(option) =>
typeof option === "string" ? option : option.displayName
}
- freeSolo={inputValue?.length ? false : true}
+ freeSolo={true}
loading={loading}
forcePopupIcon={false}
filterOptions={(x) => x}
@@ -183,19 +205,12 @@ const SearchBar: React.FC = () => {
}
}}
onInputChange={(_event, newInputValue) => {
- if (newInputValue === "") {
- currentMapCache.polygon?.remove();
- setCurrentMapCache({
- ...currentMapCache,
- polygon: null,
- });
- }
setInputValue(newInputValue);
}}
renderInput={(params) => (
Search… }
+ placeholder="Search..."
size="small"
sx={{
width: inputValue.length > 0 ? "100%" : 150,
@@ -237,7 +252,7 @@ const SearchBar: React.FC = () => {
);
return (
-