diff --git a/docker-compose.yml b/docker-compose.yml index 18b4daa..bdfcc2f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,20 +26,20 @@ services: ALLOW_CORS_ORIGINS: "http://localhost:9000 http://localhost:9001 http://localhost:9101 http://localhost:3000" SENTRY_DSN: "https://184d1d6bd39f4fbb94804ae84e9afdd5@o472821.ingest.sentry.io/5506983" - adminer: - image: adminer - restart: always - ports: - - 8080:8080 - - scrapy: - build: - context: ../crawlers - env_file: - - .env - volumes: - - db-data:/var/lib/mysql:rw - - ../crawlers:/app +# adminer: +# image: adminer +# restart: always +# ports: +# - 8080:8080 +# +# scrapy: +# build: +# context: ../crawlers +# env_file: +# - .env +# volumes: +# - db-data:/var/lib/mysql:rw +# - ../crawlers:/app volumes: db-data: { } diff --git a/requirements.txt b/requirements.txt index 288f252..60af559 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ exif==1.6.0 aiocache==0.12.2 aiohttp==3.9.3 lxml==5.1.0 +geopy==2.4.1 diff --git a/src/background_jobs/flight_plan_track.py b/src/background_jobs/flight_plan_track.py new file mode 100644 index 0000000..7eab8d8 --- /dev/null +++ b/src/background_jobs/flight_plan_track.py @@ -0,0 +1,57 @@ +import math +from datetime import datetime, timedelta + +from sqlalchemy import select, delete +from database import models +from database.transaction import get_session +from utils.gps import generate_points, get_bearing + + +async def generate_track_for_flight_plan(flight_plan_id: int): + async with (get_session() as db): + flight_plan = await models.FlightPlan.get_one(db, id=flight_plan_id) + + turn_points = ( + await db.scalars( + select(models.FlightPlanMarker) + .filter(models.FlightPlanMarker.flight_plan_id == flight_plan_id) + )).all() + + if len(turn_points) == 1: + return + + if flight_plan.track_id: + track = await models.Track.get_one(db, id=flight_plan.track_id) + await db.execute( + delete(models.TrackPoint) + .filter(models.TrackPoint.track_id == track.id) + ) + else: + track = await models.Track.create(db, {"bounds": [], "created_by_id": flight_plan.created_by_id}) + flight_plan.track_id = track.id + + point_ts = flight_plan.planned_takeoff_datetime or datetime.now() + track_points_resolution_meters = 500 + + speed_m_s = flight_plan.planned_speed / 3.6 + delta_t_sec = track_points_resolution_meters / speed_m_s + + for i in range(len(turn_points) - 1): + from_ = turn_points[i] + to = turn_points[i + 1] + + point_from = (from_.gps_latitude, from_.gps_longitude) + point_to = (to.gps_latitude, to.gps_longitude) + points_between = generate_points(point_from, point_to) + + print(delta_t_sec, speed_m_s, "AAAAAAAAAAAA") + + for point in points_between: + await models.TrackPoint.create(db, { + "track_id": track.id, + "timestamp": point_ts, + "gps_latitude": point.latitude, + "gps_longitude": point.longitude, + }) + + point_ts += timedelta(seconds=delta_t_sec) diff --git a/src/database/models.py b/src/database/models.py index 5414773..975299f 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -134,6 +134,7 @@ class Track(BaseModel): created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) flight: Mapped['Flight'] = relationship() + flight_plan: Mapped['FlightPlan'] = relationship() # created_by: Mapped['User'] = relationship() diff --git a/src/external/elevation.py b/src/external/elevation.py index a0e5bc8..e3d4fa1 100644 --- a/src/external/elevation.py +++ b/src/external/elevation.py @@ -9,10 +9,10 @@ from utils.list_utils import chunks @dataclasses.dataclass class ElevationInfo: - id: Optional[int] lat: float lng: float elevation: float + id: Optional[int] = None class ElevationAPI: diff --git a/src/graphql_schema/entities/resolvers/flight_plan.py b/src/graphql_schema/entities/resolvers/flight_plan.py index 4b69c67..7f85628 100644 --- a/src/graphql_schema/entities/resolvers/flight_plan.py +++ b/src/graphql_schema/entities/resolvers/flight_plan.py @@ -52,7 +52,6 @@ class FlightPlanMutationResolver(BaseMutationResolver): position = 0 for marker in markers: - if marker.type == 'poi': assert bool(marker.point_of_interest_id) @@ -71,6 +70,8 @@ class FlightPlanMutationResolver(BaseMutationResolver): }) position += 1 + + @staticmethod async def reset_plan_markers(db, flight_plan: models.FlightPlan): await db.execute( diff --git a/src/graphql_schema/entities/types/types.py b/src/graphql_schema/entities/types/types.py index 30473fe..937a77b 100644 --- a/src/graphql_schema/entities/types/types.py +++ b/src/graphql_schema/entities/types/types.py @@ -185,7 +185,7 @@ class FlightPlanMarker: @strawberry.type -class FlightPlanTrack: +class FlightPlanNavlog: bearing: Optional[int] distance: Optional[float] from_: FlightPlanMarker = strawberry.field(name="from") @@ -194,11 +194,11 @@ class FlightPlanTrack: @strawberry_sqlalchemy_type(model=models.FlightPlan) class FlightPlan: - async def resolve_track(self): + async def resolve_navlog(self): markers = await self.markers() if len(markers) == 1: - return [FlightPlanTrack(bearing=None, distance=None, from_=markers[0], to=None)] + return [FlightPlanNavlog(bearing=None, distance=None, from_=markers[0], to=None)] navigation = [] for i in range(len(markers) - 1): @@ -208,7 +208,7 @@ class FlightPlan: point_from = (from_.gps_latitude, from_.gps_longitude) point_to = (to.gps_latitude, to.gps_longitude) - navigation.append(FlightPlanTrack( + navigation.append(FlightPlanNavlog( bearing=round(math.degrees(get_bearing(point_from, point_to)) % 360), distance=round(get_distance(point_from, point_to), 1), from_=from_, @@ -223,9 +223,8 @@ class FlightPlan: markers: list[FlightPlanMarker] = strawberry.field( resolver=lambda root: flight_plan_markers_dataloader.load(root.id) ) - track: Optional[list[FlightPlanTrack]] = strawberry.field( - resolver=resolve_track - ) + navlog: Optional[list[FlightPlanNavlog]] = strawberry.field(resolver=resolve_navlog) + track: Optional[Track] = strawberry.field(resolver=lambda root: track_dataloader.load(root.track_id)) copilots: List[Copilot] = strawberry.field(resolver=load_copilots) aircraft: Optional[Aircraft] = strawberry.field(resolver=lambda root: aircraft_dataloader.load(root.aircraft_id)) diff --git a/src/scripts/elevation.py b/src/scripts/elevation.py index 8009366..fbe48a6 100644 --- a/src/scripts/elevation.py +++ b/src/scripts/elevation.py @@ -44,6 +44,8 @@ async def add_elevation_to_tracks(): print("all done") return await update_track_points_elevation(db, track_points) + await db.flush() + await db.commit() async def run_all(): diff --git a/src/scripts/generate_track_for_plans.py b/src/scripts/generate_track_for_plans.py new file mode 100644 index 0000000..5dbdf6e --- /dev/null +++ b/src/scripts/generate_track_for_plans.py @@ -0,0 +1,15 @@ +import asyncio +import sys + +sys.path.insert(0, "/app/src") + +from background_jobs.flight_plan_track import generate_track_for_flight_plan + + +async def run_all(): + await generate_track_for_flight_plan(11) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(run_all()) diff --git a/src/utils/gps.py b/src/utils/gps.py index c7dea04..360370f 100644 --- a/src/utils/gps.py +++ b/src/utils/gps.py @@ -1,6 +1,9 @@ from typing import Tuple import math +from geopy import Point +from geopy.distance import geodesic + EARTH_RADIUS = 6378.1 # Radius of the Earth @@ -38,3 +41,23 @@ def get_distance(a: tuple[float, float], b: tuple[float, float]): def gps_to_decimal(input: Tuple[float, float, float]) -> float: d, m, s = input return d + (m / 60.0) + (s / 3600.0) + + +def generate_points(start: tuple[float, float], end: tuple[float, float], interval_meters: int = 500): + start = Point(start[0], start[1]) + end = Point(end[0], end[1]) + + total_distance = geodesic(start, end).meters + num_intervals = int(total_distance // interval_meters) + + points = [start] + + for i in range(1, num_intervals + 1): + # Calculate intermediate point + intermediate_distance = interval_meters * i + intermediate_point = geodesic(meters=intermediate_distance).destination(start, get_bearing(start, end)) + points.append(intermediate_point) + + points.append(end) + + return points