diff --git a/src/backend/app/waypoints/waypoint_routes.py b/src/backend/app/waypoints/waypoint_routes.py index 37b4a3ff..5de0c4f6 100644 --- a/src/backend/app/waypoints/waypoint_routes.py +++ b/src/backend/app/waypoints/waypoint_routes.py @@ -1,10 +1,14 @@ import uuid import geojson +import shutil from fastapi import APIRouter, UploadFile, File, Form, HTTPException from fastapi.responses import FileResponse from app.config import settings from drone_flightplan import flightplan, waypoints +from app.models.enums import HTTPStatus +# Constant to convert gsd to Altitude above ground level +GSD_to_AGL_CONST = 29.7 # For DJI Mini 4 Pro router = APIRouter( prefix=f"{settings.API_PREFIX}/waypoint", @@ -20,9 +24,13 @@ async def generate_kmz( description="The GeoJSON file representing the project area. This file will be used to define the boundaries and paths for the flight plan.", ), altitude: float = Form( - 80, + None, description="The altitude at which the drone should fly during the mission, in meters.", ), + gsd: float = Form( + None, + description="Excepted gsd value", + ), download: bool = Form( True, description="A flag indicating weather you want to download the kmz file or view the waypoint coordinates first. If True, file will be downloaded directly", @@ -43,35 +51,67 @@ async def generate_kmz( False, description="A flag indicating weather you want to generate 3D imageries or not (To generate 3D imagery, we need to click images at 3 different angles -90, -45 and lateral 45 degree angle)", ), + terrain_follow: bool = Form( + False, + description="A flag indicating weather you want to generate flight plan with terrain follow.", + ), + dem: UploadFile = File( + None, + 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", + ), ): - try: - boundary = geojson.loads(await project_geojson.read()) - - features = boundary["features"][0] + if not (altitude or gsd): + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail="Either altitude or gsd is required", + ) - if not download: - return waypoints.create_waypoint( - features, - altitude, - forward_overlap, - side_overlap, - generate_each_points, - generate_3d, + if terrain_follow: + if not dem: + raise HTTPException( + status_code=400, + detail="DEM file is required for terrain follow", ) - else: - output_file = flightplan.generate_flightplan( - features, - altitude, - forward_overlap, - side_overlap, - generate_each_points, - generate_3d, - f"/tmp/{uuid.uuid4()}", + if dem.content_type != "image/tiff": + raise HTTPException( + status_code=400, + detail="DEM file should be in GeoTIFF format", ) - return FileResponse( - output_file, media_type="application/zip", filename="output.kmz" - ) + dem_path = f"/tmp/{dem.filename}" + with open(dem_path, "wb") as buffer: + shutil.copyfileobj(dem.file, buffer) + + boundary = geojson.loads(await project_geojson.read()) + features = boundary["features"][0] + + if not download: + # TODO This should be fixed within the drone_flightplan + if gsd: + altitude = gsd * GSD_to_AGL_CONST + + return waypoints.create_waypoint( + features, + altitude, + forward_overlap, + side_overlap, + generate_each_points, + generate_3d, + ) + else: + output_file = flightplan.generate_flightplan( + features, + altitude, + gsd, + forward_overlap, + side_overlap, + generate_each_points, + generate_3d, + terrain_follow, + dem_path if dem else None, + f"/tmp/{uuid.uuid4()}", + ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) + return FileResponse( + output_file, media_type="application/zip", filename="output.kmz" + ) diff --git a/src/backend/pdm.lock b/src/backend/pdm.lock index 0aaeaea6..3afe890b 100644 --- a/src/backend/pdm.lock +++ b/src/backend/pdm.lock @@ -151,7 +151,7 @@ summary = "DNS toolkit" [[package]] name = "drone-flightplan" -version = "0.1.2" +version = "0.2.1" requires_python = ">=3.10" summary = "Generates an optimized flight plan for drones to conduct precise and efficient aerial mapping" dependencies = [ @@ -170,12 +170,6 @@ dependencies = [ "idna>=2.0.0", ] -[[package]] -name = "exceptiongroup" -version = "1.2.2" -requires_python = ">=3.7" -summary = "Backport of PEP 654 (exception groups)" - [[package]] name = "executing" version = "2.0.1" @@ -267,7 +261,9 @@ version = "8.14.0" requires_python = ">=3.9" summary = "IPython: Productive Interactive Computing" dependencies = [ + "appnope; sys_platform == \"darwin\"", "backcall", + "colorama; sys_platform == \"win32\"", "decorator", "jedi>=0.16", "matplotlib-inline", @@ -363,7 +359,7 @@ summary = "A generic, spec-compliant, thorough implementation of the OAuth reque [[package]] name = "osm-rawdata" -version = "0.3.0" +version = "0.3.1" requires_python = ">=3.10" summary = "Make data extracts from OSM data." dependencies = [ @@ -687,7 +683,7 @@ summary = "A small Python utility to set file creation time on Windows" lock_version = "4.2" cross_platform = true groups = ["default"] -content_hash = "sha256:60f25b3665070f83375848f64c8bfbe615df923a308fb0c00903db7508fcca17" +content_hash = "sha256:96e44ede40b8b6d3ce3559102c612d8b178d8099bf119a0484663968fee74a46" [metadata.files] "aiosmtplib 3.0.2" = [ @@ -985,18 +981,14 @@ content_hash = "sha256:60f25b3665070f83375848f64c8bfbe615df923a308fb0c00903db750 {url = "https://files.pythonhosted.org/packages/37/7d/c871f55054e403fdfd6b8f65fd6d1c4e147ed100d3e9f9ba1fe695403939/dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, {url = "https://files.pythonhosted.org/packages/87/a1/8c5287991ddb8d3e4662f71356d9656d91ab3a36618c3dd11b280df0d255/dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, ] -"drone-flightplan 0.1.2" = [ - {url = "https://files.pythonhosted.org/packages/22/ef/050af622b77845f0a741e1406e7b9daa22bf82468e57c266e56fdd2b5919/drone_flightplan-0.1.2-py3-none-any.whl", hash = "sha256:c3b7a7c0709a523e3ad4285f34e2dfb0e888d498dbf3ac3a2303cae9520f9727"}, - {url = "https://files.pythonhosted.org/packages/d3/3d/4801315a6ba14f049ec870bbdae3f97c97b77eb618957e962e311216f47c/drone_flightplan-0.1.2.tar.gz", hash = "sha256:8aa7419afe0022254afb97a82ca618bcdd38e4ea18b050d631434dd3b8bf356b"}, +"drone-flightplan 0.2.1" = [ + {url = "https://files.pythonhosted.org/packages/31/ae/75dd31b060ae999737c982fb0f2928e3fc08b2d1e71be57f1124c7d9214b/drone_flightplan-0.2.1-py3-none-any.whl", hash = "sha256:e5257eb43d706fe1d44a08a75476ed511d1bd5f5bc27d4c5129158f45d504a4d"}, + {url = "https://files.pythonhosted.org/packages/43/b2/7d5ef0e3b9744d545ef940e8db63ae7654dd4d2e88c6daef38cf75b79f50/drone_flightplan-0.2.1.tar.gz", hash = "sha256:e937961a5ac226603d374fe7b651cc7ebcb931dc35530ea8180b73fc77ac4a14"}, ] "email-validator 2.2.0" = [ {url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, {url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, ] -"exceptiongroup 1.2.2" = [ - {url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] "executing 2.0.1" = [ {url = "https://files.pythonhosted.org/packages/08/41/85d2d28466fca93737592b7f3cc456d1cfd6bcd401beceeba17e8e792b50/executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, {url = "https://files.pythonhosted.org/packages/80/03/6ea8b1b2a5ab40a7a60dc464d3daa7aa546e0a74d74a9f8ff551ea7905db/executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, @@ -1231,9 +1223,9 @@ content_hash = "sha256:60f25b3665070f83375848f64c8bfbe615df923a308fb0c00903db750 {url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, {url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, ] -"osm-rawdata 0.3.0" = [ - {url = "https://files.pythonhosted.org/packages/71/7c/62dec6aada73cf12f939edff1b5a3c540531b3cf1aa52ab0d1c1e3e772b1/osm-rawdata-0.3.0.tar.gz", hash = "sha256:62778c571a1a7d52a91357733deff5bc79466f0c5df5c809d77af7a3259ff7ed"}, - {url = "https://files.pythonhosted.org/packages/af/ac/7332d5eb43ac03a6572c04ff0b363a82f75916772579e1ed782927a3f315/osm_rawdata-0.3.0-py3-none-any.whl", hash = "sha256:e803e69ad2980502ebf13586302f51829fa2b1276079bdc3f924f187565076b9"}, +"osm-rawdata 0.3.1" = [ + {url = "https://files.pythonhosted.org/packages/d7/99/b0bf848a85e4dbc97efd8e2bb7a863c3332f598f3c4fa8ff0523d462445c/osm_rawdata-0.3.1-py3-none-any.whl", hash = "sha256:21ef255381610c05ff6628a2d30bd2e84c4538c7d7a7355530f706b2dbeeab9c"}, + {url = "https://files.pythonhosted.org/packages/ea/6f/ab20af2aa087969d404814da23027d67a9c0704ab90f1bbea6ba42fe6976/osm-rawdata-0.3.1.tar.gz", hash = "sha256:8714eebc2a774b8ab366caecfce608f30a18a11d3601db19a5d3c10dddcd4514"}, ] "packaging 24.1" = [ {url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index 87956836..26943fc5 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -40,7 +40,7 @@ dependencies = [ "GDAL==3.6.2", "aiosmtplib>=3.0.1", "python-slugify>=8.0.4", - "drone-flightplan>=0.1.2", + "drone-flightplan>=0.2.1", ] requires-python = ">=3.11" license = {text = "GPL-3.0-only"}