pyopensky 2.5__tar.gz → 2.7__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.7
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>
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.7"
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>
6
6
 
7
7
  ## Installation
8
8
 
@@ -98,6 +98,7 @@ class Resolution(TypedDict, total=False):
98
98
  NAME_RESOLUTION: dict[str, Resolution] = {
99
99
  # Cache configuration
100
100
  "cache_dir": dict(
101
+ environment_variable="OPENSKY_CACHE",
101
102
  opensky_category="cache",
102
103
  opensky_name="path",
103
104
  traffic_category="cache",
@@ -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,
@@ -296,7 +297,7 @@ class Trino(OpenSkyDBAPI):
296
297
  else start_ts + pd.Timedelta("1d")
297
298
  )
298
299
 
299
- stmt = select(Table).with_only_columns( # type: ignore
300
+ stmt = select(Table).with_only_columns(
300
301
  Table.icao24,
301
302
  Table.firstseen,
302
303
  Table.estdepartureairport,
@@ -307,21 +308,17 @@ class Trino(OpenSkyDBAPI):
307
308
  *(extra_columns if extra_columns is not None else []),
308
309
  )
309
310
 
310
- stmt = self.stmt_where_str(stmt, icao24, Table.icao24) # type: ignore
311
- stmt = self.stmt_where_str(
312
- stmt,
313
- callsign,
314
- Table.callsign, # type: ignore
315
- )
311
+ stmt = self.stmt_where_str(stmt, icao24, Table.icao24)
312
+ stmt = self.stmt_where_str(stmt, callsign, Table.callsign)
316
313
  stmt = self.stmt_where_str(
317
314
  stmt,
318
315
  departure_airport,
319
- Table.estdepartureairport, # type: ignore
316
+ Table.estdepartureairport,
320
317
  )
321
318
  stmt = self.stmt_where_str(
322
319
  stmt,
323
320
  arrival_airport,
324
- Table.estarrivalairport, # type: ignore
321
+ Table.estarrivalairport,
325
322
  )
326
323
  if airport is not None and arrival_airport is not None:
327
324
  raise RuntimeError("airport may not be set if arrival_airport is")
@@ -330,8 +327,8 @@ class Trino(OpenSkyDBAPI):
330
327
  stmt = self.stmt_where_str(
331
328
  stmt,
332
329
  airport,
333
- Table.estdepartureairport, # type: ignore
334
- Table.estarrivalairport, # type: ignore
330
+ Table.estdepartureairport,
331
+ Table.estarrivalairport,
335
332
  )
336
333
 
337
334
  if departure_airport is not None:
@@ -383,6 +380,7 @@ class Trino(OpenSkyDBAPI):
383
380
  departure_airport: None | str = None,
384
381
  arrival_airport: None | str = None,
385
382
  airport: None | str = None,
383
+ time_buffer: None | str | pd.Timedelta = None,
386
384
  cached: bool = True,
387
385
  compress: bool = False,
388
386
  limit: None | int = None,
@@ -435,6 +433,10 @@ class Trino(OpenSkyDBAPI):
435
433
  :param airport: a string for the ICAO identifier of the airport. Selects
436
434
  flights departing from or arriving at the airport between the two
437
435
  timestamps;
436
+ :param time_buffer: (default: None) time buffer used to extend time
437
+ bounds for flights in the OpenSky flight tables: requests will get
438
+ flights between ``start - time_buffer`` and ``stop + time_buffer``.
439
+ If no airport is specified, the parameter is ignored.
438
440
 
439
441
  .. warning::
440
442
 
@@ -519,18 +521,26 @@ class Trino(OpenSkyDBAPI):
519
521
 
520
522
  flight_query = flight_table.subquery()
521
523
  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(
524
+
525
+ if isinstance(time_buffer, str):
526
+ time_buffer = pd.Timedelta(time_buffer)
527
+
528
+ stmt = select(StateVectorsData4).join(
529
+ flight_query,
530
+ (fd4.icao24 == StateVectorsData4.icao24)
531
+ & (fd4.callsign == StateVectorsData4.callsign),
532
+ )
533
+
534
+ if time_buffer is None:
535
+ stmt = stmt.where(
530
536
  StateVectorsData4.time >= fd4.firstseen,
531
537
  StateVectorsData4.time <= fd4.lastseen,
532
538
  )
533
- )
539
+ else:
540
+ stmt = stmt.where(
541
+ StateVectorsData4.time >= (fd4.firstseen - time_buffer),
542
+ StateVectorsData4.time <= (fd4.lastseen + time_buffer),
543
+ )
534
544
 
535
545
  stmt = self.stmt_where_str(stmt, icao24, StateVectorsData4.icao24)
536
546
  stmt = self.stmt_where_str(stmt, callsign, StateVectorsData4.callsign)
@@ -610,6 +620,63 @@ class Trino(OpenSkyDBAPI):
610
620
 
611
621
  return res
612
622
 
623
+ def flarm(
624
+ self,
625
+ start: timelike,
626
+ stop: None | timelike = None,
627
+ *args: ColumnExpressionArgument[bool],
628
+ sensor_name: None | str | list[str] = None,
629
+ cached: bool = True,
630
+ compress: bool = False,
631
+ limit: None | int = None,
632
+ correct_only: bool = True,
633
+ extra_columns: tuple[InstrumentedAttribute[Any], ...] = (),
634
+ **kwargs: Any,
635
+ ) -> None | pd.DataFrame:
636
+ start_ts = to_datetime(start)
637
+ stop_ts = (
638
+ to_datetime(stop)
639
+ if stop is not None
640
+ else start_ts + pd.Timedelta("1d")
641
+ )
642
+ stmt = select(FlarmRaw).with_only_columns(
643
+ FlarmRaw.sensoraltitude,
644
+ FlarmRaw.sensorlatitude,
645
+ FlarmRaw.sensorlongitude,
646
+ FlarmRaw.sensorname,
647
+ FlarmRaw.timestamp,
648
+ FlarmRaw.timeatserver,
649
+ FlarmRaw.timeatsensor,
650
+ FlarmRaw.rawmessage,
651
+ *extra_columns,
652
+ )
653
+ if correct_only:
654
+ stmt = stmt.where(FlarmRaw.crccorrect)
655
+ stmt = stmt.where(FlarmRaw.rawmessage.is_not(None))
656
+
657
+ if sensor_name is not None:
658
+ stmt = self.stmt_where_str(stmt, sensor_name, FlarmRaw.sensorname)
659
+
660
+ for condition in args:
661
+ stmt = stmt.where(condition)
662
+
663
+ stmt = stmt.where(
664
+ FlarmRaw.timestamp >= start_ts,
665
+ FlarmRaw.timestamp <= stop_ts,
666
+ FlarmRaw.hour >= start_ts.floor("1h"),
667
+ FlarmRaw.hour < stop_ts.ceil("1h"),
668
+ )
669
+
670
+ if limit is not None:
671
+ stmt = stmt.limit(limit)
672
+
673
+ res = self.query(stmt, cached=cached, compress=compress)
674
+
675
+ if res.shape[0] == 0:
676
+ return None
677
+
678
+ return res
679
+
613
680
  def rawdata(
614
681
  self,
615
682
  start: timelike,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes