pyopensky 2.5__tar.gz → 2.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyopensky
3
- Version: 2.5
3
+ Version: 2.6
4
4
  Summary: A Python interface for OpenSky database
5
+ Home-page: https://github.com/open-aviation/pyopensky/
5
6
  License: MIT
6
7
  Author: Xavier Olive
7
8
  Author-email: git@xoolive.org
@@ -21,13 +22,14 @@ Requires-Dist: pyjwt (>=2.7.0)
21
22
  Requires-Dist: tqdm (>=4.65.0)
22
23
  Requires-Dist: trino[sqlalchemy] (>=0.322)
23
24
  Requires-Dist: typing-extensions (>=4.5.0)
25
+ Project-URL: Repository, https://github.com/open-aviation/pyopensky/
24
26
  Description-Content-Type: text/markdown
25
27
 
26
28
  # pyopensky
27
29
 
28
30
  The `pyopensky` Python library provides functions to download data from the OpenSky Network live API and historical databases. It aims at making ADS-B and Mode S data from OpenSky easily accessible in the Python programming environment.
29
31
 
30
- Full documentation on https://open-aviation.github.io/pyopensky
32
+ Full documentation on [https://open-aviation.github.io/pyopensky](https://open-aviation.github.io/pyopensky)
31
33
 
32
34
  ## Installation
33
35
 
@@ -1,7 +1,8 @@
1
1
  [tool.poetry]
2
2
  name = "pyopensky"
3
- version = "2.5"
3
+ version = "2.6"
4
4
  description = "A Python interface for OpenSky database"
5
+ repository = "https://github.com/open-aviation/pyopensky/"
5
6
  authors = ["Xavier Olive <git@xoolive.org>", "Junzi Sun <j.sun-1@tudelft.nl>"]
6
7
  license = "MIT"
7
8
  readme = "readme.md"
@@ -2,7 +2,7 @@
2
2
 
3
3
  The `pyopensky` Python library provides functions to download data from the OpenSky Network live API and historical databases. It aims at making ADS-B and Mode S data from OpenSky easily accessible in the Python programming environment.
4
4
 
5
- Full documentation on https://open-aviation.github.io/pyopensky
5
+ Full documentation on [https://open-aviation.github.io/pyopensky](https://open-aviation.github.io/pyopensky)
6
6
 
7
7
  ## Installation
8
8
 
@@ -67,6 +67,17 @@ class UTCTimestampFloat(TypeDecorator[pd.Timestamp]):
67
67
  return super().process_bind_param(value, dialect)
68
68
 
69
69
 
70
+ class AddressString(TypeDecorator[str]):
71
+ """Automatic coercing of icao24 addresses into low characters."""
72
+
73
+ impl = String
74
+
75
+ def process_bind_param(self, value: str | None, dialect: Dialect) -> Any:
76
+ if isinstance(value, str):
77
+ return value.lower()
78
+ return super().process_bind_param(value, dialect)
79
+
80
+
70
81
  class CallsignString(TypeDecorator[str]):
71
82
  """Automatic coercing of callsigns into 8-char left padded strings.
72
83
 
@@ -122,7 +133,7 @@ class TrackRow:
122
133
 
123
134
  @dataclass
124
135
  class AirportCandidateRow:
125
- icao24: str
136
+ icao24: Address
126
137
  horizdistance: int
127
138
  vertdistance: int
128
139
 
@@ -180,6 +191,7 @@ Track = List[TrackRow]
180
191
  Sensors = List[SensorRow]
181
192
 
182
193
  Callsign = Annotated[str, mapped_column(CallsignString)]
194
+ Address = Annotated[str, mapped_column(AddressString)]
183
195
 
184
196
 
185
197
  class Base(DeclarativeBase):
@@ -188,6 +200,7 @@ class Base(DeclarativeBase):
188
200
  AirportCandidates: AirportCandidateType,
189
201
  Track: TrackType,
190
202
  Callsign: CallsignString,
203
+ Address: AddressString,
191
204
  Sensors: SensorsType,
192
205
  }
193
206
 
@@ -196,7 +209,7 @@ class StateVectorsData4(Base):
196
209
  __tablename__ = "state_vectors_data4"
197
210
 
198
211
  time: Mapped[pd.Timestamp]
199
- icao24: Mapped[str]
212
+ icao24: Mapped[Address]
200
213
  lat: Mapped[float]
201
214
  lon: Mapped[float]
202
215
  velocity: Mapped[float]
@@ -218,7 +231,7 @@ class StateVectorsData4(Base):
218
231
  class FlightsData4(Base):
219
232
  __tablename__ = "flights_data4"
220
233
 
221
- icao24: Mapped[str]
234
+ icao24: Mapped[Address]
222
235
  firstseen: Mapped[pd.Timestamp]
223
236
  estdepartureairport: Mapped[str]
224
237
  lastseen: Mapped[pd.Timestamp]
@@ -243,7 +256,7 @@ class FlightsData4(Base):
243
256
  class FlightsData5(Base):
244
257
  __tablename__ = "flights_data5"
245
258
 
246
- icao24: Mapped[str]
259
+ icao24: Mapped[Address]
247
260
  firstseen: Mapped[pd.Timestamp]
248
261
  estdepartureairport: Mapped[str]
249
262
  lastseen: Mapped[pd.Timestamp]
@@ -280,17 +293,7 @@ class RawTable(Protocol):
280
293
  mintime: Mapped[pd.Timestamp]
281
294
  maxtime: Mapped[pd.Timestamp]
282
295
  msgcount: Mapped[int]
283
- icao24: Mapped[str]
284
- message: Mapped[str]
285
- isid: Mapped[bool]
286
- flightstatus: Mapped[int]
287
- downlinkrequest: Mapped[int]
288
- utilitymsg: Mapped[int]
289
- interrogatorid: Mapped[int]
290
- identifierdesignator: Mapped[int]
291
- valuecode: Mapped[int]
292
- altitude: Mapped[float]
293
- identity: Mapped[str]
296
+ icao24: Mapped[Address]
294
297
 
295
298
  # Whatever, we pick this one as primary key BUT
296
299
  # - this is not true
@@ -306,7 +309,7 @@ class AcasData4(Base):
306
309
  mintime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
307
310
  maxtime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
308
311
  msgcount: Mapped[int]
309
- icao24: Mapped[str]
312
+ icao24: Mapped[Address]
310
313
  message: Mapped[str]
311
314
  isid: Mapped[bool]
312
315
  flightstatus: Mapped[int]
@@ -332,7 +335,7 @@ class AllcallRepliesData4(Base):
332
335
  mintime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
333
336
  maxtime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
334
337
  msgcount: Mapped[int]
335
- icao24: Mapped[str]
338
+ icao24: Mapped[Address]
336
339
  message: Mapped[str]
337
340
  isid: Mapped[bool]
338
341
  flightstatus: Mapped[int]
@@ -358,16 +361,9 @@ class IdentificationData4(Base):
358
361
  mintime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
359
362
  maxtime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
360
363
  msgcount: Mapped[int]
361
- icao24: Mapped[str]
362
- message: Mapped[str]
363
- isid: Mapped[bool]
364
- flightstatus: Mapped[int]
365
- downlinkrequest: Mapped[int]
366
- utilitymsg: Mapped[int]
367
- interrogatorid: Mapped[int]
368
- identifierdesignator: Mapped[int]
369
- valuecode: Mapped[int]
370
- altitude: Mapped[float]
364
+ icao24: Mapped[Address]
365
+ emittercategory: Mapped[int]
366
+ ftc: Mapped[int]
371
367
  identity: Mapped[str]
372
368
 
373
369
  # Whatever, we pick this one as primary key BUT
@@ -385,16 +381,33 @@ class OperationalStatusData4(Base):
385
381
  maxtime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
386
382
  msgcount: Mapped[int]
387
383
  icao24: Mapped[str]
388
- message: Mapped[str]
389
- isid: Mapped[bool]
390
- flightstatus: Mapped[int]
391
- downlinkrequest: Mapped[int]
392
- utilitymsg: Mapped[int]
393
- interrogatorid: Mapped[int]
394
- identifierdesignator: Mapped[int]
395
- valuecode: Mapped[int]
396
- altitude: Mapped[float]
397
- identity: Mapped[str]
384
+ subtypecode: Mapped[int]
385
+ unknowncapcode: Mapped[bool]
386
+ unknownopcode: Mapped[bool]
387
+ hasoperationaltcas: Mapped[int]
388
+ has1090esin: Mapped[bool]
389
+ supportsairreferencedvelocity: Mapped[int]
390
+ haslowtxpower: Mapped[int]
391
+ supportstargetstatereport: Mapped[int]
392
+ supportstargetchangereport: Mapped[int]
393
+ hasuatin: Mapped[bool]
394
+ nacv: Mapped[int]
395
+ nicsupplementc: Mapped[int]
396
+ hastcasresolutionadvisory: Mapped[bool]
397
+ hasactiveidentswitch: Mapped[bool]
398
+ usessingleantenna: Mapped[bool]
399
+ systemdesignassurance: Mapped[int]
400
+ gpsantennaoffset: Mapped[int]
401
+ airplanelength: Mapped[int]
402
+ airplanewidth: Mapped[float]
403
+ version: Mapped[int]
404
+ nicsupplementa: Mapped[bool]
405
+ positionnac: Mapped[float]
406
+ geometricverticalaccuracy: Mapped[int]
407
+ sourceintegritylevel: Mapped[int]
408
+ barometricaltitudeintegritycode: Mapped[int]
409
+ trackheadinginfo: Mapped[int]
410
+ horizontalreferencedirection: Mapped[bool]
398
411
 
399
412
  # Whatever, we pick this one as primary key BUT
400
413
  # - this is not true
@@ -410,7 +423,7 @@ class PositionData4(Base):
410
423
  mintime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
411
424
  maxtime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
412
425
  msgcount: Mapped[int]
413
- icao24: Mapped[str]
426
+ icao24: Mapped[Address]
414
427
  nicsuppla: Mapped[bool]
415
428
  hcr: Mapped[float]
416
429
  nic: Mapped[int]
@@ -442,7 +455,7 @@ class RollcallRepliesData4(Base):
442
455
  mintime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
443
456
  maxtime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
444
457
  msgcount: Mapped[int]
445
- icao24: Mapped[str]
458
+ icao24: Mapped[Address]
446
459
  message: Mapped[str]
447
460
  isid: Mapped[bool]
448
461
  flightstatus: Mapped[int]
@@ -468,17 +481,48 @@ class VelocityData4(Base):
468
481
  mintime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
469
482
  maxtime: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
470
483
  msgcount: Mapped[int]
471
- icao24: Mapped[str]
472
- message: Mapped[str]
473
- isid: Mapped[bool]
474
- flightstatus: Mapped[int]
475
- downlinkrequest: Mapped[int]
476
- utilitymsg: Mapped[int]
477
- interrogatorid: Mapped[int]
478
- identifierdesignator: Mapped[int]
479
- valuecode: Mapped[int]
480
- altitude: Mapped[float]
481
- identity: Mapped[str]
484
+ icao24: Mapped[Address]
485
+ supersonic: Mapped[bool]
486
+ intentchange: Mapped[bool]
487
+ ifrcapability: Mapped[bool]
488
+ nac: Mapped[int]
489
+ ewvelocity: Mapped[float]
490
+ nsvelocity: Mapped[float]
491
+ baro: Mapped[bool]
492
+ vertrate: Mapped[float]
493
+ geominurbaro: Mapped[float] # typo confirmed
494
+ heading: Mapped[float]
495
+ velocity: Mapped[float]
496
+
497
+ # Whatever, we pick this one as primary key BUT
498
+ # - this is not true
499
+ # - we still need one column with a primary key
500
+ hour: Mapped[pd.Timestamp] = mapped_column(primary_key=True)
501
+
502
+
503
+ class FlarmRaw(Base):
504
+ __tablename__ = "flarm_raw"
505
+
506
+ sensortype: Mapped[str]
507
+ sensorlatitude: Mapped[float]
508
+ sensorlongitude: Mapped[float]
509
+ sensoraltitude: Mapped[int]
510
+ timeatserver: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
511
+ timeatsensor: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
512
+ timestamp: Mapped[pd.Timestamp] = mapped_column(UTCTimestampFloat)
513
+ rawmessage: Mapped[str]
514
+ crc: Mapped[str]
515
+ rawsoftmessage: Mapped[str]
516
+ sensorname: Mapped[str]
517
+ ntperror: Mapped[float]
518
+ userfreqcorrection: Mapped[float]
519
+ autofreqcorrection: Mapped[float]
520
+ frequency: Mapped[float]
521
+ channel: Mapped[int]
522
+ snrdetector: Mapped[float]
523
+ snrdemodulator: Mapped[float]
524
+ typeogn: Mapped[bool]
525
+ crccorrect: Mapped[bool]
482
526
 
483
527
  # Whatever, we pick this one as primary key BUT
484
528
  # - this is not true
@@ -33,6 +33,7 @@ import pandas as pd
33
33
  from .api import HasBounds, OpenSkyDBAPI
34
34
  from .config import cache_path, trino_password, trino_username
35
35
  from .schema import (
36
+ FlarmRaw,
36
37
  FlightsData4,
37
38
  FlightsData5,
38
39
  RawTable,
@@ -383,6 +384,7 @@ class Trino(OpenSkyDBAPI):
383
384
  departure_airport: None | str = None,
384
385
  arrival_airport: None | str = None,
385
386
  airport: None | str = None,
387
+ time_buffer: None | str | pd.Timedelta = None,
386
388
  cached: bool = True,
387
389
  compress: bool = False,
388
390
  limit: None | int = None,
@@ -435,6 +437,10 @@ class Trino(OpenSkyDBAPI):
435
437
  :param airport: a string for the ICAO identifier of the airport. Selects
436
438
  flights departing from or arriving at the airport between the two
437
439
  timestamps;
440
+ :param time_buffer: (default: None) time buffer used to extend time
441
+ bounds for flights in the OpenSky flight tables: requests will get
442
+ flights between ``start - time_buffer`` and ``stop + time_buffer``.
443
+ If no airport is specified, the parameter is ignored.
438
444
 
439
445
  .. warning::
440
446
 
@@ -519,18 +525,26 @@ class Trino(OpenSkyDBAPI):
519
525
 
520
526
  flight_query = flight_table.subquery()
521
527
  fd4 = aliased(FlightsData4, alias=flight_query, adapt_on_names=True)
522
- stmt = (
523
- select(StateVectorsData4)
524
- .join(
525
- flight_query,
526
- (fd4.icao24 == StateVectorsData4.icao24)
527
- & (fd4.callsign == StateVectorsData4.callsign),
528
- )
529
- .where(
528
+
529
+ if isinstance(time_buffer, str):
530
+ time_buffer = pd.Timedelta(time_buffer)
531
+
532
+ stmt = select(StateVectorsData4).join(
533
+ flight_query,
534
+ (fd4.icao24 == StateVectorsData4.icao24)
535
+ & (fd4.callsign == StateVectorsData4.callsign),
536
+ )
537
+
538
+ if time_buffer is None:
539
+ stmt = stmt.where(
530
540
  StateVectorsData4.time >= fd4.firstseen,
531
541
  StateVectorsData4.time <= fd4.lastseen,
532
542
  )
533
- )
543
+ else:
544
+ stmt = stmt.where(
545
+ StateVectorsData4.time >= (fd4.firstseen - time_buffer),
546
+ StateVectorsData4.time <= (fd4.lastseen + time_buffer),
547
+ )
534
548
 
535
549
  stmt = self.stmt_where_str(stmt, icao24, StateVectorsData4.icao24)
536
550
  stmt = self.stmt_where_str(stmt, callsign, StateVectorsData4.callsign)
@@ -610,6 +624,63 @@ class Trino(OpenSkyDBAPI):
610
624
 
611
625
  return res
612
626
 
627
+ def flarm(
628
+ self,
629
+ start: timelike,
630
+ stop: None | timelike = None,
631
+ *args: ColumnExpressionArgument[bool],
632
+ sensor_name: None | str | list[str] = None,
633
+ cached: bool = True,
634
+ compress: bool = False,
635
+ limit: None | int = None,
636
+ correct_only: bool = True,
637
+ extra_columns: tuple[InstrumentedAttribute[Any], ...] = (),
638
+ **kwargs: Any,
639
+ ) -> None | pd.DataFrame:
640
+ start_ts = to_datetime(start)
641
+ stop_ts = (
642
+ to_datetime(stop)
643
+ if stop is not None
644
+ else start_ts + pd.Timedelta("1d")
645
+ )
646
+ stmt = select(FlarmRaw).with_only_columns(
647
+ FlarmRaw.sensoraltitude,
648
+ FlarmRaw.sensorlatitude,
649
+ FlarmRaw.sensorlongitude,
650
+ FlarmRaw.sensorname,
651
+ FlarmRaw.timestamp,
652
+ FlarmRaw.timeatserver,
653
+ FlarmRaw.timeatsensor,
654
+ FlarmRaw.rawmessage,
655
+ *extra_columns,
656
+ )
657
+ if correct_only:
658
+ stmt = stmt.where(FlarmRaw.crccorrect)
659
+ stmt = stmt.where(FlarmRaw.rawmessage.is_not(None))
660
+
661
+ if sensor_name is not None:
662
+ stmt = self.stmt_where_str(stmt, sensor_name, FlarmRaw.sensorname)
663
+
664
+ for condition in args:
665
+ stmt = stmt.where(condition)
666
+
667
+ stmt = stmt.where(
668
+ FlarmRaw.timestamp >= start_ts,
669
+ FlarmRaw.timestamp <= stop_ts,
670
+ FlarmRaw.hour >= start_ts.floor("1h"),
671
+ FlarmRaw.hour < stop_ts.ceil("1h"),
672
+ )
673
+
674
+ if limit is not None:
675
+ stmt = stmt.limit(limit)
676
+
677
+ res = self.query(stmt, cached=cached, compress=compress)
678
+
679
+ if res.shape[0] == 0:
680
+ return None
681
+
682
+ return res
683
+
613
684
  def rawdata(
614
685
  self,
615
686
  start: timelike,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes