From cee5de003f457c461ac1c07e160f0dcca7603c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Kv=C3=A1=C4=8Dek?= Date: Mon, 15 Apr 2024 18:13:37 +0200 Subject: [PATCH] Zakladni verze planovace --- src/external/gps_utils.py | 34 ++++++++++++++ src/{ => external}/openair_parser.py | 47 +++---------------- src/graphql_schema/entities/flight_plan.py | 5 +- .../entities/resolvers/flight_plan.py | 45 +++++++++++++----- src/graphql_schema/entities/types/types.py | 32 +++++++++++++ 5 files changed, 109 insertions(+), 54 deletions(-) create mode 100644 src/external/gps_utils.py rename src/{ => external}/openair_parser.py (85%) diff --git a/src/external/gps_utils.py b/src/external/gps_utils.py new file mode 100644 index 0000000..0d53b6f --- /dev/null +++ b/src/external/gps_utils.py @@ -0,0 +1,34 @@ +import math + +EARTH_RADIUS = 6378.1 # Radius of the Earth + + +def get_bearing(center_point: tuple[float, float], end_point: tuple[float, float]): + lat3 = math.radians(end_point[0]) + long3 = math.radians(end_point[1]) + lat1 = math.radians(center_point[0]) + long1 = math.radians(center_point[1]) + + dLon = long3 - long1 + + X = math.cos(lat3) * math.sin(dLon) + Y = math.cos(lat1) * math.sin(lat3) - math.sin(lat1) * math.cos(lat3) * math.cos(dLon) + + end_brng = math.atan2(X, Y) + + return end_brng + + +def get_distance(a: tuple[float, float], b: tuple[float, float]): + lat1 = math.radians(a[0]) + lon1 = math.radians(a[1]) + lat2 = math.radians(b[0]) + lon2 = math.radians(b[1]) + + dlon = lon2 - lon1 + dlat = lat2 - lat1 + + a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2 + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) + + return EARTH_RADIUS * c diff --git a/src/openair_parser.py b/src/external/openair_parser.py similarity index 85% rename from src/openair_parser.py rename to src/external/openair_parser.py index 1b86868..11ff9c7 100644 --- a/src/openair_parser.py +++ b/src/external/openair_parser.py @@ -8,6 +8,7 @@ import aiohttp from sqlalchemy import delete from database import models from database.transaction import get_session +from external.gps_utils import get_distance, get_bearing, EARTH_RADIUS # https://pyopenair.readthedocs.io/en/latest/openair.html @@ -57,40 +58,6 @@ class AirspaceItem: class ArcExpander: - R = 6378.1 # Radius of the Earth - - @staticmethod - def get_bearing(center_point, end_point): - lat3 = math.radians(end_point[0]) - long3 = math.radians(end_point[1]) - lat1 = math.radians(center_point[0]) - long1 = math.radians(center_point[1]) - - dLon = long3 - long1 - - X = math.cos(lat3) * math.sin(dLon) - Y = math.cos(lat1) * math.sin(lat3) - math.sin(lat1) * math.cos(lat3) * math.cos(dLon) - - end_brng = math.atan2(X, Y) - - return end_brng - - @classmethod - def get_distance(cls, a, b): - - lat1 = math.radians(a[0]) - lon1 = math.radians(a[1]) - lat2 = math.radians(b[0]) - lon2 = math.radians(b[1]) - - dlon = lon2 - lon1 - dlat = lat2 - lat1 - - a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2 - c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) - - return cls.R * c - @classmethod def get_arc_coordinates( cls, @@ -98,9 +65,9 @@ class ArcExpander: origin_point: tuple[float, float], end_point: tuple[float, float] ) -> list[Coordinates]: - initial_bearing = cls.get_bearing(center_point, origin_point) - target_bearing = cls.get_bearing(center_point, end_point) - distance = cls.get_distance(center_point, origin_point) + initial_bearing = get_bearing(center_point, origin_point) + target_bearing = get_bearing(center_point, end_point) + distance = get_distance(center_point, origin_point) print(f"DISTANCE {distance} meters, {center_point=}, {origin_point=}, {end_point=}") @@ -122,12 +89,12 @@ class ArcExpander: for bearing in list_bearings: lat1, lon1 = math.radians(center_point[0]), math.radians(center_point[1]) lat2 = math.asin( - math.sin(lat1) * math.cos(distance / cls.R) + math.cos(lat1) * math.sin(distance / cls.R) * math.cos(bearing) + math.sin(lat1) * math.cos(distance / EARTH_RADIUS) + math.cos(lat1) * math.sin(distance / EARTH_RADIUS) * math.cos(bearing) ) lon2 = lon1 + math.atan2( - math.sin(bearing) * math.sin(distance / cls.R) * math.cos(lat1), - math.cos(distance / cls.R) - math.sin(lat1) * math.sin(lat2) + math.sin(bearing) * math.sin(distance / EARTH_RADIUS) * math.cos(lat1), + math.cos(distance / EARTH_RADIUS) - math.sin(lat1) * math.sin(lat2) ) lat2, lon2 = math.degrees(lat2), math.degrees(lon2) diff --git a/src/graphql_schema/entities/flight_plan.py b/src/graphql_schema/entities/flight_plan.py index 53fd228..6a754d7 100644 --- a/src/graphql_schema/entities/flight_plan.py +++ b/src/graphql_schema/entities/flight_plan.py @@ -1,11 +1,10 @@ -from typing import List, Optional +from typing import List import strawberry -from graphql import GraphQLError from strawberry.types import Info from database import models from decorators.error_logging import error_logging from decorators.endpoints import authenticated_user_only, allow_public -from graphql_schema.entities.resolvers.base import BaseMutationResolver, BaseQueryResolver +from graphql_schema.entities.resolvers.base import BaseQueryResolver from graphql_schema.entities.resolvers.flight_plan import FlightPlanMutationResolver from graphql_schema.entities.types.mutation_input import CreateFlightPlanInput, EditFlightPlanInput from graphql_schema.entities.types.types import FlightPlan diff --git a/src/graphql_schema/entities/resolvers/flight_plan.py b/src/graphql_schema/entities/resolvers/flight_plan.py index c64cfe3..a141568 100644 --- a/src/graphql_schema/entities/resolvers/flight_plan.py +++ b/src/graphql_schema/entities/resolvers/flight_plan.py @@ -1,7 +1,8 @@ +from sqlalchemy import delete from database import models from database.transaction import get_session from graphql_schema.entities.resolvers.base import BaseMutationResolver -from graphql_schema.entities.types.mutation_input import CreateFlightPlanInput +from graphql_schema.entities.types.mutation_input import CreateFlightPlanInput, EditFlightPlanInput from graphql_schema.entities.types.types import FlightPlan @@ -9,22 +10,44 @@ class FlightPlanMutationResolver(BaseMutationResolver): def __init__(self): super().__init__(graphql_type=FlightPlan, model=models.FlightPlan) + @staticmethod + async def save_markers(db, flight_plan: models.FlightPlan, markers: list): + position = 0 + + for marker in markers: + await models.FlightPlanMarker.create(db, data={ + "position": position, + "flight_plan_id": flight_plan.id, + "type": marker.type, + "name": marker.name, + "gps_latitude": marker.gps_latitude, + "gps_longitude": marker.gps_longitude + }) + position += 1 + + @staticmethod + async def reset_plan_markers(db, flight_plan: models.FlightPlan): + await db.execute( + delete(models.FlightPlanMarker) + .filter(models.FlightPlanMarker.flight_plan_id == flight_plan.id) + ) + async def create(self, context, data: CreateFlightPlanInput) -> FlightPlan: input_data = data.to_dict() input_data['created_by_id'] = context.user_id async with get_session() as db: flight_plan = await self._do_create(db, data=input_data) + await self.save_markers(db, flight_plan, data.markers) + return flight_plan - position = 0 - for marker in data.markers: - await models.FlightPlanMarker.create(db, data={ - "position": position, - "flight_plan_id": flight_plan.id, - "type": marker.type, - "name": marker.name, - "gps_latitude": marker.gps_latitude, - "gps_longitude": marker.gps_longitude - }) + async def update(self, id: int, data: EditFlightPlanInput, user_id: int) -> FlightPlan: + input_data = data.to_dict() + + async with get_session() as db: + flight_plan = await self._do_update(db, id, input_data) + + await self.reset_plan_markers(db, flight_plan) + await self.save_markers(db, flight_plan, data.markers) return flight_plan diff --git a/src/graphql_schema/entities/types/types.py b/src/graphql_schema/entities/types/types.py index 7fb9610..e6db576 100644 --- a/src/graphql_schema/entities/types/types.py +++ b/src/graphql_schema/entities/types/types.py @@ -1,9 +1,12 @@ from __future__ import annotations + +import math from datetime import datetime from typing import Optional, List import strawberry from database import models from decorators.endpoints import authenticated_user_only +from external.gps_utils import get_bearing, get_distance from external.gpx_parser import GPXParser from graphql_schema.dataloaders.flight_duration import flight_duration_dataloader from graphql_schema.dataloaders.multi_models import ( @@ -185,11 +188,40 @@ class FlightPlanMarker: pass +@strawberry.type +class FlightPlanTrack: + bearing: int + distance: float + from_: FlightPlanMarker = strawberry.field(name="from") + to: FlightPlanMarker + + @strawberry_sqlalchemy_type(model=models.FlightPlan) class FlightPlan: + async def resolve_track(self): + markers = await self.markers() + navigation = [] + for i in range(len(markers) - 1): + from_ = markers[i] + to = markers[i + 1] + + point_from = (from_.gps_latitude, from_.gps_longitude) + point_to = (to.gps_latitude, to.gps_longitude) + + navigation.append(FlightPlanTrack( + bearing=round(math.degrees(get_bearing(point_from, point_to)) % 360), + distance=round(get_distance(point_from, point_to), 1), + from_=from_, + to=to) + ) + return navigation + markers: list[FlightPlanMarker] = strawberry.field( resolver=lambda root: flight_plan_markers_dataloader.load(root.id) ) + track: Optional[list[FlightPlanTrack]] = strawberry.field( + resolver=resolve_track + ) @strawberry_sqlalchemy_type(models.Copilot)