pycontrails 0.54.0__cp312-cp312-win_amd64.whl → 0.54.2__cp312-cp312-win_amd64.whl
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.
Potentially problematic release.
This version of pycontrails might be problematic. Click here for more details.
- pycontrails/_version.py +2 -2
- pycontrails/core/aircraft_performance.py +16 -5
- pycontrails/core/cache.py +14 -10
- pycontrails/core/fleet.py +27 -34
- pycontrails/core/flight.py +31 -91
- pycontrails/core/met.py +64 -67
- pycontrails/core/rgi_cython.cp312-win_amd64.pyd +0 -0
- pycontrails/core/vector.py +51 -82
- pycontrails/datalib/ecmwf/__init__.py +2 -2
- pycontrails/datalib/ecmwf/arco_era5.py +11 -6
- pycontrails/datalib/ecmwf/common.py +7 -2
- pycontrails/datalib/ecmwf/era5.py +10 -5
- pycontrails/datalib/ecmwf/era5_model_level.py +12 -8
- pycontrails/datalib/ecmwf/hres.py +12 -7
- pycontrails/datalib/ecmwf/hres_model_level.py +10 -5
- pycontrails/datalib/ecmwf/ifs.py +11 -6
- pycontrails/datalib/ecmwf/model_levels.py +3 -2
- pycontrails/datalib/gfs/gfs.py +52 -34
- pycontrails/datalib/gfs/variables.py +6 -2
- pycontrails/models/cocip/cocip.py +7 -2
- pycontrails/models/cocipgrid/cocip_grid.py +5 -3
- pycontrails/models/humidity_scaling/humidity_scaling.py +12 -7
- pycontrails/models/ps_model/ps_model.py +47 -27
- {pycontrails-0.54.0.dist-info → pycontrails-0.54.2.dist-info}/METADATA +5 -4
- {pycontrails-0.54.0.dist-info → pycontrails-0.54.2.dist-info}/RECORD +29 -29
- {pycontrails-0.54.0.dist-info → pycontrails-0.54.2.dist-info}/WHEEL +1 -1
- {pycontrails-0.54.0.dist-info → pycontrails-0.54.2.dist-info}/LICENSE +0 -0
- {pycontrails-0.54.0.dist-info → pycontrails-0.54.2.dist-info}/NOTICE +0 -0
- {pycontrails-0.54.0.dist-info → pycontrails-0.54.2.dist-info}/top_level.txt +0 -0
pycontrails/_version.py
CHANGED
|
@@ -4,14 +4,20 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import abc
|
|
6
6
|
import dataclasses
|
|
7
|
+
import sys
|
|
7
8
|
import warnings
|
|
8
9
|
from typing import Any, Generic, NoReturn, overload
|
|
9
10
|
|
|
11
|
+
if sys.version_info >= (3, 12):
|
|
12
|
+
from typing import override
|
|
13
|
+
else:
|
|
14
|
+
from typing_extensions import override
|
|
15
|
+
|
|
10
16
|
import numpy as np
|
|
11
17
|
import numpy.typing as npt
|
|
12
|
-
from overrides import overrides
|
|
13
18
|
|
|
14
19
|
from pycontrails.core import flight, fuel
|
|
20
|
+
from pycontrails.core.fleet import Fleet
|
|
15
21
|
from pycontrails.core.flight import Flight
|
|
16
22
|
from pycontrails.core.met import MetDataset
|
|
17
23
|
from pycontrails.core.models import Model, ModelParams, interpolate_met
|
|
@@ -76,6 +82,10 @@ class AircraftPerformance(Model):
|
|
|
76
82
|
|
|
77
83
|
source: Flight
|
|
78
84
|
|
|
85
|
+
@abc.abstractmethod
|
|
86
|
+
@overload
|
|
87
|
+
def eval(self, source: Fleet, **params: Any) -> Fleet: ...
|
|
88
|
+
|
|
79
89
|
@abc.abstractmethod
|
|
80
90
|
@overload
|
|
81
91
|
def eval(self, source: Flight, **params: Any) -> Flight: ...
|
|
@@ -118,7 +128,7 @@ class AircraftPerformance(Model):
|
|
|
118
128
|
Flight trajectory with aircraft performance data.
|
|
119
129
|
"""
|
|
120
130
|
|
|
121
|
-
@
|
|
131
|
+
@override
|
|
122
132
|
def set_source_met(self, *args: Any, **kwargs: Any) -> None:
|
|
123
133
|
fill_with_isa = self.params["fill_low_altitude_with_isa_temperature"]
|
|
124
134
|
if fill_with_isa and (self.met is None or "air_temperature" not in self.met):
|
|
@@ -467,10 +477,11 @@ class AircraftPerformance(Model):
|
|
|
467
477
|
tas[cond] = self.source.segment_groundspeed()[cond]
|
|
468
478
|
return tas
|
|
469
479
|
|
|
470
|
-
|
|
471
|
-
self.met is None
|
|
480
|
+
wind_available = ("eastward_wind" in self.source and "northward_wind" in self.source) or (
|
|
481
|
+
self.met is not None and "eastward_wind" in self.met and "northward_wind" in self.met
|
|
472
482
|
)
|
|
473
|
-
|
|
483
|
+
|
|
484
|
+
if not wind_available:
|
|
474
485
|
if fill_with_groundspeed:
|
|
475
486
|
tas = self.source.segment_groundspeed()
|
|
476
487
|
self.source["true_airspeed"] = tas
|
pycontrails/core/cache.py
CHANGED
|
@@ -7,14 +7,18 @@ import logging
|
|
|
7
7
|
import os
|
|
8
8
|
import pathlib
|
|
9
9
|
import shutil
|
|
10
|
+
import sys
|
|
10
11
|
import warnings
|
|
11
12
|
from abc import ABC, abstractmethod
|
|
12
13
|
from collections.abc import Sequence
|
|
13
14
|
from typing import TYPE_CHECKING, Any
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
if sys.version_info >= (3, 12):
|
|
17
|
+
from typing import override
|
|
18
|
+
else:
|
|
19
|
+
from typing_extensions import override
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
18
22
|
|
|
19
23
|
from pycontrails.utils import dependencies
|
|
20
24
|
|
|
@@ -203,20 +207,20 @@ class DiskCacheStore(CacheStore):
|
|
|
203
207
|
return f"DiskCacheStore: {self.cache_dir}"
|
|
204
208
|
|
|
205
209
|
@property
|
|
206
|
-
@
|
|
210
|
+
@override
|
|
207
211
|
def size(self) -> float:
|
|
208
212
|
disk_path = pathlib.Path(self.cache_dir)
|
|
209
213
|
size = sum(f.stat().st_size for f in disk_path.rglob("*") if f.is_file())
|
|
210
214
|
logger.debug("Disk cache size %s bytes", size)
|
|
211
215
|
return size / 1e6
|
|
212
216
|
|
|
213
|
-
@
|
|
217
|
+
@override
|
|
214
218
|
def listdir(self, path: str = "") -> list[str]:
|
|
215
219
|
path = self.path(path)
|
|
216
220
|
iter_ = pathlib.Path(path).iterdir()
|
|
217
221
|
return sorted(str(f.relative_to(path)) for f in iter_)
|
|
218
222
|
|
|
219
|
-
@
|
|
223
|
+
@override
|
|
220
224
|
def path(self, cache_path: str) -> str:
|
|
221
225
|
if cache_path.startswith(self.cache_dir):
|
|
222
226
|
disk_path = pathlib.Path(cache_path)
|
|
@@ -228,7 +232,7 @@ class DiskCacheStore(CacheStore):
|
|
|
228
232
|
|
|
229
233
|
return str(disk_path)
|
|
230
234
|
|
|
231
|
-
@
|
|
235
|
+
@override
|
|
232
236
|
def exists(self, cache_path: str) -> bool:
|
|
233
237
|
disk_path = pathlib.Path(self.path(cache_path))
|
|
234
238
|
return disk_path.exists()
|
|
@@ -551,7 +555,7 @@ class GCPCacheStore(CacheStore):
|
|
|
551
555
|
return self._client
|
|
552
556
|
|
|
553
557
|
@property
|
|
554
|
-
@
|
|
558
|
+
@override
|
|
555
559
|
def size(self) -> float:
|
|
556
560
|
# get list of blobs below this path
|
|
557
561
|
blobs = self._bucket.list_blobs(prefix=self.cache_dir)
|
|
@@ -559,7 +563,7 @@ class GCPCacheStore(CacheStore):
|
|
|
559
563
|
logger.debug("GCP cache size %s bytes", size)
|
|
560
564
|
return size / 1e6
|
|
561
565
|
|
|
562
|
-
@
|
|
566
|
+
@override
|
|
563
567
|
def listdir(self, path: str = "") -> list[str]:
|
|
564
568
|
# I don't necessarily think we want to implement this .... it might be
|
|
565
569
|
# very slow if the bucket is large. BUT, it won't be slower than the size
|
|
@@ -572,7 +576,7 @@ class GCPCacheStore(CacheStore):
|
|
|
572
576
|
"list files in the local disk cache."
|
|
573
577
|
)
|
|
574
578
|
|
|
575
|
-
@
|
|
579
|
+
@override
|
|
576
580
|
def path(self, cache_path: str) -> str:
|
|
577
581
|
if cache_path.startswith(self.cache_dir):
|
|
578
582
|
return cache_path
|
|
@@ -604,7 +608,7 @@ class GCPCacheStore(CacheStore):
|
|
|
604
608
|
bucket_path = self.path(cache_path)
|
|
605
609
|
return f"gs://{self.bucket}/{bucket_path}"
|
|
606
610
|
|
|
607
|
-
@
|
|
611
|
+
@override
|
|
608
612
|
def exists(self, cache_path: str) -> bool:
|
|
609
613
|
# see if file is in the mirror disk cache
|
|
610
614
|
if self._disk_cache.exists(cache_path):
|
pycontrails/core/fleet.py
CHANGED
|
@@ -2,14 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import sys
|
|
5
6
|
import warnings
|
|
6
7
|
from collections.abc import Iterable
|
|
7
8
|
from typing import Any, NoReturn
|
|
8
9
|
|
|
10
|
+
if sys.version_info >= (3, 11):
|
|
11
|
+
from typing import Self
|
|
12
|
+
else:
|
|
13
|
+
from typing_extensions import Self
|
|
14
|
+
|
|
15
|
+
if sys.version_info >= (3, 12):
|
|
16
|
+
from typing import override
|
|
17
|
+
else:
|
|
18
|
+
from typing_extensions import override
|
|
19
|
+
|
|
9
20
|
import numpy as np
|
|
10
21
|
import numpy.typing as npt
|
|
11
22
|
import pandas as pd
|
|
12
|
-
from overrides import overrides
|
|
13
23
|
|
|
14
24
|
from pycontrails.core.flight import Flight
|
|
15
25
|
from pycontrails.core.fuel import Fuel, JetA
|
|
@@ -121,13 +131,13 @@ class Fleet(Flight):
|
|
|
121
131
|
|
|
122
132
|
return final_waypoints, fl_attrs
|
|
123
133
|
|
|
124
|
-
@
|
|
134
|
+
@override
|
|
125
135
|
def copy(self, **kwargs: Any) -> Fleet:
|
|
126
136
|
kwargs.setdefault("fuel", self.fuel)
|
|
127
137
|
kwargs.setdefault("fl_attrs", self.fl_attrs)
|
|
128
138
|
return super().copy(**kwargs)
|
|
129
139
|
|
|
130
|
-
@
|
|
140
|
+
@override
|
|
131
141
|
def filter(self, mask: npt.NDArray[np.bool_], copy: bool = True, **kwargs: Any) -> Fleet:
|
|
132
142
|
kwargs.setdefault("fuel", self.fuel)
|
|
133
143
|
|
|
@@ -137,7 +147,7 @@ class Fleet(Flight):
|
|
|
137
147
|
|
|
138
148
|
return super().filter(mask, copy=copy, **kwargs)
|
|
139
149
|
|
|
140
|
-
@
|
|
150
|
+
@override
|
|
141
151
|
def sort(self, by: str | list[str]) -> NoReturn:
|
|
142
152
|
msg = (
|
|
143
153
|
"Fleet.sort is not implemented. A Fleet instance must be sorted "
|
|
@@ -154,7 +164,7 @@ class Fleet(Flight):
|
|
|
154
164
|
broadcast_numeric: bool = True,
|
|
155
165
|
copy: bool = True,
|
|
156
166
|
attrs: dict[str, Any] | None = None,
|
|
157
|
-
) ->
|
|
167
|
+
) -> Self:
|
|
158
168
|
"""Instantiate a :class:`Fleet` instance from an iterable of :class:`Flight`.
|
|
159
169
|
|
|
160
170
|
.. versionchanged:: 0.49.3
|
|
@@ -196,17 +206,15 @@ class Fleet(Flight):
|
|
|
196
206
|
|
|
197
207
|
fl_attrs: dict[str, Any] = {}
|
|
198
208
|
|
|
199
|
-
# Pluck from the first flight to get fuel
|
|
209
|
+
# Pluck from the first flight to get fuel and data_keys
|
|
200
210
|
fuel = seq[0].fuel
|
|
201
211
|
data_keys = set(seq[0]) # convert to a new instance to because we mutate seq[0]
|
|
202
|
-
crs = seq[0].attrs["crs"]
|
|
203
212
|
|
|
204
213
|
for fl in seq:
|
|
205
214
|
_validate_fl(
|
|
206
215
|
fl,
|
|
207
216
|
fl_attrs=fl_attrs,
|
|
208
217
|
data_keys=data_keys,
|
|
209
|
-
crs=crs,
|
|
210
218
|
fuel=fuel,
|
|
211
219
|
broadcast_numeric=broadcast_numeric,
|
|
212
220
|
)
|
|
@@ -316,44 +324,39 @@ class Fleet(Flight):
|
|
|
316
324
|
# use case isn't seen.
|
|
317
325
|
return np.concatenate(tas)
|
|
318
326
|
|
|
319
|
-
@
|
|
327
|
+
@override
|
|
320
328
|
def segment_groundspeed(self, *args: Any, **kwargs: Any) -> npt.NDArray[np.float64]:
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
raise NotImplementedError
|
|
329
|
+
fls = self.to_flight_list(copy=False)
|
|
330
|
+
gs = [fl.segment_groundspeed(*args, **kwargs) for fl in fls]
|
|
331
|
+
return np.concatenate(gs)
|
|
325
332
|
|
|
326
|
-
@
|
|
333
|
+
@override
|
|
327
334
|
def resample_and_fill(self, *args: Any, **kwargs: Any) -> Fleet:
|
|
328
335
|
flights = self.to_flight_list(copy=False)
|
|
329
336
|
flights = [fl.resample_and_fill(*args, **kwargs) for fl in flights]
|
|
330
337
|
return type(self).from_seq(flights, copy=False, broadcast_numeric=False, attrs=self.attrs)
|
|
331
338
|
|
|
332
|
-
@
|
|
339
|
+
@override
|
|
333
340
|
def segment_length(self) -> npt.NDArray[np.float64]:
|
|
334
341
|
return np.where(self.final_waypoints, np.nan, super().segment_length())
|
|
335
342
|
|
|
336
343
|
@property
|
|
337
|
-
@
|
|
344
|
+
@override
|
|
338
345
|
def max_distance_gap(self) -> float:
|
|
339
|
-
if self.attrs["crs"] != "EPSG:4326":
|
|
340
|
-
msg = "Only implemented for EPSG:4326 CRS."
|
|
341
|
-
raise NotImplementedError(msg)
|
|
342
|
-
|
|
343
346
|
return np.nanmax(self.segment_length()).item()
|
|
344
347
|
|
|
345
|
-
@
|
|
348
|
+
@override
|
|
346
349
|
def segment_azimuth(self) -> npt.NDArray[np.float64]:
|
|
347
350
|
return np.where(self.final_waypoints, np.nan, super().segment_azimuth())
|
|
348
351
|
|
|
349
|
-
@
|
|
352
|
+
@override
|
|
350
353
|
def segment_angle(self) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
|
|
351
354
|
sin_a, cos_a = super().segment_angle()
|
|
352
355
|
sin_a[self.final_waypoints] = np.nan
|
|
353
356
|
cos_a[self.final_waypoints] = np.nan
|
|
354
357
|
return sin_a, cos_a
|
|
355
358
|
|
|
356
|
-
@
|
|
359
|
+
@override
|
|
357
360
|
def clean_and_resample(
|
|
358
361
|
self,
|
|
359
362
|
freq: str = "1min",
|
|
@@ -400,7 +403,6 @@ def _validate_fl(
|
|
|
400
403
|
*,
|
|
401
404
|
fl_attrs: dict[str, Any],
|
|
402
405
|
data_keys: set[str],
|
|
403
|
-
crs: str,
|
|
404
406
|
fuel: Fuel,
|
|
405
407
|
broadcast_numeric: bool,
|
|
406
408
|
) -> None:
|
|
@@ -419,8 +421,6 @@ def _validate_fl(
|
|
|
419
421
|
Set of data keys expected in each flight.
|
|
420
422
|
fuel : Fuel
|
|
421
423
|
Fuel used all flights
|
|
422
|
-
crs : str
|
|
423
|
-
CRS to use all flights
|
|
424
424
|
broadcast_numeric : bool
|
|
425
425
|
If True, broadcast numeric attributes to data variables.
|
|
426
426
|
|
|
@@ -429,7 +429,7 @@ def _validate_fl(
|
|
|
429
429
|
KeyError
|
|
430
430
|
``fl`` does not have a ``flight_id`` key in :attr:`attrs`.
|
|
431
431
|
ValueError
|
|
432
|
-
If ``flight_id`` is duplicated or
|
|
432
|
+
If ``flight_id`` is duplicated or if ``fuel`` or ``data_keys`` are inconsistent.
|
|
433
433
|
"""
|
|
434
434
|
flight_id = _extract_flight_id(fl)
|
|
435
435
|
|
|
@@ -446,13 +446,6 @@ def _validate_fl(
|
|
|
446
446
|
"The 'fuel' attributes must be consistent between flights in a Fleet."
|
|
447
447
|
)
|
|
448
448
|
raise ValueError(msg)
|
|
449
|
-
if fl.attrs["crs"] != crs:
|
|
450
|
-
msg = (
|
|
451
|
-
f"CRS on Flight {flight_id} ({fl.attrs['crs']}) "
|
|
452
|
-
f"is not inconsistent with previous flights ({crs}). "
|
|
453
|
-
"The 'crs' attributes must be consistent between flights in a Fleet."
|
|
454
|
-
)
|
|
455
|
-
raise ValueError(msg)
|
|
456
449
|
if fl.data.keys() != data_keys:
|
|
457
450
|
msg = (
|
|
458
451
|
f"Data keys on Flight {flight_id} ({fl.data.keys()}) "
|
pycontrails/core/flight.py
CHANGED
|
@@ -4,14 +4,20 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import enum
|
|
6
6
|
import logging
|
|
7
|
+
import sys
|
|
7
8
|
import warnings
|
|
8
9
|
from typing import TYPE_CHECKING, Any, NoReturn, TypeVar
|
|
9
10
|
|
|
11
|
+
if sys.version_info >= (3, 12):
|
|
12
|
+
from typing import override
|
|
13
|
+
else:
|
|
14
|
+
from typing_extensions import override
|
|
15
|
+
|
|
16
|
+
|
|
10
17
|
import numpy as np
|
|
11
18
|
import numpy.typing as npt
|
|
12
19
|
import pandas as pd
|
|
13
20
|
import scipy.signal
|
|
14
|
-
from overrides import overrides
|
|
15
21
|
|
|
16
22
|
from pycontrails.core.fuel import Fuel, JetA
|
|
17
23
|
from pycontrails.core.vector import AttrDict, GeoVectorDataset, VectorDataDict, VectorDataset
|
|
@@ -75,9 +81,6 @@ class Flight(GeoVectorDataset):
|
|
|
75
81
|
Expect altitude in [:math:`m`].
|
|
76
82
|
Expect pressure level (`level`) in [:math:`hPa`].
|
|
77
83
|
|
|
78
|
-
Use the attribute :attr:`attrs["crs"]` to specify coordinate reference system
|
|
79
|
-
using `PROJ <https://proj.org/>`_ or `EPSG <https://epsg.org/home.html>`_ syntax.
|
|
80
|
-
|
|
81
84
|
Parameters
|
|
82
85
|
----------
|
|
83
86
|
data : dict[str, np.ndarray] | pd.DataFrame | VectorDataDict | VectorDataset | None
|
|
@@ -159,7 +162,7 @@ class Flight(GeoVectorDataset):
|
|
|
159
162
|
... })
|
|
160
163
|
>>> fl = Flight(data=df, flight_id=123) # specify a flight_id by keyword
|
|
161
164
|
>>> fl
|
|
162
|
-
Flight [4 keys x 500 length,
|
|
165
|
+
Flight [4 keys x 500 length, 1 attributes]
|
|
163
166
|
Keys: longitude, latitude, altitude, time
|
|
164
167
|
Attributes:
|
|
165
168
|
time [2021-01-01 10:00:00, 2021-01-01 15:00:00]
|
|
@@ -167,7 +170,6 @@ class Flight(GeoVectorDataset):
|
|
|
167
170
|
latitude [10.0, 40.0]
|
|
168
171
|
altitude [10500.0, 10500.0]
|
|
169
172
|
flight_id 123
|
|
170
|
-
crs EPSG:4326
|
|
171
173
|
|
|
172
174
|
>>> # Create `Flight` from keywords
|
|
173
175
|
>>> fl = Flight(
|
|
@@ -177,14 +179,13 @@ class Flight(GeoVectorDataset):
|
|
|
177
179
|
... time=pd.date_range('2021-01-01T12', '2021-01-01T14', periods=200),
|
|
178
180
|
... )
|
|
179
181
|
>>> fl
|
|
180
|
-
Flight [4 keys x 200 length,
|
|
182
|
+
Flight [4 keys x 200 length, 0 attributes]
|
|
181
183
|
Keys: longitude, latitude, time, altitude
|
|
182
184
|
Attributes:
|
|
183
185
|
time [2021-01-01 12:00:00, 2021-01-01 14:00:00]
|
|
184
186
|
longitude [20.0, 30.0]
|
|
185
187
|
latitude [30.0, 40.0]
|
|
186
188
|
altitude [11000.0, 11000.0]
|
|
187
|
-
crs EPSG:4326
|
|
188
189
|
|
|
189
190
|
>>> # Access the underlying data as DataFrame
|
|
190
191
|
>>> fl.dataframe.head()
|
|
@@ -281,19 +282,19 @@ class Flight(GeoVectorDataset):
|
|
|
281
282
|
"'drop_duplicated_times=True' or call the 'resample_and_fill' method."
|
|
282
283
|
)
|
|
283
284
|
|
|
284
|
-
@
|
|
285
|
+
@override
|
|
285
286
|
def copy(self: FlightType, **kwargs: Any) -> FlightType:
|
|
286
287
|
kwargs.setdefault("fuel", self.fuel)
|
|
287
288
|
return super().copy(**kwargs)
|
|
288
289
|
|
|
289
|
-
@
|
|
290
|
+
@override
|
|
290
291
|
def filter(
|
|
291
292
|
self: FlightType, mask: npt.NDArray[np.bool_], copy: bool = True, **kwargs: Any
|
|
292
293
|
) -> FlightType:
|
|
293
294
|
kwargs.setdefault("fuel", self.fuel)
|
|
294
295
|
return super().filter(mask, copy=copy, **kwargs)
|
|
295
296
|
|
|
296
|
-
@
|
|
297
|
+
@override
|
|
297
298
|
def sort(self, by: str | list[str]) -> NoReturn:
|
|
298
299
|
msg = (
|
|
299
300
|
"Flight.sort is not implemented. A Flight instance is automatically sorted "
|
|
@@ -369,11 +370,6 @@ class Flight(GeoVectorDataset):
|
|
|
369
370
|
float
|
|
370
371
|
Maximum distance between waypoints, [:math:`m`]
|
|
371
372
|
|
|
372
|
-
Raises
|
|
373
|
-
------
|
|
374
|
-
NotImplementedError
|
|
375
|
-
Raises when attr:`attrs["crs"]` is not EPSG:4326
|
|
376
|
-
|
|
377
373
|
Examples
|
|
378
374
|
--------
|
|
379
375
|
>>> import numpy as np
|
|
@@ -386,9 +382,6 @@ class Flight(GeoVectorDataset):
|
|
|
386
382
|
>>> fl.max_distance_gap
|
|
387
383
|
np.float64(7391.27...)
|
|
388
384
|
"""
|
|
389
|
-
if self.attrs["crs"] != "EPSG:4326":
|
|
390
|
-
raise NotImplementedError("Only implemented for EPSG:4326 CRS.")
|
|
391
|
-
|
|
392
385
|
return self.segment_length()[:-1].max()
|
|
393
386
|
|
|
394
387
|
@property
|
|
@@ -400,11 +393,6 @@ class Flight(GeoVectorDataset):
|
|
|
400
393
|
float
|
|
401
394
|
Total flight length, [:math:`m`]
|
|
402
395
|
|
|
403
|
-
Raises
|
|
404
|
-
------
|
|
405
|
-
NotImplementedError
|
|
406
|
-
Raises when attr:`attrs["crs"]` is not EPSG:4326
|
|
407
|
-
|
|
408
396
|
Examples
|
|
409
397
|
--------
|
|
410
398
|
>>> import numpy as np
|
|
@@ -417,9 +405,6 @@ class Flight(GeoVectorDataset):
|
|
|
417
405
|
>>> fl.length
|
|
418
406
|
np.float64(1436924.67...)
|
|
419
407
|
"""
|
|
420
|
-
if self.attrs["crs"] != "EPSG:4326":
|
|
421
|
-
raise NotImplementedError("Only implemented for EPSG:4326 CRS.")
|
|
422
|
-
|
|
423
408
|
# drop off the nan
|
|
424
409
|
return np.nansum(self.segment_length()[:-1])
|
|
425
410
|
|
|
@@ -461,11 +446,6 @@ class Flight(GeoVectorDataset):
|
|
|
461
446
|
npt.NDArray[np.float64]
|
|
462
447
|
Array of great circle distances in [:math:`m`] between waypoints
|
|
463
448
|
|
|
464
|
-
Raises
|
|
465
|
-
------
|
|
466
|
-
NotImplementedError
|
|
467
|
-
Raises when attr:`attrs["crs"]` is not EPSG:4326
|
|
468
|
-
|
|
469
449
|
Examples
|
|
470
450
|
--------
|
|
471
451
|
>>> from pycontrails import Flight
|
|
@@ -484,9 +464,6 @@ class Flight(GeoVectorDataset):
|
|
|
484
464
|
:func:`segment_haversine`
|
|
485
465
|
:meth:`segment_length`
|
|
486
466
|
"""
|
|
487
|
-
if self.attrs["crs"] != "EPSG:4326":
|
|
488
|
-
raise NotImplementedError("Only implemented for EPSG:4326 CRS.")
|
|
489
|
-
|
|
490
467
|
return geo.segment_haversine(self["longitude"], self["latitude"])
|
|
491
468
|
|
|
492
469
|
def segment_length(self) -> npt.NDArray[np.float64]:
|
|
@@ -500,11 +477,6 @@ class Flight(GeoVectorDataset):
|
|
|
500
477
|
npt.NDArray[np.float64]
|
|
501
478
|
Array of distances in [:math:`m`] between waypoints
|
|
502
479
|
|
|
503
|
-
Raises
|
|
504
|
-
------
|
|
505
|
-
NotImplementedError
|
|
506
|
-
Raises when attr:`attrs["crs"]` is not EPSG:4326
|
|
507
|
-
|
|
508
480
|
Examples
|
|
509
481
|
--------
|
|
510
482
|
>>> from pycontrails import Flight
|
|
@@ -522,9 +494,6 @@ class Flight(GeoVectorDataset):
|
|
|
522
494
|
--------
|
|
523
495
|
:func:`segment_length`
|
|
524
496
|
"""
|
|
525
|
-
if self.attrs["crs"] != "EPSG:4326":
|
|
526
|
-
raise NotImplementedError("Only implemented for EPSG:4326 CRS.")
|
|
527
|
-
|
|
528
497
|
return geo.segment_length(self["longitude"], self["latitude"], self.altitude)
|
|
529
498
|
|
|
530
499
|
def segment_angle(self) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
|
|
@@ -1128,7 +1097,7 @@ class Flight(GeoVectorDataset):
|
|
|
1128
1097
|
|
|
1129
1098
|
Notes
|
|
1130
1099
|
-----
|
|
1131
|
-
Algorithm is derived from :meth:`traffic.core.
|
|
1100
|
+
Algorithm is derived from :meth:`traffic.core.Flight.filter`.
|
|
1132
1101
|
|
|
1133
1102
|
The `traffic
|
|
1134
1103
|
<https://traffic-viz.github.io/api_reference/traffic.core.flight.html#traffic.core.Flight.filter>`_
|
|
@@ -1304,15 +1273,7 @@ class Flight(GeoVectorDataset):
|
|
|
1304
1273
|
pd.DataFrame | None
|
|
1305
1274
|
Generated waypoints to be merged into underlying :attr:`data`.
|
|
1306
1275
|
Return `None` if no new waypoints are created.
|
|
1307
|
-
|
|
1308
|
-
Raises
|
|
1309
|
-
------
|
|
1310
|
-
NotImplementedError
|
|
1311
|
-
Raises when attr:`attrs["crs"]` is not EPSG:4326
|
|
1312
1276
|
"""
|
|
1313
|
-
if self.attrs["crs"] != "EPSG:4326":
|
|
1314
|
-
raise NotImplementedError("Only implemented for EPSG:4326 CRS.")
|
|
1315
|
-
|
|
1316
1277
|
# Omit the final nan and ensure index + 1 (below) is well defined
|
|
1317
1278
|
segs = self.segment_haversine()[:-1]
|
|
1318
1279
|
|
|
@@ -1431,7 +1392,7 @@ class Flight(GeoVectorDataset):
|
|
|
1431
1392
|
if key is not None and key not in self.dataframe.columns:
|
|
1432
1393
|
raise KeyError(f"Column {key} does not exist in data.")
|
|
1433
1394
|
|
|
1434
|
-
jump_indices = _antimeridian_index(pd.Series(self["longitude"])
|
|
1395
|
+
jump_indices = _antimeridian_index(pd.Series(self["longitude"]))
|
|
1435
1396
|
|
|
1436
1397
|
def _group_to_feature(group: pd.DataFrame) -> dict[str, str | dict[str, Any]]:
|
|
1437
1398
|
# assigns a different value to each group of consecutive indices
|
|
@@ -1464,19 +1425,21 @@ class Flight(GeoVectorDataset):
|
|
|
1464
1425
|
return {"type": "FeatureCollection", "features": features}
|
|
1465
1426
|
|
|
1466
1427
|
def to_traffic(self) -> traffic.core.Flight:
|
|
1467
|
-
"""Convert
|
|
1468
|
-
|
|
1469
|
-
See https://traffic-viz.github.io/traffic.core.flight.html#traffic.core.Flight
|
|
1428
|
+
"""Convert to :class:`traffic.core.Flight`instance.
|
|
1470
1429
|
|
|
1471
1430
|
Returns
|
|
1472
1431
|
-------
|
|
1473
|
-
|
|
1474
|
-
|
|
1432
|
+
traffic.core.Flight
|
|
1433
|
+
traffic flight instance
|
|
1475
1434
|
|
|
1476
1435
|
Raises
|
|
1477
1436
|
------
|
|
1478
1437
|
ModuleNotFoundError
|
|
1479
1438
|
`traffic` package not installed
|
|
1439
|
+
|
|
1440
|
+
See Also
|
|
1441
|
+
--------
|
|
1442
|
+
:class:`traffic.core.Flight`
|
|
1480
1443
|
"""
|
|
1481
1444
|
try:
|
|
1482
1445
|
import traffic.core
|
|
@@ -1515,8 +1478,6 @@ class Flight(GeoVectorDataset):
|
|
|
1515
1478
|
------
|
|
1516
1479
|
KeyError
|
|
1517
1480
|
:attr:`data` does not contain column ``key``
|
|
1518
|
-
NotImplementedError
|
|
1519
|
-
Raised when ``attrs["crs"]`` is not EPSG:4326
|
|
1520
1481
|
|
|
1521
1482
|
Examples
|
|
1522
1483
|
--------
|
|
@@ -1557,8 +1518,6 @@ class Flight(GeoVectorDataset):
|
|
|
1557
1518
|
"""
|
|
1558
1519
|
if key not in self.data:
|
|
1559
1520
|
raise KeyError(f"Column {key} does not exist in data.")
|
|
1560
|
-
if self.attrs["crs"] != "EPSG:4326":
|
|
1561
|
-
raise NotImplementedError("Only implemented for EPSG:4326 CRS.")
|
|
1562
1521
|
|
|
1563
1522
|
# The column of interest may contain floating point values less than 1.
|
|
1564
1523
|
# In this case, if the default threshold is not changed, warn the user that the behavior
|
|
@@ -1668,40 +1627,23 @@ def _return_linestring(data: dict[str, npt.NDArray[np.float64]]) -> list[list[fl
|
|
|
1668
1627
|
return [list(p) for p in points]
|
|
1669
1628
|
|
|
1670
1629
|
|
|
1671
|
-
def _antimeridian_index(longitude: pd.Series
|
|
1630
|
+
def _antimeridian_index(longitude: pd.Series) -> list[int]:
|
|
1672
1631
|
"""Return indices after flight crosses antimeridian, or an empty list if flight does not cross.
|
|
1673
1632
|
|
|
1633
|
+
This function assumes EPSG:4326 coordinates.
|
|
1634
|
+
|
|
1674
1635
|
Parameters
|
|
1675
1636
|
----------
|
|
1676
1637
|
longitude : pd.Series
|
|
1677
1638
|
longitude values with an integer index
|
|
1678
|
-
crs : str, optional
|
|
1679
|
-
Coordinate Reference system for longitude specified in EPSG format.
|
|
1680
|
-
Currently only supports "EPSG:4326" and "EPSG:3857".
|
|
1681
1639
|
|
|
1682
1640
|
Returns
|
|
1683
1641
|
-------
|
|
1684
1642
|
list[int]
|
|
1685
1643
|
Indices after jump, or empty list of flight does not cross antimeridian.
|
|
1686
|
-
|
|
1687
|
-
Raises
|
|
1688
|
-
------
|
|
1689
|
-
ValueError
|
|
1690
|
-
CRS is not supported.
|
|
1691
1644
|
"""
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
l1 = (-180.0, -90.0)
|
|
1695
|
-
l2 = (90.0, 180.0)
|
|
1696
|
-
|
|
1697
|
-
# pseudo mercator
|
|
1698
|
-
elif crs in ["EPSG:3857"]:
|
|
1699
|
-
# values calculated through pyproj.Transformer
|
|
1700
|
-
l1 = (-20037508.342789244, -10018754.171394622)
|
|
1701
|
-
l2 = (10018754.171394622, 20037508.342789244)
|
|
1702
|
-
|
|
1703
|
-
else:
|
|
1704
|
-
raise ValueError("CRS must be one of EPSG:4326 or EPSG:3857")
|
|
1645
|
+
l1 = (-180.0, -90.0)
|
|
1646
|
+
l2 = (90.0, 180.0)
|
|
1705
1647
|
|
|
1706
1648
|
# TODO: When nans exist, this method *may* not find the meridian
|
|
1707
1649
|
if np.any(np.isnan(longitude)):
|
|
@@ -1711,9 +1653,7 @@ def _antimeridian_index(longitude: pd.Series, crs: str = "EPSG:4326") -> list[in
|
|
|
1711
1653
|
s2 = longitude.between(*l2)
|
|
1712
1654
|
jump12 = longitude[s1 & s2.shift()]
|
|
1713
1655
|
jump21 = longitude[s1.shift() & s2]
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
return jump_index
|
|
1656
|
+
return pd.concat([jump12, jump21]).index.to_list()
|
|
1717
1657
|
|
|
1718
1658
|
|
|
1719
1659
|
def _sg_filter(
|
|
@@ -2045,18 +1985,18 @@ def filter_altitude(
|
|
|
2045
1985
|
|
|
2046
1986
|
Notes
|
|
2047
1987
|
-----
|
|
2048
|
-
Algorithm is derived from :meth:`traffic.core.
|
|
1988
|
+
Algorithm is derived from :meth:`traffic.core.Flight.filter`.
|
|
2049
1989
|
|
|
2050
|
-
The `traffic
|
|
1990
|
+
The `traffic filter algorithm
|
|
2051
1991
|
<https://traffic-viz.github.io/api_reference/traffic.core.flight.html#traffic.core.Flight.filter>`_
|
|
2052
|
-
|
|
1992
|
+
also computes thresholds on sliding windows
|
|
2053
1993
|
and replaces unacceptable values with NaNs.
|
|
2054
1994
|
|
|
2055
1995
|
Errors may raised if the ``kernel_size`` is too large.
|
|
2056
1996
|
|
|
2057
1997
|
See Also
|
|
2058
1998
|
--------
|
|
2059
|
-
:meth:`traffic.core.
|
|
1999
|
+
:meth:`traffic.core.Flight.filter`
|
|
2060
2000
|
:func:`scipy.signal.medfilt`
|
|
2061
2001
|
"""
|
|
2062
2002
|
if not len(altitude_ft):
|