Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62e46f37ce | ||
|
|
a706303155 | ||
|
|
c8138860ca | ||
|
|
c2b066d371 | ||
|
|
cee5de003f | ||
|
|
5f0a1da052 | ||
|
|
63e2eed8c2 | ||
|
|
01f8a616bf | ||
|
|
24825108a7 |
@@ -0,0 +1,38 @@
|
||||
"""add flight_plan table
|
||||
|
||||
Revision ID: 7a3738a805c5
|
||||
Revises: aba4c3876b28
|
||||
Create Date: 2024-03-21 16:42:08.428875
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '7a3738a805c5'
|
||||
down_revision = 'aba4c3876b28'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('flight_plan',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=128), nullable=False),
|
||||
sa.Column('markers', mysql.JSON(), nullable=False),
|
||||
sa.Column('is_public', sa.Boolean(), server_default='0', nullable=False),
|
||||
sa.Column('created_by_id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False),
|
||||
sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('flight_plan')
|
||||
# ### end Alembic commands ###
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
"""make markers in flight planner separate table
|
||||
|
||||
Revision ID: de70fc471699
|
||||
Revises: 7a3738a805c5
|
||||
Create Date: 2024-03-24 21:55:12.307588
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'de70fc471699'
|
||||
down_revision = '7a3738a805c5'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('flight_plan_marker',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('flight_plan_id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=128), nullable=False),
|
||||
sa.Column('gps_latitude', sa.Float(), nullable=True),
|
||||
sa.Column('gps_longitude', sa.Float(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['flight_plan_id'], ['flight_plan.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.drop_column('flight_plan', 'markers')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('flight_plan', sa.Column('markers', mysql.LONGTEXT(charset='utf8mb4', collation='utf8mb4_bin'), nullable=False))
|
||||
op.drop_table('flight_plan_marker')
|
||||
# ### end Alembic commands ###
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
"""add type and position to flight_plan_marker
|
||||
|
||||
Revision ID: 70152c47cb49
|
||||
Revises: de70fc471699
|
||||
Create Date: 2024-03-24 22:00:35.234920
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '70152c47cb49'
|
||||
down_revision = 'de70fc471699'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('flight_plan_marker', sa.Column('type', sa.String(length=64), nullable=False))
|
||||
op.add_column('flight_plan_marker', sa.Column('position', sa.Integer(), nullable=False))
|
||||
op.alter_column('flight_plan_marker', 'gps_latitude',
|
||||
existing_type=mysql.FLOAT(),
|
||||
nullable=False)
|
||||
op.alter_column('flight_plan_marker', 'gps_longitude',
|
||||
existing_type=mysql.FLOAT(),
|
||||
nullable=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column('flight_plan_marker', 'gps_longitude',
|
||||
existing_type=mysql.FLOAT(),
|
||||
nullable=True)
|
||||
op.alter_column('flight_plan_marker', 'gps_latitude',
|
||||
existing_type=mysql.FLOAT(),
|
||||
nullable=True)
|
||||
op.drop_column('flight_plan_marker', 'position')
|
||||
op.drop_column('flight_plan_marker', 'type')
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,116 @@
|
||||
"""reworked airspace and airport db structure
|
||||
|
||||
Revision ID: cbdb018c5490
|
||||
Revises: 70152c47cb49
|
||||
Create Date: 2024-04-21 22:32:50.058166
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'cbdb018c5490'
|
||||
down_revision = '70152c47cb49'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('airspace',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('country', sa.String(length=8), nullable=False),
|
||||
sa.Column('name', sa.String(length=128), nullable=False),
|
||||
sa.Column('type', sa.String(length=8), nullable=False),
|
||||
sa.Column('lower_limit', sa.String(length=32), nullable=False),
|
||||
sa.Column('upper_limit', sa.String(length=32), nullable=False),
|
||||
sa.Column('center', mysql.JSON(), nullable=False),
|
||||
sa.Column('radius', sa.Float(), nullable=True),
|
||||
sa.Column('bounds', mysql.JSON(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('name')
|
||||
)
|
||||
op.create_index(op.f('ix_airspace_country'), 'airspace', ['country'], unique=False)
|
||||
op.create_index(op.f('ix_airspace_type'), 'airspace', ['type'], unique=False)
|
||||
op.create_table('frequency',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('airport_id', sa.Integer(), nullable=True),
|
||||
sa.Column('airspace_id', sa.Integer(), nullable=True),
|
||||
sa.Column('frequency', sa.String(length=7), nullable=False),
|
||||
sa.Column('name', sa.String(length=64), nullable=False),
|
||||
sa.ForeignKeyConstraint(['airport_id'], ['airport.id'], ),
|
||||
sa.ForeignKeyConstraint(['airspace_id'], ['airspace.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('runway',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('airport_id', sa.Integer(), nullable=False),
|
||||
sa.Column('heading', sa.Integer(), nullable=False),
|
||||
sa.Column('designator', sa.String(length=4), nullable=False),
|
||||
sa.Column('surface', sa.Enum('asphalt', 'concrete', 'grass', 'other'), server_default='other', nullable=False),
|
||||
sa.Column('length', sa.Integer(), nullable=True),
|
||||
sa.Column('width', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['airport_id'], ['airport.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.drop_table('metar')
|
||||
op.drop_table('license')
|
||||
op.drop_index('ix_airspace_item_country', table_name='airspace_item')
|
||||
op.drop_index('ix_airspace_item_type', table_name='airspace_item')
|
||||
op.drop_index('name', table_name='airspace_item')
|
||||
op.drop_table('airspace_item')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('airspace_item',
|
||||
sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False),
|
||||
sa.Column('country', mysql.VARCHAR(length=8), nullable=False),
|
||||
sa.Column('name', mysql.VARCHAR(length=128), nullable=False),
|
||||
sa.Column('type', mysql.VARCHAR(length=8), nullable=False),
|
||||
sa.Column('lower_limit', mysql.VARCHAR(length=32), nullable=False),
|
||||
sa.Column('upper_limit', mysql.VARCHAR(length=32), nullable=False),
|
||||
sa.Column('center', mysql.LONGTEXT(charset='utf8mb4', collation='utf8mb4_bin'), nullable=False),
|
||||
sa.Column('radius', mysql.FLOAT(), nullable=True),
|
||||
sa.Column('bounds', mysql.LONGTEXT(charset='utf8mb4', collation='utf8mb4_bin'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
mysql_collate='utf8mb4_general_ci',
|
||||
mysql_default_charset='utf8mb4',
|
||||
mysql_engine='InnoDB'
|
||||
)
|
||||
op.create_index('name', 'airspace_item', ['name'], unique=True)
|
||||
op.create_index('ix_airspace_item_type', 'airspace_item', ['type'], unique=False)
|
||||
op.create_index('ix_airspace_item_country', 'airspace_item', ['country'], unique=False)
|
||||
op.create_table('license',
|
||||
sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False),
|
||||
sa.Column('name', mysql.VARCHAR(length=128), nullable=False),
|
||||
sa.Column('number', mysql.VARCHAR(length=30), nullable=False),
|
||||
sa.Column('valid_until', mysql.DATETIME(), nullable=False),
|
||||
sa.Column('created_by_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False),
|
||||
sa.Column('created_at', mysql.DATETIME(), server_default=sa.text('current_timestamp()'), nullable=False),
|
||||
sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], name='license_ibfk_1'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
mysql_collate='utf8mb4_general_ci',
|
||||
mysql_default_charset='utf8mb4',
|
||||
mysql_engine='InnoDB'
|
||||
)
|
||||
op.create_table('metar',
|
||||
sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False),
|
||||
sa.Column('airport_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False),
|
||||
sa.Column('metar', mysql.TEXT(), nullable=False),
|
||||
sa.Column('issued_at', mysql.DATETIME(), nullable=False),
|
||||
sa.Column('deleted', mysql.TINYINT(display_width=1), server_default=sa.text('0'), autoincrement=False, nullable=False),
|
||||
sa.ForeignKeyConstraint(['airport_id'], ['airport.id'], name='metar_ibfk_1'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
mysql_collate='utf8mb4_general_ci',
|
||||
mysql_default_charset='utf8mb4',
|
||||
mysql_engine='InnoDB'
|
||||
)
|
||||
op.drop_table('runway')
|
||||
op.drop_table('frequency')
|
||||
op.drop_index(op.f('ix_airspace_type'), table_name='airspace')
|
||||
op.drop_index(op.f('ix_airspace_country'), table_name='airspace')
|
||||
op.drop_table('airspace')
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,32 @@
|
||||
"""remove old airspace columns, add altitude unit
|
||||
|
||||
Revision ID: e07a5c0f1dcc
|
||||
Revises: cbdb018c5490
|
||||
Create Date: 2024-04-22 23:42:52.140389
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'e07a5c0f1dcc'
|
||||
down_revision = 'cbdb018c5490'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('airspace', sa.Column('altitude_unit', sa.String(length=8), nullable=False))
|
||||
op.drop_column('airspace', 'center')
|
||||
op.drop_column('airspace', 'radius')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('airspace', sa.Column('radius', mysql.FLOAT(), nullable=True))
|
||||
op.add_column('airspace', sa.Column('center', mysql.LONGTEXT(charset='utf8mb4', collation='utf8mb4_bin'), nullable=False))
|
||||
op.drop_column('airspace', 'altitude_unit')
|
||||
# ### end Alembic commands ###
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
"""add alt unit for lower/upper limits in airspaces
|
||||
|
||||
Revision ID: 2012eca075c7
|
||||
Revises: e07a5c0f1dcc
|
||||
Create Date: 2024-04-22 23:49:17.725111
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '2012eca075c7'
|
||||
down_revision = 'e07a5c0f1dcc'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('airspace', sa.Column('lower_alt_unit', sa.String(length=8), nullable=False))
|
||||
op.add_column('airspace', sa.Column('upper_alt_unit', sa.String(length=8), nullable=False))
|
||||
op.drop_column('airspace', 'altitude_unit')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('airspace', sa.Column('altitude_unit', mysql.VARCHAR(length=8), nullable=False))
|
||||
op.drop_column('airspace', 'upper_alt_unit')
|
||||
op.drop_column('airspace', 'lower_alt_unit')
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,45 @@
|
||||
"""add table for reporting points
|
||||
|
||||
Revision ID: 03ba4dfea911
|
||||
Revises: 2012eca075c7
|
||||
Create Date: 2024-04-23 21:39:18.254033
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '03ba4dfea911'
|
||||
down_revision = '2012eca075c7'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('reporting_point',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('openaip_id', sa.String(length=32), nullable=False),
|
||||
sa.Column('name', sa.String(length=64), nullable=False),
|
||||
sa.Column('gps_latitude', sa.Float(), nullable=True),
|
||||
sa.Column('gps_longitude', sa.Float(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('airport_has_reporting_point',
|
||||
sa.Column('airport_id', sa.Integer(), nullable=False),
|
||||
sa.Column('reporting_point_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['airport_id'], ['airport.id'], ),
|
||||
sa.ForeignKeyConstraint(['reporting_point_id'], ['reporting_point.id'], ),
|
||||
sa.PrimaryKeyConstraint('airport_id', 'reporting_point_id')
|
||||
)
|
||||
op.add_column('airport', sa.Column('openaip_id', sa.String(length=32), nullable=False))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('airport', 'openaip_id')
|
||||
op.drop_table('airport_has_reporting_point')
|
||||
op.drop_table('reporting_point')
|
||||
# ### end Alembic commands ###
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
"""remove obsolete airport columns
|
||||
|
||||
Revision ID: f7ab4bdac987
|
||||
Revises: 03ba4dfea911
|
||||
Create Date: 2024-04-24 06:24:48.219726
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f7ab4bdac987'
|
||||
down_revision = '03ba4dfea911'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('airport', 'deleted')
|
||||
op.drop_column('airport', 'is_public')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('airport', sa.Column('is_public', mysql.TINYINT(display_width=1), server_default=sa.text('0'), autoincrement=False, nullable=False))
|
||||
op.add_column('airport', sa.Column('deleted', mysql.TINYINT(display_width=1), server_default=sa.text('0'), autoincrement=False, nullable=False))
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,56 @@
|
||||
"""add flight plan meta information
|
||||
|
||||
Revision ID: 790ba00fc021
|
||||
Revises: f7ab4bdac987
|
||||
Create Date: 2024-04-29 16:40:09.829317
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '790ba00fc021'
|
||||
down_revision = 'f7ab4bdac987'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('flight_plan_has_copilot',
|
||||
sa.Column('flight_plan_id', sa.Integer(), nullable=False),
|
||||
sa.Column('copilot_id', sa.Integer(), nullable=False),
|
||||
sa.Column('token', sa.String(length=64), nullable=True),
|
||||
sa.ForeignKeyConstraint(['copilot_id'], ['copilot.id'], ),
|
||||
sa.ForeignKeyConstraint(['flight_plan_id'], ['flight_plan.id'], ),
|
||||
sa.PrimaryKeyConstraint('flight_plan_id', 'copilot_id'),
|
||||
sa.UniqueConstraint('token')
|
||||
)
|
||||
op.add_column('flight_plan', sa.Column('is_default_name', sa.Boolean(), server_default='0', nullable=False))
|
||||
op.add_column('flight_plan', sa.Column('is_editable_by_copilot', sa.Boolean(), server_default='0', nullable=False))
|
||||
op.add_column('flight_plan', sa.Column('planned_speed', sa.Integer(), nullable=False))
|
||||
op.add_column('flight_plan', sa.Column('planned_takeoff_datetime', sa.DateTime(), nullable=True))
|
||||
op.add_column('flight_plan', sa.Column('aircraft_id', sa.Integer(), nullable=True))
|
||||
op.alter_column('flight_plan', 'name',
|
||||
existing_type=mysql.VARCHAR(length=128),
|
||||
type_=sa.String(length=256),
|
||||
existing_nullable=False)
|
||||
op.create_foreign_key(None, 'flight_plan', 'aircraft', ['aircraft_id'], ['id'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, 'flight_plan', type_='foreignkey')
|
||||
op.alter_column('flight_plan', 'name',
|
||||
existing_type=sa.String(length=256),
|
||||
type_=mysql.VARCHAR(length=128),
|
||||
existing_nullable=False)
|
||||
op.drop_column('flight_plan', 'aircraft_id')
|
||||
op.drop_column('flight_plan', 'planned_takeoff_datetime')
|
||||
op.drop_column('flight_plan', 'planned_speed')
|
||||
op.drop_column('flight_plan', 'is_editable_by_copilot')
|
||||
op.drop_column('flight_plan', 'is_default_name')
|
||||
op.drop_table('flight_plan_has_copilot')
|
||||
# ### end Alembic commands ###
|
||||
@@ -7,7 +7,7 @@ services:
|
||||
network_mode: "host"
|
||||
restart: "always"
|
||||
env_file:
|
||||
- .env
|
||||
- ../.env
|
||||
environment:
|
||||
SENTRY_DSN: "https://184d1d6bd39f4fbb94804ae84e9afdd5@o472821.ingest.sentry.io/5506983"
|
||||
volumes:
|
||||
|
||||
+4
-9
@@ -3,10 +3,8 @@ services:
|
||||
image: mariadb:10.11
|
||||
ports:
|
||||
- 3306:3306
|
||||
environment:
|
||||
- MYSQL_USER=root
|
||||
- MYSQL_ROOT_PASSWORD=root
|
||||
- MYSQL_DATABASE=ull_tracker
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
# - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
||||
- db-data:/var/lib/mysql:rw
|
||||
@@ -37,11 +35,8 @@ services:
|
||||
scrapy:
|
||||
build:
|
||||
context: ../crawlers
|
||||
environment:
|
||||
- MYSQL_USER=root
|
||||
- MYSQL_PASSWORD=root
|
||||
- MYSQL_DATABASE=ull_tracker
|
||||
- MYSQL_HOST=db
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- db-data:/var/lib/mysql:rw
|
||||
- ../crawlers:/app
|
||||
|
||||
@@ -8,7 +8,7 @@ from paths import FLIGHT_GPX_TRACK_PATH
|
||||
|
||||
async def add_terrain_elevation_to_flight(flight_id: int, gpx_filename: str):
|
||||
gpx_parser = GPXParser(f"{FLIGHT_GPX_TRACK_PATH}/{gpx_filename}")
|
||||
coordinates = await gpx_parser.get_coordinates()
|
||||
coordinates = await gpx_parser.get_coordinates_all()
|
||||
|
||||
try:
|
||||
elevation = await elevation_api.get_elevation_for_points(coordinates)
|
||||
|
||||
+87
-34
@@ -59,6 +59,14 @@ flight_has_copilot = Table(
|
||||
Column("token", String(64), unique=True)
|
||||
)
|
||||
|
||||
flight_plan_has_copilot = Table(
|
||||
"flight_plan_has_copilot",
|
||||
BaseModel.metadata,
|
||||
Column("flight_plan_id", ForeignKey("flight_plan.id"), primary_key=True),
|
||||
Column("copilot_id", ForeignKey("copilot.id"), primary_key=True),
|
||||
Column("token", String(64), unique=True)
|
||||
)
|
||||
|
||||
copilot_has_photo = Table(
|
||||
"copilot_has_photo",
|
||||
BaseModel.metadata,
|
||||
@@ -67,40 +75,111 @@ copilot_has_photo = Table(
|
||||
)
|
||||
|
||||
|
||||
class AirspaceItem(BaseModel):
|
||||
__tablename__ = "airspace_item"
|
||||
airport_has_reporting_point = Table(
|
||||
"airport_has_reporting_point",
|
||||
BaseModel.metadata,
|
||||
Column("airport_id", ForeignKey("airport.id"), primary_key=True),
|
||||
Column("reporting_point_id", ForeignKey("reporting_point.id"), primary_key=True),
|
||||
)
|
||||
|
||||
|
||||
class Airspace(BaseModel):
|
||||
__tablename__ = "airspace"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
country: Mapped[str] = mapped_column(String(8), nullable=False, index=True)
|
||||
name: Mapped[str] = mapped_column(String(128), nullable=False, unique=True)
|
||||
type: Mapped[str] = mapped_column(String(8), nullable=False, index=True)
|
||||
lower_limit: Mapped[str] = mapped_column(String(32), nullable=False)
|
||||
lower_alt_unit: Mapped[str] = mapped_column(String(8), nullable=False)
|
||||
upper_limit: Mapped[str] = mapped_column(String(32), nullable=False)
|
||||
center: Mapped[List[dict]] = mapped_column(JSON())
|
||||
radius: Mapped[float] = mapped_column(Float, nullable=True)
|
||||
upper_alt_unit: Mapped[str] = mapped_column(String(8), nullable=False)
|
||||
bounds: Mapped[list[tuple[float, float]]] = mapped_column(JSON())
|
||||
|
||||
|
||||
class FlightPlan(BaseModel):
|
||||
__tablename__ = "flight_plan"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(256), nullable=False)
|
||||
is_default_name: Mapped[bool] = mapped_column(Boolean, server_default='0')
|
||||
is_public: Mapped[bool] = mapped_column(Boolean, server_default='0')
|
||||
is_editable_by_copilot: Mapped[bool] = mapped_column(Boolean, server_default='0')
|
||||
planned_speed: Mapped[int] = mapped_column(Integer, nullable=False, default=150)
|
||||
planned_takeoff_datetime: Mapped[datetime] = mapped_column(DateTime, nullable=True)
|
||||
aircraft_id: Mapped[int] = mapped_column(Integer, ForeignKey("aircraft.id"), nullable=True)
|
||||
created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id'))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
||||
deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='0')
|
||||
|
||||
copilots: Mapped[List['Copilot']] = relationship(secondary=flight_plan_has_copilot)
|
||||
aircraft: Mapped['Aircraft'] = relationship()
|
||||
markers: Mapped['FlightPlanMarker'] = relationship()
|
||||
created_by: Mapped['User'] = relationship()
|
||||
|
||||
|
||||
class FlightPlanMarker(BaseModel):
|
||||
__tablename__ = "flight_plan_marker"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
flight_plan_id: Mapped[int] = mapped_column(Integer, ForeignKey('flight_plan.id'))
|
||||
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
||||
type: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
position: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
gps_latitude: Mapped[float] = mapped_column(Float, nullable=False)
|
||||
gps_longitude: Mapped[float] = mapped_column(Float, nullable=False)
|
||||
|
||||
|
||||
class Airport(BaseModel):
|
||||
__tablename__ = "airport"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
||||
openaip_id: Mapped[str] = mapped_column(String(32), nullable=False, unique=True)
|
||||
icao_code: Mapped[str] = mapped_column(String(8), nullable=False)
|
||||
gps_latitude: Mapped[float] = mapped_column(Float, nullable=True)
|
||||
gps_longitude: Mapped[float] = mapped_column(Float, nullable=True)
|
||||
elevation: Mapped[int] = mapped_column(Integer, nullable=True)
|
||||
airport_type: Mapped[str] = mapped_column(Enum("airport", "ull", "heliport"), nullable=False, server_default='airport') # noqa
|
||||
use_in_gpx_guess: Mapped[bool] = mapped_column(Boolean, server_default='1')
|
||||
is_public: Mapped[bool] = mapped_column(Boolean, server_default='0')
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
||||
created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id'), nullable=True) # automaticky import nebude mit ID # noqa
|
||||
deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='0')
|
||||
|
||||
metars: Mapped['Metar'] = relationship()
|
||||
created_by: Mapped['User'] = relationship()
|
||||
|
||||
|
||||
class Runway(BaseModel):
|
||||
__tablename__ = "runway"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
airport_id: Mapped[int] = mapped_column(Integer, ForeignKey('airport.id'))
|
||||
heading: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
designator: Mapped[str] = mapped_column(String(4), nullable=False)
|
||||
surface: Mapped[str] = mapped_column(Enum("asphalt", "concrete", "grass", "other"), nullable=False, server_default="other") # noqa
|
||||
length: Mapped[int] = mapped_column(Integer, nullable=True)
|
||||
width: Mapped[int] = mapped_column(Integer, nullable=True)
|
||||
|
||||
|
||||
class Frequency(BaseModel):
|
||||
__tablename__ = "frequency"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
airport_id: Mapped[int] = mapped_column(Integer, ForeignKey('airport.id'), nullable=True)
|
||||
airspace_id: Mapped[int] = mapped_column(Integer, ForeignKey('airspace.id'), nullable=True)
|
||||
frequency: Mapped[str] = mapped_column(String(7), nullable=False)
|
||||
name: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
|
||||
|
||||
class ReportingPoint(BaseModel):
|
||||
__tablename__ = "reporting_point"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
openaip_id: Mapped[str] = mapped_column(String(32), nullable=False)
|
||||
name: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
gps_latitude: Mapped[float] = mapped_column(Float, nullable=True)
|
||||
gps_longitude: Mapped[float] = mapped_column(Float, nullable=True)
|
||||
|
||||
|
||||
class PointOfInterestType(BaseModel):
|
||||
__tablename__ = "point_of_interest_type"
|
||||
|
||||
@@ -340,37 +419,12 @@ class Copilot(BaseModel):
|
||||
deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='0')
|
||||
|
||||
flights: Mapped[Set['Flight']] = relationship(secondary=flight_has_copilot)
|
||||
flight_plans: Mapped[Set['FlightPlan']] = relationship(secondary=flight_plan_has_copilot)
|
||||
created_by: Mapped['User'] = relationship()
|
||||
photos: Mapped[List['Photo']] = relationship(secondary=copilot_has_photo)
|
||||
title_photo: Mapped['Photo'] = relationship(foreign_keys=[title_photo_id])
|
||||
|
||||
|
||||
class Metar(BaseModel):
|
||||
__tablename__ = "metar"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
airport_id: Mapped[int] = mapped_column(Integer, ForeignKey('airport.id'))
|
||||
metar: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
issued_at: Mapped[datetime] = mapped_column(DateTime)
|
||||
deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='0')
|
||||
|
||||
airport: Mapped['Airport'] = relationship()
|
||||
|
||||
|
||||
class License(BaseModel):
|
||||
__tablename__ = "license"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
||||
number: Mapped[str] = mapped_column(String(30), nullable=False)
|
||||
valid_until: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
||||
created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id'))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
||||
|
||||
user: Mapped['User'] = relationship()
|
||||
created_by: Mapped['User'] = relationship()
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
__tablename__ = "user"
|
||||
excluded_columns_in_dict = ('password_hashed',)
|
||||
@@ -385,7 +439,6 @@ class User(BaseModel):
|
||||
password_hashed: Mapped[str] = mapped_column(String(60), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
||||
|
||||
licences: Mapped[Set['License']] = relationship()
|
||||
flights: Mapped[Set['Flight']] = relationship()
|
||||
organizations: Mapped[Set['Organization']] = relationship(secondary=user_is_in_organization)
|
||||
|
||||
|
||||
Vendored
+34
@@ -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
|
||||
Vendored
+10
-3
@@ -80,10 +80,12 @@ class GPXParser:
|
||||
|
||||
@cached()
|
||||
async def get_coordinates(self) -> List[Dict[str, float]]:
|
||||
return await self.average_coordinates(await self.get_coordinates_all())
|
||||
|
||||
@cached()
|
||||
async def get_coordinates_all(self) -> List[Dict[str, float]]:
|
||||
nodes = self.run_xpath("//gpx:trkpt")
|
||||
return await self.average_coordinates(
|
||||
[{"lat": float(node.attrib["lat"]), "lng": float(node.attrib['lon'])} for node in nodes]
|
||||
)
|
||||
return [{"lat": float(node.attrib["lat"]), "lng": float(node.attrib['lon'])} for node in nodes]
|
||||
|
||||
@cached()
|
||||
async def get_speed(self) -> List[float]:
|
||||
@@ -141,7 +143,12 @@ class GPXParser:
|
||||
continue
|
||||
|
||||
extensions = target_node.find("./extensions", self.gpx.getroot().nsmap)
|
||||
if extensions is not None:
|
||||
extensions.append(etree.XML(f"<terrain_elevation>{elevation}</terrain_elevation>"))
|
||||
else:
|
||||
target_node.append(
|
||||
etree.XML(f"<extensions><terrain_elevation>{elevation}</terrain_elevation></extensions>")
|
||||
)
|
||||
|
||||
return self.gpx
|
||||
|
||||
|
||||
+19
-52
@@ -8,12 +8,13 @@ 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
|
||||
# http://www.winpilot.com/usersguide/userairspace.asp
|
||||
|
||||
class AirspaceItemType(enum.Enum):
|
||||
class AirspaceType(enum.Enum):
|
||||
AIRSPACE_A = "A"
|
||||
AIRSPACE_B = "B"
|
||||
AIRSPACE_C = "C"
|
||||
@@ -46,8 +47,8 @@ class Coordinates:
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class AirspaceItem:
|
||||
type: AirspaceItemType
|
||||
class Airspace:
|
||||
type: AirspaceType
|
||||
info: str = ""
|
||||
upper_limit: str = None
|
||||
lower_limit: str = None
|
||||
@@ -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)
|
||||
@@ -140,7 +107,7 @@ class OpenAirParser:
|
||||
def __init__(self, file: Path | str):
|
||||
self.file = None
|
||||
|
||||
self.context_type: Optional[AirspaceItemType] = None
|
||||
self.context_type: Optional[AirspaceType] = None
|
||||
self.context = {}
|
||||
|
||||
self.file = file
|
||||
@@ -149,7 +116,7 @@ class OpenAirParser:
|
||||
if not self.context_type:
|
||||
return
|
||||
|
||||
data = AirspaceItem(type=self.context_type, **self.context)
|
||||
data = Airspace(type=self.context_type, **self.context)
|
||||
|
||||
self.context_type = None
|
||||
self.context = {}
|
||||
@@ -227,19 +194,19 @@ class OpenAirParser:
|
||||
pass
|
||||
case 'AC':
|
||||
try:
|
||||
self.context_type = AirspaceItemType(value)
|
||||
self.context_type = AirspaceType(value)
|
||||
except ValueError as e:
|
||||
print(f"Neznam {e}, ignoruji")
|
||||
return
|
||||
case 'AN':
|
||||
if self.context_type == AirspaceItemType.AIRSPACE_G:
|
||||
if self.context_type == AirspaceType.AIRSPACE_G:
|
||||
match value[0:2]:
|
||||
case 'PG':
|
||||
self.context_type = AirspaceItemType.PARAGLIDING_ZONE
|
||||
self.context_type = AirspaceType.PARAGLIDING_ZONE
|
||||
case 'LK':
|
||||
self.context_type = AirspaceItemType.ATZ
|
||||
self.context_type = AirspaceType.ATZ
|
||||
case _:
|
||||
self.context_type = AirspaceItemType.TRAGA
|
||||
self.context_type = AirspaceType.TRAGA
|
||||
|
||||
self.context['info'] = value
|
||||
case 'AL':
|
||||
@@ -282,7 +249,7 @@ async def download_airspace(source, destination):
|
||||
async def save_airspace(destination):
|
||||
parser = OpenAirParser(destination)
|
||||
async with get_session() as db:
|
||||
await db.execute(delete(models.AirspaceItem))
|
||||
await db.execute(delete(models.Airspace))
|
||||
|
||||
for item in parser.parse():
|
||||
# if 'CTR KARLOVY VARY' not in item.info:
|
||||
@@ -294,7 +261,7 @@ async def save_airspace(destination):
|
||||
# print("---" * 10)
|
||||
|
||||
async with get_session() as db:
|
||||
await models.AirspaceItem.create(db, {
|
||||
await models.Airspace.create(db, {
|
||||
"country": "cz",
|
||||
"name": item.info,
|
||||
"type": item.type.value,
|
||||
@@ -20,9 +20,33 @@ flight_copilots_dataloader = DataLoader(
|
||||
models.flight_has_copilot.c.flight_id == models.Flight.id,
|
||||
models.flight_has_copilot.c.copilot_id == models.Copilot.id,
|
||||
],
|
||||
extra_join=[models.Copilot.flights]).load,
|
||||
extra_join=[models.Copilot.flights]
|
||||
).load,
|
||||
cache=False)
|
||||
|
||||
|
||||
flight_plan_copilots_dataloader = DataLoader(
|
||||
load_fn=FlightCopilotDataloader(
|
||||
models.Copilot,
|
||||
relationship_column=models.FlightPlan.id,
|
||||
extra_select=[models.flight_plan_has_copilot.c.token],
|
||||
# filters=[
|
||||
# models.flight_plan_has_copilot.c.flight_plan_id == models.FlightPlan.id,
|
||||
# models.flight_plan_has_copilot.c.copilot_id == models.Copilot.id,
|
||||
# ],
|
||||
extra_join=[models.Copilot.flight_plans]
|
||||
).load,
|
||||
cache=False
|
||||
)
|
||||
|
||||
reporting_points_dataloader = DataLoader(
|
||||
load_fn=MultiModelsDataloader(
|
||||
models.ReportingPoint,
|
||||
relationship_column=models.airport_has_reporting_point.c.airport_id,
|
||||
extra_join=[models.airport_has_reporting_point]
|
||||
).load, cache=False
|
||||
)
|
||||
|
||||
flights_by_copilot_dataloader = DataLoader(
|
||||
load_fn=MultiModelsDataloader(
|
||||
models.Flight,
|
||||
@@ -150,3 +174,12 @@ photos_aircraft_dataloader = DataLoader(
|
||||
).load,
|
||||
cache=False
|
||||
)
|
||||
|
||||
flight_plan_markers_dataloader = DataLoader(
|
||||
load_fn=MultiModelsDataloader(
|
||||
models.FlightPlanMarker,
|
||||
relationship_column=models.FlightPlanMarker.flight_plan_id,
|
||||
order_by=[models.FlightPlanMarker.position, models.FlightPlanMarker.id]
|
||||
).load,
|
||||
cache=False
|
||||
)
|
||||
|
||||
@@ -16,11 +16,11 @@ class AirspaceQueries:
|
||||
|
||||
filters = []
|
||||
if country:
|
||||
filters.append(models.AirspaceItem.country == country)
|
||||
filters.append(models.Airspace.country == country)
|
||||
if types:
|
||||
filters.append(models.AirspaceItem.type.in_(types))
|
||||
filters.append(models.Airspace.type.in_(types))
|
||||
|
||||
return await BaseQueryResolver(Airspace, models.AirspaceItem).get_list(
|
||||
return await BaseQueryResolver(Airspace, models.Airspace).get_list(
|
||||
info.context.user_id,
|
||||
country=country,
|
||||
filters=filters
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
from typing import List
|
||||
import strawberry
|
||||
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 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
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class FlightPlanQueries:
|
||||
@strawberry.field()
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def flight_plans(root, info: Info) -> List[FlightPlan]:
|
||||
return await BaseQueryResolver(FlightPlan, models.FlightPlan).get_list(info.context.user_id)
|
||||
|
||||
@strawberry.field()
|
||||
@error_logging
|
||||
@allow_public
|
||||
async def flight_plan(root, info: Info, id: int) -> FlightPlan:
|
||||
return await BaseQueryResolver(FlightPlan, models.FlightPlan).get_one(
|
||||
user_id=info.context.user_id,
|
||||
object_id=id
|
||||
)
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class FlightPlanMutation:
|
||||
@strawberry.mutation
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def create_flight_plan(root, info, input: CreateFlightPlanInput) -> FlightPlan:
|
||||
return await FlightPlanMutationResolver().create(info.context, data=input)
|
||||
|
||||
@strawberry.mutation
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def edit_flight_plan(root, info, id: int, input: EditFlightPlanInput) -> FlightPlan:
|
||||
return await FlightPlanMutationResolver().update(id, input, info.context.user_id)
|
||||
@@ -87,7 +87,7 @@ class BaseQueryResolver(BaseResolver):
|
||||
|
||||
|
||||
class BaseMutationResolver(BaseResolver):
|
||||
async def _get_one(self, db: AsyncSession, id: int, created_by_id: int) -> models.BaseModel:
|
||||
async def _get_one(self, db: AsyncSession, id: int, created_by_id: int):
|
||||
query = self.query_builder.get_simple_query(created_by_id=created_by_id).filter(self.model.id == id)
|
||||
return (await db.scalars(query)).one()
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import asyncio
|
||||
|
||||
from sqlalchemy import delete, select
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from database import models
|
||||
from database.models import flight_plan_has_copilot
|
||||
from database.transaction import get_session
|
||||
from graphql_schema.entities.helpers.combobox import handle_combobox_save
|
||||
from graphql_schema.entities.resolvers.base import BaseMutationResolver
|
||||
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.types import FlightPlan
|
||||
from utils.str_utils import random_str
|
||||
|
||||
|
||||
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
|
||||
|
||||
async def save_copilots(self, db, flight_plan_id: int, copilots: list, user_id: int):
|
||||
await db.execute(delete(flight_plan_has_copilot).filter_by(flight_plan_id=flight_plan_id))
|
||||
copilots = await asyncio.gather(*[
|
||||
handle_combobox_save(db, models.Copilot, copilot, user_id) for copilot in copilots
|
||||
])
|
||||
|
||||
for copilot_id in copilots:
|
||||
await db.execute(insert(flight_plan_has_copilot).values(
|
||||
flight_plan_id=flight_plan_id,
|
||||
copilot_id=copilot_id,
|
||||
token=random_str(64)
|
||||
))
|
||||
|
||||
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_model = await self._get_one(db, id=id, created_by_id=user_id)
|
||||
|
||||
if data.aircraft is not None:
|
||||
input_data['aircraft_id'] = await handle_aircraft_save(db, user_id, data.aircraft)
|
||||
|
||||
if data.markers is not None:
|
||||
await self.reset_plan_markers(db, flight_plan_model)
|
||||
await self.save_markers(db, flight_plan_model, data.markers)
|
||||
|
||||
if flight_plan_model.is_default_name:
|
||||
if data.markers:
|
||||
markers = data.markers
|
||||
else:
|
||||
markers = (await db.scalars(select(models.FlightPlanMarker).filter(models.FlightPlanMarker.flight_plan_id == id))).all()
|
||||
|
||||
used_markers = evenly_spaced_elements(markers, 5)
|
||||
input_data['name'] = " - ".join(m.name for m in used_markers)
|
||||
|
||||
if data.copilots is not None:
|
||||
await self.save_copilots(
|
||||
db,
|
||||
flight_plan_id=id,
|
||||
copilots=data.copilots,
|
||||
user_id=user_id
|
||||
)
|
||||
|
||||
flight_plan = await self._do_update(db, obj=flight_plan_model, data=input_data)
|
||||
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)]
|
||||
@@ -5,6 +5,7 @@ import strawberry
|
||||
from strawberry.file_uploads import Upload
|
||||
from database import models
|
||||
from graphql_schema.entities.types.base import BaseGraphqlInputType
|
||||
from graphql_schema.entities.types.types import Aircraft
|
||||
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_input
|
||||
|
||||
|
||||
@@ -14,11 +15,33 @@ class ComboboxInput:
|
||||
name: str
|
||||
|
||||
|
||||
@strawberry_sqlalchemy_input(model=models.FlightPlanMarker, exclude_fields=["id", "flight_plan_id"])
|
||||
class FlightPlanMarkerInput(BaseGraphqlInputType):
|
||||
position: Optional[int] = 0
|
||||
|
||||
|
||||
@strawberry_sqlalchemy_input(model=models.Copilot, exclude_fields=["id"])
|
||||
class CreateCopilotInput(BaseGraphqlInputType):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_sqlalchemy_input(model=models.FlightPlan, exclude_fields=["id", "markers"])
|
||||
class CreateFlightPlanInput(BaseGraphqlInputType):
|
||||
markers: list[FlightPlanMarkerInput]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
data = super().to_dict()
|
||||
data['markers'] = [marker.to_dict() for marker in self.markers]
|
||||
return data
|
||||
|
||||
|
||||
@strawberry_sqlalchemy_input(model=models.FlightPlan, exclude_fields=["id", "markers"], all_optional=True)
|
||||
class EditFlightPlanInput(BaseGraphqlInputType):
|
||||
markers: Optional[list[FlightPlanMarkerInput]] = None
|
||||
copilots: Optional[list[ComboboxInput]] = None
|
||||
aircraft: Optional[ComboboxInput] = None
|
||||
|
||||
|
||||
@strawberry_sqlalchemy_input(model=models.Copilot, exclude_fields=["id"], all_optional=True)
|
||||
class EditCopilotInput(BaseGraphqlInputType):
|
||||
pass
|
||||
|
||||
@@ -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 (
|
||||
@@ -11,7 +14,8 @@ from graphql_schema.dataloaders.multi_models import (
|
||||
photos_dataloader, flights_by_aircraft_dataloader, users_in_organization_dataloader,
|
||||
aircrafts_from_organization_dataloader, user_organizations_dataloader, flights_by_event_dataloader,
|
||||
flights_by_copilot_dataloader, public_flights_by_event_dataloader, public_flights_by_copilot_dataloader,
|
||||
photo_copilots_dataloader, photos_aircraft_dataloader, copilots_in_photo_dataloader
|
||||
photo_copilots_dataloader, photos_aircraft_dataloader, copilots_in_photo_dataloader, flight_plan_markers_dataloader,
|
||||
reporting_points_dataloader, flight_plan_copilots_dataloader
|
||||
)
|
||||
from graphql_schema.dataloaders.single_model import (
|
||||
poi_dataloader, poi_type_dataloader, event_dataloader, aircraft_dataloader, airport_dataloader,
|
||||
@@ -43,9 +47,19 @@ class GPXTrack:
|
||||
avg_altitude: float
|
||||
|
||||
|
||||
@strawberry_sqlalchemy_type(models.ReportingPoint)
|
||||
class ReportingPoint:
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_sqlalchemy_type(models.Airport)
|
||||
class Airport:
|
||||
pass
|
||||
reporting_points: List[ReportingPoint] = strawberry.field(
|
||||
resolver=lambda root: reporting_points_dataloader.load(root.id)
|
||||
)
|
||||
|
||||
# TODO: loader na ikonu
|
||||
# TODO: loader na RWY a frekvence
|
||||
|
||||
|
||||
@strawberry.type
|
||||
@@ -55,15 +69,12 @@ class Airspace:
|
||||
country: str
|
||||
type: str
|
||||
lower_limit: str
|
||||
lower_alt_unit: str
|
||||
upper_alt_unit: str
|
||||
upper_limit: str
|
||||
center: Optional[tuple[float, float]]
|
||||
radius: Optional[float]
|
||||
bounds: list[tuple[float, float]]
|
||||
map_center: Optional[Point] = strawberry.field(
|
||||
resolver=lambda root: Point(lat=root.center[0], lng=root.center[1]) if root.center else None
|
||||
)
|
||||
map_bounds: list[Point] = strawberry.field(
|
||||
resolver=lambda root: [Point(lat=point[0], lng=point[1]) for point in root.bounds]
|
||||
resolver=lambda root: [Point(lat=point[1], lng=point[0]) for point in root.bounds]
|
||||
)
|
||||
|
||||
|
||||
@@ -180,6 +191,57 @@ class Flight:
|
||||
)
|
||||
|
||||
|
||||
@strawberry_sqlalchemy_type(model=models.FlightPlanMarker)
|
||||
class FlightPlanMarker:
|
||||
pass
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class FlightPlanTrack:
|
||||
bearing: Optional[int]
|
||||
distance: Optional[float]
|
||||
from_: FlightPlanMarker = strawberry.field(name="from")
|
||||
to: Optional[FlightPlanMarker]
|
||||
|
||||
|
||||
@strawberry_sqlalchemy_type(model=models.FlightPlan)
|
||||
class FlightPlan:
|
||||
async def resolve_track(self):
|
||||
markers = await self.markers()
|
||||
|
||||
if len(markers) == 1:
|
||||
return [FlightPlanTrack(bearing=None, distance=None, from_=markers[0], to=None)]
|
||||
|
||||
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
|
||||
|
||||
@authenticated_user_only(raise_when_unauthorized=False, return_value_unauthorized=[])
|
||||
async def load_copilots(root):
|
||||
return await flight_plan_copilots_dataloader.load(root.id)
|
||||
|
||||
markers: list[FlightPlanMarker] = strawberry.field(
|
||||
resolver=lambda root: flight_plan_markers_dataloader.load(root.id)
|
||||
)
|
||||
track: Optional[list[FlightPlanTrack]] = strawberry.field(
|
||||
resolver=resolve_track
|
||||
)
|
||||
copilots: List[Copilot] = strawberry.field(resolver=load_copilots)
|
||||
aircraft: Optional[Aircraft] = strawberry.field(resolver=lambda root: aircraft_dataloader.load(root.aircraft_id))
|
||||
|
||||
|
||||
@strawberry_sqlalchemy_type(models.Copilot)
|
||||
class Copilot:
|
||||
async def resolve_flights(root, info):
|
||||
|
||||
@@ -3,6 +3,7 @@ from graphql_schema.entities.aircraft import AircraftMutation
|
||||
from graphql_schema.entities.copilot import CopilotMutation
|
||||
from graphql_schema.entities.event import EventMutation
|
||||
from graphql_schema.entities.flight import FlightMutation
|
||||
from graphql_schema.entities.flight_plan import FlightPlanMutation
|
||||
from graphql_schema.entities.organization import OrganizationUserMutation, OrganizationMutation
|
||||
from graphql_schema.entities.photo import PhotoMutation
|
||||
from graphql_schema.entities.poi import PointOfInterestMutation
|
||||
@@ -11,6 +12,7 @@ from graphql_schema.entities.user import EditUserMutation
|
||||
Mutation = merge_types("Mutation", (
|
||||
AircraftMutation,
|
||||
FlightMutation,
|
||||
FlightPlanMutation,
|
||||
PhotoMutation,
|
||||
PointOfInterestMutation,
|
||||
CopilotMutation,
|
||||
|
||||
@@ -5,6 +5,7 @@ from .entities.airspace import AirspaceQueries
|
||||
from .entities.copilot import CopilotQueries
|
||||
from .entities.event import EventQueries
|
||||
from .entities.flight import FlightQueries
|
||||
from .entities.flight_plan import FlightPlanQueries
|
||||
from .entities.organization import OrganizationQueries
|
||||
from .entities.photo import PhotoQueries
|
||||
from .entities.poi import PointOfInterestQueries
|
||||
@@ -19,6 +20,7 @@ Query = merge_types('Query', (
|
||||
AirportQueries,
|
||||
AirspaceQueries,
|
||||
FlightQueries,
|
||||
FlightPlanQueries,
|
||||
CopilotQueries,
|
||||
UserQueries,
|
||||
PhotoQueries,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import asyncio
|
||||
import sys
|
||||
from sqlalchemy import select
|
||||
|
||||
sys.path.insert(0, "/app/src")
|
||||
from paths import FLIGHT_GPX_TRACK_PATH # noqa
|
||||
from database import async_session, models # noqa
|
||||
@@ -46,13 +45,18 @@ async def add_elevation_to_tracks():
|
||||
|
||||
for flight in flights:
|
||||
gpx_file = f"{FLIGHT_GPX_TRACK_PATH}/{flight.gpx_track_filename}"
|
||||
try:
|
||||
gpx = GPXParser(gpx_file)
|
||||
except OSError:
|
||||
print(f"ERROR, cannot open {gpx_file=}, skipping")
|
||||
continue
|
||||
|
||||
coordinates = await gpx.get_coordinates()
|
||||
coordinates = await gpx.get_coordinates_all()
|
||||
elevation = await elevation_api.get_elevation_for_points(coordinates)
|
||||
|
||||
gpx_with_elevation = gpx.add_terrain_elevation(elevation)
|
||||
|
||||
output_name = f"terrain_{flight.gpx_track_filename[30:]}"
|
||||
output_name = f"terrain_{flight.gpx_track_filename[-30:]}"
|
||||
gpx.write(gpx_with_elevation, output=f"{FLIGHT_GPX_TRACK_PATH}/{output_name}")
|
||||
await models.Flight.update(
|
||||
db_session=session, obj=flight, data={
|
||||
|
||||
Reference in New Issue
Block a user