Skip to content

Update support for DJI air 3 drones #413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/backend/app/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,9 @@ class HTTPStatus(IntEnum):
NOT_IMPLEMENTED = 501


class DroneType(IntEnum):
DJI_MINI_4_PRO = 1
class DroneType(StrEnum):
DJI_MINI_4_PRO = "DJI_MINI_4_PRO"
DJI_AIR_3 = "DJI_AIR_3"


class UserRole(int, Enum):
Expand Down
15 changes: 12 additions & 3 deletions src/backend/app/waypoints/waypoint_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@

from app.config import settings
from app.db import database
from app.models.enums import FlightMode, HTTPStatus
from app.models.enums import FlightMode, HTTPStatus, DroneType
from app.projects import project_deps
from app.s3 import get_file_from_bucket
from app.tasks.task_logic import (
get_take_off_point_from_db,
get_task_geojson,
update_take_off_point_in_db,
)
from app.utils import merge_multipolygon
from app.utils import merge_multipolygon, calculate_flight_time_from_placemarks
from app.waypoints import waypoint_schemas
from app.waypoints.waypoint_logic import (
check_point_within_buffer,
Expand All @@ -56,6 +56,7 @@ async def get_task_waypoint(
download: bool = True,
mode: FlightMode = FlightMode.waylines,
rotation_angle: float = 0,
drone_type: DroneType = DroneType.DJI_MINI_4_PRO,
take_off_point: waypoint_schemas.PointField = None,
):
"""Retrieve task waypoints and download a flight plan.
Expand Down Expand Up @@ -115,6 +116,7 @@ async def get_task_waypoint(
altitude,
gsd,
2, # Image Interval is set to 2
drone_type,
)

# Common parameters for create_waypoint
Expand All @@ -127,6 +129,7 @@ async def get_task_waypoint(
"rotation_angle": rotation_angle,
"generate_3d": generate_3d,
"take_off_point": take_off_point,
"drone_type": drone_type,
}

if project.is_terrain_follow:
Expand Down Expand Up @@ -175,7 +178,10 @@ async def get_task_waypoint(
media_type="application/vnd.google-earth.kmz",
filename=f"{task_id}_flight_plan.kmz",
)
return {"results": placemarks}
flight_data = calculate_flight_time_from_placemarks(placemarks)

drones = list(DroneType.__members__.keys())
return {"results": placemarks, "flight_data": flight_data, "drones": drones}


@router.post("/")
Expand Down Expand Up @@ -221,6 +227,7 @@ async def generate_kmz(
description="The Digital Elevation Model (DEM) file that will be used to generate the terrain follow flight plan. This file should be in GeoTIFF format",
),
take_off_point: waypoint_schemas.PointField = None,
drone_type: DroneType = DroneType.DJI_MINI_4_PRO,
):
if not (altitude or gsd):
raise HTTPException(
Expand Down Expand Up @@ -264,6 +271,7 @@ async def generate_kmz(
generate_each_points=generate_each_points,
generate_3d=generate_3d,
take_off_point=take_off_point,
drone_type=drone_type,
)
return geojson.loads(points)
else:
Expand All @@ -277,6 +285,7 @@ async def generate_kmz(
dem=dem_path if dem else None,
outfile=f"/tmp/{uuid.uuid4()}",
take_off_point=take_off_point,
drone_type=drone_type,
)

return FileResponse(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import argparse
import logging
from drone_flightplan.drone_type import DroneType

# Configure logging
logging.basicConfig(level=logging.INFO, format="%(message)s")
Expand All @@ -12,6 +13,7 @@ def calculate_parameters(
agl: float,
gsd: float = None,
image_interval: int = 2,
drone_type: DroneType = DroneType.DJI_MINI_4_PRO,
):
"""Parameters
---------------------------------
Expand All @@ -33,10 +35,11 @@ def calculate_parameters(
ground speed = forward spacing / image interval = 10

"""
# Constants ( For DJI Mini 4 Pro)
VERTICAL_FOV = 0.71
HORIZONTAL_FOV = 1.26
GSD_to_AGL_CONST = 29.7
# Get the drone specifications from the Enum
drone_specs = drone_type.value
VERTICAL_FOV = drone_specs["VERTICAL_FOV"]
HORIZONTAL_FOV = drone_specs["HORIZONTAL_FOV"]
GSD_to_AGL_CONST = drone_specs["GSD_to_AGL_CONST"]

if gsd:
agl = gsd * GSD_to_AGL_CONST
Expand Down Expand Up @@ -74,6 +77,12 @@ def main():

group = parser.add_mutually_exclusive_group(required=True)

parser.add_argument(
"--drone_type",
type=lambda dt: DroneType[dt.upper()],
default=DroneType.DJI_MINI_4_PRO,
help="The type of drone to use, e.g., DJI_MINI_4_PRO.",
)
group.add_argument(
"--altitude_above_ground_level",
type=float,
Expand Down Expand Up @@ -112,6 +121,7 @@ def main():
args.altitude_above_ground_level,
args.gsd,
args.image_interval,
args.drone_type,
)

for key, value in results.items():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from drone_flightplan.create_placemarks import create_placemarks
from drone_flightplan.waypoints import create_waypoint
from drone_flightplan.wpml import create_wpml
from drone_flightplan.drone_type import DroneType

# Instantiate logger
log = logging.getLogger(__name__)
Expand All @@ -27,6 +28,7 @@ def create_flightplan(
generate_each_points: bool = False,
rotation_angle: float = 0.0,
take_off_point: list[float] = None,
drone_type: DroneType = DroneType.DJI_MINI_4_PRO,
):
"""Arguments:
aoi: The area of interest in GeoJSON format.
Expand All @@ -41,7 +43,12 @@ def create_flightplan(
Drone Flightplan in kmz format
"""
parameters = calculate_parameters(
forward_overlap, side_overlap, agl, gsd, image_interval
forward_overlap,
side_overlap,
agl,
gsd,
image_interval,
drone_type,
)

waypoints = create_waypoint(
Expand All @@ -53,6 +60,7 @@ def create_flightplan(
rotation_angle,
generate_each_points,
take_off_point=take_off_point,
drone_type=drone_type,
)

# Add elevation data to the waypoints
Expand Down Expand Up @@ -110,6 +118,12 @@ def main():
type=float,
help="The ground sampling distance in cm/px.",
)
parser.add_argument(
"--drone_type",
type=lambda dt: DroneType[dt.upper()],
default=DroneType.DJI_MINI_4_PRO,
help="The type of drone to use, e.g., DJI_MINI_4_PRO.",
)

parser.add_argument(
"--forward_overlap",
Expand Down Expand Up @@ -167,6 +181,7 @@ def main():
args.generate_each_points,
args.rotation_angle,
args.take_off_point,
args.drone_type,
)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from enum import Enum


class DroneType(int, Enum):
DJI_MINI_4_PRO = {
# Constants (For DJI Mini 4 Pro)
"VERTICAL_FOV": 0.71,
"HORIZONTAL_FOV": 1.26,
"GSD_to_AGL_CONST": 29.7,
}

DJI_AIR_3 = {
# Constants (For DJI Air 3)
# Vertical Field of View (FOV):
# Formula: Vertical FOV = 2 * tan^(-1)(sensor height / 2 * focal length)
# The vertical FOV for DJI Air 3 is calculated as: 2 * tan^(-1)(6.3 / (2 * 19.4)) β‰ˆ 0.3224 radians
"VERTICAL_FOV": 0.3224,
# Horizontal Field of View (FOV):
# Formula: Horizontal FOV = 2 * tan^(-1)(sensor width / 2 * focal length)
# The horizontal FOV for DJI Air 3 is calculated as: 2 * tan^(-1)(8.4 / (2 * 19.4)) β‰ˆ 0.4269 radians
"HORIZONTAL_FOV": 0.4269,
# Ground Sampling Distance constant (GSD to AGL constant):
# GSD = (sensor height / focal length) * AGL
# This constant helps in determining the ground resolution per pixel at a given altitude (AGL).
"GSD_to_AGL_CONST": 30.5,
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from shapely.ops import transform

from drone_flightplan.calculate_parameters import calculate_parameters as cp
from drone_flightplan.drone_type import DroneType

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -388,6 +389,7 @@ def create_waypoint(
no_fly_zones: dict = None,
take_off_point: list[float] = None,
mode: str = "waylines",
drone_type: DroneType = DroneType.DJI_MINI_4_PRO,
) -> str:
"""Create waypoints or waylines for a given project area based on specified parameters.

Expand Down Expand Up @@ -429,7 +431,7 @@ def create_waypoint(
}

"""
parameters = cp(forward_overlap, side_overlap, agl, gsd)
parameters = cp(forward_overlap, side_overlap, agl, gsd, drone_type)
side_spacing = parameters["side_spacing"]
forward_spacing = parameters["forward_spacing"]

Expand Down