Skip to content

Commit

Permalink
fix: merge conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
nrjadkry committed Dec 12, 2024
2 parents db6f91b + a42c5d4 commit ed39f49
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 83 deletions.
Binary file added docs/images/diagrams/dtm-gcp-workflow.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions docs/manuals/ground-control-points.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Ground Control Points

Ground Control Points are simply a way of mapping a known coordinate
in the real world (ideally in very accurate coordinates such as
[ECEF](https://en.wikipedia.org/wiki/Earth-centered,_Earth-fixed_coordinate_system)),
to a pixel on an image for georeferencing.

When multiple GCPs are used together in the final stitching of drone flight
imagery together, the final georeferencing should be very accurate.

Further details can be found on the OpenDroneMap
[docs page about GCPs](https://docs.opendronemap.org/gcp)

## Our Workflow

![](../images/diagrams/dtm-gcp-workflow.jpg)
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ nav:
- The Team: about/team.md
- User Manuals:
- Flightplans: manuals/generate-flightplans.md
- Ground Control Points: manuals/ground-control-points.md
- Developer Guide:
- Setup: dev/setup.md
- Timeline: timeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
94 changes: 50 additions & 44 deletions src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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: {
Expand All @@ -93,8 +95,9 @@ const MapSection = ({ className }: { className?: string }) => {
isFetching: taskDataPolygonIsFetching,
}: Record<string, any> = 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: [
Expand Down Expand Up @@ -310,6 +313,7 @@ const MapSection = ({ className }: { className?: string }) => {
: [firstFeature, ...restFeatures],
},
rotationDegreeParam,
centroidRef.current,
);
if (sourceToRotate && sourceToRotate instanceof GeoJSONSource) {
// @ts-ignore
Expand All @@ -336,6 +340,7 @@ const MapSection = ({ className }: { className?: string }) => {
type: 'FeatureCollection',
},
rotationDegreeParam,
centroidRef.current,
);
if (sourceToRotate && sourceToRotate instanceof GeoJSONSource) {
// @ts-ignore
Expand Down Expand Up @@ -623,29 +628,28 @@ const MapSection = ({ className }: { className?: string }) => {
</>
)}

<div className="naxatw-absolute naxatw-bottom-3 naxatw-right-[calc(50%-5.4rem)] naxatw-z-30 naxatw-h-fit lg:naxatw-right-3 lg:naxatw-top-3">
<Button
withLoader
leftIcon="place"
className="naxatw-w-[11.8rem] naxatw-bg-red"
onClick={() => {
if (newTakeOffPoint) {
handleSaveStartingPoint();
} else {
dispatch(toggleModal('update-flight-take-off-point'));
}
}}
isLoading={isUpdatingTakeOffPoint}
>
{newTakeOffPoint
? 'Save Take off Point'
: 'Change Take off Point'}
</Button>
</div>
{isRotationEnabled && (
<div className="naxatw-absolute naxatw-bottom-3 naxatw-right-[calc(50%-5.4rem)] naxatw-z-50 naxatw-h-fit naxatw-cursor-pointer lg:naxatw-right-3 lg:naxatw-top-3">
<Button
withLoader
leftIcon="save"
className="naxatw-w-[10.8rem] naxatw-bg-red"
>
<FlexColumn className="naxatw-gap-1">
<p className="naxatw-leading-3 naxatw-tracking-wide">
Save Rotated Flight Plan
</p>
{/* <p className="naxatw-font-normal naxatw-leading-3">
Rotated: {rotationAngle.toFixed(2)}°
</p> */}
</FlexColumn>
</Button>
</div>
)}
<div className="naxatw-absolute naxatw-left-[0.575rem] naxatw-top-[5.75rem] naxatw-z-30 naxatw-h-fit">
<Button
variant="ghost"
className={`naxatw-grid naxatw-h-[1.85rem] naxatw-place-items-center naxatw-border naxatw-bg-[#F5F5F5] !naxatw-px-[0.315rem] ${isRotationEnabled ? 'has-dropshadow naxatw-border-red' : 'naxatw-border-gray-400'}`}
className={`naxatw-grid naxatw-h-[1.85rem] naxatw-place-items-center naxatw-border !naxatw-px-[0.315rem] ${isRotationEnabled ? 'naxatw-border-red naxatw-bg-[#ffe0e0]' : 'naxatw-border-gray-400 naxatw-bg-[#F5F5F5]'}`}
onClick={() => handleRotationToggle()}
>
<ToolTip
Expand All @@ -661,7 +665,7 @@ const MapSection = ({ className }: { className?: string }) => {
<div className="naxatw-absolute naxatw-left-[0.575rem] naxatw-top-[8.25rem] naxatw-z-30 naxatw-h-fit naxatw-overflow-hidden naxatw-pb-1 naxatw-pr-1">
<Button
variant="ghost"
className={`naxatw-grid naxatw-h-[1.85rem] naxatw-place-items-center naxatw-border naxatw-bg-[#F5F5F5] ${showTakeOffPoint ? 'has-dropshadow naxatw-border-red' : 'naxatw-border-gray-400'} !naxatw-px-[0.315rem]`}
className={`naxatw-grid naxatw-h-[1.85rem] naxatw-place-items-center naxatw-border !naxatw-px-[0.315rem] ${showTakeOffPoint ? 'naxatw-border-red naxatw-bg-[#ffe0e0]' : 'naxatw-border-gray-400 naxatw-bg-[#F5F5F5]'}`}
onClick={() => handleTaskWayPoint()}
>
<ToolTip
Expand All @@ -681,7 +685,7 @@ const MapSection = ({ className }: { className?: string }) => {
<div className="naxatw-absolute naxatw-left-[0.575rem] naxatw-top-[10.75rem] naxatw-z-30 naxatw-h-fit">
<Button
variant="ghost"
className={`naxatw-grid naxatw-h-[1.85rem] naxatw-place-items-center naxatw-border naxatw-bg-[#F5F5F5] !naxatw-px-[0.315rem] ${showOrthoPhotoLayer ? 'has-dropshadow naxatw-border-red' : 'naxatw-border-gray-400'}`}
className={`naxatw-grid naxatw-h-[1.85rem] naxatw-place-items-center naxatw-border !naxatw-px-[0.315rem] ${showOrthoPhotoLayer ? 'naxatw-border-red naxatw-bg-[#ffe0e0]' : 'naxatw-border-gray-400 naxatw-bg-[#F5F5F5]'}`}
onClick={() => handleOtrhophotoLayerView()}
>
<ToolTip
Expand All @@ -695,25 +699,27 @@ const MapSection = ({ className }: { className?: string }) => {
</div>
)
)}
<div className="naxatw-absolute naxatw-bottom-3 naxatw-right-[calc(50%-5.4rem)] naxatw-z-30 naxatw-h-fit lg:naxatw-right-3 lg:naxatw-top-3">
<Button
withLoader
leftIcon="place"
className="naxatw-w-[11.8rem] naxatw-bg-red"
onClick={() => {
if (newTakeOffPoint) {
handleSaveStartingPoint();
} else {
dispatch(toggleModal('update-flight-take-off-point'));
}
}}
isLoading={isUpdatingTakeOffPoint}
>
{newTakeOffPoint
? 'Save Take off Point'
: 'Change Take off Point'}
</Button>
</div>
{!isRotationEnabled && (
<div className="naxatw-absolute naxatw-bottom-3 naxatw-right-[calc(50%-5.4rem)] naxatw-z-30 naxatw-h-fit lg:naxatw-right-3 lg:naxatw-top-3">
<Button
withLoader
leftIcon="place"
className="naxatw-w-[11.8rem] naxatw-bg-red"
onClick={() => {
if (newTakeOffPoint) {
handleSaveStartingPoint();
} else {
dispatch(toggleModal('update-flight-take-off-point'));
}
}}
isLoading={isUpdatingTakeOffPoint}
>
{newTakeOffPoint
? 'Save Take off Point'
: 'Change Take off Point'}
</Button>
</div>
)}

{newTakeOffPoint && (
<VectorLayer
Expand All @@ -729,7 +735,7 @@ const MapSection = ({ className }: { className?: string }) => {
/>
)}
{isRotationEnabled && (
<div className="naxatw-absolute naxatw-bottom-4 naxatw-right-[calc(50%-5.4rem)] naxatw-z-50 lg:naxatw-right-2 lg:naxatw-top-8">
<div className="naxatw-absolute naxatw-bottom-10 naxatw-right-[calc(50%-5.4rem)] naxatw-z-30 lg:naxatw-right-2 lg:naxatw-top-10">
<RotatingCircle
setRotation={setRotationAngle}
rotation={rotationAngle}
Expand Down
117 changes: 92 additions & 25 deletions src/frontend/src/components/common/RotationCue/index.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,47 @@
/* eslint-disable no-unused-vars */
import React, { useState } from 'react';
import React, { useEffect, useRef, 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 circleRef = useRef<HTMLDivElement>(null);
const [startAngle, setStartAngle] = useState(0);
const radius = 56; // Adjust to match circle size (half of `naxatw-h-28`)

const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
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<HTMLDivElement>) => {
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) {
Expand All @@ -46,37 +51,99 @@ const RotationCue = ({
setRotation(rotationDelta);
};

const handleMouseUp = () => {
const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
const { clientX, clientY } = event;
const circle = (circleRef.current as HTMLElement).getBoundingClientRect();
handleStart(clientX, clientY, circle);
};

const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
if (!dragging) return;

const { clientX, clientY } = event;
const circle = (circleRef.current as HTMLElement).getBoundingClientRect();
handleMove(clientX, clientY, circle);
};

const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {
const { clientX, clientY } = event.touches[0];
const circle = (circleRef.current as HTMLElement).getBoundingClientRect();
handleStart(clientX, clientY, circle);
};

const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {
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 (
<div
className="naxatw-flex naxatw-h-48 naxatw-w-48 naxatw-flex-col naxatw-items-center naxatw-justify-center naxatw-bg-transparent"
className="naxatw-relative naxatw-flex naxatw-h-48 naxatw-w-48 naxatw-flex-col naxatw-items-center naxatw-justify-center"
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
onMouseUp={handleEnd}
onTouchMove={handleTouchMove}
onTouchEnd={handleEnd}
role="presentation"
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
onDoubleClick={e => e.preventDefault()}
onClick={e => e.preventDefault()}
ref={circleRef}
>
{/* Circle */}
<div
className="naxatw-relative naxatw-flex naxatw-h-28 naxatw-w-28 naxatw-cursor-grab naxatw-items-center naxatw-justify-center naxatw-rounded-full naxatw-border-2 naxatw-border-red naxatw-bg-white"
onMouseDown={handleMouseDown}
className="naxatw-relative naxatw-flex naxatw-h-28 naxatw-w-28 naxatw-items-center naxatw-justify-center naxatw-rounded-full naxatw-border-4 naxatw-border-red naxatw-bg-white naxatw-outline naxatw-outline-4 naxatw-outline-white"
role="presentation"
>
{/* Rotating Line */}
{/* Handle */}
<div
className="naxatw-absolute naxatw-top-3 naxatw-h-10 naxatw-w-1 naxatw-origin-bottom naxatw-bg-red"
className="naxatw-absolute naxatw-h-5 naxatw-w-5 naxatw-cursor-grab naxatw-rounded-full naxatw-bg-red naxatw-outline naxatw-outline-white"
style={{
transform: `rotate(${rotation}deg)`,
left: `${handleX - 14.5}px`, // Offset by half of handle size to center
top: `${handleY - 14.5}px`, // Offset by half of handle size to center
}}
onMouseMove={handleMouseMove}
onMouseUp={handleEnd}
role="presentation"
onMouseDown={handleMouseDown}
onClick={e => e.preventDefault()}
/>

{/* Static Center */}
<p className="naxatw-absolute naxatw-bottom-4 naxatw-select-none naxatw-text-sm">
<p
className="naxatw-absolute naxatw-left-1/2 naxatw-top-1/2 naxatw-translate-x-[-50%] naxatw-translate-y-[-50%] naxatw-select-none naxatw-text-sm"
draggable="false"
>
{rotation.toFixed(2)}
</p>
<div className="naxatw-absolute naxatw-h-4 naxatw-w-4 naxatw-rounded-full naxatw-bg-red" />
</div>
{/* Rotation Display */}
</div>
);
};
Expand Down
Loading

0 comments on commit ed39f49

Please sign in to comment.