Nova struktura DB pro data z OpenAIP API

This commit is contained in:
Michal Kváček
2024-04-29 06:44:33 +02:00
parent c2b066d371
commit c8138860ca
11 changed files with 342 additions and 66 deletions
+45 -34
View File
@@ -67,17 +67,25 @@ 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())
@@ -111,21 +119,51 @@ class Airport(BaseModel):
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"
@@ -370,32 +408,6 @@ class Copilot(BaseModel):
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',)
@@ -410,7 +422,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)
+12 -12
View File
@@ -14,7 +14,7 @@ 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"
@@ -47,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
@@ -107,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
@@ -116,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 = {}
@@ -194,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':
@@ -249,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:
@@ -261,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,
@@ -23,6 +23,14 @@ flight_copilots_dataloader = DataLoader(
extra_join=[models.Copilot.flights]).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,
+3 -3
View File
@@ -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
+15 -8
View File
@@ -14,7 +14,7 @@ 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, flight_plan_markers_dataloader
photo_copilots_dataloader, photos_aircraft_dataloader, copilots_in_photo_dataloader, flight_plan_markers_dataloader, reporting_points_dataloader
)
from graphql_schema.dataloaders.single_model import (
poi_dataloader, poi_type_dataloader, event_dataloader, aircraft_dataloader, airport_dataloader,
@@ -46,9 +46,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
@@ -58,15 +68,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]
)