pycontrails 0.54.0__cp311-cp311-macosx_10_9_x86_64.whl → 0.54.1__cp311-cp311-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 CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.54.0'
16
- __version_tuple__ = version_tuple = (0, 54, 0)
15
+ __version__ = version = '0.54.1'
16
+ __version_tuple__ = version_tuple = (0, 54, 1)
@@ -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
- met_incomplete = (
471
- self.met is None or "eastward_wind" not in self.met or "northward_wind" not in self.met
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
- if met_incomplete:
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, data_keys, and crs
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
- # Implement if we have a usecase for this.
322
- # Because the super() method uses a smoothing pattern, it will not reliably
323
- # work on Fleet.
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 incompatible CRS found.
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()}) "
@@ -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, 2 attributes]
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, 1 attributes]
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"]), self.attrs["crs"])
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, crs: str = "EPSG:4326") -> list[int]:
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
- # WGS84
1693
- if crs in ["EPSG:4326"]:
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
- jump_index = pd.concat([jump12, jump21]).index.to_list()
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: tuple[Hashable, Hashable, Hashable, Hashable] = (
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
- for dim in self.dim_order:
101
- if dim not in self.data.dims:
102
- if dim == "level":
103
- msg = (
104
- f"Meteorology data must contain dimension '{dim}'. "
105
- "For single level data, set 'level' coordinate to constant -1 "
106
- "using `ds = ds.expand_dims({'level': [-1]})`"
107
- )
108
- else:
109
- msg = f"Meteorology data must contain dimension '{dim}'."
110
- raise ValueError(msg)
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
- "Initiate with 'copy=True' to convert to float64. "
127
- "Initiate with 'validate=False' to skip validation."
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
- "Initiate with 'copy=True' to convert to float64. "
171
- "Initiate with 'validate=False' to skip validation."
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 `time` not sorted. Initiate with `copy=True`.")
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. Initiate with 'copy=True'.")
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}'. Initiate with"
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. Initiate with 'copy=True'."
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. `time` or `level`) from underlying `xr.Dataset` object, "
749
- "use the `data` attribute."
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, 1 attributes]
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)
@@ -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
- # set CRS to "EPSG:4326" by default
1368
- crs = self.attrs.setdefault("crs", "EPSG:4326")
1369
-
1370
- if crs == "EPSG:4326":
1371
- longitude = self["longitude"]
1372
- if np.any(longitude > 180.0) or np.any(longitude < -180.0):
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 inferred from the :attr:`attrs["crs"]` attribute.
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
- GeoVectorDatasetType
1549
- Converted dataset with new coordinate reference system.
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
- transformer = pyproj.Transformer.from_crs(self.attrs["crs"], crs, always_xy=True)
1563
- lon, lat = transformer.transform(self["longitude"], self["latitude"])
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
- ARCOERA5,
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
- "ARCOERA5",
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 ARCOERA5(ecmwf_common.ECMWFAPI):
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/cdsapp#!/home>`_
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.ARCOERA5`
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.ARCOERA5` accesses data through Google's
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/cdsapp#!/home>`_
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
- At least one of ``lnsp`` or ``sp`` must be provided.
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=params["dt_integration"],
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=params["dt_integration"],
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=params["dt_integration"],
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
- true_airspeed = self.ensure_true_airspeed_on_source().copy()
134
- true_airspeed[true_airspeed == 0.0] = np.nan
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 = self.source.attrs["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
- self.source.attrs.setdefault("aircraft_performance_model", self.name)
152
- self.source.attrs.setdefault("aircraft_type_ps", atyp_ps)
153
- self.source.attrs.setdefault("n_engine", aircraft_params.n_engine)
154
-
155
- self.source.attrs.setdefault("wingspan", aircraft_params.wing_span)
156
- self.source.attrs.setdefault("max_mach", aircraft_params.max_mach_num)
157
- self.source.attrs.setdefault("max_altitude", units.ft_to_m(aircraft_params.fl_max * 100.0))
158
- self.source.attrs.setdefault("n_engine", aircraft_params.n_engine)
159
-
160
- amass_oew = self.source.attrs.get("amass_oew", aircraft_params.amass_oew)
161
- amass_mtow = self.source.attrs.get("amass_mtow", aircraft_params.amass_mtow)
162
- amass_mpl = self.source.attrs.get("amass_mpl", aircraft_params.amass_mpl)
163
- load_factor = self.source.attrs.get("load_factor", DEFAULT_LOAD_FACTOR)
164
- takeoff_mass = self.source.attrs.get("takeoff_mass")
165
- q_fuel = self.source.fuel.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=self.source.altitude_ft,
171
- time=self.source["time"],
185
+ altitude_ft=fl.altitude_ft,
186
+ time=fl["time"],
172
187
  true_airspeed=true_airspeed,
173
- air_temperature=self.source["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
- self.source.setdefault(var, getattr(aircraft_performance, var))
212
+ fl.setdefault(var, getattr(aircraft_performance, var))
198
213
 
199
214
  self._cleanup_indices()
200
215
 
201
- self.source.attrs["total_fuel_burn"] = np.nansum(aircraft_performance.fuel_burn).item()
216
+ fl.attrs["total_fuel_burn"] = np.nansum(aircraft_performance.fuel_burn).item()
202
217
 
203
- return self.source
218
+ return fl
204
219
 
205
220
  @overrides
206
221
  def calculate_aircraft_performance(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pycontrails
3
- Version: 0.54.0
3
+ Version: 0.54.1
4
4
  Summary: Python library for modeling aviation climate impacts
5
5
  Author-email: Breakthrough Energy <py@contrails.org>
6
6
  License: Apache-2.0
@@ -1,25 +1,25 @@
1
- pycontrails-0.54.0.dist-info/RECORD,,
2
- pycontrails-0.54.0.dist-info/LICENSE,sha256=gJ-h7SFFD1mCfR6a7HILvEtodDT6Iig8bLXdgqR6ucA,10175
3
- pycontrails-0.54.0.dist-info/WHEEL,sha256=8WdzK5c4WNxCJSm166MSYu-d0xZlUHP9uhBLxUSypoM,110
4
- pycontrails-0.54.0.dist-info/NOTICE,sha256=gKI8DcN1WhiXB2SFRKDogcjONldGubTvBxiOYdC4CXU,1926
5
- pycontrails-0.54.0.dist-info/top_level.txt,sha256=Z8J1R_AiBAyCVjNw6jYLdrA68PrQqTg0t3_Yek_IZ0Q,29
6
- pycontrails-0.54.0.dist-info/METADATA,sha256=ZK_iyLfHqxvbeM92Lbzdhw0hSOqdE1NPpPn4m7IbkTE,9074
7
- pycontrails/_version.py,sha256=Cy6-7BAUUEvyIJZFLyqb_w3IXvuVEBdc837EO9ql3TM,413
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=8WdzK5c4WNxCJSm166MSYu-d0xZlUHP9uhBLxUSypoM,110
4
+ pycontrails-0.54.1.dist-info/NOTICE,sha256=gKI8DcN1WhiXB2SFRKDogcjONldGubTvBxiOYdC4CXU,1926
5
+ pycontrails-0.54.1.dist-info/top_level.txt,sha256=Z8J1R_AiBAyCVjNw6jYLdrA68PrQqTg0t3_Yek_IZ0Q,29
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/rgi_cython.cpython-311-darwin.so,sha256=_D-dXJyYyf0ZJseVPeSXNFjRgyyiI5U4I5vVHWprKRs,295584
11
- pycontrails/core/vector.py,sha256=Y-YTk_dv_IguaThQFLFeZETNhsNu5lKcbo301cvHYgw,71540
10
+ pycontrails/core/rgi_cython.cpython-311-darwin.so,sha256=36qzUtvSgjeqzSnpKvc9rtz2hNJfvcfA4sEYvWgZQE8,295584
11
+ pycontrails/core/vector.py,sha256=6ESLZ_mzZiehzCrwyb1Ib5zLnp3Vf37wHDmnx-RKWZQ,70530
12
12
  pycontrails/core/models.py,sha256=mB3fhmBorFxt7uEhBFcuu0PIMWmBRB4KBRsPiFpPcvo,39282
13
13
  pycontrails/core/interpolation.py,sha256=yxVLO9lzNcNFeLwDyrQ7yfz4JEHLHTpgIRBrcOezsXg,25617
14
- pycontrails/core/fleet.py,sha256=wqYY_2xD9X-Og0_oxU8ZPqTHYDau9TOPLQcmEnB1kiQ,16140
15
- pycontrails/core/flight.py,sha256=s_teDDqnoiX15pqJ_hmchKCjut1WiNtcrL2QXXMBNGc,85173
14
+ pycontrails/core/fleet.py,sha256=u4Mw57KPePSycBRvhVSbhr-7hQ2rTiEWXNagXIQKOuY,15551
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=0lGZqGu-_EnulU9Df05xo0I-IYX2MRQXvJ7PgCjU6p0,101342
22
- pycontrails/core/aircraft_performance.py,sha256=4KnLj0zK-mk8Oo3As1CXUkQWBQGMeDdrKi5TeOhOmUA,26107
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=ZBN3ZMgoeUVMkVTdWlSMRpJ6Fpwzx2EtPGJXZqoJiog,12348
33
- pycontrails/datalib/ecmwf/era5.py,sha256=gWZaDydDq7Z-UzNM_41sY-zgOQbVBzQ9E9CnqVk3OqA,19017
34
- pycontrails/datalib/ecmwf/era5_model_level.py,sha256=tTE_aY90NtjP8HUl64CtbnUKsAOoPHgeDImDij_mVWM,19343
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=e2LXD2ebsu0tNKZHabI6-SvzYw6zdsci2B3z6cFgBZQ,2021
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=Sf11onDST2YOF_1yRxCdlttDJNnR8pv26y2oU3jl9tY,16830
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=Cj79eJxHfq6205zNPxrwSs_Cx337MU3qvc7ds8sHAjA,33013
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=B1-f3D62fg3OeLp7xnxANAsneg9JRiiRVMqs-REFaII,94347
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