diff --git a/src/pyopensky/schema.py b/src/pyopensky/schema.py index 282859b..5334ce0 100644 --- a/src/pyopensky/schema.py +++ b/src/pyopensky/schema.py @@ -294,16 +294,6 @@ class RawTable(Protocol): maxtime: Mapped[pd.Timestamp] msgcount: Mapped[int] icao24: Mapped[Address] - message: Mapped[str] - isid: Mapped[bool] - flightstatus: Mapped[int] - downlinkrequest: Mapped[int] - utilitymsg: Mapped[int] - interrogatorid: Mapped[int] - identifierdesignator: Mapped[int] - valuecode: Mapped[int] - altitude: Mapped[float] - identity: Mapped[str] # Whatever, we pick this one as primary key BUT # - this is not true @@ -372,15 +362,8 @@ class IdentificationData4(Base): maxtime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat) msgcount: Mapped[int] icao24: Mapped[Address] - message: Mapped[str] - isid: Mapped[bool] - flightstatus: Mapped[int] - downlinkrequest: Mapped[int] - utilitymsg: Mapped[int] - interrogatorid: Mapped[int] - identifierdesignator: Mapped[int] - valuecode: Mapped[int] - altitude: Mapped[float] + emittercategory: Mapped[int] + ftc: Mapped[int] identity: Mapped[str] # Whatever, we pick this one as primary key BUT @@ -499,16 +482,47 @@ class VelocityData4(Base): maxtime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat) msgcount: Mapped[int] icao24: Mapped[Address] - message: Mapped[str] - isid: Mapped[bool] - flightstatus: Mapped[int] - downlinkrequest: Mapped[int] - utilitymsg: Mapped[int] - interrogatorid: Mapped[int] - identifierdesignator: Mapped[int] - valuecode: Mapped[int] - altitude: Mapped[float] - identity: Mapped[str] + supersonic: Mapped[bool] + intentchange: Mapped[bool] + ifrcapability: Mapped[bool] + nac: Mapped[int] + ewvelocity: Mapped[float] + nsvelocity: Mapped[float] + baro: Mapped[bool] + vertrate: Mapped[float] + geominurbaro: Mapped[float] # typo confirmed + heading: Mapped[float] + velocity: Mapped[float] + + # Whatever, we pick this one as primary key BUT + # - this is not true + # - we still need one column with a primary key + hour: Mapped[pd.Timestamp] = mapped_column(primary_key=True) + + +class FlarmRaw(Base): + __tablename__ = "flarm_raw" + + sensortype: Mapped[str] + sensorlatitude: Mapped[float] + sensorlongitude: Mapped[float] + sensoraltitude: Mapped[int] + timeatserver: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat) + timeatsensor: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat) + timestamp: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat) + rawmessage: Mapped[str] + crc: Mapped[str] + rawsoftmessage: Mapped[str] + sensorname: Mapped[str] + ntperror: Mapped[float] + userfreqcorrection: Mapped[float] + autofreqcorrection: Mapped[float] + frequency: Mapped[float] + channel: Mapped[int] + snrdetector: Mapped[float] + snrdemodulator: Mapped[float] + typeogn: Mapped[bool] + crccorrect: Mapped[bool] # Whatever, we pick this one as primary key BUT # - this is not true diff --git a/src/pyopensky/trino.py b/src/pyopensky/trino.py index 8c3cdf0..ea24d07 100644 --- a/src/pyopensky/trino.py +++ b/src/pyopensky/trino.py @@ -33,6 +33,7 @@ from .api import HasBounds, OpenSkyDBAPI from .config import cache_path, trino_password, trino_username from .schema import ( + FlarmRaw, FlightsData4, FlightsData5, RawTable, @@ -623,6 +624,63 @@ def transform_column(col: str) -> InstrumentedAttribute[Any]: return res + def flarm( + self, + start: timelike, + stop: None | timelike = None, + *args: ColumnExpressionArgument[bool], + sensor_name: None | str | list[str] = None, + cached: bool = True, + compress: bool = False, + limit: None | int = None, + correct_only: bool = True, + extra_columns: tuple[InstrumentedAttribute[Any], ...] = (), + **kwargs: Any, + ) -> None | pd.DataFrame: + start_ts = to_datetime(start) + stop_ts = ( + to_datetime(stop) + if stop is not None + else start_ts + pd.Timedelta("1d") + ) + stmt = select(FlarmRaw).with_only_columns( + FlarmRaw.sensoraltitude, + FlarmRaw.sensorlatitude, + FlarmRaw.sensorlongitude, + FlarmRaw.sensorname, + FlarmRaw.timestamp, + FlarmRaw.timeatserver, + FlarmRaw.timeatsensor, + FlarmRaw.rawmessage, + *extra_columns, + ) + if correct_only: + stmt = stmt.where(FlarmRaw.crccorrect) + stmt = stmt.where(FlarmRaw.rawmessage.is_not(None)) + + if sensor_name is not None: + stmt = self.stmt_where_str(stmt, sensor_name, FlarmRaw.sensorname) + + for condition in args: + stmt = stmt.where(condition) + + stmt = stmt.where( + FlarmRaw.timestamp >= start_ts, + FlarmRaw.timestamp <= stop_ts, + FlarmRaw.hour >= start_ts.floor("1h"), + FlarmRaw.hour < stop_ts.ceil("1h"), + ) + + if limit is not None: + stmt = stmt.limit(limit) + + res = self.query(stmt, cached=cached, compress=compress) + + if res.shape[0] == 0: + return None + + return res + def rawdata( self, start: timelike, diff --git a/tests/test_trino.py b/tests/test_trino.py index b17f9e7..1a615c2 100644 --- a/tests/test_trino.py +++ b/tests/test_trino.py @@ -1,5 +1,5 @@ import pytest -from pyopensky.schema import FlightsData4, StateVectorsData4 +from pyopensky.schema import FlarmRaw, FlightsData4, StateVectorsData4 from pyopensky.trino import Trino from sqlalchemy import func, not_, select @@ -306,3 +306,13 @@ def test_icao24_lowcase() -> None: icao24="400A0E", ) assert df is not None + + +def test_flarm() -> None: + df = trino.flarm( + "2018-09-11 07:00", + "2018-09-11 08:00", + limit=10, + sensor_name="LS%", + ) + assert df is not None