Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25b0beb958 | ||
|
|
2e70f7bc2b | ||
|
|
90eec72d0e |
+14
-14
@@ -26,20 +26,20 @@ services:
|
|||||||
ALLOW_CORS_ORIGINS: "http://localhost:9000 http://localhost:9001 http://localhost:9101 http://localhost:3000"
|
ALLOW_CORS_ORIGINS: "http://localhost:9000 http://localhost:9001 http://localhost:9101 http://localhost:3000"
|
||||||
SENTRY_DSN: "https://184d1d6bd39f4fbb94804ae84e9afdd5@o472821.ingest.sentry.io/5506983"
|
SENTRY_DSN: "https://184d1d6bd39f4fbb94804ae84e9afdd5@o472821.ingest.sentry.io/5506983"
|
||||||
|
|
||||||
adminer:
|
# adminer:
|
||||||
image: adminer
|
# image: adminer
|
||||||
restart: always
|
# restart: always
|
||||||
ports:
|
# ports:
|
||||||
- 8080:8080
|
# - 8080:8080
|
||||||
|
#
|
||||||
scrapy:
|
# scrapy:
|
||||||
build:
|
# build:
|
||||||
context: ../crawlers
|
# context: ../crawlers
|
||||||
env_file:
|
# env_file:
|
||||||
- .env
|
# - .env
|
||||||
volumes:
|
# volumes:
|
||||||
- db-data:/var/lib/mysql:rw
|
# - db-data:/var/lib/mysql:rw
|
||||||
- ../crawlers:/app
|
# - ../crawlers:/app
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
db-data: { }
|
db-data: { }
|
||||||
|
|||||||
@@ -14,3 +14,4 @@ exif==1.6.0
|
|||||||
aiocache==0.12.2
|
aiocache==0.12.2
|
||||||
aiohttp==3.9.3
|
aiohttp==3.9.3
|
||||||
lxml==5.1.0
|
lxml==5.1.0
|
||||||
|
geopy==2.4.1
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -134,6 +134,7 @@ class Track(BaseModel):
|
|||||||
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
||||||
|
|
||||||
flight: Mapped['Flight'] = relationship()
|
flight: Mapped['Flight'] = relationship()
|
||||||
|
flight_plan: Mapped['FlightPlan'] = relationship()
|
||||||
# created_by: Mapped['User'] = relationship()
|
# created_by: Mapped['User'] = relationship()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -9,10 +9,10 @@ from utils.list_utils import chunks
|
|||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class ElevationInfo:
|
class ElevationInfo:
|
||||||
id: Optional[int]
|
|
||||||
lat: float
|
lat: float
|
||||||
lng: float
|
lng: float
|
||||||
elevation: float
|
elevation: float
|
||||||
|
id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class ElevationAPI:
|
class ElevationAPI:
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import asyncio
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from sqlalchemy import delete, select
|
from sqlalchemy import delete, select
|
||||||
from sqlalchemy.dialects.mysql import insert
|
from sqlalchemy.dialects.mysql import insert
|
||||||
|
|
||||||
|
from background_jobs.flight_plan_track import generate_track_for_flight_plan
|
||||||
from database import models
|
from database import models
|
||||||
from database.models import flight_plan_has_copilot
|
from database.models import flight_plan_has_copilot
|
||||||
from database.transaction import get_session
|
from database.transaction import get_session
|
||||||
@@ -10,6 +12,7 @@ from graphql_schema.entities.resolvers.base import BaseMutationResolver, BaseQue
|
|||||||
from graphql_schema.entities.resolvers.flight import handle_aircraft_save
|
from graphql_schema.entities.resolvers.flight import handle_aircraft_save
|
||||||
from graphql_schema.entities.types.mutation_input import CreateFlightPlanInput, EditFlightPlanInput
|
from graphql_schema.entities.types.mutation_input import CreateFlightPlanInput, EditFlightPlanInput
|
||||||
from graphql_schema.entities.types.types import FlightPlan
|
from graphql_schema.entities.types.types import FlightPlan
|
||||||
|
from utils.list_utils import evenly_spaced_elements
|
||||||
from utils.str_utils import random_str
|
from utils.str_utils import random_str
|
||||||
|
|
||||||
|
|
||||||
@@ -52,7 +55,6 @@ class FlightPlanMutationResolver(BaseMutationResolver):
|
|||||||
position = 0
|
position = 0
|
||||||
|
|
||||||
for marker in markers:
|
for marker in markers:
|
||||||
|
|
||||||
if marker.type == 'poi':
|
if marker.type == 'poi':
|
||||||
assert bool(marker.point_of_interest_id)
|
assert bool(marker.point_of_interest_id)
|
||||||
|
|
||||||
@@ -117,7 +119,10 @@ class FlightPlanMutationResolver(BaseMutationResolver):
|
|||||||
if data.markers:
|
if data.markers:
|
||||||
markers = data.markers
|
markers = data.markers
|
||||||
else:
|
else:
|
||||||
markers = (await db.scalars(select(models.FlightPlanMarker).filter(models.FlightPlanMarker.flight_plan_id == id))).all()
|
markers = (await db.scalars(
|
||||||
|
select(models.FlightPlanMarker)
|
||||||
|
.filter(models.FlightPlanMarker.flight_plan_id == id)
|
||||||
|
)).all()
|
||||||
|
|
||||||
used_markers = evenly_spaced_elements(markers, 5)
|
used_markers = evenly_spaced_elements(markers, 5)
|
||||||
input_data['name'] = " - ".join(m.name for m in used_markers)
|
input_data['name'] = " - ".join(m.name for m in used_markers)
|
||||||
@@ -131,11 +136,7 @@ class FlightPlanMutationResolver(BaseMutationResolver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
flight_plan = await self._do_update(db, obj=flight_plan_model, data=input_data)
|
flight_plan = await self._do_update(db, obj=flight_plan_model, data=input_data)
|
||||||
|
|
||||||
|
await generate_track_for_flight_plan(flight_plan.id)
|
||||||
|
|
||||||
return flight_plan
|
return flight_plan
|
||||||
|
|
||||||
|
|
||||||
def evenly_spaced_elements(lst: list, count: int) -> list:
|
|
||||||
if count > len(lst):
|
|
||||||
return lst
|
|
||||||
interval = (len(lst) - 1) / (count - 1) if count > 1 else 0
|
|
||||||
return [lst[int(round(i * interval))] for i in range(count)]
|
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ class FlightPlanMarker:
|
|||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class FlightPlanTrack:
|
class FlightPlanNavlog:
|
||||||
bearing: Optional[int]
|
bearing: Optional[int]
|
||||||
distance: Optional[float]
|
distance: Optional[float]
|
||||||
from_: FlightPlanMarker = strawberry.field(name="from")
|
from_: FlightPlanMarker = strawberry.field(name="from")
|
||||||
@@ -194,11 +194,11 @@ class FlightPlanTrack:
|
|||||||
|
|
||||||
@strawberry_sqlalchemy_type(model=models.FlightPlan)
|
@strawberry_sqlalchemy_type(model=models.FlightPlan)
|
||||||
class FlightPlan:
|
class FlightPlan:
|
||||||
async def resolve_track(self):
|
async def resolve_navlog(self):
|
||||||
markers = await self.markers()
|
markers = await self.markers()
|
||||||
|
|
||||||
if len(markers) == 1:
|
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 = []
|
navigation = []
|
||||||
for i in range(len(markers) - 1):
|
for i in range(len(markers) - 1):
|
||||||
@@ -208,7 +208,7 @@ class FlightPlan:
|
|||||||
point_from = (from_.gps_latitude, from_.gps_longitude)
|
point_from = (from_.gps_latitude, from_.gps_longitude)
|
||||||
point_to = (to.gps_latitude, to.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),
|
bearing=round(math.degrees(get_bearing(point_from, point_to)) % 360),
|
||||||
distance=round(get_distance(point_from, point_to), 1),
|
distance=round(get_distance(point_from, point_to), 1),
|
||||||
from_=from_,
|
from_=from_,
|
||||||
@@ -223,9 +223,8 @@ class FlightPlan:
|
|||||||
markers: list[FlightPlanMarker] = strawberry.field(
|
markers: list[FlightPlanMarker] = strawberry.field(
|
||||||
resolver=lambda root: flight_plan_markers_dataloader.load(root.id)
|
resolver=lambda root: flight_plan_markers_dataloader.load(root.id)
|
||||||
)
|
)
|
||||||
track: Optional[list[FlightPlanTrack]] = strawberry.field(
|
navlog: Optional[list[FlightPlanNavlog]] = strawberry.field(resolver=resolve_navlog)
|
||||||
resolver=resolve_track
|
track: Optional[Track] = strawberry.field(resolver=lambda root: track_dataloader.load(root.track_id))
|
||||||
)
|
|
||||||
copilots: List[Copilot] = strawberry.field(resolver=load_copilots)
|
copilots: List[Copilot] = strawberry.field(resolver=load_copilots)
|
||||||
aircraft: Optional[Aircraft] = strawberry.field(resolver=lambda root: aircraft_dataloader.load(root.aircraft_id))
|
aircraft: Optional[Aircraft] = strawberry.field(resolver=lambda root: aircraft_dataloader.load(root.aircraft_id))
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
import math
|
import math
|
||||||
|
|
||||||
|
from geopy import Point
|
||||||
|
from geopy.distance import geodesic
|
||||||
|
|
||||||
EARTH_RADIUS = 6378.1 # Radius of the Earth
|
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:
|
def gps_to_decimal(input: Tuple[float, float, float]) -> float:
|
||||||
d, m, s = input
|
d, m, s = input
|
||||||
return d + (m / 60.0) + (s / 3600.0)
|
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
|
||||||
|
|||||||
@@ -12,3 +12,10 @@ def chunks(items: list | dict, n: int) -> typing.Generator:
|
|||||||
it = iter(items)
|
it = iter(items)
|
||||||
for i in range(0, len(items), n):
|
for i in range(0, len(items), n):
|
||||||
yield {k: items[k] for k in islice(it, n)}
|
yield {k: items[k] for k in islice(it, n)}
|
||||||
|
|
||||||
|
|
||||||
|
def evenly_spaced_elements(lst: list, count: int) -> list:
|
||||||
|
if count > len(lst):
|
||||||
|
return lst
|
||||||
|
interval = (len(lst) - 1) / (count - 1) if count > 1 else 0
|
||||||
|
return [lst[int(round(i * interval))] for i in range(count)]
|
||||||
|
|||||||
Reference in New Issue
Block a user