From e176604928f750d6e7148c1e368838c7e2adda66 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Tue, 10 Dec 2024 12:57:42 +0545 Subject: [PATCH] fix: update the flight plan time & updated waypoints conuts if user terrian follow --- src/backend/app/projects/project_routes.py | 32 +++++++++++- src/backend/app/utils.py | 51 +++++++++++++++++++- src/backend/app/waypoints/waypoint_routes.py | 11 +++-- 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index a3872075..e6799b32 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -1,5 +1,6 @@ import json import os +import shutil import uuid from typing import Annotated, Optional from uuid import UUID @@ -39,7 +40,7 @@ ) from app.users import user_schemas from minio.deleteobjects import DeleteObject -from drone_flightplan import waypoints +from drone_flightplan import waypoints, add_elevation_from_dem router = APIRouter( prefix=f"{settings.API_PREFIX}/projects", @@ -668,6 +669,7 @@ async def get_project_waypoints_counts( meters: float = 100, project_geojson: UploadFile = File(...), is_terrain_follow: bool = False, + dem: UploadFile = File(None), user_data: AuthUser = Depends(login_required), ): """ @@ -708,6 +710,32 @@ async def get_project_waypoints_counts( generate_3d=generate_3d, take_off_point=None, ) + + # Handle terrain-following logic if a DEM is provided + points_with_elevation = points + if is_terrain_follow and dem: + temp_dir = f"/tmp/{uuid.uuid4()}" + try: + os.makedirs(temp_dir, exist_ok=True) + dem_path = os.path.join(temp_dir, "dem.tif") + outfile_with_elevation = os.path.join( + temp_dir, "output_file_with_elevation.geojson" + ) + + with open(dem_path, "wb") as dem_file: + dem_file.write(await dem.read()) + + add_elevation_from_dem(dem_path, points, outfile_with_elevation) + + with open(outfile_with_elevation, "r") as inpointsfile: + points_with_elevation = inpointsfile.read() + except Exception as e: + log.error(f"Error processing DEM: {e}") + + finally: + if os.path.exists(temp_dir): + shutil.rmtree(temp_dir) + return { - "avg_no_of_waypoints": len(json.loads(points)["features"]), + "avg_no_of_waypoints": len(json.loads(points_with_elevation)["features"]), } diff --git a/src/backend/app/utils.py b/src/backend/app/utils.py index bcc54154..41212919 100644 --- a/src/backend/app/utils.py +++ b/src/backend/app/utils.py @@ -5,7 +5,7 @@ import json import base64 from datetime import datetime, timezone -from typing import Optional, Union, Any +from typing import Dict, Optional, Union, Any from geojson_pydantic import Feature, MultiPolygon, Polygon from geojson_pydantic import FeatureCollection as FeatCol from geoalchemy2 import WKBElement @@ -21,6 +21,9 @@ from email.mime.text import MIMEText from email.utils import formataddr from aiosmtplib import send as send_email +from shapely.geometry import Point +from shapely.ops import transform +from pyproj import Transformer log = logging.getLogger(__name__) @@ -551,3 +554,49 @@ async def send_project_approval_email_to_regulator( subject="Project Review Request for Drone Operations Approval", html_content=html_content, ) + + +def calculate_flight_time_from_placemarks(placemarks: Dict) -> Dict: + """ + Calculate the total and average flight time based on placemarks and dynamically format the output. + + Args: + placemarks (Dict): GeoJSON-like data structure with flight plan. + + Returns: + Dict: Contains formatted total flight time and segment times. + """ + total_time = 0 + features = placemarks["features"] + transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True) + for i in range(1, len(features)): + # Extract current and previous coordinates + prev_coords = features[i - 1]["geometry"]["coordinates"][:2] + curr_coords = features[i]["geometry"]["coordinates"][:2] + speed = features[i]["properties"]["speed"] # Speed in m/s + + # Create Shapely Points and transform to planar coordinates for distance calculation + prev_point = Point(transform(transformer.transform, Point(prev_coords))) + curr_point = Point(transform(transformer.transform, Point(curr_coords))) + + # Calculate distance (meters) and time (seconds) + distance = prev_point.distance(curr_point) + segment_time = distance / speed + total_time += segment_time + + # Dynamically format the total flight time + hours = int(total_time // 3600) + minutes = int((total_time % 3600) // 60) + seconds = round(total_time % 60, 2) + + if total_time < 60: + formatted_time = f"{seconds} seconds" + elif total_time < 3600: + formatted_time = f"{minutes} minutes {seconds:.2f} seconds" + else: + formatted_time = f"{hours} hours {minutes} minutes {seconds:.2f} seconds" + + return { + "total_flight_time": formatted_time, + "total_flight_time_seconds": round(total_time, 2), + } diff --git a/src/backend/app/waypoints/waypoint_routes.py b/src/backend/app/waypoints/waypoint_routes.py index fab6f6ba..4613e08c 100644 --- a/src/backend/app/waypoints/waypoint_routes.py +++ b/src/backend/app/waypoints/waypoint_routes.py @@ -18,9 +18,11 @@ get_take_off_point_from_db, update_take_off_point_in_db, ) -from app.waypoints.waypoint_logic import check_point_within_buffer +from app.waypoints.waypoint_logic import ( + check_point_within_buffer, +) from app.db import database -from app.utils import merge_multipolygon +from app.utils import calculate_flight_time_from_placemarks, merge_multipolygon from app.s3 import get_file_from_bucket from typing import Annotated from psycopg import Connection @@ -28,6 +30,7 @@ from shapely.geometry import shape from app.waypoints import waypoint_schemas + # Constant to convert gsd to Altitude above ground level GSD_to_AGL_CONST = 29.7 # For DJI Mini 4 Pro @@ -146,8 +149,8 @@ async def get_task_waypoint( return FileResponse( kmz_file, media_type="application/zip", filename="flight_plan.kmz" ) - - return placemarks + flight_data = calculate_flight_time_from_placemarks(placemarks) + return {"results": placemarks, "flight_data": flight_data} @router.post("/")