Skip to content

Commit

Permalink
fix: update the flight plan time & updated waypoints conuts if user t…
Browse files Browse the repository at this point in the history
…errian follow (#390)
  • Loading branch information
Pradip-p authored Dec 11, 2024
1 parent 43f6543 commit 858af11
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 7 deletions.
32 changes: 30 additions & 2 deletions src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import os
import shutil
import uuid
from typing import Annotated, Optional
from uuid import UUID
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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),
):
"""
Expand Down Expand Up @@ -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"]),
}
51 changes: 50 additions & 1 deletion src/backend/app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__)
Expand Down Expand Up @@ -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),
}
11 changes: 7 additions & 4 deletions src/backend/app/waypoints/waypoint_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,19 @@
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
from app.projects import project_deps
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

Expand Down Expand Up @@ -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("/")
Expand Down

0 comments on commit 858af11

Please sign in to comment.