diff --git a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/MapBox/index.tsx b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/MapBox/index.tsx index ad9e1f25..b33f10e1 100644 --- a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/MapBox/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/MapBox/index.tsx @@ -215,18 +215,6 @@ const ImageMapBox = () => { 'circle-stroke-width': 4, 'circle-stroke-color': 'red', 'circle-stroke-opacity': 1, - 'circle-opacity': [ - 'match', - ['get', 'index'], - 0, - 0, - Number( - // eslint-disable-next-line no-unsafe-optional-chaining - imageFilesGeoJsonData?.features?.length - 1, - ), - 0, - 1, - ], }, }} zoomToExtent diff --git a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx index fb6eb1c4..a1afa300 100644 --- a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx @@ -45,6 +45,7 @@ import Skeleton from '@Components/RadixComponents/Skeleton'; import rotateGeoJSON from '@Utils/rotateGeojsonData'; import COGOrthophotoViewer from '@Components/common/MapLibreComponents/COGOrthophotoViewer'; import { toast } from 'react-toastify'; +import { FlexColumn } from '@Components/common/Layouts'; import RotatingCircle from '@Components/common/RotationCue'; import { mapLayerIDs } from '@Constants/droneOperator'; import { findNearestCoordinate, swapFirstAndLast } from '@Utils/index'; @@ -69,6 +70,7 @@ const MapSection = ({ className }: { className?: string }) => { const [isRotationEnabled, setIsRotationEnabled] = useState(false); const [rotationAngle, setRotationAngle] = useState(0); const [dragging, setDragging] = useState(false); + const centroidRef = useRef(); const { map, isMapLoaded } = useMapLibreGLMap({ containerId: 'dashboard-map', mapOptions: { @@ -93,8 +95,9 @@ const MapSection = ({ className }: { className?: string }) => { isFetching: taskDataPolygonIsFetching, }: Record = useGetIndividualTaskQuery(taskId as string, { select: (projectRes: any) => { - const taskPolygon = projectRes.data.outline; - const { geometry } = taskPolygon; + const taskPolygon = projectRes.data; + centroidRef.current = taskPolygon.centroid.coordinates; + const { geometry } = taskPolygon.outline; return { type: 'FeatureCollection', features: [ @@ -310,6 +313,7 @@ const MapSection = ({ className }: { className?: string }) => { : [firstFeature, ...restFeatures], }, rotationDegreeParam, + centroidRef.current, ); if (sourceToRotate && sourceToRotate instanceof GeoJSONSource) { // @ts-ignore @@ -336,6 +340,7 @@ const MapSection = ({ className }: { className?: string }) => { type: 'FeatureCollection', }, rotationDegreeParam, + centroidRef.current, ); if (sourceToRotate && sourceToRotate instanceof GeoJSONSource) { // @ts-ignore @@ -623,29 +628,28 @@ const MapSection = ({ className }: { className?: string }) => { )} -
- -
+ {isRotationEnabled && ( +
+ +
+ )}
) )} -
- -
+ {!isRotationEnabled && ( +
+ +
+ )} {newTakeOffPoint && ( { /> )} {isRotationEnabled && ( -
+
void; @@ -7,36 +7,41 @@ type RotationCueProps = { dragging: boolean; setDragging: (dragging: boolean) => void; }; + const RotationCue = ({ setRotation, rotation, setDragging, dragging, }: RotationCueProps) => { + const circleRef = useRef(null); const [startAngle, setStartAngle] = useState(0); + const radius = 56; // Adjust to match circle size (half of `naxatw-h-28`) - const handleMouseDown = (event: React.MouseEvent) => { - const { clientX, clientY } = event; - const circle = (event.target as HTMLElement).getBoundingClientRect(); + const calculateAngle = ( + clientX: number, + clientY: number, + circle: DOMRect, + ) => { 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; + + return degrees; + }; + + const handleStart = (clientX: number, clientY: number, circle: DOMRect) => { + const degrees = calculateAngle(clientX, clientY, circle); setStartAngle(degrees - rotation); // Offset for smooth dragging setDragging(true); }; - const handleMouseMove = (event: React.MouseEvent) => { + const handleMove = (clientX: number, clientY: number, circle: DOMRect) => { if (!dragging) return; - const { clientX, clientY } = event; - const circle = (event.target as HTMLElement).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; + const degrees = calculateAngle(clientX, clientY, circle); let rotationDelta = degrees - startAngle; if (rotationDelta < 0) { @@ -46,37 +51,99 @@ const RotationCue = ({ setRotation(rotationDelta); }; - const handleMouseUp = () => { + const handleMouseDown = (event: React.MouseEvent) => { + const { clientX, clientY } = event; + const circle = (circleRef.current as HTMLElement).getBoundingClientRect(); + handleStart(clientX, clientY, circle); + }; + + const handleMouseMove = (event: React.MouseEvent) => { + if (!dragging) return; + + const { clientX, clientY } = event; + const circle = (circleRef.current as HTMLElement).getBoundingClientRect(); + handleMove(clientX, clientY, circle); + }; + + const handleTouchStart = (event: React.TouchEvent) => { + const { clientX, clientY } = event.touches[0]; + const circle = (circleRef.current as HTMLElement).getBoundingClientRect(); + handleStart(clientX, clientY, circle); + }; + + const handleTouchMove = (event: React.TouchEvent) => { + if (!dragging) return; + + const { clientX, clientY } = event.touches[0]; + const circle = (circleRef.current as HTMLElement).getBoundingClientRect(); + handleMove(clientX, clientY, circle); + }; + + const handleEnd = () => { setDragging(false); }; + useEffect(() => { + const preventDefault = (e: TouchEvent | WheelEvent) => { + e.preventDefault(); + }; + if (!dragging) return () => {}; + + // Disable scroll on mobile devices + document.body.style.overflow = 'hidden'; + window.addEventListener('touchmove', preventDefault, { passive: false }); + window.addEventListener('wheel', preventDefault, { passive: false }); + + return () => { + window.removeEventListener('touchmove', preventDefault); + window.removeEventListener('wheel', preventDefault); + document.body.style.overflow = ''; + }; + }, [dragging]); + // Calculate handle position + const radians = (rotation * Math.PI) / 180; + const handleX = radius + radius * Math.cos(radians); + const handleY = radius + radius * Math.sin(radians); return (
e.preventDefault()} + onClick={e => e.preventDefault()} + ref={circleRef} > + {/* Circle */}
- {/* Rotating Line */} + {/* Handle */}
e.preventDefault()} /> - - {/* Static Center */} -

+

{rotation.toFixed(2)}

-
+ {/* Rotation Display */}
); }; diff --git a/src/frontend/src/utils/getExifData.ts b/src/frontend/src/utils/getExifData.ts index f0194df6..89dbf7db 100644 --- a/src/frontend/src/utils/getExifData.ts +++ b/src/frontend/src/utils/getExifData.ts @@ -25,8 +25,17 @@ const getExifData = (file: File): Promise => { const tags = EXIFReader.load(arrayBuffer) as EXIFTags; const dateTime = tags.DateTime?.description; - const gpsLatitude = tags.GPSLatitude?.description; - const gpsLongitude = tags.GPSLongitude?.description; + let gpsLatitude = tags.GPSLatitude?.description; + const gpsLatitudeRef = tags.GPSLatitudeRef?.value; + let gpsLongitude = tags.GPSLongitude?.description; + const gpsLongitudeRef = tags.GPSLongitudeRef?.value; + + if (gpsLatitudeRef[0] === 'S' && gpsLatitude) { + gpsLatitude = -gpsLatitude; + } + if (gpsLongitudeRef[0] === 'W' && gpsLongitude) { + gpsLongitude = -gpsLongitude; + } if (gpsLatitude && gpsLongitude) { const latitude = gpsLatitude;