Skip to content

Commit

Permalink
Implement Waylines flight mode in flights with terrain following enab…
Browse files Browse the repository at this point in the history
…led. (#411)

* flight mode enums

* updated drone_flightplan package

* implement waylines in terrain following as well

* upgrade drone_flightplan package

* remove: generate each points in waypoints generation

* feat: update waypint service to accept waypoint mode

* feat: add reducer state and action for waypoint mode value update

* feat: add constant waypoint options

* feat: fetch waypoint data as per selected waypoint mode

* style: download waypoints/waylines as per waypoint mode

---------

Co-authored-by: Sujit <sujitkarki703@gmail.com>
Co-authored-by: Sujit <suzeett10@gmail.com>
  • Loading branch information
3 people authored Dec 24, 2024
1 parent b552aee commit d91fa3f
Show file tree
Hide file tree
Showing 12 changed files with 101 additions and 28 deletions.
12 changes: 12 additions & 0 deletions src/backend/app/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,15 @@ class EventType(str, Enum):
UNLOCK = "unlock"
IMAGE_UPLOAD = "image_upload"
IMAGE_PROCESSING_START = "image_processing_start"


class FlightMode(str, Enum):
"""The flight mode of the drone.
The flight mode can be:
- ``waylines``
- ``waypoints``
"""

waylines = "waylines"
waypoints = "waypoints"
42 changes: 28 additions & 14 deletions src/backend/app/waypoints/waypoint_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
create_placemarks,
calculate_parameters,
add_elevation_from_dem,
terrain_following_waylines,
wpml,
waypoints,
)
from app.models.enums import HTTPStatus
from app.models.enums import HTTPStatus, FlightMode
from app.tasks.task_logic import (
get_task_geojson,
get_take_off_point_from_db,
Expand Down Expand Up @@ -48,6 +49,7 @@ async def get_task_waypoint(
project_id: uuid.UUID,
task_id: uuid.UUID,
download: bool = True,
mode: FlightMode = FlightMode.waylines,
take_off_point: waypoint_schemas.PointField = None,
):
"""
Expand Down Expand Up @@ -95,26 +97,13 @@ async def get_task_waypoint(

forward_overlap = project.front_overlap if project.front_overlap else 70
side_overlap = project.side_overlap if project.side_overlap else 70
generate_each_points = True if project.is_terrain_follow else False
generate_3d = (
False # TODO: For 3d imageries drone_flightplan package needs to be updated.
)

gsd = project.gsd_cm_px
altitude = project.altitude_from_ground

points = waypoints.create_waypoint(
project_area=task_geojson,
agl=altitude,
gsd=gsd,
forward_overlap=forward_overlap,
side_overlap=side_overlap,
rotation_angle=0,
generate_each_points=generate_each_points,
generate_3d=generate_3d,
take_off_point=take_off_point,
)

parameters = calculate_parameters(
forward_overlap,
side_overlap,
Expand All @@ -123,8 +112,25 @@ async def get_task_waypoint(
2, # Image Interval is set to 2
)

# Common parameters for create_waypoint
waypoint_params = {
"project_area": task_geojson,
"agl": altitude,
"gsd": gsd,
"forward_overlap": forward_overlap,
"side_overlap": side_overlap,
"rotation_angle": 0,
"generate_3d": generate_3d,
"take_off_point": take_off_point,
}

if project.is_terrain_follow:
dem_path = f"/tmp/{uuid.uuid4()}/dem.tif"

# Terrain follow uses waypoints mode, waylines are generated later
waypoint_params["mode"] = FlightMode.waypoints
points = waypoints.create_waypoint(**waypoint_params)

try:
get_file_from_bucket(
settings.S3_BUCKET_NAME,
Expand All @@ -142,8 +148,16 @@ async def get_task_waypoint(
points_with_elevation = points

placemarks = create_placemarks(geojson.loads(points_with_elevation), parameters)

# Create a flight plan with terrain follow in waylines mode
if mode == FlightMode.waylines:
placemarks = terrain_following_waylines.waypoints2waylines(placemarks, 5)

else:
waypoint_params["mode"] = mode
points = waypoints.create_waypoint(**waypoint_params)
placemarks = create_placemarks(geojson.loads(points), parameters)

if download:
outfile = outfile = f"/tmp/{uuid.uuid4()}"
kmz_file = wpml.create_wpml(placemarks, outfile)
Expand Down
2 changes: 1 addition & 1 deletion src/backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ dependencies = [
"pyodm>=1.5.11",
"asgiref>=3.8.1",
"bcrypt>=4.2.1",
"drone-flightplan>=0.3.2",
"drone-flightplan>=0.3.4rc2",
"Scrapy==2.12.0",
"asgi-lifespan>=2.1.0",
]
Expand Down
8 changes: 4 additions & 4 deletions src/backend/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions src/frontend/src/api/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query';
export const useGetTaskWaypointQuery = (
projectId: string,
taskId: string,
mode: string,
queryOptions?: Partial<UseQueryOptions>,
) => {
return useQuery({
queryKey: ['task-waypoints'],
queryKey: ['task-waypoints', mode],
enabled: !!(projectId && taskId),
queryFn: () => getTaskWaypoint(projectId, taskId),
queryFn: () => getTaskWaypoint(projectId, taskId, mode),
select: (res: any) => res.data,
...queryOptions,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,14 @@ const DescriptionBox = () => {
const uploadedImageType = useTypedSelector(
state => state.droneOperatorTask.uploadedImagesType,
);
const waypointMode = useTypedSelector(
state => state.droneOperatorTask.waypointMode,
);

const { data: taskWayPoints }: any = useGetTaskWaypointQuery(
projectId as string,
taskId as string,
waypointMode as string,
{
select: (data: any) => {
return data.data.features;
Expand Down Expand Up @@ -92,6 +96,7 @@ const DescriptionBox = () => {
const { data: flightTimeData }: any = useGetTaskWaypointQuery(
projectId as string,
taskId as string,
waypointMode as string,
{
select: ({ data }: any) => data.flight_data,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/* eslint-disable no-nested-ternary */
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import { useTypedSelector } from '@Store/hooks';
import { toast } from 'react-toastify';
import { useGetIndividualTaskQuery, useGetTaskWaypointQuery } from '@Api/tasks';
import { Button } from '@Components/RadixComponents/Button';
import useWindowDimensions from '@Hooks/useWindowDimensions';
import hasErrorBoundary from '@Utils/hasErrorBoundary';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import { toast } from 'react-toastify';
import MapSection from '../MapSection';
import DescriptionBox from './DescriptionBox';

Expand All @@ -17,13 +18,17 @@ const DroneOperatorDescriptionBox = () => {
useState<boolean>(false);
const { width } = useWindowDimensions();
const Token = localStorage.getItem('token');
const waypointMode = useTypedSelector(
state => state.droneOperatorTask.waypointMode,
);

const { data: taskDescription }: Record<string, any> =
useGetIndividualTaskQuery(taskId as string);

const { data: taskWayPoints }: any = useGetTaskWaypointQuery(
projectId as string,
taskId as string,
waypointMode as string,
{
select: (data: any) => {
return data.data?.results.features;
Expand All @@ -33,7 +38,7 @@ const DroneOperatorDescriptionBox = () => {

const downloadFlightPlanKmz = () => {
fetch(
`${BASE_URL}/waypoint/task/${taskId}/?project_id=${projectId}&download=true`,
`${BASE_URL}/waypoint/task/${taskId}/?project_id=${projectId}&download=true&mode=${waypointMode}`,
{ method: 'POST' },
)
.then(response => {
Expand All @@ -60,6 +65,7 @@ const DroneOperatorDescriptionBox = () => {

const downloadFlightPlanGeojson = () => {
if (!taskWayPoints) return;

const waypointGeojson = {
type: 'FeatureCollection',
features: taskWayPoints,
Expand All @@ -70,7 +76,7 @@ const DroneOperatorDescriptionBox = () => {
const url = window.URL.createObjectURL(fileBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'waypoint.geojson';
link.download = `${waypointMode}.geojson`;
document.body.appendChild(link);
link.click();
link.remove();
Expand Down
20 changes: 20 additions & 0 deletions src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
setSelectedTakeOffPoint,
setSelectedTakeOffPointOption,
setSelectedTaskDetailToViewOrthophoto,
setWaypointMode,
} from '@Store/actions/droneOperatorTask';
import { useTypedSelector } from '@Store/hooks';
import { useMutation, useQueryClient } from '@tanstack/react-query';
Expand All @@ -48,7 +49,9 @@ import { toast } from 'react-toastify';
import { FlexColumn } from '@Components/common/Layouts';
import RotatingCircle from '@Components/common/RotationCue';
import { mapLayerIDs } from '@Constants/droneOperator';
import SwitchTab from '@Components/common/SwitchTab';
import { findNearestCoordinate, swapFirstAndLast } from '@Utils/index';
import { waypointModeOptions } from '@Constants/taskDescription';
import GetCoordinatesOnClick from './GetCoordinatesOnClick';
import ShowInfo from './ShowInfo';

Expand Down Expand Up @@ -83,6 +86,9 @@ const MapSection = ({ className }: { className?: string }) => {
const newTakeOffPoint = useTypedSelector(
state => state.droneOperatorTask.selectedTakeOffPoint,
);
const waypointMode = useTypedSelector(
state => state.droneOperatorTask.waypointMode,
);

function setVisibilityOfLayers(layerIds: string[], visibility: string) {
layerIds.forEach(layerId => {
Expand Down Expand Up @@ -125,6 +131,7 @@ const MapSection = ({ className }: { className?: string }) => {
const { data: taskWayPointsData }: any = useGetTaskWaypointQuery(
projectId as string,
taskId as string,
waypointMode as string,
{
select: ({ data }: any) => {
const modifiedTaskWayPointsData = {
Expand Down Expand Up @@ -830,6 +837,19 @@ const MapSection = ({ className }: { className?: string }) => {
hideButton
getCoordOnProperties
/>

<div className="naxatw-absolute naxatw-right-3 naxatw-top-3 naxatw-z-40 lg:naxatw-right-64">
<SwitchTab
activeClassName="naxatw-bg-red naxatw-text-white"
options={waypointModeOptions}
labelKey="label"
valueKey="value"
selectedValue={waypointMode}
onChange={(value: Record<string, any>) => {
dispatch(setWaypointMode(value.value));
}}
/>
</div>
</MapContainer>
</div>
</>
Expand Down
5 changes: 5 additions & 0 deletions src/frontend/src/constants/taskDescription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ export const takeOffPointOptions = [
name: 'take_off_point',
},
];

export const waypointModeOptions = [
{ label: 'Waypoints', value: 'waypoints' },
{ label: 'Waylines', value: 'waylines' },
];
8 changes: 6 additions & 2 deletions src/frontend/src/services/tasks.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { api, authenticated } from '.';

export const getTaskWaypoint = (projectId: string, taskId: string) =>
export const getTaskWaypoint = (
projectId: string,
taskId: string,
mode: string,
) =>
authenticated(api).post(
`/waypoint/task/${taskId}/?project_id=${projectId}&download=false`,
`/waypoint/task/${taskId}/?project_id=${projectId}&download=false&mode=${mode}`,
);

export const getIndividualTask = (taskId: string) =>
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/store/actions/droneOperatorTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export const {
setSelectedTaskDetailToViewOrthophoto,
setFilesExifData,
resetFilesExifData,
setWaypointMode,
} = droneOperatorTaskSlice.actions;
5 changes: 5 additions & 0 deletions src/frontend/src/store/slices/droneOperartorTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface IDroneOperatorTaskState {
uploadedImagesType: 'add' | 'replace';
selectedTaskDetailToViewOrthophoto: any;
filesExifData: IFilesExifData[];
waypointMode: 'waypoints' | 'waylines';
}

const initialState: IDroneOperatorTaskState = {
Expand All @@ -32,6 +33,7 @@ const initialState: IDroneOperatorTaskState = {
uploadedImagesType: 'add',
selectedTaskDetailToViewOrthophoto: null,
filesExifData: [],
waypointMode: 'waypoints',
};

export const droneOperatorTaskSlice = createSlice({
Expand Down Expand Up @@ -90,6 +92,9 @@ export const droneOperatorTaskSlice = createSlice({
resetFilesExifData: state => {
state.filesExifData = [];
},
setWaypointMode: (state, action) => {
state.waypointMode = action.payload;
},
},
});

Expand Down

0 comments on commit d91fa3f

Please sign in to comment.