From e6fdfc8129c611da6586187aaafa0280ee2fc5b0 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Tue, 2 Jul 2024 20:16:07 +0200 Subject: [PATCH 01/32] Implemented simple 3D window Signed-off-by: Emil Balitzki --- frontend/package-lock.json | 46 ++++- frontend/package.json | 4 +- .../src/components/MapView/MapOptions.css | 27 ++- .../src/components/MapView/MapOptions.tsx | 54 +++-- frontend/src/components/MapView/MapView.tsx | 189 ++++++++++-------- .../src/components/ThreeDView/ThreeDView.tsx | 91 +++++++++ 6 files changed, 295 insertions(+), 116 deletions(-) create mode 100644 frontend/src/components/ThreeDView/ThreeDView.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a4136a56..c0585daa 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,6 +20,7 @@ "@types/geojson": "^7946.0.14", "@types/leaflet.markercluster": "^1.5.4", "@types/proj4leaflet": "^1.0.10", + "@types/three": "^0.166.0", "autosuggest-highlight": "^3.3.4", "axios": "^1.7.2", "geojson": "^0.5.0", @@ -29,7 +30,8 @@ "proj4leaflet": "^1.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-leaflet": "^4.2.1" + "react-leaflet": "^4.2.1", + "three": "^0.166.1" }, "devDependencies": { "@types/leaflet": "^1.9.12", @@ -1744,6 +1746,11 @@ "win32" ] }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.2", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.2.tgz", + "integrity": "sha512-kMCNaZCJugWI86xiEHaY338CU5JpD0B97p1j1IKNn/Zto8PgACjQx0UxbHjmOcLl/dDOBnItwD07KmCs75pxtQ==" + }, "node_modules/@types/autosuggest-highlight": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/@types/autosuggest-highlight/-/autosuggest-highlight-3.2.3.tgz", @@ -1877,6 +1884,28 @@ "@types/react": "*" } }, + "node_modules/@types/stats.js": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz", + "integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==" + }, + "node_modules/@types/three": { + "version": "0.166.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.166.0.tgz", + "integrity": "sha512-FHMnpcdhdbdOOIYbfkTkUVpYMW53odxbTRwd0/xJpYnTzEsjnVnondGAvHZb4z06UW0vo6WPVuvH0/9qrxKx7g==", + "dependencies": { + "@tweenjs/tween.js": "~23.1.2", + "@types/stats.js": "*", + "@types/webxr": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~0.18.1" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.19.tgz", + "integrity": "sha512-4hxA+NwohSgImdTSlPXEqDqqFktNgmTXQ05ff1uWam05tNGroCMp4G+4XVl6qWm1p7GQ/9oD41kAYsSssF6Mzw==" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz", @@ -2842,6 +2871,11 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3380,6 +3414,11 @@ "node": ">= 8" } }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==" + }, "node_modules/mgrs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mgrs/-/mgrs-1.0.0.tgz", @@ -4032,6 +4071,11 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/three": { + "version": "0.166.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.166.1.tgz", + "integrity": "sha512-LtuafkKHHzm61AQA1be2MAYIw1IjmhOUxhBa0prrLpEMWbV7ijvxCRHjSgHPGp2493wLBzwKV46tA9nivLEgKg==" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index ada8ff3d..d3199c61 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,6 +22,7 @@ "@types/geojson": "^7946.0.14", "@types/leaflet.markercluster": "^1.5.4", "@types/proj4leaflet": "^1.0.10", + "@types/three": "^0.166.0", "autosuggest-highlight": "^3.3.4", "axios": "^1.7.2", "geojson": "^0.5.0", @@ -31,7 +32,8 @@ "proj4leaflet": "^1.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-leaflet": "^4.2.1" + "react-leaflet": "^4.2.1", + "three": "^0.166.1" }, "devDependencies": { "@types/leaflet": "^1.9.12", diff --git a/frontend/src/components/MapView/MapOptions.css b/frontend/src/components/MapView/MapOptions.css index d2b02e6e..74720908 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; +} + +.threed-map-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; +} +.threed-map-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..5308c473 100644 --- a/frontend/src/components/MapView/MapOptions.tsx +++ b/frontend/src/components/MapView/MapOptions.tsx @@ -1,16 +1,20 @@ import React, { useState } from "react"; -import { Paper, Popover, Grid, Typography, Box } from "@mui/material"; +import { Paper, Popover, Grid, Typography, Box, Tooltip } from "@mui/material"; import "./MapOptions.css"; -import { StackSimple } from "@phosphor-icons/react"; +import { StackSimple, ThreeD } from "@phosphor-icons/react"; import SearchBar from "../SearchBar/SearchBar"; interface MapOptionsProps { onMapTypeChange: ( type: "normal" | "satellite" | "parzellar" | "aerial" ) => void; + toggle3D: () => void; } -const MapOptions: React.FC = ({ onMapTypeChange }) => { +const MapOptions: React.FC = ({ + onMapTypeChange, + toggle3D, +}) => { const [anchorEl, setAnchorEl] = useState(null); const handleClick = (event: React.MouseEvent) => { @@ -36,12 +40,25 @@ const MapOptions: React.FC = ({ onMapTypeChange }) => {
-
- -
+ +
+ +
+
+ +
+ +
+
= ({ onMapTypeChange }) => { handleClose(); }} /> - Aerial - {/* - - Satellite { - handleMapTypeChange("parzellar"); - handleClose(); - }} - /> - - Parzellar - - - */} diff --git a/frontend/src/components/MapView/MapView.tsx b/frontend/src/components/MapView/MapView.tsx index ba0c7569..7c4ce450 100644 --- a/frontend/src/components/MapView/MapView.tsx +++ b/frontend/src/components/MapView/MapView.tsx @@ -20,6 +20,7 @@ import MapDatasetVisualizer from "./MapDatasetVisualizer"; import MapEventsHandler from "./MapEventsHandler"; import { Dataset } from "../../types/DatasetTypes"; import ZoomWarningLabel from "../ZoomWarningLabel/ZoomWarningLabel"; +import ThreeDView from "../ThreeDView/ThreeDView"; const DefaultIcon = L.icon({ iconUrl: icon, @@ -42,7 +43,20 @@ const MapView: React.FC = ({ datasetId }) => { const [mapType, setMapType] = useState< "normal" | "satellite" | "parzellar" | "aerial" >("normal"); + const [if3D, setIf3D] = useState(false); + + /** + * Toggles the 3D View + */ + const toggle3D = () => { + console.log("3D"); + setIf3D(!if3D); + }; + /** + * Changes the layer type + * @param type type of the layer + */ const handleMapTypeChange = ( type: "normal" | "satellite" | "parzellar" | "aerial" ) => { @@ -51,6 +65,9 @@ const MapView: React.FC = ({ datasetId }) => { const currentTab = getCurrentTab(); + /** + * Fetches the metadata of the current tab + */ useEffect(() => { if (currentTab) { getOrFetchMetadata(currentTab.dataset.id); @@ -101,89 +118,95 @@ const MapView: React.FC = ({ datasetId }) => { return (
- - - - {isGrayscale ? ( - - ) : ( -
- {pinnedFeatureCollections.map((dataset: Dataset, index: number) => ( - - ))} - {!isCurrentDataPinned && currentTab && ( - - )} -
- )} - - - {mapType === "satellite" && ( -
- - -
- )} - {mapType === "aerial" && ( -
- - -
- )} - {mapType === "normal" && ( -
- -
- )} - {mapType === "parzellar" && ( -
- - - -
- )} - -
+ {if3D ? ( + + ) : ( + + + {isGrayscale ? ( + + ) : ( +
+ {pinnedFeatureCollections.map( + (dataset: Dataset, index: number) => ( + + ) + )} + {!isCurrentDataPinned && currentTab && ( + + )} +
+ )} + + + {mapType === "satellite" && ( +
+ + +
+ )} + {mapType === "aerial" && ( +
+ + +
+ )} + {mapType === "normal" && ( +
+ +
+ )} + {mapType === "parzellar" && ( +
+ + + +
+ )} + +
+ )} +
); }; diff --git a/frontend/src/components/ThreeDView/ThreeDView.tsx b/frontend/src/components/ThreeDView/ThreeDView.tsx new file mode 100644 index 00000000..c4a9eddb --- /dev/null +++ b/frontend/src/components/ThreeDView/ThreeDView.tsx @@ -0,0 +1,91 @@ +import React, { useRef, useEffect } from "react"; +import * as THREE from "three"; +import { OrbitControls } from "three/examples/jsm/Addons.js"; + +const ThreeDView: React.FC = () => { + const mountRef = useRef(null); + + useEffect(() => { + const mount = mountRef.current!; + + // Scene setup + const scene = new THREE.Scene(); + scene.background = new THREE.Color(0x87ceeb); // Light blue sky + const camera = new THREE.PerspectiveCamera( + 75, + mount.clientWidth / mount.clientHeight, + 0.1, + 1000 + ); + camera.position.set(0, 5, 10); + + const renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer.setSize(mount.clientWidth, mount.clientHeight); + mount.appendChild(renderer.domElement); + + // Plane + const planeGeometry = new THREE.PlaneGeometry(100, 100); + const planeMaterial = new THREE.MeshBasicMaterial({ color: 0x808080 }); + const plane = new THREE.Mesh(planeGeometry, planeMaterial); + plane.rotation.x = -Math.PI / 2; + scene.add(plane); + + // Light + const light = new THREE.DirectionalLight(0xffffff, 1); + light.position.set(10, 10, 10).normalize(); + scene.add(light); + + // Controls + const controls = new OrbitControls(camera, renderer.domElement); + + // Keyboard controls + const moveSpeed = 0.5; + const keyState: { [key: string]: boolean } = {}; + + const onKeyDown = (event: KeyboardEvent) => { + keyState[event.code] = true; + }; + + const onKeyUp = (event: KeyboardEvent) => { + keyState[event.code] = false; + }; + + const updateCameraPosition = () => { + if (keyState["ArrowUp"] || keyState["KeyW"]) { + camera.position.z -= moveSpeed; + } + if (keyState["ArrowDown"] || keyState["KeyS"]) { + camera.position.z += moveSpeed; + } + if (keyState["ArrowLeft"] || keyState["KeyA"]) { + camera.position.x -= moveSpeed; + } + if (keyState["ArrowRight"] || keyState["KeyD"]) { + camera.position.x += moveSpeed; + } + }; + + window.addEventListener("keydown", onKeyDown); + window.addEventListener("keyup", onKeyUp); + + // Animate + const animate = () => { + requestAnimationFrame(animate); + updateCameraPosition(); + controls.update(); + renderer.render(scene, camera); + }; + animate(); + + // Cleanup on component unmount + return () => { + mount.removeChild(renderer.domElement); + window.removeEventListener("keydown", onKeyDown); + window.removeEventListener("keyup", onKeyUp); + }; + }, []); + + return
; +}; + +export default ThreeDView; From a90c1ca4d9ccf79c789d0c6253bdb3946a13c8ef Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Tue, 2 Jul 2024 20:47:13 +0200 Subject: [PATCH 02/32] Added leaflet map render Signed-off-by: Emil Balitzki --- .../src/components/ThreeDView/ThreeDView.tsx | 57 ++++++++++++++++--- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/ThreeDView/ThreeDView.tsx b/frontend/src/components/ThreeDView/ThreeDView.tsx index c4a9eddb..80c54c9f 100644 --- a/frontend/src/components/ThreeDView/ThreeDView.tsx +++ b/frontend/src/components/ThreeDView/ThreeDView.tsx @@ -1,12 +1,20 @@ import React, { useRef, useEffect } from "react"; import * as THREE from "three"; -import { OrbitControls } from "three/examples/jsm/Addons.js"; +import { + CSS3DObject, + CSS3DRenderer, + OrbitControls, +} from "three/examples/jsm/Addons.js"; +import L from "leaflet"; +import "leaflet/dist/leaflet.css"; const ThreeDView: React.FC = () => { const mountRef = useRef(null); + const mapRef = useRef(null); useEffect(() => { const mount = mountRef.current!; + const mapElement = mapRef.current!; // Scene setup const scene = new THREE.Scene(); @@ -23,12 +31,26 @@ const ThreeDView: React.FC = () => { renderer.setSize(mount.clientWidth, mount.clientHeight); mount.appendChild(renderer.domElement); - // Plane - const planeGeometry = new THREE.PlaneGeometry(100, 100); - const planeMaterial = new THREE.MeshBasicMaterial({ color: 0x808080 }); - const plane = new THREE.Mesh(planeGeometry, planeMaterial); - plane.rotation.x = -Math.PI / 2; - scene.add(plane); + const cssRenderer = new CSS3DRenderer(); + cssRenderer.setSize(mount.clientWidth, mount.clientHeight); + cssRenderer.domElement.style.position = "absolute"; + cssRenderer.domElement.style.top = "0"; + cssRenderer.domElement.style.pointerEvents = "none"; // Allow mouse events to pass through + mount.appendChild(cssRenderer.domElement); + + // Initialize Leaflet map + mapElement.style.display = "block"; // Ensure the element is visible + mapElement.style.pointerEvents = "none"; // Ensure the element does not consume events + const map = L.map(mapElement).setView([51.505, -0.09], 13); + + L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { + attribution: "© OpenStreetMap contributors", + }).addTo(map); + + const cssObject = new CSS3DObject(mapElement); + cssObject.rotation.x = -Math.PI / 2; // Rotate to lie flat + cssObject.position.set(0, 0, 0); // Position at ground level + scene.add(cssObject); // Light const light = new THREE.DirectionalLight(0xffffff, 1); @@ -74,18 +96,37 @@ const ThreeDView: React.FC = () => { updateCameraPosition(); controls.update(); renderer.render(scene, camera); + cssRenderer.render(scene, camera); }; animate(); // Cleanup on component unmount return () => { mount.removeChild(renderer.domElement); + mount.removeChild(cssRenderer.domElement); + map.remove(); window.removeEventListener("keydown", onKeyDown); window.removeEventListener("keyup", onKeyUp); }; }, []); - return
; + return ( + <> +
+ + + +
{ + setCurrentMapCache({ + ...currentMapCache, + isDrawing: !currentMapCache.isDrawing, + }); + }} + className="draw-polygon-icon-container leaflet-touch leaflet-bar leaflet-control leaflet-control-custom" + > +
= ({ datasetId }) => { const [if3D, setIf3D] = useState(false); const [mapType, setMapType] = useState< - "normal" | "satellite" | "parzellar" | "aerial" + "normal" | "satellite" | "parcel" | "aerial" >("normal"); /** @@ -34,7 +34,7 @@ const MapView: React.FC = ({ datasetId }) => { * @param type type of the layer */ const handleMapTypeChange = ( - type: "normal" | "satellite" | "parzellar" | "aerial" + type: "normal" | "satellite" | "parcel" | "aerial" ) => { setMapType(type); }; diff --git a/frontend/src/contexts/MapContext.tsx b/frontend/src/contexts/MapContext.tsx index 2b1aa05d..e2d3858d 100644 --- a/frontend/src/contexts/MapContext.tsx +++ b/frontend/src/contexts/MapContext.tsx @@ -6,13 +6,14 @@ import React, { createContext, useState, ReactNode } from "react"; // Map Cache Type export type MapCacheProps = { mapInstance: L.Map | null; - selectedCoordinates: LatLng | LatLng[] | null; - loadedCoordinates: LatLng | LatLng[] | null; + selectedCoordinates: LatLng | L.Polygon | null; + loadedCoordinates: LatLng | L.Polygon | null; currentTabID: string | null; mapCenter: LatLng; mapBounds: LatLngBounds; zoom: number; polygon: L.GeoJSON | null; + isDrawing: boolean; }; // Map Context Type @@ -38,6 +39,7 @@ const defaultMapCache: MapCacheProps = { mapBounds: L.latLngBounds([49.5732, 11.0288], [49.5732, 11.0288]), zoom: 13, polygon: null, + isDrawing: false, }; // Actual value of the context diff --git a/frontend/src/services/locationDataService.ts b/frontend/src/services/locationDataService.ts index b89e9c1e..e53cf643 100644 --- a/frontend/src/services/locationDataService.ts +++ b/frontend/src/services/locationDataService.ts @@ -18,6 +18,7 @@ export const fetchLocationData = async ( datasetId: datasetId, location: location, }; + console.log(requestBody); try { const response = await axios.put( getAPIGatewayURL() + "/api/loadLocationData", From 657108aeebf34e0797d16fcdfcfe445f0ad9d0f2 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Thu, 4 Jul 2024 17:39:00 +0200 Subject: [PATCH 12/32] Fixed not changing isDrawing Signed-off-by: Emil Balitzki --- frontend/src/components/MapView/LeafletMap.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx index 08750cad..a855a4b1 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -47,6 +47,7 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { setCurrentMapCache({ ...currentMapCache, selectedCoordinates: drawnObject, + isDrawing: false, }); } }); From 1742ff19b856134bcb9f4bb57406de8d01f726d3 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Thu, 4 Jul 2024 17:47:29 +0200 Subject: [PATCH 13/32] Removing old polygon when drawing a new one Signed-off-by: Emil Balitzki --- frontend/src/components/MapView/LeafletMap.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx index a855a4b1..eccee10d 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -13,6 +13,7 @@ import SatelliteMap from "./BackgroundMaps/SatelliteMap"; import AerialMap from "./BackgroundMaps/AerialMap"; import NormalMap from "./BackgroundMaps/NormalMap"; import ParcelMap from "./BackgroundMaps/ParcelMap"; +import { Console } from "console"; interface LeafletMapProps { datasetId: string; @@ -28,6 +29,7 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { const [polygonDrawer, setPolygonDrawer] = useState( null ); + const [drawnItems, setDrawnItems] = useState(null); const currentTab = getCurrentTab(); /** @@ -37,6 +39,7 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { if (map) { // Allow for drawing polygons const drawnItems = new L.FeatureGroup(); + setDrawnItems(drawnItems); map.addLayer(drawnItems); setPolygonDrawer(new L.Draw.Polygon(map as L.DrawMap)); // Bind for polygon created @@ -61,12 +64,15 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { useEffect(() => { if (polygonDrawer) { if (currentMapCache.isDrawing) { + if (drawnItems) { + drawnItems.clearLayers(); + } polygonDrawer.enable(); } else { polygonDrawer.disable(); } } - }, [currentMapCache.isDrawing, polygonDrawer]); + }, [currentMapCache.isDrawing, drawnItems, polygonDrawer]); /** * Fetches the metadata of the current tab From a76058cc3eac441dc026e6cd1008eb90ec8644a1 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Thu, 4 Jul 2024 17:51:47 +0200 Subject: [PATCH 14/32] Disable draw polygon tooltips Signed-off-by: Emil Balitzki --- frontend/src/components/MapView/LeafletMap.css | 3 +++ frontend/src/components/MapView/LeafletMap.tsx | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/MapView/LeafletMap.css diff --git a/frontend/src/components/MapView/LeafletMap.css b/frontend/src/components/MapView/LeafletMap.css new file mode 100644 index 00000000..56b6ccb7 --- /dev/null +++ b/frontend/src/components/MapView/LeafletMap.css @@ -0,0 +1,3 @@ +.leaflet-draw-tooltip { + display: none; +} diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx index eccee10d..0ac306f2 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -13,7 +13,7 @@ import SatelliteMap from "./BackgroundMaps/SatelliteMap"; import AerialMap from "./BackgroundMaps/AerialMap"; import NormalMap from "./BackgroundMaps/NormalMap"; import ParcelMap from "./BackgroundMaps/ParcelMap"; -import { Console } from "console"; +import "./LeafletMap.css"; interface LeafletMapProps { datasetId: string; From 7bba4d49ea5743d19d16803ab6c284f6ae55cf2b Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Thu, 4 Jul 2024 23:19:20 +0200 Subject: [PATCH 15/32] Finished the styling of the polygon Signed-off-by: Emil Balitzki --- .../src/components/MapView/LeafletMap.css | 21 ++++++++++++++++++ .../src/components/MapView/LeafletMap.tsx | 22 +++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/MapView/LeafletMap.css b/frontend/src/components/MapView/LeafletMap.css index 56b6ccb7..fa277b3e 100644 --- a/frontend/src/components/MapView/LeafletMap.css +++ b/frontend/src/components/MapView/LeafletMap.css @@ -1,3 +1,24 @@ .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-interactive { + fill: none; + stroke: #ff0000; + stroke-width: 2px; + stroke-opacity: 1; +} + +.leaflet-draw-guide-dash { + background-color: #ff0000 !important; +} diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx index 0ac306f2..840b37cb 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -6,7 +6,7 @@ import MapDatasetVisualizer from "./MapDatasetVisualizer"; import { Dataset } from "../../types/DatasetTypes"; import MapEventsHandler from "./MapEventsHandler"; import ZoomWarningLabel from "../ZoomWarningLabel/ZoomWarningLabel"; -import L, { LeafletEvent } from "leaflet"; +import L, { LatLng, LeafletEvent } from "leaflet"; import "leaflet-draw/dist/leaflet.draw.css"; import "leaflet-draw"; import SatelliteMap from "./BackgroundMaps/SatelliteMap"; @@ -67,12 +67,27 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { if (drawnItems) { drawnItems.clearLayers(); } + setCurrentMapCache({ ...currentMapCache, selectedCoordinates: null }); polygonDrawer.enable(); } else { polygonDrawer.disable(); } } - }, [currentMapCache.isDrawing, drawnItems, polygonDrawer]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentMapCache.isDrawing]); + + /** + * Delete the selection if other coordinate was selected + */ + useEffect(() => { + if (currentMapCache.selectedCoordinates instanceof (LatLng || null)) { + polygonDrawer?.disable(); + if (drawnItems) { + drawnItems.clearLayers(); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentMapCache.selectedCoordinates]); /** * Fetches the metadata of the current tab @@ -83,6 +98,9 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { } }, [currentTab, getOrFetchMetadata]); + /** + * Refresh the map bounds on map change + */ useEffect(() => { if (map) { const initialBounds = map.getBounds(); From e49e4b439f9171b91ac166eab9ca4c0544cd8424 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Thu, 4 Jul 2024 23:57:29 +0200 Subject: [PATCH 16/32] Fixed display of coordinates in dataview Signed-off-by: Emil Balitzki --- frontend/src/components/DataView/DataView.css | 5 ++ frontend/src/components/DataView/DataView.tsx | 24 +++++----- .../src/components/MapView/LeafletMap.tsx | 48 ++++++++----------- 3 files changed, 37 insertions(+), 40 deletions(-) diff --git a/frontend/src/components/DataView/DataView.css b/frontend/src/components/DataView/DataView.css index f735d6ef..de24cdd0 100644 --- a/frontend/src/components/DataView/DataView.css +++ b/frontend/src/components/DataView/DataView.css @@ -86,3 +86,8 @@ width: 1rem; height: 1rem; } + +.sub-text { + color: gray; + font-size: 0.6rem; +} diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index 7cc27b6e..c1e10b9e 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -9,6 +9,7 @@ import LoadDataButton from "./LoadDataButton"; import { LocationDataResponse } from "../../types/LocationDataTypes"; import { fetchLocationData } from "../../services/locationDataService"; import { LatLng } from "leaflet"; +import L from "leaflet"; function DataView() { const { currentTabsCache } = useContext(TabsContext); @@ -77,14 +78,9 @@ function DataView() { // If a single coordinates are selected coords = [currentCoords]; console.log("single"); - } else if ( + } else if (currentCoords instanceof L.Polygon) { // If an area (array of coordinates) is selected - Array.isArray(currentCoords) && - currentCoords.every((obj) => { - obj instanceof LatLng; - }) - ) { - coords = currentCoords; + coords = currentCoords.getLatLngs() as LatLng[]; console.log("area"); } const responseData = await fetchLocationData(currentID, coords); @@ -103,13 +99,17 @@ function DataView() {
- {currentMapCache.loadedCoordinates instanceof LatLng ? ( + {currentMapCache.loadedCoordinates instanceof LatLng && (
- {currentMapCache.loadedCoordinates.lat.toFixed(6)},{" "} - {currentMapCache.loadedCoordinates.lng.toFixed(6)} + Custom Marker{" "} + + ({currentMapCache.loadedCoordinates.lat.toFixed(6)},{" "} + {currentMapCache.loadedCoordinates.lng.toFixed(6)}) +
- ) : ( -
+ )} + {currentMapCache.loadedCoordinates instanceof L.Polygon && ( +
Custom Polygon
)}
diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx index 840b37cb..18ab2e09 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -32,32 +32,6 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { const [drawnItems, setDrawnItems] = useState(null); const currentTab = getCurrentTab(); - /** - * Add the draw polygon layer - */ - useEffect(() => { - if (map) { - // Allow for drawing polygons - const drawnItems = new L.FeatureGroup(); - setDrawnItems(drawnItems); - map.addLayer(drawnItems); - setPolygonDrawer(new L.Draw.Polygon(map as L.DrawMap)); - // 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) { - drawnItems.addLayer(drawnObject); - setCurrentMapCache({ - ...currentMapCache, - selectedCoordinates: drawnObject, - isDrawing: false, - }); - } - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [map]); - /** * Toggle polygon drawer */ @@ -106,7 +80,6 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { const initialBounds = map.getBounds(); const initialCenter = map.getCenter(); const initialZoom = map.getZoom(); - setCurrentMapCache((prevCache) => ({ ...prevCache, mapInstance: map, @@ -114,8 +87,27 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { mapBounds: initialBounds, zoom: initialZoom, })); + // Allow for drawing polygons + const drawnItems = new L.FeatureGroup(); + setDrawnItems(drawnItems); + map.addLayer(drawnItems); + setPolygonDrawer(new L.Draw.Polygon(map as L.DrawMap)); + // 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); + setCurrentMapCache({ + ...currentMapCache, + selectedCoordinates: drawnObject, + isDrawing: false, + }); + } + } + }); } - }, [currentMapCache, map, setCurrentMapCache]); + }, [map]); /** * Check for the zoom level threshold to apply the zoom warning and its effects From 720a4c68fd7278a4e228613eff9a49c658cfa872 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 12:40:10 +0200 Subject: [PATCH 17/32] Created a selection type Signed-off-by: Emil Balitzki --- frontend/src/components/DataView/DataView.tsx | 15 ++++++---- .../src/components/MapView/LeafletMap.tsx | 19 +++++++++++-- .../components/MapView/MapEventsHandler.tsx | 13 ++++----- frontend/src/contexts/MapContext.tsx | 9 +++--- frontend/src/types/MapSelectionTypes.tsx | 28 +++++++++++++++++++ 5 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 frontend/src/types/MapSelectionTypes.tsx diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index c1e10b9e..7af18588 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -10,6 +10,10 @@ import { LocationDataResponse } from "../../types/LocationDataTypes"; import { fetchLocationData } from "../../services/locationDataService"; import { LatLng } from "leaflet"; import L from "leaflet"; +import { + MarkerSelection, + PolygonSelection, +} from "../../types/MapSelectionTypes"; function DataView() { const { currentTabsCache } = useContext(TabsContext); @@ -99,18 +103,17 @@ function DataView() {
- {currentMapCache.loadedCoordinates instanceof LatLng && ( + {currentMapCache.loadedCoordinates instanceof MarkerSelection && (
Custom Marker{" "} - ({currentMapCache.loadedCoordinates.lat.toFixed(6)},{" "} - {currentMapCache.loadedCoordinates.lng.toFixed(6)}) + ({currentMapCache.loadedCoordinates.marker.lat.toFixed(6)},{" "} + {currentMapCache.loadedCoordinates.marker.lng.toFixed(6)})
)} - {currentMapCache.loadedCoordinates instanceof L.Polygon && ( -
Custom Polygon
- )} + {currentMapCache.loadedCoordinates instanceof + PolygonSelection &&
Custom Polygon
}
= ({ datasetId, mapType }) => { * Delete the selection if other coordinate was selected */ useEffect(() => { - if (currentMapCache.selectedCoordinates instanceof (LatLng || null)) { + if (currentMapCache.selectedCoordinates instanceof MarkerSelection) { polygonDrawer?.disable(); if (drawnItems) { drawnItems.clearLayers(); @@ -98,15 +102,24 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { if (drawnObject instanceof L.Polygon) { if (drawnItems) { drawnItems.addLayer(drawnObject); + const latLongs = drawnObject.toGeoJSON(); + const polygonSelection = new PolygonSelection( + latLongs, + "Custom Polygon", + true + ); + console.log(polygonSelection); + console.log(currentMapCache); setCurrentMapCache({ ...currentMapCache, - selectedCoordinates: drawnObject, + selectedCoordinates: polygonSelection, isDrawing: false, }); } } }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [map]); /** diff --git a/frontend/src/components/MapView/MapEventsHandler.tsx b/frontend/src/components/MapView/MapEventsHandler.tsx index 6b349678..a8039cb7 100644 --- a/frontend/src/components/MapView/MapEventsHandler.tsx +++ b/frontend/src/components/MapView/MapEventsHandler.tsx @@ -2,10 +2,11 @@ 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, LatLng } from "leaflet"; +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) => { @@ -31,11 +32,10 @@ const MapEventsHandler: React.FC = () => { useMapEvents({ click: (event) => { if (!currentMapCache.isDrawing) { - currentMapCache.polygon?.remove(); + const markerSelection = new MarkerSelection(event.latlng); setCurrentMapCache({ ...currentMapCache, - selectedCoordinates: event.latlng, - polygon: null, + selectedCoordinates: markerSelection, }); } }, @@ -49,10 +49,9 @@ const MapEventsHandler: React.FC = () => { }, }); - return currentMapCache.selectedCoordinates !== null && - currentMapCache.selectedCoordinates instanceof LatLng ? ( + return currentMapCache.selectedCoordinates instanceof MarkerSelection ? ( ) : ( diff --git a/frontend/src/contexts/MapContext.tsx b/frontend/src/contexts/MapContext.tsx index e2d3858d..6e6a43bb 100644 --- a/frontend/src/contexts/MapContext.tsx +++ b/frontend/src/contexts/MapContext.tsx @@ -1,18 +1,18 @@ import L, { LatLng, LatLngBounds } from "leaflet"; import React, { createContext, useState, ReactNode } from "react"; +import { MapSelection } from "../types/MapSelectionTypes"; //// TYPES //// // Map Cache Type export type MapCacheProps = { mapInstance: L.Map | null; - selectedCoordinates: LatLng | L.Polygon | null; - loadedCoordinates: LatLng | L.Polygon | null; - currentTabID: string | null; + selectedCoordinates: MapSelection; + loadedCoordinates: MapSelection; + currentTabID: null | string; mapCenter: LatLng; mapBounds: LatLngBounds; zoom: number; - polygon: L.GeoJSON | null; isDrawing: boolean; }; @@ -38,7 +38,6 @@ const defaultMapCache: MapCacheProps = { mapCenter: L.latLng([49.5732, 11.0288]), mapBounds: L.latLngBounds([49.5732, 11.0288], [49.5732, 11.0288]), zoom: 13, - polygon: null, isDrawing: false, }; diff --git a/frontend/src/types/MapSelectionTypes.tsx b/frontend/src/types/MapSelectionTypes.tsx new file mode 100644 index 00000000..5466d232 --- /dev/null +++ b/frontend/src/types/MapSelectionTypes.tsx @@ -0,0 +1,28 @@ +import { LatLng } from "leaflet"; +import { GeoJSON } from "geojson"; + +// An interface for the map selection + +export type MapSelection = PolygonSelection | MarkerSelection | null; + +// Define PolygonSelection class +export class PolygonSelection { + polygon: GeoJSON; + displayName: string; + ifHandDrawn: boolean; + + constructor(polygon: GeoJSON, displayName: string, ifHandDrawn: boolean) { + this.polygon = polygon; + this.displayName = displayName; + this.ifHandDrawn = ifHandDrawn; + } +} + +// An interface for a single marker selection +export class MarkerSelection { + marker: LatLng; + + constructor(marker: LatLng) { + this.marker = marker; + } +} From d2a5beba432009e985a0db04290b597bfdcdd54f Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 12:43:58 +0200 Subject: [PATCH 18/32] Added mapcontext reference for polygon event Signed-off-by: Emil Balitzki --- frontend/src/components/MapView/LeafletMap.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx index bfad005b..148768fc 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -1,4 +1,10 @@ -import React, { Fragment, useContext, useEffect, useState } from "react"; +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"; @@ -29,6 +35,7 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { 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 @@ -36,6 +43,11 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { const [drawnItems, setDrawnItems] = useState(null); const currentTab = getCurrentTab(); + // Update ref value whenever currentMapCache changes + useEffect(() => { + currentMapCacheRef.current = currentMapCache; + }, [currentMapCache]); + /** * Toggle polygon drawer */ @@ -109,9 +121,9 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { true ); console.log(polygonSelection); - console.log(currentMapCache); + console.log(currentMapCacheRef.current); setCurrentMapCache({ - ...currentMapCache, + ...currentMapCacheRef.current, selectedCoordinates: polygonSelection, isDrawing: false, }); From f3a71e6ecf5f413d76d4545e23122bfa9fc2446f Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 13:07:06 +0200 Subject: [PATCH 19/32] Fixed location endpoint calls Signed-off-by: Emil Balitzki --- frontend/src/components/DataView/DataView.tsx | 36 +++++++++++-------- .../src/components/MapView/LeafletMap.tsx | 8 +++-- frontend/src/services/locationDataService.ts | 4 +-- frontend/src/types/MapSelectionTypes.tsx | 10 ++++-- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index 7af18588..f3077b51 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -8,15 +8,14 @@ import { MapContext } from "../../contexts/MapContext"; import LoadDataButton from "./LoadDataButton"; import { LocationDataResponse } from "../../types/LocationDataTypes"; import { fetchLocationData } from "../../services/locationDataService"; -import { LatLng } from "leaflet"; -import L from "leaflet"; import { MarkerSelection, PolygonSelection, } from "../../types/MapSelectionTypes"; +import { Position } from "geojson"; function DataView() { - const { currentTabsCache } = useContext(TabsContext); + const { currentTabsCache, getCurrentTab } = useContext(TabsContext); const { currentMapCache, setCurrentMapCache } = useContext(MapContext); const [filterValue, setFilterValue] = useState(""); const [ifNeedsReloading, setIfNeedsReloading] = useState(false); @@ -77,19 +76,26 @@ function DataView() { }); // Prepare the location data if (currentCoords) { - let coords: LatLng[] = []; - if (currentCoords instanceof LatLng) { - // If a single coordinates are selected - coords = [currentCoords]; - console.log("single"); - } else if (currentCoords instanceof L.Polygon) { - // If an area (array of coordinates) is selected - coords = currentCoords.getLatLngs() as LatLng[]; - console.log("area"); + let coords: Position[] = []; + if (currentCoords instanceof MarkerSelection) { + const singlePosition: Position = [ + currentCoords.marker.lng, + currentCoords.marker.lat, + ]; + coords = [singlePosition]; + } else if (currentCoords instanceof PolygonSelection) { + coords = currentCoords.polygon.geometry.coordinates[0]; } - const responseData = await fetchLocationData(currentID, coords); - if (responseData) { - setData(responseData); + // Send the location request + const currentTab = getCurrentTab(); + if (currentTab) { + const responseData = await fetchLocationData( + currentTab.dataset.id, + coords + ); + if (responseData) { + setData(responseData); + } } } else { console.log("Currently selected coordinates are null."); diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx index 148768fc..a58f9576 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -24,6 +24,7 @@ import { MarkerSelection, PolygonSelection, } from "../../types/MapSelectionTypes"; +import { Feature, GeoJsonProperties, Polygon } from "geojson"; interface LeafletMapProps { datasetId: string; @@ -114,14 +115,15 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { if (drawnObject instanceof L.Polygon) { if (drawnItems) { drawnItems.addLayer(drawnObject); - const latLongs = drawnObject.toGeoJSON(); + const latLongs = drawnObject.toGeoJSON() as Feature< + Polygon, + GeoJsonProperties + >; const polygonSelection = new PolygonSelection( latLongs, "Custom Polygon", true ); - console.log(polygonSelection); - console.log(currentMapCacheRef.current); setCurrentMapCache({ ...currentMapCacheRef.current, selectedCoordinates: polygonSelection, diff --git a/frontend/src/services/locationDataService.ts b/frontend/src/services/locationDataService.ts index e53cf643..668156c3 100644 --- a/frontend/src/services/locationDataService.ts +++ b/frontend/src/services/locationDataService.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { LocationDataResponse } from "../types/LocationDataTypes"; import { getAPIGatewayURL } from "../utils/apiGatewayURL"; -import { LatLng } from "leaflet"; +import { Position } from "geojson"; /** * Fetches the data from a specific location @@ -11,7 +11,7 @@ import { LatLng } from "leaflet"; */ export const fetchLocationData = async ( datasetId: string, - location: LatLng[] + location: Position[] ): Promise => { // Build the request body const requestBody = { diff --git a/frontend/src/types/MapSelectionTypes.tsx b/frontend/src/types/MapSelectionTypes.tsx index 5466d232..91e7150c 100644 --- a/frontend/src/types/MapSelectionTypes.tsx +++ b/frontend/src/types/MapSelectionTypes.tsx @@ -1,5 +1,5 @@ import { LatLng } from "leaflet"; -import { GeoJSON } from "geojson"; +import { Feature, Polygon } from "geojson"; // An interface for the map selection @@ -7,11 +7,15 @@ export type MapSelection = PolygonSelection | MarkerSelection | null; // Define PolygonSelection class export class PolygonSelection { - polygon: GeoJSON; + polygon: Feature; displayName: string; ifHandDrawn: boolean; - constructor(polygon: GeoJSON, displayName: string, ifHandDrawn: boolean) { + constructor( + polygon: Feature, + displayName: string, + ifHandDrawn: boolean + ) { this.polygon = polygon; this.displayName = displayName; this.ifHandDrawn = ifHandDrawn; From 7416af61b711374c176d743fa2b15f9a25723515 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 13:55:11 +0200 Subject: [PATCH 20/32] Added working selections with search bar Signed-off-by: Emil Balitzki --- frontend/src/components/DataView/DataView.tsx | 10 +++--- .../src/components/MapView/LeafletMap.tsx | 4 +-- .../src/components/SearchBar/SearchBar.tsx | 35 ++++++++++++------- frontend/src/contexts/MapContext.tsx | 6 ++-- frontend/src/services/locationDataService.ts | 2 +- frontend/src/types/MapSelectionTypes.tsx | 10 ++---- 6 files changed, 38 insertions(+), 29 deletions(-) diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index f3077b51..de0f4819 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -76,15 +76,15 @@ function DataView() { }); // Prepare the location data if (currentCoords) { - let coords: Position[] = []; + let coords: Position[][][] = []; if (currentCoords instanceof MarkerSelection) { const singlePosition: Position = [ currentCoords.marker.lng, currentCoords.marker.lat, ]; - coords = [singlePosition]; + coords = [[[singlePosition]]]; } else if (currentCoords instanceof PolygonSelection) { - coords = currentCoords.polygon.geometry.coordinates[0]; + coords = currentCoords.polygon.geometry.coordinates; } // Send the location request const currentTab = getCurrentTab(); @@ -119,7 +119,9 @@ function DataView() {
)} {currentMapCache.loadedCoordinates instanceof - PolygonSelection &&
Custom Polygon
} + PolygonSelection && ( +
{currentMapCache.loadedCoordinates.displayName}
+ )} = ({ datasetId, mapType }) => { if (drawnItems) { drawnItems.addLayer(drawnObject); const latLongs = drawnObject.toGeoJSON() as Feature< - Polygon, + MultiPolygon, GeoJsonProperties >; const polygonSelection = new PolygonSelection( diff --git a/frontend/src/components/SearchBar/SearchBar.tsx b/frontend/src/components/SearchBar/SearchBar.tsx index 6dd978a2..0de23e4c 100644 --- a/frontend/src/components/SearchBar/SearchBar.tsx +++ b/frontend/src/components/SearchBar/SearchBar.tsx @@ -10,7 +10,11 @@ import { LatLng } from "leaflet"; import { MapContext } from "../../contexts/MapContext"; import L from "leaflet"; import "./SearchBar.css"; -import { GeoJSON } from "geojson"; +import { Feature, GeoJSON, MultiPolygon } from "geojson"; +import { + MarkerSelection, + PolygonSelection, +} from "../../types/MapSelectionTypes"; declare module "leaflet-geosearch/dist/providers/openStreetMapProvider.js" { interface RawResult { @@ -119,16 +123,24 @@ const SearchBar: React.FC = () => { }, }); drawPolygon.addTo(mapInstance); - setCurrentMapCache({ - ...currentMapCache, - polygon: drawPolygon, - selectedCoordinates: null, - }); + if (item.polygon) { + const polygonSelection = new PolygonSelection( + item.polygon as Feature, + item.displayName, + false + ); + setCurrentMapCache({ + ...currentMapCache, + selectedCoordinates: polygonSelection, + }); + } } else { - currentMapCache.selectedCoordinates = targetPosition; + // Select a marker on the map + const markerSelection = new MarkerSelection(targetPosition); + currentMapCache.selectedCoordinates = markerSelection; mapInstance.flyTo(targetPosition, 13, { animate: true, duration: 5 }); } - } else console.log("no map instance"); + } else console.log("No map instance"); }; const getUniqueOptions = (options: MapSelection[]) => { @@ -156,7 +168,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} @@ -184,10 +196,9 @@ const SearchBar: React.FC = () => { }} onInputChange={(_event, newInputValue) => { if (newInputValue === "") { - currentMapCache.polygon?.remove(); setCurrentMapCache({ ...currentMapCache, - polygon: null, + selectedCoordinates: null, }); } setInputValue(newInputValue); @@ -237,7 +248,7 @@ const SearchBar: React.FC = () => { ); return ( -
  • +
  • => { // Build the request body const requestBody = { diff --git a/frontend/src/types/MapSelectionTypes.tsx b/frontend/src/types/MapSelectionTypes.tsx index 91e7150c..f474962b 100644 --- a/frontend/src/types/MapSelectionTypes.tsx +++ b/frontend/src/types/MapSelectionTypes.tsx @@ -1,18 +1,14 @@ import { LatLng } from "leaflet"; -import { Feature, Polygon } from "geojson"; - -// An interface for the map selection - -export type MapSelection = PolygonSelection | MarkerSelection | null; +import { Feature, MultiPolygon } from "geojson"; // Define PolygonSelection class export class PolygonSelection { - polygon: Feature; + polygon: Feature; displayName: string; ifHandDrawn: boolean; constructor( - polygon: Feature, + polygon: Feature, displayName: string, ifHandDrawn: boolean ) { From 505b816f4484bb3885f49190d24f6580f60478eb Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 14:13:58 +0200 Subject: [PATCH 21/32] Added ability to store marker names Signed-off-by: Emil Balitzki --- frontend/src/components/DataView/DataView.tsx | 18 +++++++++++---- .../components/MapView/MapEventsHandler.tsx | 6 ++++- .../src/components/SearchBar/SearchBar.tsx | 23 +++++++++++-------- frontend/src/types/MapSelectionTypes.tsx | 12 ++++++---- 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index de0f4819..aa74057f 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -2,7 +2,7 @@ import DataPanel from "./DataPanel"; import "./DataView.css"; import { Fragment, useContext, useEffect, useState } from "react"; import { TabsContext } from "../../contexts/TabsContext"; -import { Box, TextField } from "@mui/material"; +import { Box, TextField, Tooltip } from "@mui/material"; import { Funnel, MapPin, MapPinLine } from "@phosphor-icons/react"; import { MapContext } from "../../contexts/MapContext"; import LoadDataButton from "./LoadDataButton"; @@ -111,11 +111,21 @@ function DataView() { {currentMapCache.loadedCoordinates instanceof MarkerSelection && (
    - Custom Marker{" "} - + + + {currentMapCache.loadedCoordinates.displayName.substring( + 0, + 40 + ) + "... "} + + +
    ({currentMapCache.loadedCoordinates.marker.lat.toFixed(6)},{" "} {currentMapCache.loadedCoordinates.marker.lng.toFixed(6)}) - +
    )} {currentMapCache.loadedCoordinates instanceof diff --git a/frontend/src/components/MapView/MapEventsHandler.tsx b/frontend/src/components/MapView/MapEventsHandler.tsx index a8039cb7..e59cc526 100644 --- a/frontend/src/components/MapView/MapEventsHandler.tsx +++ b/frontend/src/components/MapView/MapEventsHandler.tsx @@ -32,7 +32,11 @@ const MapEventsHandler: React.FC = () => { useMapEvents({ click: (event) => { if (!currentMapCache.isDrawing) { - const markerSelection = new MarkerSelection(event.latlng); + const markerSelection = new MarkerSelection( + event.latlng, + "Custom Marker", + true + ); setCurrentMapCache({ ...currentMapCache, selectedCoordinates: markerSelection, diff --git a/frontend/src/components/SearchBar/SearchBar.tsx b/frontend/src/components/SearchBar/SearchBar.tsx index 0de23e4c..443ba7a5 100644 --- a/frontend/src/components/SearchBar/SearchBar.tsx +++ b/frontend/src/components/SearchBar/SearchBar.tsx @@ -8,7 +8,6 @@ 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 { Feature, GeoJSON, MultiPolygon } from "geojson"; import { @@ -115,14 +114,14 @@ 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); + // const drawPolygon = L.geoJSON(item.polygon, { + // style: { + // color: "#ff0000", + // weight: 2, + // fillOpacity: 0, + // }, + // }); + // drawPolygon.addTo(mapInstance); if (item.polygon) { const polygonSelection = new PolygonSelection( item.polygon as Feature, @@ -136,7 +135,11 @@ const SearchBar: React.FC = () => { } } else { // Select a marker on the map - const markerSelection = new MarkerSelection(targetPosition); + const markerSelection = new MarkerSelection( + targetPosition, + item.displayName, + false + ); currentMapCache.selectedCoordinates = markerSelection; mapInstance.flyTo(targetPosition, 13, { animate: true, duration: 5 }); } diff --git a/frontend/src/types/MapSelectionTypes.tsx b/frontend/src/types/MapSelectionTypes.tsx index f474962b..e651198e 100644 --- a/frontend/src/types/MapSelectionTypes.tsx +++ b/frontend/src/types/MapSelectionTypes.tsx @@ -5,24 +5,28 @@ import { Feature, MultiPolygon } from "geojson"; export class PolygonSelection { polygon: Feature; displayName: string; - ifHandDrawn: boolean; + ifHandSelected: boolean; constructor( polygon: Feature, displayName: string, - ifHandDrawn: boolean + ifHandSelected: boolean ) { this.polygon = polygon; this.displayName = displayName; - this.ifHandDrawn = ifHandDrawn; + this.ifHandSelected = ifHandSelected; } } // An interface for a single marker selection export class MarkerSelection { marker: LatLng; + displayName: string; + ifHandSelected: boolean; - constructor(marker: LatLng) { + constructor(marker: LatLng, displayName: string, ifHandSelected: boolean) { this.marker = marker; + this.displayName = displayName; + this.ifHandSelected = ifHandSelected; } } From 59ca8787e939406b3aae1a49884b1e6948d5f267 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 14:29:55 +0200 Subject: [PATCH 22/32] Fixed drawing of searched addresses + cleanup of last search Signed-off-by: Emil Balitzki --- frontend/src/components/DataView/DataView.tsx | 38 +++++++++---------- .../src/components/MapView/LeafletMap.tsx | 14 +++---- .../src/components/SearchBar/SearchBar.tsx | 20 ++++++---- frontend/src/contexts/MapContext.tsx | 2 + 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index aa74057f..a992a10f 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -109,29 +109,29 @@ function DataView() {
    - {currentMapCache.loadedCoordinates instanceof MarkerSelection && ( -
    - - - {currentMapCache.loadedCoordinates.displayName.substring( - 0, - 40 - ) + "... "} - - +
    + + + {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)})
    -
    - )} - {currentMapCache.loadedCoordinates instanceof - PolygonSelection && ( -
    {currentMapCache.loadedCoordinates.displayName}
    - )} + )} +
    = ({ datasetId, mapType }) => { const [polygonDrawer, setPolygonDrawer] = useState( null ); - const [drawnItems, setDrawnItems] = useState(null); const currentTab = getCurrentTab(); // Update ref value whenever currentMapCache changes @@ -55,8 +54,8 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { useEffect(() => { if (polygonDrawer) { if (currentMapCache.isDrawing) { - if (drawnItems) { - drawnItems.clearLayers(); + if (currentMapCache.drawnItems) { + currentMapCache.drawnItems.clearLayers(); } setCurrentMapCache({ ...currentMapCache, selectedCoordinates: null }); polygonDrawer.enable(); @@ -73,8 +72,8 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { useEffect(() => { if (currentMapCache.selectedCoordinates instanceof MarkerSelection) { polygonDrawer?.disable(); - if (drawnItems) { - drawnItems.clearLayers(); + if (currentMapCache.drawnItems) { + currentMapCache.drawnItems.clearLayers(); } } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -97,16 +96,17 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { 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 - const drawnItems = new L.FeatureGroup(); - setDrawnItems(drawnItems); map.addLayer(drawnItems); setPolygonDrawer(new L.Draw.Polygon(map as L.DrawMap)); // Bind for polygon created diff --git a/frontend/src/components/SearchBar/SearchBar.tsx b/frontend/src/components/SearchBar/SearchBar.tsx index 443ba7a5..f5700e05 100644 --- a/frontend/src/components/SearchBar/SearchBar.tsx +++ b/frontend/src/components/SearchBar/SearchBar.tsx @@ -14,6 +14,7 @@ import { MarkerSelection, PolygonSelection, } from "../../types/MapSelectionTypes"; +import L from "leaflet"; declare module "leaflet-geosearch/dist/providers/openStreetMapProvider.js" { interface RawResult { @@ -114,15 +115,18 @@ 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); + if (currentMapCache.drawnItems) { + currentMapCache.drawnItems.clearLayers(); + } if (item.polygon) { + const drawPolygon = L.geoJSON(item.polygon, { + style: { + color: "#ff0000", + weight: 2, + fillOpacity: 0, + }, + }); + drawPolygon.addTo(currentMapCache.drawnItems!); const polygonSelection = new PolygonSelection( item.polygon as Feature, item.displayName, diff --git a/frontend/src/contexts/MapContext.tsx b/frontend/src/contexts/MapContext.tsx index ce5ad388..cf6effb6 100644 --- a/frontend/src/contexts/MapContext.tsx +++ b/frontend/src/contexts/MapContext.tsx @@ -14,6 +14,7 @@ export type MapCacheProps = { mapBounds: LatLngBounds; zoom: number; isDrawing: boolean; + drawnItems: L.FeatureGroup | null; }; // Map Context Type @@ -39,6 +40,7 @@ const defaultMapCache: MapCacheProps = { mapBounds: L.latLngBounds([49.5732, 11.0288], [49.5732, 11.0288]), zoom: 13, isDrawing: false, + drawnItems: null, }; // Actual value of the context From 55330d7c4c289d645b2bb2b429e3536d919c6c40 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 15:15:13 +0200 Subject: [PATCH 23/32] Fixed location endpoint body Signed-off-by: Emil Balitzki --- frontend/src/components/DataView/DataView.tsx | 17 ++++++++++++++--- frontend/src/components/MapView/LeafletMap.css | 1 + frontend/src/components/MapView/LeafletMap.tsx | 2 +- frontend/src/components/SearchBar/SearchBar.tsx | 4 ++-- frontend/src/services/locationDataService.ts | 3 +-- frontend/src/types/MapSelectionTypes.tsx | 6 +++--- 6 files changed, 22 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index a992a10f..f715d734 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -76,16 +76,27 @@ function DataView() { }); // Prepare the location data if (currentCoords) { - let coords: Position[][][] = []; + let coords: Position[][] | Position[][][] = []; if (currentCoords instanceof MarkerSelection) { const singlePosition: Position = [ currentCoords.marker.lng, currentCoords.marker.lat, ]; - coords = [[[singlePosition]]]; + coords = [[singlePosition]]; } else if (currentCoords instanceof PolygonSelection) { - coords = currentCoords.polygon.geometry.coordinates; + console.log(currentCoords.polygon); + coords = currentCoords.polygon.coordinates; + // Iterate over the coordinate pairs and reverse each one + // for (let i = 0; i < coords.length; i++) { + // for (let j = 0; j < coords[i].length; j++) { + // const lat = coords[i][j][1]; + // const long = coords[i][j][0]; + // coords[i][j][0] = lat; + // coords[i][j][1] = long; + // } + // } } + console.log(coords); // Send the location request const currentTab = getCurrentTab(); if (currentTab) { diff --git a/frontend/src/components/MapView/LeafletMap.css b/frontend/src/components/MapView/LeafletMap.css index fa277b3e..ccfe8566 100644 --- a/frontend/src/components/MapView/LeafletMap.css +++ b/frontend/src/components/MapView/LeafletMap.css @@ -21,4 +21,5 @@ .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 index 01e66f82..a49f4c3b 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -120,7 +120,7 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { GeoJsonProperties >; const polygonSelection = new PolygonSelection( - latLongs, + latLongs.geometry, "Custom Polygon", true ); diff --git a/frontend/src/components/SearchBar/SearchBar.tsx b/frontend/src/components/SearchBar/SearchBar.tsx index f5700e05..fec106b2 100644 --- a/frontend/src/components/SearchBar/SearchBar.tsx +++ b/frontend/src/components/SearchBar/SearchBar.tsx @@ -9,7 +9,7 @@ import { OpenStreetMapProvider } from "leaflet-geosearch"; import { LatLng } from "leaflet"; import { MapContext } from "../../contexts/MapContext"; import "./SearchBar.css"; -import { Feature, GeoJSON, MultiPolygon } from "geojson"; +import { GeoJSON, MultiPolygon } from "geojson"; import { MarkerSelection, PolygonSelection, @@ -128,7 +128,7 @@ const SearchBar: React.FC = () => { }); drawPolygon.addTo(currentMapCache.drawnItems!); const polygonSelection = new PolygonSelection( - item.polygon as Feature, + item.polygon as MultiPolygon, item.displayName, false ); diff --git a/frontend/src/services/locationDataService.ts b/frontend/src/services/locationDataService.ts index d3452acb..0a9a9028 100644 --- a/frontend/src/services/locationDataService.ts +++ b/frontend/src/services/locationDataService.ts @@ -11,14 +11,13 @@ import { Position } from "geojson"; */ export const fetchLocationData = async ( datasetId: string, - location: Position[][][] + location: Position[][][] | Position[][] ): Promise => { // Build the request body const requestBody = { datasetId: datasetId, location: location, }; - console.log(requestBody); try { const response = await axios.put( getAPIGatewayURL() + "/api/loadLocationData", diff --git a/frontend/src/types/MapSelectionTypes.tsx b/frontend/src/types/MapSelectionTypes.tsx index e651198e..f1c0f8c3 100644 --- a/frontend/src/types/MapSelectionTypes.tsx +++ b/frontend/src/types/MapSelectionTypes.tsx @@ -1,14 +1,14 @@ import { LatLng } from "leaflet"; -import { Feature, MultiPolygon } from "geojson"; +import { MultiPolygon } from "geojson"; // Define PolygonSelection class export class PolygonSelection { - polygon: Feature; + polygon: MultiPolygon; displayName: string; ifHandSelected: boolean; constructor( - polygon: Feature, + polygon: MultiPolygon, displayName: string, ifHandSelected: boolean ) { From cfc092d13fd36f434467bc0a93616bee4a9475cb Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 15:16:25 +0200 Subject: [PATCH 24/32] Cleanup Signed-off-by: Emil Balitzki --- frontend/src/components/DataView/DataView.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index f715d734..d8b2a8f0 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -86,15 +86,6 @@ function DataView() { } else if (currentCoords instanceof PolygonSelection) { console.log(currentCoords.polygon); coords = currentCoords.polygon.coordinates; - // Iterate over the coordinate pairs and reverse each one - // for (let i = 0; i < coords.length; i++) { - // for (let j = 0; j < coords[i].length; j++) { - // const lat = coords[i][j][1]; - // const long = coords[i][j][0]; - // coords[i][j][0] = lat; - // coords[i][j][1] = long; - // } - // } } console.log(coords); // Send the location request From 3f678e12859d533d6702376733f933de774fb025 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 16:14:12 +0200 Subject: [PATCH 25/32] Implemented the open as a map functionality Signed-off-by: Emil Balitzki --- .../src/components/DataView/DataPanel.tsx | 143 +++++++++++++----- .../components/DatasetsList/DatasetsList.tsx | 50 ++---- frontend/src/contexts/TabsContext.tsx | 38 ++++- frontend/src/services/locationDataService.ts | 63 +++++++- 4 files changed, 212 insertions(+), 82 deletions(-) diff --git a/frontend/src/components/DataView/DataPanel.tsx b/frontend/src/components/DataView/DataPanel.tsx index a6fd10ae..7d653725 100644 --- a/frontend/src/components/DataView/DataPanel.tsx +++ b/frontend/src/components/DataView/DataPanel.tsx @@ -5,43 +5,15 @@ import { CaretDown, MapTrifold } from "@phosphor-icons/react"; import "./DataPanel.css"; import { Tooltip } from "@mui/material"; import { GridToolbar, GridToolbarProps } from "@mui/x-data-grid"; -import { useState } from "react"; - -// Returns a button if the "button" value is set to 1 -const renderDetailsButton = (params: GridRenderCellParams) => { - const value = params.value; - if (value === 1) { - return ( - - - - - - - - ); - } else { - return null; - } -}; - -// Defines the columns of the Datagrid -const columns: GridColDef[] = [ - { - field: "button", - headerName: "button", - width: 60, - renderCell: renderDetailsButton, - }, - { field: "key", headerName: "key", width: 250 }, - { - field: "value", - headerName: "value", - type: "number", - width: 250, - getApplyQuickFilterFn: undefined, - }, -]; +import { useContext, useState } from "react"; +import { DatasetItem } from "../../types/LocationDataTypes"; +import { TabsContext } from "../../contexts/TabsContext"; +import { fetchDatasets } from "../../services/datasetsService"; +import { AlertContext } from "../../contexts/AlertContext"; +import { Dataset } from "../../types/DatasetTypes"; +import L from "leaflet"; +import CustomSvgIcon from "../DatasetsList/CustomSvgIcon"; +import { svgIconDefault } from "../DatasetsList/DatasetsList"; function MyCustomToolbar(props: GridToolbarProps) { return ; @@ -50,9 +22,9 @@ function MyCustomToolbar(props: GridToolbarProps) { interface DataPanelProps { listTitle: string; filterValue: string; - mapRows: object[]; - genericRows: object[]; - extraRows: object[]; + mapRows: DatasetItem[]; + genericRows: DatasetItem[]; + extraRows: DatasetItem[]; } /* @@ -72,6 +44,88 @@ const DataPanel: React.FC = ({ useState(false); const [ifExtraDataTabHidden, toggleExtraDataHidden] = useState(false); + const { currentAlertCache, setCurrentAlertCache } = useContext(AlertContext); + + const { openNewTab } = useContext(TabsContext); + + const openDatasetFromMapIcon = async (mapId: string) => { + const datasetsData = await fetchDatasets(); + if (datasetsData) { + const datasetToOpen = datasetsData.find( + (dataset) => dataset.datasetId === mapId + ); + if (datasetToOpen) { + const datasetToOpenTransformed: Dataset = { + id: datasetToOpen.datasetId, + displayName: datasetToOpen.name, + shortDescription: datasetToOpen.shortDescription, + datasetIcon: datasetToOpen.icon ? ( + + ) : ( + + ), + metaData: undefined, + data: { + type: "FeatureCollection", + features: [], + }, + lastDataRequestBounds: L.latLngBounds(L.latLng(0, 0), L.latLng(0, 0)), + }; + + openNewTab(datasetToOpenTransformed); + } else { + // Display alert + setCurrentAlertCache({ + ...currentAlertCache, + isAlertOpened: true, + text: "Dataset with provided ID does not exist.", + }); + console.error("Dataset with provided ID does not exist."); + } + } + }; + + // Returns a button if the "button" value is set to 1 + const renderDetailsButton = (params: GridRenderCellParams) => { + const dataObject = params.row as DatasetItem; + if (dataObject.mapId !== "") { + return ( + + + { + openDatasetFromMapIcon(dataObject.mapId); + }} + > + + + + + ); + } else { + return null; + } + }; + + // Defines the columns of the Datagrid + const columns: GridColDef[] = [ + { + field: "button", + headerName: "button", + width: 60, + renderCell: renderDetailsButton, + }, + { field: "key", headerName: "key", width: 250 }, + { + field: "value", + headerName: "value", + type: "number", + width: 250, + getApplyQuickFilterFn: undefined, + }, + ]; return (
    @@ -99,6 +153,9 @@ const DataPanel: React.FC = ({ }`} > { + return row.key + row.value; + }} hideFooter={true} disableColumnMenu columnHeaderHeight={0} @@ -161,6 +218,9 @@ const DataPanel: React.FC = ({ }`} > { + return row.key + row.value; + }} hideFooter={true} disableColumnMenu columnHeaderHeight={0} @@ -223,6 +283,9 @@ const DataPanel: React.FC = ({ }`} > { + return row.key + row.value; + }} hideFooter={true} disableColumnMenu columnHeaderHeight={0} diff --git a/frontend/src/components/DatasetsList/DatasetsList.tsx b/frontend/src/components/DatasetsList/DatasetsList.tsx index 8794bc80..c865c35b 100644 --- a/frontend/src/components/DatasetsList/DatasetsList.tsx +++ b/frontend/src/components/DatasetsList/DatasetsList.tsx @@ -7,22 +7,14 @@ import { ListItemText, } from "@mui/material"; import { useContext, useEffect, useState } from "react"; -import { TabProps, TabsContext } from "../../contexts/TabsContext"; +import { TabsContext } from "../../contexts/TabsContext"; import "./DatasetsList.css"; -import { AlertContext } from "../../contexts/AlertContext"; -import { FeatureCollection } from "geojson"; import L from "leaflet"; import { Dataset, DatasetBasicData } from "../../types/DatasetTypes"; import CustomSvgIcon from "./CustomSvgIcon"; import { fetchDatasets } from "../../services/datasetsService"; -// Define an empty FeatureCollection -const emptyFeatureCollection: FeatureCollection = { - type: "FeatureCollection", - features: [], -}; - -const svgIconDefault: string = +export const svgIconDefault: string = ''; interface DatasetsListProps { @@ -31,8 +23,7 @@ interface DatasetsListProps { const DatasetsList: React.FC = ({ 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,30 +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, - currentTabID: newTab.id, - openedTabs: [...currentTabsCache.openedTabs, newTab], - }); + const openNewDataset = (dataset: Dataset) => { + const ifOpened = openNewTab(dataset); // Close the dialog if necessary - closeDialog(); + if (ifOpened) { + closeDialog(); + } }; return ( @@ -103,7 +79,7 @@ const DatasetsList: React.FC = ({ closeDialog }) => { { - openNewTab(dataset); + openNewDataset(dataset); }} > {dataset.datasetIcon} diff --git a/frontend/src/contexts/TabsContext.tsx b/frontend/src/contexts/TabsContext.tsx index 412d192a..84cc9469 100644 --- a/frontend/src/contexts/TabsContext.tsx +++ b/frontend/src/contexts/TabsContext.tsx @@ -1,6 +1,7 @@ -import React, { createContext, useState, ReactNode } from "react"; +import React, { createContext, useState, ReactNode, useContext } from "react"; import { fetchMetadataForDataset } from "../services/metadataService"; import { Dataset, DatasetMetaData } from "../types/DatasetTypes"; +import { AlertContext } from "./AlertContext"; //// TYPES //// @@ -25,6 +26,7 @@ type TabsContextValue = { getOrFetchMetadata: ( datasetID: string ) => Promise; + openNewTab: (datasetID: Dataset) => boolean; }; // Provider component props type @@ -46,6 +48,7 @@ export const TabsContext = createContext({ setCurrentTabsCache: () => null, getCurrentTab: () => undefined, getOrFetchMetadata: async () => undefined, + openNewTab: () => false, }); // Provider component @@ -54,6 +57,7 @@ export const TabsContextProvider: React.FC = ({ }) => { const [currentTabsCache, setCurrentTabsCache] = useState(defaultTabsCache); + const { currentAlertCache, setCurrentAlertCache } = useContext(AlertContext); /** * Returns the currently opened tab @@ -113,11 +117,43 @@ export const TabsContextProvider: React.FC = ({ } }; + /** + * Opens a new tab + * @param dataset a dataset id to open + */ + 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 false; + } + + const newTabID = currentTabsCache.openedTabs.length + 1; + const newTab: TabProps = { + id: newTabID.toString(), + dataset: dataset, + ifPinned: false, + }; + + setCurrentTabsCache({ + ...currentTabsCache, + currentTabID: newTab.id, + openedTabs: [...currentTabsCache.openedTabs, newTab], + }); + return true; + }; + const value = { currentTabsCache, setCurrentTabsCache, getCurrentTab, getOrFetchMetadata, + openNewTab, }; return {children}; diff --git a/frontend/src/services/locationDataService.ts b/frontend/src/services/locationDataService.ts index 0a9a9028..8f4ed796 100644 --- a/frontend/src/services/locationDataService.ts +++ b/frontend/src/services/locationDataService.ts @@ -19,10 +19,65 @@ export const fetchLocationData = async ( location: location, }; try { - const response = await axios.put( - getAPIGatewayURL() + "/api/loadLocationData", - requestBody - ); + // const response = await axios.put( + // getAPIGatewayURL() + "/api/loadLocationData", + // requestBody + // ); + + const response: LocationDataResponse = { + currentDatasetData: [ + { + key: "temperature", + value: "22°C", + mapId: "", + }, + { + key: "humidity", + value: "45%", + mapId: "", + }, + { + key: "windSpeed", + value: "15 km/h", + mapId: "", + }, + ], + generalData: [ + { + key: "population", + value: "1,000,000", + mapId: "map004", + }, + { + key: "area", + value: "500 sq km", + mapId: "", + }, + { + key: "elevation", + value: "300 meters", + mapId: "", + }, + ], + extraRows: [ + { + key: "timeZone", + value: "GMT+2", + mapId: "", + }, + { + key: "language", + value: "English", + mapId: "EV_charging_stations", + }, + { + key: "currency", + value: "USD", + mapId: "", + }, + ], + }; + return response; if (response.status === 200) { return response.data; From c035d6c7996f57a33799d44d8370d512cc664b79 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 16:15:07 +0200 Subject: [PATCH 26/32] Removed temp location values Signed-off-by: Emil Balitzki --- frontend/src/services/locationDataService.ts | 63 ++------------------ 1 file changed, 4 insertions(+), 59 deletions(-) diff --git a/frontend/src/services/locationDataService.ts b/frontend/src/services/locationDataService.ts index 8f4ed796..0a9a9028 100644 --- a/frontend/src/services/locationDataService.ts +++ b/frontend/src/services/locationDataService.ts @@ -19,65 +19,10 @@ export const fetchLocationData = async ( location: location, }; try { - // const response = await axios.put( - // getAPIGatewayURL() + "/api/loadLocationData", - // requestBody - // ); - - const response: LocationDataResponse = { - currentDatasetData: [ - { - key: "temperature", - value: "22°C", - mapId: "", - }, - { - key: "humidity", - value: "45%", - mapId: "", - }, - { - key: "windSpeed", - value: "15 km/h", - mapId: "", - }, - ], - generalData: [ - { - key: "population", - value: "1,000,000", - mapId: "map004", - }, - { - key: "area", - value: "500 sq km", - mapId: "", - }, - { - key: "elevation", - value: "300 meters", - mapId: "", - }, - ], - extraRows: [ - { - key: "timeZone", - value: "GMT+2", - mapId: "", - }, - { - key: "language", - value: "English", - mapId: "EV_charging_stations", - }, - { - key: "currency", - value: "USD", - mapId: "", - }, - ], - }; - return response; + const response = await axios.put( + getAPIGatewayURL() + "/api/loadLocationData", + requestBody + ); if (response.status === 200) { return response.data; From dda4db730f0309332583e33f33910d71397be8c0 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 16:46:12 +0200 Subject: [PATCH 27/32] Removed 3D Signed-off-by: Emil Balitzki --- .../src/components/MapView/MapOptions.css | 23 +-- .../src/components/MapView/MapOptions.tsx | 16 +- frontend/src/components/MapView/MapView.tsx | 18 +- .../src/components/ThreeDView/ThreeDView.tsx | 154 ------------------ 4 files changed, 5 insertions(+), 206 deletions(-) delete mode 100644 frontend/src/components/ThreeDView/ThreeDView.tsx diff --git a/frontend/src/components/MapView/MapOptions.css b/frontend/src/components/MapView/MapOptions.css index 9eaa2e5a..620e3629 100644 --- a/frontend/src/components/MapView/MapOptions.css +++ b/frontend/src/components/MapView/MapOptions.css @@ -68,32 +68,11 @@ border-color: blue; } -.threed-map-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; -} -.threed-map-icon-container:hover { - background-color: #f4f4f4; - color: #535bf2; -} - .draw-polygon-icon-container { width: 2.1rem; height: 2.1rem; position: absolute; - top: 10rem; + top: 7.5rem; right: 0.6rem; cursor: pointer; z-index: 2; diff --git a/frontend/src/components/MapView/MapOptions.tsx b/frontend/src/components/MapView/MapOptions.tsx index 61cbd1e3..8db0839a 100644 --- a/frontend/src/components/MapView/MapOptions.tsx +++ b/frontend/src/components/MapView/MapOptions.tsx @@ -1,19 +1,15 @@ import React, { useContext, useState } from "react"; import { Paper, Popover, Grid, Typography, Box, Tooltip } from "@mui/material"; import "./MapOptions.css"; -import { Polygon, StackSimple, ThreeD } 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" | "parcel" | "aerial") => void; - toggle3D: () => void; } -const MapOptions: React.FC = ({ - onMapTypeChange, - toggle3D, -}) => { +const MapOptions: React.FC = ({ onMapTypeChange }) => { const [anchorEl, setAnchorEl] = useState(null); const { currentMapCache, setCurrentMapCache } = useContext(MapContext); @@ -48,14 +44,6 @@ const MapOptions: React.FC = ({
    - -
    - -
    -
    { diff --git a/frontend/src/components/MapView/MapView.tsx b/frontend/src/components/MapView/MapView.tsx index 0e22da93..5f0e1981 100644 --- a/frontend/src/components/MapView/MapView.tsx +++ b/frontend/src/components/MapView/MapView.tsx @@ -8,7 +8,6 @@ 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 ThreeDView from "../ThreeDView/ThreeDView"; import LeafletMap from "./LeafletMap"; const DefaultIcon = L.icon({ @@ -24,7 +23,6 @@ interface MapViewProps { } const MapView: React.FC = ({ datasetId }) => { - const [if3D, setIf3D] = useState(false); const [mapType, setMapType] = useState< "normal" | "satellite" | "parcel" | "aerial" >("normal"); @@ -39,22 +37,10 @@ const MapView: React.FC = ({ datasetId }) => { setMapType(type); }; - /** - * Toggles the 3D View - */ - const toggle3D = () => { - console.log("3D"); - setIf3D(!if3D); - }; - return (
    - {if3D ? ( - - ) : ( - - )} - + +
    ); }; diff --git a/frontend/src/components/ThreeDView/ThreeDView.tsx b/frontend/src/components/ThreeDView/ThreeDView.tsx deleted file mode 100644 index ad4d3938..00000000 --- a/frontend/src/components/ThreeDView/ThreeDView.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import React, { useRef, useEffect } from "react"; -import * as THREE from "three"; -import { - CSS3DObject, - CSS3DRenderer, -} from "three/examples/jsm/renderers/CSS3DRenderer.js"; -import { MapControls } from "three/examples/jsm/controls/MapControls.js"; -import "leaflet/dist/leaflet.css"; -import LeafletMap from "../MapView/LeafletMap"; - -interface ThreeDViewProps { - datasetId: string; - mapType: string; -} - -const ThreeDView: React.FC = ({ datasetId, mapType }) => { - const mountRef = useRef(null); - const threedMapRef = useRef(null); - - useEffect(() => { - const mount = mountRef.current!; - const mapElement = threedMapRef.current!; - - // Scene setup - const scene = new THREE.Scene(); - scene.background = new THREE.Color(0x87ceeb); // Light blue sky - const camera = new THREE.PerspectiveCamera( - 75, - mount.clientWidth / mount.clientHeight, - 0.1, - 1000 - ); - camera.position.set(0, 400, 10); - camera.rotateX(-1); - - const renderer = new THREE.WebGLRenderer({ antialias: true }); - renderer.setSize(mount.clientWidth, mount.clientHeight); - mount.appendChild(renderer.domElement); - - const cssRenderer = new CSS3DRenderer(); - cssRenderer.setSize(mount.clientWidth, mount.clientHeight); - cssRenderer.domElement.style.position = "absolute"; - cssRenderer.domElement.style.top = "0"; - cssRenderer.domElement.style.pointerEvents = "none"; // Allow mouse events to pass through - mount.appendChild(cssRenderer.domElement); - - // Ensure the map container is displayed and has dimensions before initializing Leaflet - mapElement.style.display = "block"; // Ensure the element is visible - mapElement.style.pointerEvents = "auto"; // Ensure the element can consume events - - const cssObject = new CSS3DObject(mapElement); - cssObject.rotation.x = -Math.PI / 2; // Rotate to lie flat - cssObject.position.set(0, 0, 0); // Position at ground level - scene.add(cssObject); - - // Light - const light = new THREE.DirectionalLight(0xffffff, 1); - light.position.set(10, 10, 10).normalize(); - scene.add(light); - - // Set up MapControls - const controls = new MapControls(camera, cssRenderer.domElement); - controls.enableDamping = true; // Enable damping (inertia) - controls.dampingFactor = 0.05; // Damping factor - controls.screenSpacePanning = false; // Prevent camera from moving vertically in screen space - controls.minDistance = 10; // Minimum zoom distance - controls.maxDistance = 500; // Maximum zoom distance - controls.maxPolarAngle = Math.PI / 2; // Limit angle from the top - - // Configure controls to respond only to right-click - controls.mouseButtons = { - LEFT: null, - MIDDLE: null, - RIGHT: THREE.MOUSE.ROTATE, - }; - controls.enableZoom = false; - - // Function to forward events to Leaflet map - const forwardEventToMap = (event: Event) => { - if (event instanceof WheelEvent) { - if (event.deltaY < 0) { - // Simulate zoom in - const simulatedEvent = new WheelEvent(event.type, { - ...event, - deltaY: -1, - }); - mapElement.dispatchEvent(simulatedEvent); - } else { - // Simulate zoom out - const simulatedEvent = new WheelEvent(event.type, { - ...event, - deltaY: 1, - }); - mapElement.dispatchEvent(simulatedEvent); - } - } else if (event instanceof MouseEvent && event.button !== 2) { - // Prevent right-click events from being forwarded - const simulatedEvent = new MouseEvent(event.type, { - bubbles: true, - cancelable: true, - clientX: event.clientX - 1, - clientY: event.clientY - 1, - }); - mapElement.dispatchEvent(simulatedEvent); - } - }; - - // Add event listeners to forward events - const events: (keyof HTMLElementEventMap)[] = ["mousedown", "wheel"]; - events.forEach((eventName) => { - renderer.domElement.addEventListener( - eventName, - forwardEventToMap as EventListener - ); - }); - - // Animate - const animate = () => { - requestAnimationFrame(animate); - controls.update(); // Update controls - renderer.render(scene, camera); - cssRenderer.render(scene, camera); - }; - animate(); - - // Cleanup on component unmount - return () => { - mount.removeChild(renderer.domElement); - mount.removeChild(cssRenderer.domElement); - }; - }, []); - - return ( - <> -
    -
    - -
    - - ); -}; - -export default ThreeDView; From 3772bca4e6db7edb45ec1bbfcd775722b8e09208 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 16:55:17 +0200 Subject: [PATCH 28/32] Fixed styling issue with polygon drawing Signed-off-by: Emil Balitzki --- frontend/src/components/MapView/LeafletMap.css | 7 ------- frontend/src/components/MapView/LeafletMap.tsx | 10 +++++++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/MapView/LeafletMap.css b/frontend/src/components/MapView/LeafletMap.css index ccfe8566..e337a3fe 100644 --- a/frontend/src/components/MapView/LeafletMap.css +++ b/frontend/src/components/MapView/LeafletMap.css @@ -12,13 +12,6 @@ height: 8px !important; } -.leaflet-interactive { - fill: none; - stroke: #ff0000; - stroke-width: 2px; - stroke-opacity: 1; -} - .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 index a49f4c3b..5b41f242 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -108,7 +108,15 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { })); // Allow for drawing polygons map.addLayer(drawnItems); - setPolygonDrawer(new L.Draw.Polygon(map as L.DrawMap)); + // Define the options for the polygon drawer + const polygonOptions = { + shapeOptions: { + color: "#ff0000", + weight: 3, + fillOpacity: 0, + }, + }; + 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; From eed1cc9c95aa439981d6b566c2ebf89d4a4c1f65 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 16:56:25 +0200 Subject: [PATCH 29/32] Uninstalled Three.js Signed-off-by: Emil Balitzki --- frontend/package-lock.json | 46 +------------------------------------- frontend/package.json | 4 +--- 2 files changed, 2 insertions(+), 48 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 67105804..7eeb9bd5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -21,7 +21,6 @@ "@types/leaflet-draw": "^1.0.11", "@types/leaflet.markercluster": "^1.5.4", "@types/proj4leaflet": "^1.0.10", - "@types/three": "^0.166.0", "autosuggest-highlight": "^3.3.4", "axios": "^1.7.2", "geojson": "^0.5.0", @@ -32,8 +31,7 @@ "proj4leaflet": "^1.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-leaflet": "^4.2.1", - "three": "^0.166.1" + "react-leaflet": "^4.2.1" }, "devDependencies": { "@types/leaflet": "^1.9.12", @@ -1748,11 +1746,6 @@ "win32" ] }, - "node_modules/@tweenjs/tween.js": { - "version": "23.1.2", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.2.tgz", - "integrity": "sha512-kMCNaZCJugWI86xiEHaY338CU5JpD0B97p1j1IKNn/Zto8PgACjQx0UxbHjmOcLl/dDOBnItwD07KmCs75pxtQ==" - }, "node_modules/@types/autosuggest-highlight": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/@types/autosuggest-highlight/-/autosuggest-highlight-3.2.3.tgz", @@ -1894,28 +1887,6 @@ "@types/react": "*" } }, - "node_modules/@types/stats.js": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz", - "integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==" - }, - "node_modules/@types/three": { - "version": "0.166.0", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.166.0.tgz", - "integrity": "sha512-FHMnpcdhdbdOOIYbfkTkUVpYMW53odxbTRwd0/xJpYnTzEsjnVnondGAvHZb4z06UW0vo6WPVuvH0/9qrxKx7g==", - "dependencies": { - "@tweenjs/tween.js": "~23.1.2", - "@types/stats.js": "*", - "@types/webxr": "*", - "fflate": "~0.8.2", - "meshoptimizer": "~0.18.1" - } - }, - "node_modules/@types/webxr": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.19.tgz", - "integrity": "sha512-4hxA+NwohSgImdTSlPXEqDqqFktNgmTXQ05ff1uWam05tNGroCMp4G+4XVl6qWm1p7GQ/9oD41kAYsSssF6Mzw==" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz", @@ -2881,11 +2852,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3429,11 +3395,6 @@ "node": ">= 8" } }, - "node_modules/meshoptimizer": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", - "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==" - }, "node_modules/mgrs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mgrs/-/mgrs-1.0.0.tgz", @@ -4086,11 +4047,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/three": { - "version": "0.166.1", - "resolved": "https://registry.npmjs.org/three/-/three-0.166.1.tgz", - "integrity": "sha512-LtuafkKHHzm61AQA1be2MAYIw1IjmhOUxhBa0prrLpEMWbV7ijvxCRHjSgHPGp2493wLBzwKV46tA9nivLEgKg==" - }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 63208c13..023b86b8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,7 +23,6 @@ "@types/leaflet-draw": "^1.0.11", "@types/leaflet.markercluster": "^1.5.4", "@types/proj4leaflet": "^1.0.10", - "@types/three": "^0.166.0", "autosuggest-highlight": "^3.3.4", "axios": "^1.7.2", "geojson": "^0.5.0", @@ -34,8 +33,7 @@ "proj4leaflet": "^1.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-leaflet": "^4.2.1", - "three": "^0.166.1" + "react-leaflet": "^4.2.1" }, "devDependencies": { "@types/leaflet": "^1.9.12", From e37254bdd3fa3d12c51bce89bb7bcd1569ecbd93 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 19:03:10 +0200 Subject: [PATCH 30/32] Small fixes Signed-off-by: Emil Balitzki --- frontend/src/components/DataView/DataView.tsx | 1 - frontend/src/components/MapView/LeafletMap.tsx | 2 +- frontend/src/components/MapView/MapView.css | 2 +- frontend/src/components/SearchBar/SearchBar.tsx | 15 ++++++--------- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index d8b2a8f0..40182479 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -84,7 +84,6 @@ function DataView() { ]; coords = [[singlePosition]]; } else if (currentCoords instanceof PolygonSelection) { - console.log(currentCoords.polygon); coords = currentCoords.polygon.coordinates; } console.log(coords); diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx index 5b41f242..efae7c43 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -113,7 +113,7 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { shapeOptions: { color: "#ff0000", weight: 3, - fillOpacity: 0, + fillOpacity: 0.06, }, }; setPolygonDrawer(new L.Draw.Polygon(map as L.DrawMap, polygonOptions)); 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/SearchBar/SearchBar.tsx b/frontend/src/components/SearchBar/SearchBar.tsx index fec106b2..40ca0c6a 100644 --- a/frontend/src/components/SearchBar/SearchBar.tsx +++ b/frontend/src/components/SearchBar/SearchBar.tsx @@ -123,7 +123,7 @@ const SearchBar: React.FC = () => { style: { color: "#ff0000", weight: 2, - fillOpacity: 0, + fillOpacity: 0.06, }, }); drawPolygon.addTo(currentMapCache.drawnItems!); @@ -145,7 +145,10 @@ const SearchBar: React.FC = () => { false ); currentMapCache.selectedCoordinates = markerSelection; - mapInstance.flyTo(targetPosition, 13, { animate: true, duration: 5 }); + mapInstance.flyTo(targetPosition, currentMapCache.zoom, { + animate: true, + duration: 5, + }); } } else console.log("No map instance"); }; @@ -202,18 +205,12 @@ const SearchBar: React.FC = () => { } }} onInputChange={(_event, newInputValue) => { - if (newInputValue === "") { - setCurrentMapCache({ - ...currentMapCache, - selectedCoordinates: null, - }); - } setInputValue(newInputValue); }} renderInput={(params) => ( Search…
    } + placeholder="Search..." size="small" sx={{ width: inputValue.length > 0 ? "100%" : 150, From 3a16353c883615c4501c0880ef290ed5a87e1ec0 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sat, 6 Jul 2024 19:06:37 +0200 Subject: [PATCH 31/32] Fixed data not loading at the start Signed-off-by: Emil Balitzki --- frontend/src/components/MapView/GeoDataFetcher.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/src/components/MapView/GeoDataFetcher.tsx b/frontend/src/components/MapView/GeoDataFetcher.tsx index 8a3bb9d4..9fc37136 100644 --- a/frontend/src/components/MapView/GeoDataFetcher.tsx +++ b/frontend/src/components/MapView/GeoDataFetcher.tsx @@ -79,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; }; From 38f6f55d8985375286403c1ef4a1cbb13f42aa20 Mon Sep 17 00:00:00 2001 From: Lucas-Nan <166795065+Lucas-Nan@users.noreply.github.com> Date: Sun, 7 Jul 2024 10:14:03 +0200 Subject: [PATCH 32/32] fixed/added multipolygon handling Signed-off-by: Lucas-Nan <166795065+Lucas-Nan@users.noreply.github.com> --- frontend/src/components/DataView/DataView.tsx | 18 ++++++++--- .../src/components/MapView/LeafletMap.tsx | 30 ++++++++++++++++--- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index 40182479..188cd5d2 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -12,7 +12,13 @@ import { MarkerSelection, PolygonSelection, } from "../../types/MapSelectionTypes"; -import { Position } from "geojson"; +import { MultiPolygon, Position } from "geojson"; + +// Function to filter and return an array of outer polygons +function getOuterPolygons(multiPolygon: MultiPolygon): Position[][] { + // Filter out the inner polygons (holes) and keep only the outer ones + return multiPolygon.coordinates.map((polygon) => polygon[0]); +} function DataView() { const { currentTabsCache, getCurrentTab } = useContext(TabsContext); @@ -76,7 +82,8 @@ function DataView() { }); // Prepare the location data if (currentCoords) { - let coords: Position[][] | Position[][][] = []; + let coords: Position[][] = []; + if (currentCoords instanceof MarkerSelection) { const singlePosition: Position = [ currentCoords.marker.lng, @@ -84,9 +91,12 @@ function DataView() { ]; coords = [[singlePosition]]; } else if (currentCoords instanceof PolygonSelection) { - coords = currentCoords.polygon.coordinates; + // we have multipolygons which can have quite complicated inner structures. + // we simplfiy fot the current api in a way that we ignore all inner "holes" or other parts and only take + // the outer parts. so the independent general polygons. + coords = getOuterPolygons(currentCoords.polygon); } - console.log(coords); + // Send the location request const currentTab = getCurrentTab(); if (currentTab) { diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx index efae7c43..a41dd0a2 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -24,7 +24,13 @@ import { MarkerSelection, PolygonSelection, } from "../../types/MapSelectionTypes"; -import { Feature, GeoJsonProperties, MultiPolygon } from "geojson"; +import { + Feature, + GeoJsonProperties, + Geometry, + MultiPolygon, + Position, +} from "geojson"; interface LeafletMapProps { datasetId: string; @@ -123,15 +129,31 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { if (drawnObject instanceof L.Polygon) { if (drawnItems) { drawnItems.addLayer(drawnObject); - const latLongs = drawnObject.toGeoJSON() as Feature< - MultiPolygon, + 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( - latLongs.geometry, + multiPolygon, "Custom Polygon", true ); + setCurrentMapCache({ ...currentMapCacheRef.current, selectedCoordinates: polygonSelection,