From 7725889e71f271f9670d7c92c5f8a53692143886 Mon Sep 17 00:00:00 2001 From: Bijay Rauniyar Date: Tue, 10 Dec 2024 11:06:03 +0545 Subject: [PATCH 01/18] feat: update rotation funcitons --- .../DroneOperatorTask/MapSection/index.tsx | 283 +++++++++++------- 1 file changed, 170 insertions(+), 113 deletions(-) diff --git a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx index 76420d8e..3109dc70 100644 --- a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx @@ -1,3 +1,7 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable no-param-reassign */ +/* eslint-disable no-underscore-dangle */ +/* eslint-disable prefer-destructuring */ /* eslint-disable react/no-array-index-key */ import { useGetTaskAssetsInfo, useGetTaskWaypointQuery } from '@Api/tasks'; import marker from '@Assets/images/marker.png'; @@ -37,13 +41,11 @@ import ToolTip from '@Components/RadixComponents/ToolTip'; import Skeleton from '@Components/RadixComponents/Skeleton'; import rotateGeoJSON from '@Utils/rotateGeojsonData'; import COGOrthophotoViewer from '@Components/common/MapLibreComponents/COGOrthophotoViewer'; -import { - calculateAngle, - calculateCentroid, - // calculateCentroidFromCoordinates, -} from '@Utils/index'; import { useGetProjectsDetailQuery } from '@Api/projects'; import { toast } from 'react-toastify'; +import RotatingCircle from '@Components/common/RotationCue'; +import { mapLayerIDs } from '@Constants/droneOperator'; +import { getLastCoordinates } from '@Utils/index'; import GetCoordinatesOnClick from './GetCoordinatesOnClick'; import ShowInfo from './ShowInfo'; @@ -51,15 +53,12 @@ const { COG_URL } = process.env; const MapSection = ({ className }: { className?: string }) => { const dispatch = useDispatch(); - const [rotationDegree, setRotationDegree] = useState(0); const bboxRef = useRef(); const mapRef = useRef(); - const rotatedTaskWayPointsRef = useRef>(); const [taskWayPoints, setTaskWayPoints] = useState | null>(); - const draggingRef = useRef(false); const { projectId, taskId } = useParams(); const centeroidRef = useRef<[number, number]>(); const queryClient = useQueryClient(); @@ -67,6 +66,8 @@ const MapSection = ({ className }: { className?: string }) => { const [showOrthoPhotoLayer, setShowOrthoPhotoLayer] = useState(true); const [showTakeOffPoint, setShowTakeOffPoint] = useState(true); const [isRotationEnabled, setIsRotationEnabled] = useState(false); + const [rotationAngle, setRotationAngle] = useState(0); + const [dragging, setDragging] = useState(false); const { map, isMapLoaded } = useMapLibreGLMap({ containerId: 'dashboard-map', mapOptions: { @@ -85,6 +86,12 @@ const MapSection = ({ className }: { className?: string }) => { mapRef.current = map; }, [map, isMapLoaded]); + function setVisibilityOfLayers(layerIds: string[], visibility: string) { + layerIds.forEach(layerId => { + map?.setLayoutProperty(layerId, 'visibility', visibility); + }); + } + const { data: taskDataPolygon, // isFetching: isProjectDataFetching, @@ -253,49 +260,60 @@ const MapSection = ({ className }: { className?: string }) => { function handleTaskWayPoint() { if (!map || !isMapLoaded) return; - map.setLayoutProperty( - 'waypoint-points-layer', - 'visibility', - `${!showTakeOffPoint ? 'visible' : 'none'}`, - ); - map.setLayoutProperty( - 'waypoint-points-image-layer', - 'visibility', - `${!showTakeOffPoint ? 'visible' : 'none'}`, - ); - map.setLayoutProperty( - 'waypoint-line-layer', - 'visibility', - `${!showTakeOffPoint ? 'visible' : 'none'}`, - ); - map.setLayoutProperty( - 'waypoint-points-image-image/logo', - 'visibility', - `${!showTakeOffPoint ? 'visible' : 'none'}`, - ); - map.setLayoutProperty( - 'waypoint-line-image/logo', - 'visibility', + setVisibilityOfLayers( + mapLayerIDs, `${!showTakeOffPoint ? 'visible' : 'none'}`, ); + // map.setLayoutProperty( + // 'waypoint-points-layer', + // 'visibility', + // `${!showTakeOffPoint ? 'visible' : 'none'}`, + // ); + // map.setLayoutProperty( + // 'waypoint-points-image-layer', + // 'visibility', + // `${!showTakeOffPoint ? 'visible' : 'none'}`, + // ); + // map.setLayoutProperty( + // 'waypoint-line-layer', + // 'visibility', + // `${!showTakeOffPoint ? 'visible' : 'none'}`, + // ); + // map.setLayoutProperty( + // 'waypoint-points-image-image/logo', + // 'visibility', + // `${!showTakeOffPoint ? 'visible' : 'none'}`, + // ); + // map.setLayoutProperty( + // 'waypoint-line-image/logo', + // 'visibility', + // `${!showTakeOffPoint ? 'visible' : 'none'}`, + // ); setShowTakeOffPoint(!showTakeOffPoint); } const rotateLayerGeoJSON = ( layerIds: string[], rotationDegreeParam: number, + baseLayerIds: string[], ) => { if (!mapRef.current) return; - layerIds.forEach(layerId => { - const source = mapRef.current?.getSource(layerId); + baseLayerIds.forEach((baseLayerId, index) => { + const source = mapRef.current?.getSource(baseLayerId); + const sourceToRotate = mapRef.current?.getSource(layerIds[index]); if (source && source instanceof GeoJSONSource) { - // eslint-disable-next-line no-underscore-dangle - const geojsonData = source._data; - // @ts-ignore - const rotatedGeoJSON = rotateGeoJSON(geojsonData, rotationDegreeParam); - source.setData(rotatedGeoJSON); + const baseGeoData = source._data; + const rotatedGeoJSON = rotateGeoJSON( + // @ts-ignore + baseGeoData, + rotationDegreeParam, + ); + if (sourceToRotate && sourceToRotate instanceof GeoJSONSource) { + // @ts-ignore + sourceToRotate.setData(rotatedGeoJSON); + } } }); }; @@ -321,81 +339,122 @@ const MapSection = ({ className }: { className?: string }) => { }; }, [taskWayPointsData]); - useEffect(() => { - if (!rotatedTaskWayPoints) return; - rotatedTaskWayPointsRef.current = rotatedTaskWayPoints; - }, [rotatedTaskWayPoints]); - - // function that handles drag and calculates rotation - const handleDrag = useCallback((event: any) => { - if (!draggingRef.current) { - draggingRef.current = true; - } - const { originalCoordinates } = event; - const centroidCoordinates = calculateCentroid(bboxRef.current || []); + function findNearestCoordinate( + coord1: number[], + coord2: number[], + center: number[], + ) { + // Function to calculate distance between two points + const calculateDistance = (point1: number[], point2: number[]) => { + const xDiff = point2[0] - point1[0]; + const yDiff = point2[1] - point1[1]; + return Math.sqrt(xDiff * xDiff + yDiff * yDiff); + }; - const calculatedAngleFromCoordinates = calculateAngle( - [originalCoordinates[0], originalCoordinates[1]], - [event.lngLat?.lng, event.lngLat?.lat], - [centroidCoordinates.lng, centroidCoordinates.lat], - ); - // Update rotation continuously while dragging - setRotationDegree(calculatedAngleFromCoordinates); + // Calculate the distance of the first and second coordinates from the center + const distance1 = calculateDistance(coord1, center); + const distance2 = calculateDistance(coord2, center); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + // Return the nearest coordinate + return distance1 <= distance2 ? 'first' : 'second'; + } + + function updateLayerCoordinates( + layerIds: Record[], + coordinate: [number, number], + ) { + // Iterate over the array of layer IDs + if (!map || !isMapLoaded) return; + layerIds.forEach(layerId => { + // Check if the layer is of type 'symbol' (or any other type) + const source = map.getSource(layerId.id); // Get the source of the layer - function getGeoJSONDataFromMap(sourceIds: string[]) { - const geojsonData: Record = {}; + // Update the feature on the map - if (!mapRef.current) return geojsonData; - sourceIds.forEach(sourceId => { - const source = mapRef.current?.getSource(sourceId); if (source && source instanceof GeoJSONSource) { - // eslint-disable-next-line no-underscore-dangle - geojsonData[sourceId] = source._data; + const geoJsonData = source._data; + // @ts-ignore + const { features, ...restGeoData } = geoJsonData; + const coordinates = features[0].geometry.coordinates; + if (layerId.type === 'MultiString') { + const nearestCoordinate = findNearestCoordinate( + coordinates[0], + coordinates[coordinates.length - 1], + centeroidRef.current || [0, 0], + ); + let indexToReplace = 0; + if (nearestCoordinate === 'second') { + indexToReplace = coordinates.length - 1; + } + features[0].geometry.coordinates[indexToReplace] = coordinate; + source.setData({ features, ...restGeoData }); + } + if (layerId.type === 'Points') { + const nearestPoint = findNearestCoordinate( + coordinates, + features[features.length - 1].geometry.coordinates, + centeroidRef.current || [0, 0], + ); + let pointIndexToReplace = 0; + if (nearestPoint === 'second') { + pointIndexToReplace = features.length - 1; + } + features[pointIndexToReplace].geometry.coordinates = coordinate; + const updatedFeatures = features.map((featurex: any) => { + if (pointIndexToReplace === 0) return featurex; + return { + ...featurex, + properties: { + ...featurex.properties, + heading: -90, + }, + }; + }); + source.setData({ features: updatedFeatures, ...restGeoData }); + } + // console.log(modifiedGeoJson, 'modifiedGeoJson'); } }); - - return geojsonData; } - // function to handle end of drag - const handleDragEnd = useCallback(() => { - const dataRetrieved = getGeoJSONDataFromMap([ - 'rotated-waypoint-line', - 'rotated-waypoint-points', - 'rotated-waypoint-points-image', - ]); - const newTaskGeoJson = { - geojsonAsLineString: dataRetrieved['rotated-waypoint-line'], - geojsonListOfPoint: dataRetrieved['rotated-waypoint-points'], - }; - setTaskWayPoints(newTaskGeoJson); - if (draggingRef.current) { - draggingRef.current = false; // Reset dragging state + useEffect(() => { + if (!dragging) { + rotateLayerGeoJSON(['waypoint-line', 'waypoint-points'], rotationAngle, [ + 'waypoint-line', + 'waypoint-points', + ]); + updateLayerCoordinates( + [ + { id: 'waypoint-line', type: 'MultiString' }, + { id: 'waypoint-points', type: 'Points' }, + ], + centeroidRef.current || [0, 0], + ); + + return; } - }, []); + rotateLayerGeoJSON( + ['rotated-waypoint-line', 'rotated-waypoint-points'], + rotationAngle, + ['waypoint-line', 'waypoint-points'], + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [rotationAngle, dragging]); function handleRotationToggle() { if (!map || !isMapLoaded) return; setIsRotationEnabled(!isRotationEnabled); - if (!isRotationEnabled) { - map.dragPan.disable(); - } else { - map.dragPan.enable(); - } } + useEffect(() => { + if (!map || !isMapLoaded) return; - // Call the function to update rotation for each layer - rotateLayerGeoJSON( - [ - 'rotated-waypoint-line', - 'rotated-waypoint-points', - 'rotated-waypoint-points-image', - ], - rotationDegree, - ); + if (!dragging) { + setVisibilityOfLayers(mapLayerIDs, 'visible'); + return; + } + setVisibilityOfLayers(mapLayerIDs, 'none'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dragging, isMapLoaded, map]); return ( <> @@ -436,9 +495,6 @@ const MapSection = ({ className }: { className?: string }) => { image={right} symbolPlacement="line" iconAnchor="center" - onDrag={handleDrag} - onDragEnd={handleDragEnd} - needDragEvent={isRotationEnabled} /> {/* render points */} { ], }, }} - onDrag={handleDrag} - onDragEnd={handleDragEnd} - needDragEvent={isRotationEnabled} /> {/* render image and only if index is 0 */} { imageLayerOptions={{ filter: ['==', 'index', 0], }} - onDrag={handleDrag} - onDragEnd={handleDragEnd} /> )} - {isRotationEnabled && draggingRef.current && ( + {isRotationEnabled && dragging && ( <> {/* render line */} { ], }, }} - onDrag={handleDrag} - onDragEnd={handleDragEnd} /> {/* render image and only if index is 0 */} { imageLayerOptions={{ filter: ['==', 'index', 0], }} - onDrag={handleDrag} - onDragEnd={handleDragEnd} /> )} @@ -619,7 +666,7 @@ const MapSection = ({ className }: { className?: string }) => {
-
+
+
+ )} +
+ ); +} + +const AsyncPopup = forwardRef( + ( + { + map, + fetchPopupData, + popupUI, + title, + handleBtnClick, + isLoading = false, + onClose, + hideButton = false, + getCoordOnProperties = false, + showPopup = (_clickedFeature: Record) => true, + openPopupFor, + popupCoordinate, + }: IAsyncPopup, + ref, + ) => { + const [properties, setProperties] = useState | null>( + null, + ); + const [coordinates, setCoordinates] = useState(null); + const [isPopupOpen, setIsPopupOpen] = useState(false); + + useEffect(() => { + if (!map) return; + function displayPopup(e: MapMouseEvent): void { + if (!map) return; + const features = map.queryRenderedFeatures(e.point); + const clickedFeature = features?.[0]; + + // in case of popup rendering conditionally + if (!showPopup(clickedFeature)) return; + + if (!clickedFeature) return; + setProperties( + getCoordOnProperties + ? { + ...clickedFeature.properties, + layer: clickedFeature.source, + coordinates: e.lngLat, + } + : { + ...clickedFeature.properties, + layer: clickedFeature.source, + }, + ); + + setCoordinates(e.lngLat); + // popup.setLngLat(e.lngLat); + } + map.on('click', displayPopup); + }, [map, getCoordOnProperties, showPopup]); + + useEffect(() => { + if (!map || !properties) return; + fetchPopupData?.(properties); + }, [map, properties]); // eslint-disable-line + + useEffect(() => { + if (!map || !properties || !popupUI) return; + const htmlString = renderToString( + void} + />, + ); + popup.setHTML(htmlString).addTo(map); + popup.setLngLat(coordinates); + setIsPopupOpen(true); + }, [ + handleBtnClick, + hideButton, + isLoading, + map, + onClose, + popupUI, + properties, + title, + coordinates, + ]); + + useEffect(() => { + if (!map || !openPopupFor || !popupCoordinate) return; + setProperties(openPopupFor); + setCoordinates(popupCoordinate); + }, [map, openPopupFor, popupCoordinate]); + + useEffect(() => { + const closeBtn = document.getElementById('popup-close-button-new'); + const popupBtn = document.getElementById('popup-button'); + + const handleCloseBtnClick = () => { + popup.remove(); + onClose?.(); + setProperties(null); + }; + + const handlePopupBtnClick = () => { + if (!properties) return; + handleBtnClick?.(properties); + }; + + closeBtn?.addEventListener('click', handleCloseBtnClick); + popupBtn?.addEventListener('click', handlePopupBtnClick); + + return () => { + closeBtn?.removeEventListener('click', handleCloseBtnClick); + popupBtn?.removeEventListener('click', handlePopupBtnClick); + }; + }, [onClose, isPopupOpen, properties, handleBtnClick]); + + if (!properties) return
; + return null; + }, +); +export default AsyncPopup; From 56befcdc2d42c8a115c3c2947523677226256c36 Mon Sep 17 00:00:00 2001 From: Bijay Rauniyar Date: Tue, 10 Dec 2024 17:29:26 +0545 Subject: [PATCH 05/18] feat: add rotattionCue component --- .../components/common/RotationCue/index.tsx | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/frontend/src/components/common/RotationCue/index.tsx diff --git a/src/frontend/src/components/common/RotationCue/index.tsx b/src/frontend/src/components/common/RotationCue/index.tsx new file mode 100644 index 00000000..b8855161 --- /dev/null +++ b/src/frontend/src/components/common/RotationCue/index.tsx @@ -0,0 +1,84 @@ +/* eslint-disable no-unused-vars */ +import React, { useState } from 'react'; + +type RotationCueProps = { + setRotation: (rotation: number) => void; + rotation: number; + dragging: boolean; + setDragging: (dragging: boolean) => void; +}; +const RotationCue = ({ + setRotation, + rotation, + setDragging, + dragging, +}: RotationCueProps) => { + const [startAngle, setStartAngle] = useState(0); + + const handleMouseDown = (event: any) => { + const { clientX, clientY } = event; + const circle = event.target.getBoundingClientRect(); + const centerX = circle.left + circle.width / 2; + const centerY = circle.top + circle.height / 2; + + const radians = Math.atan2(clientY - centerY, clientX - centerX); + const degrees = (radians * (180 / Math.PI) + 360) % 360; + setStartAngle(degrees - rotation); // Offset for smooth dragging + setDragging(true); + }; + + const handleMouseMove = (event: any) => { + if (!dragging) return; + + const { clientX, clientY } = event; + const circle = event.target.getBoundingClientRect(); + const centerX = circle.left + circle.width / 2; + const centerY = circle.top + circle.height / 2; + + const radians = Math.atan2(clientY - centerY, clientX - centerX); + const degrees = (radians * (180 / Math.PI) + 360) % 360; + + let rotationDelta = degrees - startAngle; + if (rotationDelta < 0) { + rotationDelta += 360; + } + + setRotation(rotationDelta); + }; + + const handleMouseUp = () => { + setDragging(false); + }; + + return ( +
+
+ {/* Rotating Line */} +
+ + {/* Static Center */} +

+ {rotation.toFixed(2)} +

+
+
+
+ ); +}; + +export default RotationCue; From 75c4e7c9619d71e44868766ce7aa46885becf1e8 Mon Sep 17 00:00:00 2001 From: Bijay Rauniyar Date: Tue, 10 Dec 2024 17:29:56 +0545 Subject: [PATCH 06/18] feat: optimize flightplan rotation --- .../DroneOperatorTask/MapSection/index.tsx | 72 +++++++++++++++---- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx index 8c493865..14e835d9 100644 --- a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx @@ -222,8 +222,11 @@ const MapSection = ({ className }: { className?: string }) => { }, [popupData]); const handleSaveStartingPoint = () => { - const { geometry } = newTakeOffPoint as Record; - const [lng, lat] = geometry.coordinates; + const { geometry: startingPonyGeometry } = newTakeOffPoint as Record< + string, + any + >; + const [lng, lat] = startingPonyGeometry.coordinates; postWaypoint({ projectId, taskId, @@ -275,6 +278,7 @@ const MapSection = ({ className }: { className?: string }) => { layerIds: string[], rotationDegreeParam: number, baseLayerIds: string[], + excludeFirstFeature?: boolean, ) => { if (!map || !isMapLoaded) return; @@ -284,14 +288,51 @@ const MapSection = ({ className }: { className?: string }) => { if (source && source instanceof GeoJSONSource) { const baseGeoData = source._data; - const rotatedGeoJSON = rotateGeoJSON( - // @ts-ignore - baseGeoData, - rotationDegreeParam, - ); - if (sourceToRotate && sourceToRotate instanceof GeoJSONSource) { - // @ts-ignore - sourceToRotate.setData(rotatedGeoJSON); + if (!baseGeoData) return; + const [firstFeature, ...restFeatures] = ( + baseGeoData as Record + ).features; + if (firstFeature.geometry.type === 'Point') { + const pointRotatedGeoJson = rotateGeoJSON( + // @ts-ignore + { + ...(baseGeoData as object), + features: excludeFirstFeature + ? restFeatures + : [firstFeature, ...restFeatures], + }, + rotationDegreeParam, + ); + if (sourceToRotate && sourceToRotate instanceof GeoJSONSource) { + // @ts-ignore + sourceToRotate.setData(pointRotatedGeoJson); + } + } + if (firstFeature.geometry.type === 'LineString') { + const [firstCoordinate, ...restCoordinates] = + firstFeature.geometry.coordinates; + const rotatedGeoJson = rotateGeoJSON( + { + features: [ + // @ts-ignore + { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: excludeFirstFeature + ? restCoordinates + : [firstCoordinate, ...restCoordinates], + }, + }, + ], + type: 'FeatureCollection', + }, + rotationDegreeParam, + ); + if (sourceToRotate && sourceToRotate instanceof GeoJSONSource) { + // @ts-ignore + sourceToRotate.setData(rotatedGeoJson); + } } } }); @@ -386,10 +427,12 @@ const MapSection = ({ className }: { className?: string }) => { useEffect(() => { if (!dragging) { - rotateLayerGeoJSON(['waypoint-line', 'waypoint-points'], rotationAngle, [ - 'waypoint-line', - 'waypoint-points', - ]); + rotateLayerGeoJSON( + ['waypoint-line', 'waypoint-points'], + rotationAngle, + ['waypoint-line', 'waypoint-points'], + false, + ); updateLayerCoordinates( [ { id: 'waypoint-line', type: 'MultiString' }, @@ -404,6 +447,7 @@ const MapSection = ({ className }: { className?: string }) => { ['rotated-waypoint-line', 'rotated-waypoint-points'], rotationAngle, ['waypoint-line', 'waypoint-points'], + true, ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [rotationAngle, dragging]); From 6441699d5654e1aa5f920595b8159561892f1559 Mon Sep 17 00:00:00 2001 From: Bijay Rauniyar Date: Tue, 10 Dec 2024 17:30:11 +0545 Subject: [PATCH 07/18] feat: add layer ids constants --- src/frontend/src/constants/droneOperator.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/frontend/src/constants/droneOperator.ts b/src/frontend/src/constants/droneOperator.ts index 8624cc25..6a70bac7 100644 --- a/src/frontend/src/constants/droneOperator.ts +++ b/src/frontend/src/constants/droneOperator.ts @@ -29,3 +29,11 @@ export const descriptionData = [ ], ]; export const descriptionTitle = ['Task Description', 'Flight Parameters']; + +export const mapLayerIDs = [ + 'waypoint-points-layer', + 'waypoint-points-image-layer', + 'waypoint-line-layer', + 'waypoint-points-image-image/logo', + 'waypoint-line-image/logo', +]; From 2957cfd605130eba1f74a43c85ce9b7713af562c Mon Sep 17 00:00:00 2001 From: Bijay Rauniyar Date: Tue, 10 Dec 2024 17:31:55 +0545 Subject: [PATCH 08/18] refactor: remove unused vars --- .../components/DroneOperatorTask/MapSection/index.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx index 14e835d9..b4ba7d29 100644 --- a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-vars */ /* eslint-disable no-param-reassign */ /* eslint-disable no-underscore-dangle */ /* eslint-disable prefer-destructuring */ @@ -45,11 +44,7 @@ import { useGetProjectsDetailQuery } from '@Api/projects'; import { toast } from 'react-toastify'; import RotatingCircle from '@Components/common/RotationCue'; import { mapLayerIDs } from '@Constants/droneOperator'; -import { - findNearestCoordinate, - getLastCoordinates, - swapFirstAndLast, -} from '@Utils/index'; +import { findNearestCoordinate, swapFirstAndLast } from '@Utils/index'; import GetCoordinatesOnClick from './GetCoordinatesOnClick'; import ShowInfo from './ShowInfo'; @@ -414,9 +409,9 @@ const MapSection = ({ className }: { className?: string }) => { }); } features[pointIndexToReplace].geometry.coordinates = coordinate; - let rotatedFeatures = features; + const rotatedFeatures = features; if (pointIndexToReplace !== 0) { - rotatedFeatures = swapFirstAndLast(rotatedFeatures); + swapFirstAndLast(rotatedFeatures); features.pop(); } source.setData({ features, ...restGeoData }); From d28cfac703021df92c44e1e7f1b57e6235d77905 Mon Sep 17 00:00:00 2001 From: Bijay Rauniyar Date: Tue, 10 Dec 2024 17:32:31 +0545 Subject: [PATCH 09/18] feat: add function to swap first and last coordinate and fucntion to get last coordinate --- src/frontend/src/utils/index.ts | 85 +++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/src/frontend/src/utils/index.ts b/src/frontend/src/utils/index.ts index c39b93a3..ae05e4e6 100644 --- a/src/frontend/src/utils/index.ts +++ b/src/frontend/src/utils/index.ts @@ -213,3 +213,88 @@ export function calculateCentroidFromCoordinates(coordinates: any[]) { // Convert the results to degrees return [RadtoDegrees(lat), RadtoDegrees(lon)]; } + +type Coordinate = [number, number]; +type GeoJSONFeature = { + type: 'Feature'; + geometry: { + type: 'LineString' | 'Polygon'; + coordinates: Coordinate[] | Coordinate[][]; + }; + properties: Record; +}; + +export function swapFirstAndLastCoordinate( + geojson: GeoJSONFeature, +): GeoJSONFeature { + // Clone the GeoJSON object to avoid mutation + const updatedGeoJSON: GeoJSONFeature = { + ...geojson, + geometry: { ...geojson.geometry }, + }; + + if (geojson.geometry.type === 'LineString') { + const coordinates = [...(geojson.geometry.coordinates as Coordinate[])]; + if (coordinates.length < 2) { + throw new Error('LineString must have at least two coordinates to swap.'); + } + // Swap the first and last coordinates + [coordinates[0], coordinates[coordinates.length - 1]] = [ + coordinates[coordinates.length - 1], + coordinates[0], + ]; + updatedGeoJSON.geometry.coordinates = coordinates; + } else if (geojson.geometry.type === 'Polygon') { + const coordinates = [...(geojson.geometry.coordinates as Coordinate[][])]; + const firstRing = coordinates[0]; + if (!firstRing || firstRing.length < 2) { + throw new Error( + 'Polygon must have at least two coordinates in the first ring to swap.', + ); + } + // Swap the first and last coordinates of the first ring + [firstRing[0], firstRing[firstRing.length - 1]] = [ + firstRing[firstRing.length - 1], + firstRing[0], + ]; + updatedGeoJSON.geometry.coordinates = coordinates; + } else { + throw new Error( + 'Unsupported geometry type. Only LineString and Polygon are supported.', + ); + } + + return updatedGeoJSON; +} + +export function findNearestCoordinate( + coord1: number[], + coord2: number[], + center: number[], +) { + // Function to calculate distance between two points + const calculateDistance = (point1: number[], point2: number[]) => { + const xDiff = point2[0] - point1[0]; + const yDiff = point2[1] - point1[1]; + return Math.sqrt(xDiff * xDiff + yDiff * yDiff); + }; + + // Calculate the distance of the first and second coordinates from the center + const distance1 = calculateDistance(coord1, center); + const distance2 = calculateDistance(coord2, center); + + // Return the nearest coordinate + return distance1 <= distance2 ? 'first' : 'second'; +} + +export function swapFirstAndLast(arr: T[]): T[] { + if (arr.length < 2) { + throw new Error('Array must have at least two elements to swap.'); + } + + // Swap the first and last elements using destructuring + // eslint-disable-next-line no-param-reassign + [arr[0], arr[arr.length - 1]] = [arr[arr.length - 1], arr[0]]; + + return arr; +} From c1e67436ce1084a892b5808985afa1c826ce94fa Mon Sep 17 00:00:00 2001 From: Bijay Rauniyar Date: Tue, 10 Dec 2024 17:38:48 +0545 Subject: [PATCH 10/18] style: add responsive attributes to rotation cue --- .../src/components/DroneOperatorTask/MapSection/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx index b4ba7d29..39a1ecaf 100644 --- a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx @@ -716,7 +716,7 @@ const MapSection = ({ className }: { className?: string }) => { /> )} {isRotationEnabled && ( -
+
Date: Tue, 10 Dec 2024 17:46:21 +0545 Subject: [PATCH 11/18] refactor: update swapFirstAndLastCoordinate --- src/frontend/src/utils/index.ts | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/frontend/src/utils/index.ts b/src/frontend/src/utils/index.ts index ae05e4e6..79981520 100644 --- a/src/frontend/src/utils/index.ts +++ b/src/frontend/src/utils/index.ts @@ -235,33 +235,13 @@ export function swapFirstAndLastCoordinate( if (geojson.geometry.type === 'LineString') { const coordinates = [...(geojson.geometry.coordinates as Coordinate[])]; - if (coordinates.length < 2) { - throw new Error('LineString must have at least two coordinates to swap.'); - } + // Swap the first and last coordinates [coordinates[0], coordinates[coordinates.length - 1]] = [ coordinates[coordinates.length - 1], coordinates[0], ]; updatedGeoJSON.geometry.coordinates = coordinates; - } else if (geojson.geometry.type === 'Polygon') { - const coordinates = [...(geojson.geometry.coordinates as Coordinate[][])]; - const firstRing = coordinates[0]; - if (!firstRing || firstRing.length < 2) { - throw new Error( - 'Polygon must have at least two coordinates in the first ring to swap.', - ); - } - // Swap the first and last coordinates of the first ring - [firstRing[0], firstRing[firstRing.length - 1]] = [ - firstRing[firstRing.length - 1], - firstRing[0], - ]; - updatedGeoJSON.geometry.coordinates = coordinates; - } else { - throw new Error( - 'Unsupported geometry type. Only LineString and Polygon are supported.', - ); } return updatedGeoJSON; From cd9fde0ed5a80760586ef7b94ebb295df7f8680c Mon Sep 17 00:00:00 2001 From: Bijay Rauniyar Date: Tue, 10 Dec 2024 17:47:59 +0545 Subject: [PATCH 12/18] refactor: remove swapFirstAndLast coordinate function --- src/frontend/src/utils/index.ts | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/src/frontend/src/utils/index.ts b/src/frontend/src/utils/index.ts index 79981520..bf63d50d 100644 --- a/src/frontend/src/utils/index.ts +++ b/src/frontend/src/utils/index.ts @@ -214,39 +214,6 @@ export function calculateCentroidFromCoordinates(coordinates: any[]) { return [RadtoDegrees(lat), RadtoDegrees(lon)]; } -type Coordinate = [number, number]; -type GeoJSONFeature = { - type: 'Feature'; - geometry: { - type: 'LineString' | 'Polygon'; - coordinates: Coordinate[] | Coordinate[][]; - }; - properties: Record; -}; - -export function swapFirstAndLastCoordinate( - geojson: GeoJSONFeature, -): GeoJSONFeature { - // Clone the GeoJSON object to avoid mutation - const updatedGeoJSON: GeoJSONFeature = { - ...geojson, - geometry: { ...geojson.geometry }, - }; - - if (geojson.geometry.type === 'LineString') { - const coordinates = [...(geojson.geometry.coordinates as Coordinate[])]; - - // Swap the first and last coordinates - [coordinates[0], coordinates[coordinates.length - 1]] = [ - coordinates[coordinates.length - 1], - coordinates[0], - ]; - updatedGeoJSON.geometry.coordinates = coordinates; - } - - return updatedGeoJSON; -} - export function findNearestCoordinate( coord1: number[], coord2: number[], From 85bd95f43d7b835da7fe1d57b3155b31d0737475 Mon Sep 17 00:00:00 2001 From: Bijay Rauniyar Date: Wed, 11 Dec 2024 11:11:16 +0545 Subject: [PATCH 13/18] feat: update api for taskDataPolygon --- .../DroneOperatorTask/MapSection/index.tsx | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx index 39a1ecaf..616acfc1 100644 --- a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx @@ -2,7 +2,11 @@ /* eslint-disable no-underscore-dangle */ /* eslint-disable prefer-destructuring */ /* eslint-disable react/no-array-index-key */ -import { useGetTaskAssetsInfo, useGetTaskWaypointQuery } from '@Api/tasks'; +import { + useGetIndividualTaskQuery, + useGetTaskAssetsInfo, + useGetTaskWaypointQuery, +} from '@Api/tasks'; import marker from '@Assets/images/marker.png'; import right from '@Assets/images/rightArrow.png'; import BaseLayerSwitcherUI from '@Components/common/BaseLayerSwitcher'; @@ -40,7 +44,6 @@ import ToolTip from '@Components/RadixComponents/ToolTip'; import Skeleton from '@Components/RadixComponents/Skeleton'; import rotateGeoJSON from '@Utils/rotateGeojsonData'; import COGOrthophotoViewer from '@Components/common/MapLibreComponents/COGOrthophotoViewer'; -import { useGetProjectsDetailQuery } from '@Api/projects'; import { toast } from 'react-toastify'; import RotatingCircle from '@Components/common/RotationCue'; import { mapLayerIDs } from '@Constants/droneOperator'; @@ -87,24 +90,34 @@ const MapSection = ({ className }: { className?: string }) => { const { data: taskDataPolygon, - isFetching: isProjectDataFetching, - }: Record = useGetProjectsDetailQuery(projectId as string, { + isFetching: taskDataPolygonIsFetching, + }: Record = useGetIndividualTaskQuery(taskId as string, { select: (projectRes: any) => { - const taskPolygon = projectRes.data.tasks.find( - (task: Record) => task.id === taskId, - ); - const { geometry } = taskPolygon.outline; + const taskPolygon = projectRes.data.outline; + const { geometry } = taskPolygon; return { type: 'FeatureCollection', features: [ { type: 'Feature', - geometry, + geometry: { + type: 'Polygon', + coordinates: geometry.coordinates, + }, properties: {}, }, ], }; }, + onSuccess: () => { + if (map) { + const layers = map.getStyle().layers; + if (layers && layers.length > 0) { + const firstLayerId = layers[4].id; // Get the first layer + map.moveLayer('task-polygon-layer', firstLayerId); // Move the layer before the first layer + } + } + }, }); const { data: taskWayPointsData }: any = useGetTaskWaypointQuery( projectId as string, @@ -479,7 +492,7 @@ const MapSection = ({ className }: { className?: string }) => { - {taskWayPoints && !isProjectDataFetching && ( + {taskWayPoints && !taskDataPolygonIsFetching && ( <> {/* render line */} { 'fill-opacity': 0.6, }, }} - // layerOptions={getLayerOptionsByStatus( - // taskStatusObj?.[`${task?.id}`], - // )} - // hasImage={ - // taskStatusObj?.[`${task?.id}`] === 'LOCKED_FOR_MAPPING' || false - // } - // image={lock} /> {newTakeOffPoint === 'place_on_map' && ( From 4e84a1dea4f67f01d59161e436c9b326186311ce Mon Sep 17 00:00:00 2001 From: Bijay Rauniyar Date: Wed, 11 Dec 2024 11:11:37 +0545 Subject: [PATCH 14/18] refactor: update popup close fn --- .../common/MapLibreComponents/NewAsyncPopup/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/components/common/MapLibreComponents/NewAsyncPopup/index.tsx b/src/frontend/src/components/common/MapLibreComponents/NewAsyncPopup/index.tsx index 152b1ea7..3e88f5b3 100644 --- a/src/frontend/src/components/common/MapLibreComponents/NewAsyncPopup/index.tsx +++ b/src/frontend/src/components/common/MapLibreComponents/NewAsyncPopup/index.tsx @@ -48,7 +48,7 @@ function PopupUIComponent({

{title}

)} ( }, [map, openPopupFor, popupCoordinate]); useEffect(() => { - const closeBtn = document.getElementById('popup-close-button-new'); + const closeBtn = document.getElementById('popup-close-button'); const popupBtn = document.getElementById('popup-button'); const handleCloseBtnClick = () => { popup.remove(); onClose?.(); setProperties(null); + setIsPopupOpen(false); }; const handlePopupBtnClick = () => { From de31906b4c2aee4629b824fbc5b4f7cb9de8a87c Mon Sep 17 00:00:00 2001 From: Bijay Rauniyar Date: Wed, 11 Dec 2024 11:12:13 +0545 Subject: [PATCH 15/18] refactor: update type for mouse Events --- src/frontend/src/components/common/RotationCue/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/components/common/RotationCue/index.tsx b/src/frontend/src/components/common/RotationCue/index.tsx index b8855161..1eba21f5 100644 --- a/src/frontend/src/components/common/RotationCue/index.tsx +++ b/src/frontend/src/components/common/RotationCue/index.tsx @@ -15,9 +15,9 @@ const RotationCue = ({ }: RotationCueProps) => { const [startAngle, setStartAngle] = useState(0); - const handleMouseDown = (event: any) => { + const handleMouseDown = (event: React.MouseEvent) => { const { clientX, clientY } = event; - const circle = event.target.getBoundingClientRect(); + const circle = (event.target as HTMLElement).getBoundingClientRect(); const centerX = circle.left + circle.width / 2; const centerY = circle.top + circle.height / 2; @@ -27,11 +27,11 @@ const RotationCue = ({ setDragging(true); }; - const handleMouseMove = (event: any) => { + const handleMouseMove = (event: React.MouseEvent) => { if (!dragging) return; const { clientX, clientY } = event; - const circle = event.target.getBoundingClientRect(); + const circle = (event.target as HTMLElement).getBoundingClientRect(); const centerX = circle.left + circle.width / 2; const centerY = circle.top + circle.height / 2; From 0b8c888be558d57cf3797d119719c4fe01b1d125 Mon Sep 17 00:00:00 2001 From: Bijay Rauniyar Date: Wed, 11 Dec 2024 11:27:13 +0545 Subject: [PATCH 16/18] refactor: update taskWayPointsData data --- .../src/components/DroneOperatorTask/MapSection/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx index 616acfc1..fb6eb1c4 100644 --- a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx @@ -123,9 +123,9 @@ const MapSection = ({ className }: { className?: string }) => { projectId as string, taskId as string, { - select: (data: any) => { + select: ({ data }: any) => { const modifiedTaskWayPointsData = { - geojsonListOfPoint: data.data, + geojsonListOfPoint: data.results, geojsonAsLineString: { type: 'FeatureCollection', features: [ @@ -135,7 +135,7 @@ const MapSection = ({ className }: { className?: string }) => { geometry: { type: 'LineString', // get all coordinates - coordinates: coordAll(data.data), + coordinates: coordAll(data.results), }, }, ], @@ -658,7 +658,7 @@ const MapSection = ({ className }: { className?: string }) => {
-
+