pycontrails 0.54.0__cp310-cp310-macosx_10_9_x86_64.whl → 0.54.1__cp310-cp310-macosx_10_9_x86_64.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 +9 -3
- pycontrails/core/fleet.py +5 -22
- pycontrails/core/flight.py +9 -77
- pycontrails/core/met.py +30 -45
- pycontrails/core/rgi_cython.cpython-310-darwin.so +0 -0
- pycontrails/core/vector.py +13 -44
- pycontrails/datalib/ecmwf/__init__.py +2 -2
- pycontrails/datalib/ecmwf/arco_era5.py +1 -1
- pycontrails/datalib/ecmwf/era5.py +1 -1
- pycontrails/datalib/ecmwf/era5_model_level.py +3 -3
- pycontrails/datalib/ecmwf/model_levels.py +3 -2
- pycontrails/models/cocipgrid/cocip_grid.py +5 -3
- pycontrails/models/ps_model/ps_model.py +39 -24
- {pycontrails-0.54.0.dist-info → pycontrails-0.54.1.dist-info}/METADATA +1 -1
- {pycontrails-0.54.0.dist-info → pycontrails-0.54.1.dist-info}/RECORD +20 -20
- {pycontrails-0.54.0.dist-info → pycontrails-0.54.1.dist-info}/LICENSE +0 -0
- {pycontrails-0.54.0.dist-info → pycontrails-0.54.1.dist-info}/NOTICE +0 -0
- {pycontrails-0.54.0.dist-info → pycontrails-0.54.1.dist-info}/WHEEL +0 -0
- {pycontrails-0.54.0.dist-info → pycontrails-0.54.1.dist-info}/top_level.txt +0 -0
pycontrails/_version.py
CHANGED
|
@@ -12,6 +12,7 @@ import numpy.typing as npt
|
|
|
12
12
|
from overrides import overrides
|
|
13
13
|
|
|
14
14
|
from pycontrails.core import flight, fuel
|
|
15
|
+
from pycontrails.core.fleet import Fleet
|
|
15
16
|
from pycontrails.core.flight import Flight
|
|
16
17
|
from pycontrails.core.met import MetDataset
|
|
17
18
|
from pycontrails.core.models import Model, ModelParams, interpolate_met
|
|
@@ -76,6 +77,10 @@ class AircraftPerformance(Model):
|
|
|
76
77
|
|
|
77
78
|
source: Flight
|
|
78
79
|
|
|
80
|
+
@abc.abstractmethod
|
|
81
|
+
@overload
|
|
82
|
+
def eval(self, source: Fleet, **params: Any) -> Fleet: ...
|
|
83
|
+
|
|
79
84
|
@abc.abstractmethod
|
|
80
85
|
@overload
|
|
81
86
|
def eval(self, source: Flight, **params: Any) -> Flight: ...
|
|
@@ -467,10 +472,11 @@ class AircraftPerformance(Model):
|
|
|
467
472
|
tas[cond] = self.source.segment_groundspeed()[cond]
|
|
468
473
|
return tas
|
|
469
474
|
|
|
470
|
-
|
|
471
|
-
self.met is None
|
|
475
|
+
wind_available = ("eastward_wind" in self.source and "northward_wind" in self.source) or (
|
|
476
|
+
self.met is not None and "eastward_wind" in self.met and "northward_wind" in self.met
|
|
472
477
|
)
|
|
473
|
-
|
|
478
|
+
|
|
479
|
+
if not wind_available:
|
|
474
480
|
if fill_with_groundspeed:
|
|
475
481
|
tas = self.source.segment_groundspeed()
|
|
476
482
|
self.source["true_airspeed"] = tas
|
pycontrails/core/fleet.py
CHANGED
|
@@ -196,17 +196,15 @@ class Fleet(Flight):
|
|
|
196
196
|
|
|
197
197
|
fl_attrs: dict[str, Any] = {}
|
|
198
198
|
|
|
199
|
-
# Pluck from the first flight to get fuel
|
|
199
|
+
# Pluck from the first flight to get fuel and data_keys
|
|
200
200
|
fuel = seq[0].fuel
|
|
201
201
|
data_keys = set(seq[0]) # convert to a new instance to because we mutate seq[0]
|
|
202
|
-
crs = seq[0].attrs["crs"]
|
|
203
202
|
|
|
204
203
|
for fl in seq:
|
|
205
204
|
_validate_fl(
|
|
206
205
|
fl,
|
|
207
206
|
fl_attrs=fl_attrs,
|
|
208
207
|
data_keys=data_keys,
|
|
209
|
-
crs=crs,
|
|
210
208
|
fuel=fuel,
|
|
211
209
|
broadcast_numeric=broadcast_numeric,
|
|
212
210
|
)
|
|
@@ -318,10 +316,9 @@ class Fleet(Flight):
|
|
|
318
316
|
|
|
319
317
|
@overrides
|
|
320
318
|
def segment_groundspeed(self, *args: Any, **kwargs: Any) -> npt.NDArray[np.float64]:
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
raise NotImplementedError
|
|
319
|
+
fls = self.to_flight_list(copy=False)
|
|
320
|
+
gs = [fl.segment_groundspeed(*args, **kwargs) for fl in fls]
|
|
321
|
+
return np.concatenate(gs)
|
|
325
322
|
|
|
326
323
|
@overrides
|
|
327
324
|
def resample_and_fill(self, *args: Any, **kwargs: Any) -> Fleet:
|
|
@@ -336,10 +333,6 @@ class Fleet(Flight):
|
|
|
336
333
|
@property
|
|
337
334
|
@overrides
|
|
338
335
|
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
336
|
return np.nanmax(self.segment_length()).item()
|
|
344
337
|
|
|
345
338
|
@overrides
|
|
@@ -400,7 +393,6 @@ def _validate_fl(
|
|
|
400
393
|
*,
|
|
401
394
|
fl_attrs: dict[str, Any],
|
|
402
395
|
data_keys: set[str],
|
|
403
|
-
crs: str,
|
|
404
396
|
fuel: Fuel,
|
|
405
397
|
broadcast_numeric: bool,
|
|
406
398
|
) -> None:
|
|
@@ -419,8 +411,6 @@ def _validate_fl(
|
|
|
419
411
|
Set of data keys expected in each flight.
|
|
420
412
|
fuel : Fuel
|
|
421
413
|
Fuel used all flights
|
|
422
|
-
crs : str
|
|
423
|
-
CRS to use all flights
|
|
424
414
|
broadcast_numeric : bool
|
|
425
415
|
If True, broadcast numeric attributes to data variables.
|
|
426
416
|
|
|
@@ -429,7 +419,7 @@ def _validate_fl(
|
|
|
429
419
|
KeyError
|
|
430
420
|
``fl`` does not have a ``flight_id`` key in :attr:`attrs`.
|
|
431
421
|
ValueError
|
|
432
|
-
If ``flight_id`` is duplicated or
|
|
422
|
+
If ``flight_id`` is duplicated or if ``fuel`` or ``data_keys`` are inconsistent.
|
|
433
423
|
"""
|
|
434
424
|
flight_id = _extract_flight_id(fl)
|
|
435
425
|
|
|
@@ -446,13 +436,6 @@ def _validate_fl(
|
|
|
446
436
|
"The 'fuel' attributes must be consistent between flights in a Fleet."
|
|
447
437
|
)
|
|
448
438
|
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
439
|
if fl.data.keys() != data_keys:
|
|
457
440
|
msg = (
|
|
458
441
|
f"Data keys on Flight {flight_id} ({fl.data.keys()}) "
|
pycontrails/core/flight.py
CHANGED
|
@@ -75,9 +75,6 @@ class Flight(GeoVectorDataset):
|
|
|
75
75
|
Expect altitude in [:math:`m`].
|
|
76
76
|
Expect pressure level (`level`) in [:math:`hPa`].
|
|
77
77
|
|
|
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
78
|
Parameters
|
|
82
79
|
----------
|
|
83
80
|
data : dict[str, np.ndarray] | pd.DataFrame | VectorDataDict | VectorDataset | None
|
|
@@ -159,7 +156,7 @@ class Flight(GeoVectorDataset):
|
|
|
159
156
|
... })
|
|
160
157
|
>>> fl = Flight(data=df, flight_id=123) # specify a flight_id by keyword
|
|
161
158
|
>>> fl
|
|
162
|
-
Flight [4 keys x 500 length,
|
|
159
|
+
Flight [4 keys x 500 length, 1 attributes]
|
|
163
160
|
Keys: longitude, latitude, altitude, time
|
|
164
161
|
Attributes:
|
|
165
162
|
time [2021-01-01 10:00:00, 2021-01-01 15:00:00]
|
|
@@ -167,7 +164,6 @@ class Flight(GeoVectorDataset):
|
|
|
167
164
|
latitude [10.0, 40.0]
|
|
168
165
|
altitude [10500.0, 10500.0]
|
|
169
166
|
flight_id 123
|
|
170
|
-
crs EPSG:4326
|
|
171
167
|
|
|
172
168
|
>>> # Create `Flight` from keywords
|
|
173
169
|
>>> fl = Flight(
|
|
@@ -177,14 +173,13 @@ class Flight(GeoVectorDataset):
|
|
|
177
173
|
... time=pd.date_range('2021-01-01T12', '2021-01-01T14', periods=200),
|
|
178
174
|
... )
|
|
179
175
|
>>> fl
|
|
180
|
-
Flight [4 keys x 200 length,
|
|
176
|
+
Flight [4 keys x 200 length, 0 attributes]
|
|
181
177
|
Keys: longitude, latitude, time, altitude
|
|
182
178
|
Attributes:
|
|
183
179
|
time [2021-01-01 12:00:00, 2021-01-01 14:00:00]
|
|
184
180
|
longitude [20.0, 30.0]
|
|
185
181
|
latitude [30.0, 40.0]
|
|
186
182
|
altitude [11000.0, 11000.0]
|
|
187
|
-
crs EPSG:4326
|
|
188
183
|
|
|
189
184
|
>>> # Access the underlying data as DataFrame
|
|
190
185
|
>>> fl.dataframe.head()
|
|
@@ -369,11 +364,6 @@ class Flight(GeoVectorDataset):
|
|
|
369
364
|
float
|
|
370
365
|
Maximum distance between waypoints, [:math:`m`]
|
|
371
366
|
|
|
372
|
-
Raises
|
|
373
|
-
------
|
|
374
|
-
NotImplementedError
|
|
375
|
-
Raises when attr:`attrs["crs"]` is not EPSG:4326
|
|
376
|
-
|
|
377
367
|
Examples
|
|
378
368
|
--------
|
|
379
369
|
>>> import numpy as np
|
|
@@ -386,9 +376,6 @@ class Flight(GeoVectorDataset):
|
|
|
386
376
|
>>> fl.max_distance_gap
|
|
387
377
|
np.float64(7391.27...)
|
|
388
378
|
"""
|
|
389
|
-
if self.attrs["crs"] != "EPSG:4326":
|
|
390
|
-
raise NotImplementedError("Only implemented for EPSG:4326 CRS.")
|
|
391
|
-
|
|
392
379
|
return self.segment_length()[:-1].max()
|
|
393
380
|
|
|
394
381
|
@property
|
|
@@ -400,11 +387,6 @@ class Flight(GeoVectorDataset):
|
|
|
400
387
|
float
|
|
401
388
|
Total flight length, [:math:`m`]
|
|
402
389
|
|
|
403
|
-
Raises
|
|
404
|
-
------
|
|
405
|
-
NotImplementedError
|
|
406
|
-
Raises when attr:`attrs["crs"]` is not EPSG:4326
|
|
407
|
-
|
|
408
390
|
Examples
|
|
409
391
|
--------
|
|
410
392
|
>>> import numpy as np
|
|
@@ -417,9 +399,6 @@ class Flight(GeoVectorDataset):
|
|
|
417
399
|
>>> fl.length
|
|
418
400
|
np.float64(1436924.67...)
|
|
419
401
|
"""
|
|
420
|
-
if self.attrs["crs"] != "EPSG:4326":
|
|
421
|
-
raise NotImplementedError("Only implemented for EPSG:4326 CRS.")
|
|
422
|
-
|
|
423
402
|
# drop off the nan
|
|
424
403
|
return np.nansum(self.segment_length()[:-1])
|
|
425
404
|
|
|
@@ -461,11 +440,6 @@ class Flight(GeoVectorDataset):
|
|
|
461
440
|
npt.NDArray[np.float64]
|
|
462
441
|
Array of great circle distances in [:math:`m`] between waypoints
|
|
463
442
|
|
|
464
|
-
Raises
|
|
465
|
-
------
|
|
466
|
-
NotImplementedError
|
|
467
|
-
Raises when attr:`attrs["crs"]` is not EPSG:4326
|
|
468
|
-
|
|
469
443
|
Examples
|
|
470
444
|
--------
|
|
471
445
|
>>> from pycontrails import Flight
|
|
@@ -484,9 +458,6 @@ class Flight(GeoVectorDataset):
|
|
|
484
458
|
:func:`segment_haversine`
|
|
485
459
|
:meth:`segment_length`
|
|
486
460
|
"""
|
|
487
|
-
if self.attrs["crs"] != "EPSG:4326":
|
|
488
|
-
raise NotImplementedError("Only implemented for EPSG:4326 CRS.")
|
|
489
|
-
|
|
490
461
|
return geo.segment_haversine(self["longitude"], self["latitude"])
|
|
491
462
|
|
|
492
463
|
def segment_length(self) -> npt.NDArray[np.float64]:
|
|
@@ -500,11 +471,6 @@ class Flight(GeoVectorDataset):
|
|
|
500
471
|
npt.NDArray[np.float64]
|
|
501
472
|
Array of distances in [:math:`m`] between waypoints
|
|
502
473
|
|
|
503
|
-
Raises
|
|
504
|
-
------
|
|
505
|
-
NotImplementedError
|
|
506
|
-
Raises when attr:`attrs["crs"]` is not EPSG:4326
|
|
507
|
-
|
|
508
474
|
Examples
|
|
509
475
|
--------
|
|
510
476
|
>>> from pycontrails import Flight
|
|
@@ -522,9 +488,6 @@ class Flight(GeoVectorDataset):
|
|
|
522
488
|
--------
|
|
523
489
|
:func:`segment_length`
|
|
524
490
|
"""
|
|
525
|
-
if self.attrs["crs"] != "EPSG:4326":
|
|
526
|
-
raise NotImplementedError("Only implemented for EPSG:4326 CRS.")
|
|
527
|
-
|
|
528
491
|
return geo.segment_length(self["longitude"], self["latitude"], self.altitude)
|
|
529
492
|
|
|
530
493
|
def segment_angle(self) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
|
|
@@ -1304,15 +1267,7 @@ class Flight(GeoVectorDataset):
|
|
|
1304
1267
|
pd.DataFrame | None
|
|
1305
1268
|
Generated waypoints to be merged into underlying :attr:`data`.
|
|
1306
1269
|
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
1270
|
"""
|
|
1313
|
-
if self.attrs["crs"] != "EPSG:4326":
|
|
1314
|
-
raise NotImplementedError("Only implemented for EPSG:4326 CRS.")
|
|
1315
|
-
|
|
1316
1271
|
# Omit the final nan and ensure index + 1 (below) is well defined
|
|
1317
1272
|
segs = self.segment_haversine()[:-1]
|
|
1318
1273
|
|
|
@@ -1431,7 +1386,7 @@ class Flight(GeoVectorDataset):
|
|
|
1431
1386
|
if key is not None and key not in self.dataframe.columns:
|
|
1432
1387
|
raise KeyError(f"Column {key} does not exist in data.")
|
|
1433
1388
|
|
|
1434
|
-
jump_indices = _antimeridian_index(pd.Series(self["longitude"])
|
|
1389
|
+
jump_indices = _antimeridian_index(pd.Series(self["longitude"]))
|
|
1435
1390
|
|
|
1436
1391
|
def _group_to_feature(group: pd.DataFrame) -> dict[str, str | dict[str, Any]]:
|
|
1437
1392
|
# assigns a different value to each group of consecutive indices
|
|
@@ -1515,8 +1470,6 @@ class Flight(GeoVectorDataset):
|
|
|
1515
1470
|
------
|
|
1516
1471
|
KeyError
|
|
1517
1472
|
:attr:`data` does not contain column ``key``
|
|
1518
|
-
NotImplementedError
|
|
1519
|
-
Raised when ``attrs["crs"]`` is not EPSG:4326
|
|
1520
1473
|
|
|
1521
1474
|
Examples
|
|
1522
1475
|
--------
|
|
@@ -1557,8 +1510,6 @@ class Flight(GeoVectorDataset):
|
|
|
1557
1510
|
"""
|
|
1558
1511
|
if key not in self.data:
|
|
1559
1512
|
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
1513
|
|
|
1563
1514
|
# The column of interest may contain floating point values less than 1.
|
|
1564
1515
|
# In this case, if the default threshold is not changed, warn the user that the behavior
|
|
@@ -1668,40 +1619,23 @@ def _return_linestring(data: dict[str, npt.NDArray[np.float64]]) -> list[list[fl
|
|
|
1668
1619
|
return [list(p) for p in points]
|
|
1669
1620
|
|
|
1670
1621
|
|
|
1671
|
-
def _antimeridian_index(longitude: pd.Series
|
|
1622
|
+
def _antimeridian_index(longitude: pd.Series) -> list[int]:
|
|
1672
1623
|
"""Return indices after flight crosses antimeridian, or an empty list if flight does not cross.
|
|
1673
1624
|
|
|
1625
|
+
This function assumes EPSG:4326 coordinates.
|
|
1626
|
+
|
|
1674
1627
|
Parameters
|
|
1675
1628
|
----------
|
|
1676
1629
|
longitude : pd.Series
|
|
1677
1630
|
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
1631
|
|
|
1682
1632
|
Returns
|
|
1683
1633
|
-------
|
|
1684
1634
|
list[int]
|
|
1685
1635
|
Indices after jump, or empty list of flight does not cross antimeridian.
|
|
1686
|
-
|
|
1687
|
-
Raises
|
|
1688
|
-
------
|
|
1689
|
-
ValueError
|
|
1690
|
-
CRS is not supported.
|
|
1691
1636
|
"""
|
|
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")
|
|
1637
|
+
l1 = (-180.0, -90.0)
|
|
1638
|
+
l2 = (90.0, 180.0)
|
|
1705
1639
|
|
|
1706
1640
|
# TODO: When nans exist, this method *may* not find the meridian
|
|
1707
1641
|
if np.any(np.isnan(longitude)):
|
|
@@ -1711,9 +1645,7 @@ def _antimeridian_index(longitude: pd.Series, crs: str = "EPSG:4326") -> list[in
|
|
|
1711
1645
|
s2 = longitude.between(*l2)
|
|
1712
1646
|
jump12 = longitude[s1 & s2.shift()]
|
|
1713
1647
|
jump21 = longitude[s1.shift() & s2]
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
return jump_index
|
|
1648
|
+
return pd.concat([jump12, jump21]).index.to_list()
|
|
1717
1649
|
|
|
1718
1650
|
|
|
1719
1651
|
def _sg_filter(
|
pycontrails/core/met.py
CHANGED
|
@@ -70,7 +70,7 @@ class MetBase(ABC, Generic[XArrayType]):
|
|
|
70
70
|
cachestore: CacheStore | None
|
|
71
71
|
|
|
72
72
|
#: Default dimension order for DataArray or Dataset (x, y, z, t)
|
|
73
|
-
dim_order
|
|
73
|
+
dim_order = (
|
|
74
74
|
"longitude",
|
|
75
75
|
"latitude",
|
|
76
76
|
"level",
|
|
@@ -97,17 +97,18 @@ class MetBase(ABC, Generic[XArrayType]):
|
|
|
97
97
|
ValueError
|
|
98
98
|
If data does not contain all four coordinates (longitude, latitude, level, time).
|
|
99
99
|
"""
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
missing = set(self.dim_order).difference(self.data.dims)
|
|
101
|
+
if not missing:
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
dim = sorted(missing)
|
|
105
|
+
msg = f"Meteorology data must contain dimension(s): {dim}."
|
|
106
|
+
if "level" in dim:
|
|
107
|
+
msg += (
|
|
108
|
+
" For single level data, set 'level' coordinate to constant -1 "
|
|
109
|
+
"using `ds = ds.expand_dims({'level': [-1]})`"
|
|
110
|
+
)
|
|
111
|
+
raise ValueError(msg)
|
|
111
112
|
|
|
112
113
|
def _validate_longitude(self) -> None:
|
|
113
114
|
"""Check longitude bounds.
|
|
@@ -123,8 +124,8 @@ class MetBase(ABC, Generic[XArrayType]):
|
|
|
123
124
|
if longitude.dtype != COORD_DTYPE:
|
|
124
125
|
raise ValueError(
|
|
125
126
|
"Longitude values must be of type float64. "
|
|
126
|
-
"
|
|
127
|
-
"
|
|
127
|
+
"Instantiate with 'copy=True' to convert to float64. "
|
|
128
|
+
"Instantiate with 'validate=False' to skip validation."
|
|
128
129
|
)
|
|
129
130
|
|
|
130
131
|
if self.is_wrapped:
|
|
@@ -167,8 +168,8 @@ class MetBase(ABC, Generic[XArrayType]):
|
|
|
167
168
|
if latitude.dtype != COORD_DTYPE:
|
|
168
169
|
raise ValueError(
|
|
169
170
|
"Latitude values must be of type float64. "
|
|
170
|
-
"
|
|
171
|
-
"
|
|
171
|
+
"Instantiate with 'copy=True' to convert to float64. "
|
|
172
|
+
"Instantiate with 'validate=False' to skip validation."
|
|
172
173
|
)
|
|
173
174
|
|
|
174
175
|
if latitude[0] < -90.0:
|
|
@@ -192,10 +193,10 @@ class MetBase(ABC, Generic[XArrayType]):
|
|
|
192
193
|
"""
|
|
193
194
|
indexes = self.indexes
|
|
194
195
|
if not np.all(np.diff(indexes["time"]) > np.timedelta64(0, "ns")):
|
|
195
|
-
raise ValueError("Coordinate
|
|
196
|
+
raise ValueError("Coordinate 'time' not sorted. Instantiate with 'copy=True'.")
|
|
196
197
|
for coord in self.dim_order[:3]: # exclude time, the 4th dimension
|
|
197
198
|
if not np.all(np.diff(indexes[coord]) > 0.0):
|
|
198
|
-
raise ValueError(f"Coordinate '{coord}' not sorted.
|
|
199
|
+
raise ValueError(f"Coordinate '{coord}' not sorted. Instantiate with 'copy=True'.")
|
|
199
200
|
|
|
200
201
|
def _validate_transpose(self) -> None:
|
|
201
202
|
"""Check that data is transposed according to :attr:`dim_order`."""
|
|
@@ -204,11 +205,11 @@ class MetBase(ABC, Generic[XArrayType]):
|
|
|
204
205
|
if da.dims != self.dim_order:
|
|
205
206
|
if key is not None:
|
|
206
207
|
msg = (
|
|
207
|
-
f"Data dimension not transposed on variable '{key}'.
|
|
208
|
+
f"Data dimension not transposed on variable '{key}'. Instantiate with"
|
|
208
209
|
" 'copy=True'."
|
|
209
210
|
)
|
|
210
211
|
else:
|
|
211
|
-
msg = "Data dimension not transposed.
|
|
212
|
+
msg = "Data dimension not transposed. Instantiate with 'copy=True'."
|
|
212
213
|
raise ValueError(msg)
|
|
213
214
|
|
|
214
215
|
data = self.data
|
|
@@ -228,6 +229,12 @@ class MetBase(ABC, Generic[XArrayType]):
|
|
|
228
229
|
self._validate_longitude()
|
|
229
230
|
self._validate_latitude()
|
|
230
231
|
self._validate_transpose()
|
|
232
|
+
if self.data["level"].dtype != COORD_DTYPE:
|
|
233
|
+
raise ValueError(
|
|
234
|
+
"Level values must be of type float64. "
|
|
235
|
+
"Instantiate with 'copy=True' to convert to float64. "
|
|
236
|
+
"Instantiate with 'validate=False' to skip validation."
|
|
237
|
+
)
|
|
231
238
|
|
|
232
239
|
def _preprocess_dims(self, wrap_longitude: bool) -> None:
|
|
233
240
|
"""Confirm DataArray or Dataset include required dimension in a consistent format.
|
|
@@ -363,16 +370,6 @@ class MetBase(ABC, Generic[XArrayType]):
|
|
|
363
370
|
"time": variables["time"].to_numpy(),
|
|
364
371
|
}
|
|
365
372
|
|
|
366
|
-
@property
|
|
367
|
-
def variables(self) -> dict[Hashable, pd.Index]:
|
|
368
|
-
"""See :attr:`indexes`."""
|
|
369
|
-
warnings.warn(
|
|
370
|
-
"The 'variables' property is deprecated and will be removed in a future release. "
|
|
371
|
-
"Use 'indexes' instead.",
|
|
372
|
-
DeprecationWarning,
|
|
373
|
-
)
|
|
374
|
-
return self.indexes
|
|
375
|
-
|
|
376
373
|
@property
|
|
377
374
|
def indexes(self) -> dict[Hashable, pd.Index]:
|
|
378
375
|
"""Low level access to underlying :attr:`data` indexes.
|
|
@@ -745,8 +742,8 @@ class MetDataset(MetBase):
|
|
|
745
742
|
except KeyError as e:
|
|
746
743
|
raise KeyError(
|
|
747
744
|
f"Variable {key} not found. Available variables: {', '.join(self.data.data_vars)}. "
|
|
748
|
-
"To get items (e.g.
|
|
749
|
-
"use the
|
|
745
|
+
"To get items (e.g. 'time' or 'level') from underlying xr.Dataset object, "
|
|
746
|
+
"use the 'data' attribute."
|
|
750
747
|
) from e
|
|
751
748
|
return MetDataArray(da, copy=False, validate=False)
|
|
752
749
|
|
|
@@ -1057,14 +1054,13 @@ class MetDataset(MetBase):
|
|
|
1057
1054
|
>>> era5 = ERA5(time=times, variables=variables, pressure_levels=levels)
|
|
1058
1055
|
>>> met = era5.open_metdataset()
|
|
1059
1056
|
>>> met.to_vector(transfer_attrs=False)
|
|
1060
|
-
GeoVectorDataset [6 keys x 4152960 length,
|
|
1057
|
+
GeoVectorDataset [6 keys x 4152960 length, 0 attributes]
|
|
1061
1058
|
Keys: longitude, latitude, level, time, air_temperature, ..., specific_humidity
|
|
1062
1059
|
Attributes:
|
|
1063
1060
|
time [2022-03-01 00:00:00, 2022-03-01 01:00:00]
|
|
1064
1061
|
longitude [-180.0, 179.75]
|
|
1065
1062
|
latitude [-90.0, 90.0]
|
|
1066
1063
|
altitude [10362.8, 11783.9]
|
|
1067
|
-
crs EPSG:4326
|
|
1068
1064
|
|
|
1069
1065
|
"""
|
|
1070
1066
|
coords_keys = self.data.dims
|
|
@@ -1374,20 +1370,9 @@ class MetDataArray(MetBase):
|
|
|
1374
1370
|
copy: bool = True,
|
|
1375
1371
|
validate: bool = True,
|
|
1376
1372
|
name: Hashable | None = None,
|
|
1377
|
-
**kwargs: Any,
|
|
1378
1373
|
) -> None:
|
|
1379
|
-
# init cache
|
|
1380
1374
|
self.cachestore = cachestore
|
|
1381
1375
|
|
|
1382
|
-
# try to create DataArray out of input data and **kwargs
|
|
1383
|
-
if not isinstance(data, xr.DataArray):
|
|
1384
|
-
warnings.warn(
|
|
1385
|
-
"Input 'data' must be an xarray DataArray. "
|
|
1386
|
-
"Passing arbitrary kwargs will be removed in future versions.",
|
|
1387
|
-
DeprecationWarning,
|
|
1388
|
-
)
|
|
1389
|
-
data = xr.DataArray(data, **kwargs)
|
|
1390
|
-
|
|
1391
1376
|
if copy:
|
|
1392
1377
|
self.data = data.copy()
|
|
1393
1378
|
self._preprocess_dims(wrap_longitude)
|
|
Binary file
|
pycontrails/core/vector.py
CHANGED
|
@@ -1044,7 +1044,6 @@ class VectorDataset:
|
|
|
1044
1044
|
>>> pprint.pprint(fl.to_dict())
|
|
1045
1045
|
{'aircraft_type': 'B737',
|
|
1046
1046
|
'altitude_ft': [38661.0, 38661.0, 38661.0, 38661.0, 38661.0, 38661.0, 38661.0],
|
|
1047
|
-
'crs': 'EPSG:4326',
|
|
1048
1047
|
'latitude': [40.0, 41.724, 43.428, 45.111, 46.769, 48.399, 50.0],
|
|
1049
1048
|
'longitude': [-100.0,
|
|
1050
1049
|
-101.441,
|
|
@@ -1215,9 +1214,6 @@ class GeoVectorDataset(VectorDataset):
|
|
|
1215
1214
|
Each spatial variable is expected to have "float32" or "float64" ``dtype``.
|
|
1216
1215
|
The time variable is expected to have "datetime64[ns]" ``dtype``.
|
|
1217
1216
|
|
|
1218
|
-
Use the attribute :attr:`attr["crs"]` to specify coordinate reference system
|
|
1219
|
-
using `PROJ <https://proj.org/>`_ or `EPSG <https://epsg.org/home.html>`_ syntax.
|
|
1220
|
-
|
|
1221
1217
|
Parameters
|
|
1222
1218
|
----------
|
|
1223
1219
|
data : dict[str, npt.ArrayLike] | pd.DataFrame | VectorDataDict | VectorDataset | None, optional
|
|
@@ -1364,16 +1360,12 @@ class GeoVectorDataset(VectorDataset):
|
|
|
1364
1360
|
if arr.dtype not in float_dtype:
|
|
1365
1361
|
self.update({coord: arr.astype(np.float64)})
|
|
1366
1362
|
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
raise ValueError("EPSG:4326 longitude coordinates should lie between [-180, 180).")
|
|
1374
|
-
latitude = self["latitude"]
|
|
1375
|
-
if np.any(latitude > 90.0) or np.any(latitude < -90.0):
|
|
1376
|
-
raise ValueError("EPSG:4326 latitude coordinates should lie between [-90, 90].")
|
|
1363
|
+
longitude = self["longitude"]
|
|
1364
|
+
if np.any(longitude > 180.0) or np.any(longitude < -180.0):
|
|
1365
|
+
raise ValueError("EPSG:4326 longitude coordinates should lie between [-180, 180).")
|
|
1366
|
+
latitude = self["latitude"]
|
|
1367
|
+
if np.any(latitude > 90.0) or np.any(latitude < -90.0):
|
|
1368
|
+
raise ValueError("EPSG:4326 latitude coordinates should lie between [-90, 90].")
|
|
1377
1369
|
|
|
1378
1370
|
@overrides
|
|
1379
1371
|
def _display_attrs(self) -> dict[str, str]:
|
|
@@ -1530,24 +1522,21 @@ class GeoVectorDataset(VectorDataset):
|
|
|
1530
1522
|
# Utilities
|
|
1531
1523
|
# ------------
|
|
1532
1524
|
|
|
1533
|
-
def transform_crs(
|
|
1534
|
-
self: GeoVectorDatasetType, crs: str, copy: bool = True
|
|
1535
|
-
) -> GeoVectorDatasetType:
|
|
1525
|
+
def transform_crs(self, crs: str) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
|
|
1536
1526
|
"""Transform trajectory data from one coordinate reference system (CRS) to another.
|
|
1537
1527
|
|
|
1538
1528
|
Parameters
|
|
1539
1529
|
----------
|
|
1540
1530
|
crs : str
|
|
1541
1531
|
Target CRS. Passed into to :class:`pyproj.Transformer`. The source CRS
|
|
1542
|
-
is
|
|
1532
|
+
is assumed to be EPSG:4326.
|
|
1543
1533
|
copy : bool, optional
|
|
1544
1534
|
Copy data on transformation. Defaults to True.
|
|
1545
1535
|
|
|
1546
1536
|
Returns
|
|
1547
1537
|
-------
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
:attr:`attrs["crs"]` reflects new crs.
|
|
1538
|
+
tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]
|
|
1539
|
+
New x and y coordinates in the target CRS.
|
|
1551
1540
|
"""
|
|
1552
1541
|
try:
|
|
1553
1542
|
import pyproj
|
|
@@ -1559,14 +1548,9 @@ class GeoVectorDataset(VectorDataset):
|
|
|
1559
1548
|
pycontrails_optional_package="pyproj",
|
|
1560
1549
|
)
|
|
1561
1550
|
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
ret = self.copy() if copy else self
|
|
1566
|
-
|
|
1567
|
-
ret.update(longitude=lon, latitude=lat)
|
|
1568
|
-
ret.attrs.update(crs=crs)
|
|
1569
|
-
return ret
|
|
1551
|
+
crs_from = "EPSG:4326"
|
|
1552
|
+
transformer = pyproj.Transformer.from_crs(crs_from, crs, always_xy=True)
|
|
1553
|
+
return transformer.transform(self["longitude"], self["latitude"])
|
|
1570
1554
|
|
|
1571
1555
|
def T_isa(self) -> npt.NDArray[np.float64]:
|
|
1572
1556
|
"""Calculate the ICAO standard atmosphere temperature at each point.
|
|
@@ -1961,21 +1945,6 @@ class GeoVectorDataset(VectorDataset):
|
|
|
1961
1945
|
"""
|
|
1962
1946
|
return json_utils.dataframe_to_geojson_points(self.dataframe)
|
|
1963
1947
|
|
|
1964
|
-
def to_pseudo_mercator(self: GeoVectorDatasetType, copy: bool = True) -> GeoVectorDatasetType:
|
|
1965
|
-
"""Convert data from :attr:`attrs["crs"]` to Pseudo Mercator (EPSG:3857).
|
|
1966
|
-
|
|
1967
|
-
Parameters
|
|
1968
|
-
----------
|
|
1969
|
-
copy : bool, optional
|
|
1970
|
-
Copy data on transformation.
|
|
1971
|
-
Defaults to True.
|
|
1972
|
-
|
|
1973
|
-
Returns
|
|
1974
|
-
-------
|
|
1975
|
-
GeoVectorDatasetType
|
|
1976
|
-
"""
|
|
1977
|
-
return self.transform_crs("EPSG:3857", copy=copy)
|
|
1978
|
-
|
|
1979
1948
|
# ------------
|
|
1980
1949
|
# Vector to grid
|
|
1981
1950
|
# ------------
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from pycontrails.datalib.ecmwf.arco_era5 import (
|
|
6
|
-
|
|
6
|
+
ERA5ARCO,
|
|
7
7
|
open_arco_era5_model_level_data,
|
|
8
8
|
open_arco_era5_single_level,
|
|
9
9
|
)
|
|
@@ -40,7 +40,7 @@ from pycontrails.datalib.ecmwf.variables import (
|
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
__all__ = [
|
|
43
|
-
"
|
|
43
|
+
"ERA5ARCO",
|
|
44
44
|
"CDSCredentialsNotFound",
|
|
45
45
|
"ERA5",
|
|
46
46
|
"ERA5ModelLevel",
|
|
@@ -197,7 +197,7 @@ def open_arco_era5_single_level(
|
|
|
197
197
|
return MetDataset(ds).data
|
|
198
198
|
|
|
199
199
|
|
|
200
|
-
class
|
|
200
|
+
class ERA5ARCO(ecmwf_common.ECMWFAPI):
|
|
201
201
|
r"""ARCO ERA5 data accessed remotely through Google Cloud Storage.
|
|
202
202
|
|
|
203
203
|
This is a high-level interface to access and cache
|
|
@@ -34,7 +34,7 @@ class ERA5(ECMWFAPI):
|
|
|
34
34
|
"""Class to support ERA5 data access, download, and organization.
|
|
35
35
|
|
|
36
36
|
Requires account with
|
|
37
|
-
`Copernicus Data Portal <https://cds.climate.copernicus.eu/
|
|
37
|
+
`Copernicus Data Portal <https://cds.climate.copernicus.eu/how-to-api>`_
|
|
38
38
|
and local credentials.
|
|
39
39
|
|
|
40
40
|
API credentials can be stored in a ``~/.cdsapirc`` file
|
|
@@ -7,9 +7,9 @@ This module supports
|
|
|
7
7
|
- Local caching of processed netCDF files.
|
|
8
8
|
- Opening processed and cached files as a :class:`pycontrails.MetDataset` object.
|
|
9
9
|
|
|
10
|
-
Consider using :class:`pycontrails.datalib.ecmwf.
|
|
10
|
+
Consider using :class:`pycontrails.datalib.ecmwf.ERA5ARCO`
|
|
11
11
|
to access model-level data from the nominal ERA5 reanalysis between 1959 and 2022.
|
|
12
|
-
:class:`pycontrails.datalib.ecmwf.
|
|
12
|
+
:class:`pycontrails.datalib.ecmwf.ERA5ARCO` accesses data through Google's
|
|
13
13
|
`Analysis-Ready, Cloud Optimized ERA5 dataset <https://cloud.google.com/storage/docs/public-datasets/era5>`_
|
|
14
14
|
and has lower latency than this module, which retrieves data from the
|
|
15
15
|
`Copernicus Climate Data Store <https://cds.climate.copernicus.eu/#!/home>`_.
|
|
@@ -56,7 +56,7 @@ class ERA5ModelLevel(ECMWFAPI):
|
|
|
56
56
|
pressure-level with much lower vertical resolution.
|
|
57
57
|
|
|
58
58
|
Requires account with
|
|
59
|
-
`Copernicus Data Portal <https://cds.climate.copernicus.eu/
|
|
59
|
+
`Copernicus Data Portal <https://cds.climate.copernicus.eu/how-to-api>`_
|
|
60
60
|
and local credentials.
|
|
61
61
|
|
|
62
62
|
API credentials can be stored in a ``~/.cdsapirc`` file
|
|
@@ -384,9 +384,10 @@ def ml_to_pl(
|
|
|
384
384
|
lnsp : xr.DataArray
|
|
385
385
|
Natural logarithm of surface pressure, [:math:`\ln(\text{Pa})`]. If provided,
|
|
386
386
|
``sp`` is ignored. At least one of ``lnsp`` or ``sp`` must be provided.
|
|
387
|
+
The chunking over dimensions in common with ``ds`` must be the same as ``ds``.
|
|
387
388
|
sp : xr.DataArray
|
|
388
|
-
Surface pressure, [:math:`\text{Pa}`].
|
|
389
|
-
|
|
389
|
+
Surface pressure, [:math:`\text{Pa}`]. At least one of ``lnsp`` or ``sp`` must be provided.
|
|
390
|
+
The chunking over dimensions in common with ``ds`` must be the same as ``ds``.
|
|
390
391
|
|
|
391
392
|
Returns
|
|
392
393
|
-------
|
|
@@ -1671,6 +1671,8 @@ def calc_evolve_one_step(
|
|
|
1671
1671
|
segment_length_t1, segment_length_t2
|
|
1672
1672
|
)
|
|
1673
1673
|
|
|
1674
|
+
dt = next_contrail["time"] - curr_contrail["time"]
|
|
1675
|
+
|
|
1674
1676
|
sigma_yy_t2, sigma_zz_t2, sigma_yz_t2 = contrail_properties.plume_temporal_evolution(
|
|
1675
1677
|
width_t1=width_t1,
|
|
1676
1678
|
depth_t1=depth_t1,
|
|
@@ -1679,7 +1681,7 @@ def calc_evolve_one_step(
|
|
|
1679
1681
|
diffuse_h_t1=diffuse_h_t1,
|
|
1680
1682
|
diffuse_v_t1=diffuse_v_t1,
|
|
1681
1683
|
seg_ratio=seg_ratio_t12,
|
|
1682
|
-
dt=
|
|
1684
|
+
dt=dt,
|
|
1683
1685
|
max_depth=params["max_depth"],
|
|
1684
1686
|
)
|
|
1685
1687
|
|
|
@@ -1716,7 +1718,7 @@ def calc_evolve_one_step(
|
|
|
1716
1718
|
dn_dt_agg=dn_dt_agg,
|
|
1717
1719
|
dn_dt_turb=dn_dt_turb,
|
|
1718
1720
|
seg_ratio=seg_ratio_t12,
|
|
1719
|
-
dt=
|
|
1721
|
+
dt=dt,
|
|
1720
1722
|
)
|
|
1721
1723
|
next_contrail["n_ice_per_m"] = n_ice_per_m_t2
|
|
1722
1724
|
|
|
@@ -1737,7 +1739,7 @@ def calc_evolve_one_step(
|
|
|
1737
1739
|
width_t1=width_t1,
|
|
1738
1740
|
width_t2=width_t2,
|
|
1739
1741
|
seg_length_t2=segment_length_t2,
|
|
1740
|
-
dt=
|
|
1742
|
+
dt=dt,
|
|
1741
1743
|
)
|
|
1742
1744
|
# NOTE: This will get masked below if `persistent` is False
|
|
1743
1745
|
# That is, we are taking a right Riemann sum of a decreasing function, so we are
|
|
@@ -20,6 +20,7 @@ from pycontrails.core.aircraft_performance import (
|
|
|
20
20
|
AircraftPerformanceData,
|
|
21
21
|
AircraftPerformanceParams,
|
|
22
22
|
)
|
|
23
|
+
from pycontrails.core.fleet import Fleet
|
|
23
24
|
from pycontrails.core.flight import Flight
|
|
24
25
|
from pycontrails.core.met import MetDataset
|
|
25
26
|
from pycontrails.core.met_var import AirTemperature, EastwardWind, NorthwardWind
|
|
@@ -115,6 +116,9 @@ class PSFlight(AircraftPerformance):
|
|
|
115
116
|
raise KeyError(msg)
|
|
116
117
|
return False
|
|
117
118
|
|
|
119
|
+
@overload
|
|
120
|
+
def eval(self, source: Fleet, **params: Any) -> Fleet: ...
|
|
121
|
+
|
|
118
122
|
@overload
|
|
119
123
|
def eval(self, source: Flight, **params: Any) -> Flight: ...
|
|
120
124
|
|
|
@@ -130,12 +134,20 @@ class PSFlight(AircraftPerformance):
|
|
|
130
134
|
self.set_source_met()
|
|
131
135
|
|
|
132
136
|
# Calculate true airspeed if not included on source
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
self.ensure_true_airspeed_on_source()
|
|
138
|
+
|
|
139
|
+
if isinstance(self.source, Fleet):
|
|
140
|
+
fls = [self._eval_flight(fl) for fl in self.source.to_flight_list()]
|
|
141
|
+
self.source = Fleet.from_seq(fls, attrs=self.source.attrs, broadcast_numeric=False)
|
|
142
|
+
return self.source
|
|
143
|
+
|
|
144
|
+
self.source = self._eval_flight(self.source)
|
|
145
|
+
return self.source
|
|
135
146
|
|
|
147
|
+
def _eval_flight(self, fl: Flight) -> Flight:
|
|
136
148
|
# Ensure aircraft type is available
|
|
137
149
|
try:
|
|
138
|
-
aircraft_type =
|
|
150
|
+
aircraft_type = fl.attrs["aircraft_type"]
|
|
139
151
|
except KeyError as exc:
|
|
140
152
|
msg = "`aircraft_type` required on flight attrs"
|
|
141
153
|
raise KeyError(msg) from exc
|
|
@@ -148,29 +160,32 @@ class PSFlight(AircraftPerformance):
|
|
|
148
160
|
raise KeyError(msg) from exc
|
|
149
161
|
|
|
150
162
|
# Set flight attributes based on engine, if they aren't already defined
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
amass_oew =
|
|
161
|
-
amass_mtow =
|
|
162
|
-
amass_mpl =
|
|
163
|
-
load_factor =
|
|
164
|
-
takeoff_mass =
|
|
165
|
-
q_fuel =
|
|
163
|
+
fl.attrs.setdefault("aircraft_performance_model", self.name)
|
|
164
|
+
fl.attrs.setdefault("aircraft_type_ps", atyp_ps)
|
|
165
|
+
fl.attrs.setdefault("n_engine", aircraft_params.n_engine)
|
|
166
|
+
|
|
167
|
+
fl.attrs.setdefault("wingspan", aircraft_params.wing_span)
|
|
168
|
+
fl.attrs.setdefault("max_mach", aircraft_params.max_mach_num)
|
|
169
|
+
fl.attrs.setdefault("max_altitude", units.ft_to_m(aircraft_params.fl_max * 100.0))
|
|
170
|
+
fl.attrs.setdefault("n_engine", aircraft_params.n_engine)
|
|
171
|
+
|
|
172
|
+
amass_oew = fl.attrs.get("amass_oew", aircraft_params.amass_oew)
|
|
173
|
+
amass_mtow = fl.attrs.get("amass_mtow", aircraft_params.amass_mtow)
|
|
174
|
+
amass_mpl = fl.attrs.get("amass_mpl", aircraft_params.amass_mpl)
|
|
175
|
+
load_factor = fl.attrs.get("load_factor", DEFAULT_LOAD_FACTOR)
|
|
176
|
+
takeoff_mass = fl.attrs.get("takeoff_mass")
|
|
177
|
+
q_fuel = fl.fuel.q_fuel
|
|
178
|
+
|
|
179
|
+
true_airspeed = fl["true_airspeed"] # attached in PSFlight.eval
|
|
180
|
+
true_airspeed = np.where(true_airspeed == 0.0, np.nan, true_airspeed)
|
|
166
181
|
|
|
167
182
|
# Run the simulation
|
|
168
183
|
aircraft_performance = self.simulate_fuel_and_performance(
|
|
169
184
|
aircraft_type=atyp_ps,
|
|
170
|
-
altitude_ft=
|
|
171
|
-
time=
|
|
185
|
+
altitude_ft=fl.altitude_ft,
|
|
186
|
+
time=fl["time"],
|
|
172
187
|
true_airspeed=true_airspeed,
|
|
173
|
-
air_temperature=
|
|
188
|
+
air_temperature=fl["air_temperature"],
|
|
174
189
|
aircraft_mass=self.get_source_param("aircraft_mass", None),
|
|
175
190
|
thrust=self.get_source_param("thrust", None),
|
|
176
191
|
engine_efficiency=self.get_source_param("engine_efficiency", None),
|
|
@@ -194,13 +209,13 @@ class PSFlight(AircraftPerformance):
|
|
|
194
209
|
"thrust",
|
|
195
210
|
"rocd",
|
|
196
211
|
):
|
|
197
|
-
|
|
212
|
+
fl.setdefault(var, getattr(aircraft_performance, var))
|
|
198
213
|
|
|
199
214
|
self._cleanup_indices()
|
|
200
215
|
|
|
201
|
-
|
|
216
|
+
fl.attrs["total_fuel_burn"] = np.nansum(aircraft_performance.fuel_burn).item()
|
|
202
217
|
|
|
203
|
-
return
|
|
218
|
+
return fl
|
|
204
219
|
|
|
205
220
|
@overrides
|
|
206
221
|
def calculate_aircraft_performance(
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
pycontrails-0.54.
|
|
2
|
-
pycontrails-0.54.
|
|
3
|
-
pycontrails-0.54.
|
|
4
|
-
pycontrails-0.54.
|
|
5
|
-
pycontrails-0.54.
|
|
6
|
-
pycontrails-0.54.
|
|
7
|
-
pycontrails/_version.py,sha256=
|
|
1
|
+
pycontrails-0.54.1.dist-info/RECORD,,
|
|
2
|
+
pycontrails-0.54.1.dist-info/LICENSE,sha256=gJ-h7SFFD1mCfR6a7HILvEtodDT6Iig8bLXdgqR6ucA,10175
|
|
3
|
+
pycontrails-0.54.1.dist-info/WHEEL,sha256=pM6w_2-HMcmsJME874nrq5Wowbr_ttce_14nLq621Mg,110
|
|
4
|
+
pycontrails-0.54.1.dist-info/NOTICE,sha256=gKI8DcN1WhiXB2SFRKDogcjONldGubTvBxiOYdC4CXU,1926
|
|
5
|
+
pycontrails-0.54.1.dist-info/top_level.txt,sha256=dwaYXVcMhF92QWtAYcLvL0k02vyBqwhsv92lYs2V6zQ,23
|
|
6
|
+
pycontrails-0.54.1.dist-info/METADATA,sha256=nnFiKTAtEVMBl81BkYHnQ6HHanzpEFtonQgABT0R28E,9074
|
|
7
|
+
pycontrails/_version.py,sha256=k6EdzGzEntY3_PJAluVNA2mhyXh37neOah9DFH4IwDw,413
|
|
8
8
|
pycontrails/__init__.py,sha256=O2T9kXCMhcELcMZz7HEnwiBhh4Gfcj-yG1HtrotOKHQ,2001
|
|
9
9
|
pycontrails/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
pycontrails/core/vector.py,sha256=
|
|
10
|
+
pycontrails/core/vector.py,sha256=6ESLZ_mzZiehzCrwyb1Ib5zLnp3Vf37wHDmnx-RKWZQ,70530
|
|
11
11
|
pycontrails/core/models.py,sha256=mB3fhmBorFxt7uEhBFcuu0PIMWmBRB4KBRsPiFpPcvo,39282
|
|
12
12
|
pycontrails/core/interpolation.py,sha256=yxVLO9lzNcNFeLwDyrQ7yfz4JEHLHTpgIRBrcOezsXg,25617
|
|
13
|
-
pycontrails/core/fleet.py,sha256=
|
|
14
|
-
pycontrails/core/rgi_cython.cpython-310-darwin.so,sha256=
|
|
15
|
-
pycontrails/core/flight.py,sha256=
|
|
13
|
+
pycontrails/core/fleet.py,sha256=u4Mw57KPePSycBRvhVSbhr-7hQ2rTiEWXNagXIQKOuY,15551
|
|
14
|
+
pycontrails/core/rgi_cython.cpython-310-darwin.so,sha256=Swbb6LKVFjEZnZ9C5Cqrx0FOj-J2PP9-63BiFqbjQWM,295344
|
|
15
|
+
pycontrails/core/flight.py,sha256=SbXO0w8je7v4YASoEHu4mGzLvyUyMJp720JniHJBz5c,82909
|
|
16
16
|
pycontrails/core/fuel.py,sha256=kJZ3P1lPm1L6rdPREM55XQ-VfJ_pt35cP4sO2Nnvmjs,4332
|
|
17
17
|
pycontrails/core/polygon.py,sha256=gosyZBX1XBKD2EcHycIZb7uM-xGs8rCfdpiSZlhc2Hc,18028
|
|
18
18
|
pycontrails/core/cache.py,sha256=ly2Prq5CUxxc2pClZUXDeH-E8zkj3zZkLoKpdKUCyGs,27984
|
|
19
19
|
pycontrails/core/__init__.py,sha256=x1z6x8w3sYmEqYcNWyWHuNkS9lPUPbHUoYJZs1K0q98,856
|
|
20
20
|
pycontrails/core/flightplan.py,sha256=UO4vL087d5TZMlU984-FxfotGTxFbqK78w2fLDRiel4,7335
|
|
21
|
-
pycontrails/core/met.py,sha256=
|
|
22
|
-
pycontrails/core/aircraft_performance.py,sha256=
|
|
21
|
+
pycontrails/core/met.py,sha256=vWUR2qNGvYrP4I9KKmk6rQoRkPV2mbvkNfxf2b7HV4U,100726
|
|
22
|
+
pycontrails/core/aircraft_performance.py,sha256=BsYMMK80lpVzSYS8sem4xgigLUpwlXglql2qztaHp0M,26325
|
|
23
23
|
pycontrails/core/airports.py,sha256=aeyAXVkioIRomrP79UtNrxindL4f1DJyXFaojZCuBBw,6758
|
|
24
24
|
pycontrails/core/met_var.py,sha256=GC5ijw4oGuIefmFOSz4vmxMEBj_SVs5Z75IMhDP56Cw,9183
|
|
25
25
|
pycontrails/core/coordinates.py,sha256=0ySsHtqTon7GMbuwmmxMbI92j3ueMteJZh4xxNm5zto,5391
|
|
@@ -29,15 +29,15 @@ pycontrails/datalib/spire.py,sha256=66SnMdA8KOS69USjKmqrJmTKPK08Ehih9tnlsCt-AJw,
|
|
|
29
29
|
pycontrails/datalib/__init__.py,sha256=hW9NWdFPC3y_2vHMteQ7GgQdop3917MkDaf5ZhU2RBY,369
|
|
30
30
|
pycontrails/datalib/sentinel.py,sha256=pKB92KzKjvNOKnuxolXoz2ZnpXQ50iQ8g-EHDVMsnoA,17221
|
|
31
31
|
pycontrails/datalib/_met_utils/metsource.py,sha256=BGActBGApWb4yI97nBS9ui5j-PzIQotFMUtbMEBkvm8,23966
|
|
32
|
-
pycontrails/datalib/ecmwf/arco_era5.py,sha256=
|
|
33
|
-
pycontrails/datalib/ecmwf/era5.py,sha256=
|
|
34
|
-
pycontrails/datalib/ecmwf/era5_model_level.py,sha256=
|
|
32
|
+
pycontrails/datalib/ecmwf/arco_era5.py,sha256=gezuXJo2gNT7WVK4xVwL8bPSxnPm8SGdj8g6THC4nXg,12348
|
|
33
|
+
pycontrails/datalib/ecmwf/era5.py,sha256=VV-t0WUCDSpNzAlerWNJMjtownjLX3MSzxRdVaTMn3M,19014
|
|
34
|
+
pycontrails/datalib/ecmwf/era5_model_level.py,sha256=Aey0juIzL2ZIAuPhFlsjTky9i45vKTT9gIeGxIrtIqs,19340
|
|
35
35
|
pycontrails/datalib/ecmwf/hres.py,sha256=p_l0ytCEEWGam7G7aVynpLmH4H4LQNeVe0Ay7Tw6fp8,28240
|
|
36
36
|
pycontrails/datalib/ecmwf/variables.py,sha256=G6LlGTrlzdn819F-7kjEMXT-Ystp1gc79LOmQTZKrtQ,9865
|
|
37
37
|
pycontrails/datalib/ecmwf/hres_model_level.py,sha256=DiMw1cgbON_pu9ADjC0NC_itHSVa9n9zICLs1_iDq7c,17568
|
|
38
|
-
pycontrails/datalib/ecmwf/__init__.py,sha256=
|
|
38
|
+
pycontrails/datalib/ecmwf/__init__.py,sha256=7OovwVTCo2DVH10NioUAc18evZkgb9b7Tn42S7tsJfU,2021
|
|
39
39
|
pycontrails/datalib/ecmwf/common.py,sha256=PIkEdYEmlmwxQ7v4TenW_BaHX7mslnmdJW3iZYXb7Kg,3904
|
|
40
|
-
pycontrails/datalib/ecmwf/model_levels.py,sha256=
|
|
40
|
+
pycontrails/datalib/ecmwf/model_levels.py,sha256=_kgpnogaS6MlfvTX9dB5ASTHFUlZuQ_DRb-VADwEa0k,16996
|
|
41
41
|
pycontrails/datalib/ecmwf/ifs.py,sha256=2heema398PoEVCfiTZSBawN25PXAa_CpWm_pGLZ1GuY,10662
|
|
42
42
|
pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv,sha256=PmvGLRzn6uuCKSwiasSuVcehvvmSaqP7cnLuN6hhCQQ,9788
|
|
43
43
|
pycontrails/datalib/_leo_utils/vis.py,sha256=-fLcm1D5cP6lThVHovV3MJSiadWyTUAvYDMvr4drMU4,1802
|
|
@@ -92,7 +92,7 @@ pycontrails/models/cocip/radiative_heating.py,sha256=YRpwfXgFnf89iuJiIM96q-jbdcM
|
|
|
92
92
|
pycontrails/models/cocip/contrail_properties.py,sha256=tycCxKf8j9GvVYDQBPxjtp6xLll-r00C0XW-w1jGbMI,55594
|
|
93
93
|
pycontrails/models/cocip/unterstrasser_wake_vortex.py,sha256=kDxFpAIkcqqhGmwXoxv3_cSESj1Ur45GbLJF56IACJs,14573
|
|
94
94
|
pycontrails/models/ps_model/__init__.py,sha256=5L-HympF1gJaZ6xiNkIQJygJhkDxM3-ejS_T2z-83hQ,495
|
|
95
|
-
pycontrails/models/ps_model/ps_model.py,sha256=
|
|
95
|
+
pycontrails/models/ps_model/ps_model.py,sha256=mhcSIq2ZGCScWpS2aVipvNKORv3QdspGme69Hm8LdvE,33411
|
|
96
96
|
pycontrails/models/ps_model/ps_aircraft_params.py,sha256=-PfT2JC6RckVi_zTDVTqAMyaS-id6I2klUoXoEXreAc,13077
|
|
97
97
|
pycontrails/models/ps_model/ps_operational_limits.py,sha256=_vFJiPqGuZJRzwuY10-z07-7eEyomnpxPm_Js1Cd5So,16832
|
|
98
98
|
pycontrails/models/ps_model/ps_grid.py,sha256=AqZCEytWhrNbyujlJTufI4cxDonkPchGnrB3IvtRID4,18667
|
|
@@ -100,7 +100,7 @@ pycontrails/models/ps_model/static/ps-synonym-list-20240524.csv,sha256=ksrpQTHkx
|
|
|
100
100
|
pycontrails/models/ps_model/static/ps-aircraft-params-20240524.csv,sha256=3eNhSwzut0gon04k2EYKKaXRvQSUlau3yBAbHS0EBao,25784
|
|
101
101
|
pycontrails/models/cocipgrid/cocip_grid_params.py,sha256=l4vBPrOKCJDz5Y1uMjmOGVyUcSWgfZtFWbjW968OPz8,5875
|
|
102
102
|
pycontrails/models/cocipgrid/__init__.py,sha256=ar6bF_8Pusbb-myujz_q5ntFylQTNH8yiM8fxP7Zk30,262
|
|
103
|
-
pycontrails/models/cocipgrid/cocip_grid.py,sha256=
|
|
103
|
+
pycontrails/models/cocipgrid/cocip_grid.py,sha256=JV-arscdjzvUWbtaOoDuo_87VlKIwID7Vbw-KwCurzI,94337
|
|
104
104
|
pycontrails/physics/geo.py,sha256=9ZWIXyEEgrBNqsoeBBlYLTA-8GUTgyc-jgeVgchxXa8,30288
|
|
105
105
|
pycontrails/physics/units.py,sha256=j-G5AC9eWIvv2MTOq9lUOoOQKFNJJuHzWLanHRji2tE,12272
|
|
106
106
|
pycontrails/physics/constants.py,sha256=pHQQmccMUwuNnY4hFtm3L8G2rnUQcfJnroyQr8HAVeM,3146
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|