pycontrails 0.51.2__cp310-cp310-win_amd64.whl → 0.52.1__cp310-cp310-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.

Files changed (45) hide show
  1. pycontrails/__init__.py +1 -1
  2. pycontrails/_version.py +2 -2
  3. pycontrails/core/__init__.py +1 -1
  4. pycontrails/core/cache.py +1 -1
  5. pycontrails/core/coordinates.py +5 -5
  6. pycontrails/core/flight.py +36 -32
  7. pycontrails/core/interpolation.py +2 -2
  8. pycontrails/core/met.py +23 -14
  9. pycontrails/core/polygon.py +1 -1
  10. pycontrails/core/rgi_cython.cp310-win_amd64.pyd +0 -0
  11. pycontrails/core/vector.py +1 -1
  12. pycontrails/datalib/__init__.py +4 -1
  13. pycontrails/datalib/_leo_utils/search.py +250 -0
  14. pycontrails/datalib/_leo_utils/static/bq_roi_query.sql +6 -0
  15. pycontrails/datalib/_leo_utils/vis.py +60 -0
  16. pycontrails/{core/datalib.py → datalib/_met_utils/metsource.py} +1 -5
  17. pycontrails/datalib/ecmwf/arco_era5.py +8 -7
  18. pycontrails/datalib/ecmwf/common.py +3 -2
  19. pycontrails/datalib/ecmwf/era5.py +12 -11
  20. pycontrails/datalib/ecmwf/era5_model_level.py +12 -11
  21. pycontrails/datalib/ecmwf/hres.py +14 -13
  22. pycontrails/datalib/ecmwf/hres_model_level.py +15 -14
  23. pycontrails/datalib/ecmwf/ifs.py +14 -13
  24. pycontrails/datalib/gfs/gfs.py +15 -14
  25. pycontrails/datalib/goes.py +12 -5
  26. pycontrails/datalib/landsat.py +567 -0
  27. pycontrails/datalib/sentinel.py +512 -0
  28. pycontrails/models/apcemm/__init__.py +8 -0
  29. pycontrails/models/apcemm/apcemm.py +983 -0
  30. pycontrails/models/apcemm/inputs.py +226 -0
  31. pycontrails/models/apcemm/static/apcemm_yaml_template.yaml +183 -0
  32. pycontrails/models/apcemm/utils.py +437 -0
  33. pycontrails/models/cocip/cocip_uncertainty.py +9 -9
  34. pycontrails/models/cocip/output_formats.py +3 -2
  35. pycontrails/models/cocipgrid/cocip_grid.py +7 -6
  36. pycontrails/models/dry_advection.py +14 -5
  37. pycontrails/physics/geo.py +1 -1
  38. {pycontrails-0.51.2.dist-info → pycontrails-0.52.1.dist-info}/METADATA +31 -12
  39. {pycontrails-0.51.2.dist-info → pycontrails-0.52.1.dist-info}/RECORD +44 -35
  40. {pycontrails-0.51.2.dist-info → pycontrails-0.52.1.dist-info}/WHEEL +1 -1
  41. pycontrails/datalib/spire/__init__.py +0 -19
  42. /pycontrails/datalib/{spire/spire.py → spire.py} +0 -0
  43. {pycontrails-0.51.2.dist-info → pycontrails-0.52.1.dist-info}/LICENSE +0 -0
  44. {pycontrails-0.51.2.dist-info → pycontrails-0.52.1.dist-info}/NOTICE +0 -0
  45. {pycontrails-0.51.2.dist-info → pycontrails-0.52.1.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- """Datalib utilities."""
1
+ """Met datalib definitions and utilities."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -35,9 +35,6 @@ DEFAULT_CHUNKS: dict[str, int] = {"time": 1}
35
35
  #: Whether to open multi-file datasets in parallel
36
36
  OPEN_IN_PARALLEL: bool = False
37
37
 
38
- #: Whether to use file locking when opening multi-file datasets
39
- OPEN_WITH_LOCK: bool = False
40
-
41
38
 
42
39
  def parse_timesteps(time: TimeInput | None, freq: str | None = "1h") -> list[datetime]:
43
40
  """Parse time input into set of time steps.
@@ -741,5 +738,4 @@ class MetDataSource(abc.ABC):
741
738
  xr_kwargs.setdefault("engine", NETCDF_ENGINE)
742
739
  xr_kwargs.setdefault("chunks", DEFAULT_CHUNKS)
743
740
  xr_kwargs.setdefault("parallel", OPEN_IN_PARALLEL)
744
- xr_kwargs.setdefault("lock", OPEN_WITH_LOCK)
745
741
  return xr.open_mfdataset(disk_paths, **xr_kwargs)
@@ -32,8 +32,9 @@ from typing import Any
32
32
  import xarray as xr
33
33
  from overrides import overrides
34
34
 
35
- from pycontrails.core import cache, datalib, met_var
35
+ from pycontrails.core import cache, met_var
36
36
  from pycontrails.core.met import MetDataset
37
+ from pycontrails.datalib._met_utils import metsource
37
38
  from pycontrails.datalib.ecmwf import common as ecmwf_common
38
39
  from pycontrails.datalib.ecmwf import variables as ecmwf_variables
39
40
  from pycontrails.datalib.ecmwf.model_levels import pressure_levels_at_model_levels
@@ -374,23 +375,23 @@ class ARCOERA5(ecmwf_common.ECMWFAPI):
374
375
 
375
376
  def __init__(
376
377
  self,
377
- time: datalib.TimeInput,
378
- variables: datalib.VariableInput,
379
- pressure_levels: datalib.PressureLevelInput | None = None,
378
+ time: metsource.TimeInput,
379
+ variables: metsource.VariableInput,
380
+ pressure_levels: metsource.PressureLevelInput | None = None,
380
381
  grid: float = 0.25,
381
382
  cachestore: cache.CacheStore | None = __marker, # type: ignore[assignment]
382
383
  n_jobs: int = 1,
383
384
  cleanup_metview_tempfiles: bool = True,
384
385
  ) -> None:
385
- self.timesteps = datalib.parse_timesteps(time)
386
+ self.timesteps = metsource.parse_timesteps(time)
386
387
 
387
388
  if pressure_levels is None:
388
389
  self.pressure_levels = pressure_levels_at_model_levels(20_000.0, 50_000.0)
389
390
  else:
390
- self.pressure_levels = datalib.parse_pressure_levels(pressure_levels)
391
+ self.pressure_levels = metsource.parse_pressure_levels(pressure_levels)
391
392
 
392
393
  self.paths = None
393
- self.variables = datalib.parse_variables(variables, self.supported_variables)
394
+ self.variables = metsource.parse_variables(variables, self.supported_variables)
394
395
  self.grid = grid
395
396
  self.cachestore = cache.DiskCacheStore() if cachestore is self.__marker else cachestore
396
397
  self.n_jobs = max(1, n_jobs)
@@ -13,10 +13,11 @@ import pandas as pd
13
13
  import xarray as xr
14
14
  from overrides import overrides
15
15
 
16
- from pycontrails.core import datalib, met
16
+ from pycontrails.core import met
17
+ from pycontrails.datalib._met_utils import metsource
17
18
 
18
19
 
19
- class ECMWFAPI(datalib.MetDataSource):
20
+ class ECMWFAPI(metsource.MetDataSource):
20
21
  """Abstract class for all ECMWF data accessed remotely through CDS / MARS."""
21
22
 
22
23
  @property
@@ -19,8 +19,9 @@ import xarray as xr
19
19
  from overrides import overrides
20
20
 
21
21
  import pycontrails
22
- from pycontrails.core import cache, datalib
22
+ from pycontrails.core import cache
23
23
  from pycontrails.core.met import MetDataset, MetVariable
24
+ from pycontrails.datalib._met_utils import metsource
24
25
  from pycontrails.datalib.ecmwf.common import ECMWFAPI, CDSCredentialsNotFound
25
26
  from pycontrails.datalib.ecmwf.variables import PRESSURE_LEVEL_VARIABLES, SURFACE_VARIABLES
26
27
  from pycontrails.utils import dependencies, temp
@@ -49,16 +50,16 @@ class ERA5(ECMWFAPI):
49
50
 
50
51
  Parameters
51
52
  ----------
52
- time : datalib.TimeInput | None
53
+ time : metsource.TimeInput | None
53
54
  The time range for data retrieval, either a single datetime or (start, end) datetime range.
54
55
  Input must be datetime-like or tuple of datetime-like
55
56
  (`datetime`, :class:`pd.Timestamp`, :class:`np.datetime64`)
56
57
  specifying the (start, end) of the date range, inclusive.
57
58
  Datafiles will be downloaded from CDS for each day to reduce requests.
58
59
  If None, ``paths`` must be defined and all time coordinates will be loaded from files.
59
- variables : datalib.VariableInput
60
+ variables : metsource.VariableInput
60
61
  Variable name (i.e. "t", "air_temperature", ["air_temperature, relative_humidity"])
61
- pressure_levels : datalib.PressureLevelInput, optional
62
+ pressure_levels : metsource.PressureLevelInput, optional
62
63
  Pressure levels for data, in hPa (mbar)
63
64
  Set to -1 for to download surface level parameters.
64
65
  Defaults to -1.
@@ -145,9 +146,9 @@ class ERA5(ECMWFAPI):
145
146
 
146
147
  def __init__(
147
148
  self,
148
- time: datalib.TimeInput | None,
149
- variables: datalib.VariableInput,
150
- pressure_levels: datalib.PressureLevelInput = -1,
149
+ time: metsource.TimeInput | None,
150
+ variables: metsource.VariableInput,
151
+ pressure_levels: metsource.PressureLevelInput = -1,
151
152
  paths: str | list[str] | pathlib.Path | list[pathlib.Path] | None = None,
152
153
  timestep_freq: str | None = None,
153
154
  product_type: str = "reanalysis",
@@ -193,11 +194,11 @@ class ERA5(ECMWFAPI):
193
194
  if timestep_freq is None:
194
195
  timestep_freq = "1h" if product_type == "reanalysis" else "3h"
195
196
 
196
- self.timesteps = datalib.parse_timesteps(time, freq=timestep_freq)
197
- self.pressure_levels = datalib.parse_pressure_levels(
197
+ self.timesteps = metsource.parse_timesteps(time, freq=timestep_freq)
198
+ self.pressure_levels = metsource.parse_pressure_levels(
198
199
  pressure_levels, self.supported_pressure_levels
199
200
  )
200
- self.variables = datalib.parse_variables(variables, self.supported_variables)
201
+ self.variables = metsource.parse_variables(variables, self.supported_variables)
201
202
 
202
203
  # ensemble_mean, etc - time is only available on the 0, 3, 6, etc
203
204
  if product_type.startswith("ensemble") and any(t.hour % 3 for t in self.timesteps):
@@ -482,7 +483,7 @@ class ERA5(ECMWFAPI):
482
483
 
483
484
  # open file, edit, and save for each hourly time step
484
485
  ds = stack.enter_context(
485
- xr.open_dataset(cds_temp_filename, engine=datalib.NETCDF_ENGINE)
486
+ xr.open_dataset(cds_temp_filename, engine=metsource.NETCDF_ENGINE)
486
487
  )
487
488
 
488
489
  # run preprocessing before cache
@@ -40,8 +40,9 @@ import pandas as pd
40
40
  import xarray as xr
41
41
 
42
42
  import pycontrails
43
- from pycontrails.core import cache, datalib
43
+ from pycontrails.core import cache
44
44
  from pycontrails.core.met import MetDataset, MetVariable
45
+ from pycontrails.datalib._met_utils import metsource
45
46
  from pycontrails.datalib.ecmwf.common import ECMWFAPI, CDSCredentialsNotFound
46
47
  from pycontrails.datalib.ecmwf.model_levels import pressure_levels_at_model_levels
47
48
  from pycontrails.datalib.ecmwf.variables import MODEL_LEVEL_VARIABLES
@@ -73,7 +74,7 @@ class ERA5ModelLevel(ECMWFAPI):
73
74
 
74
75
  Parameters
75
76
  ----------
76
- time : datalib.TimeInput | None
77
+ time : metsource.TimeInput | None
77
78
  The time range for data retrieval, either a single datetime or (start, end) datetime range.
78
79
  Input must be datetime-like or tuple of datetime-like
79
80
  (:py:class:`datetime.datetime`, :class:`pandas.Timestamp`, :class:`numpy.datetime64`)
@@ -82,9 +83,9 @@ class ERA5ModelLevel(ECMWFAPI):
82
83
  for the nominal reanalysis and no larger than 1 day for ensemble members.
83
84
  This ensures that exactly one request is submitted per file on tape accessed.
84
85
  If None, ``paths`` must be defined and all time coordinates will be loaded from files.
85
- variables : datalib.VariableInput
86
+ variables : metsource.VariableInput
86
87
  Variable name (i.e. "t", "air_temperature", ["air_temperature, specific_humidity"])
87
- pressure_levels : datalib.PressureLevelInput, optional
88
+ pressure_levels : metsource.PressureLevelInput, optional
88
89
  Pressure levels for data, in hPa (mbar).
89
90
  To download surface-level parameters, use :class:`pycontrails.datalib.ecmwf.ERA5`.
90
91
  Defaults to pressure levels that match model levels at a nominal surface pressure.
@@ -123,9 +124,9 @@ class ERA5ModelLevel(ECMWFAPI):
123
124
 
124
125
  def __init__(
125
126
  self,
126
- time: datalib.TimeInput,
127
- variables: datalib.VariableInput,
128
- pressure_levels: datalib.PressureLevelInput | None = None,
127
+ time: metsource.TimeInput,
128
+ variables: metsource.VariableInput,
129
+ pressure_levels: metsource.PressureLevelInput | None = None,
129
130
  timestep_freq: str | None = None,
130
131
  product_type: str = "reanalysis",
131
132
  grid: float | None = None,
@@ -186,18 +187,18 @@ class ERA5ModelLevel(ECMWFAPI):
186
187
  datasource_timestep_freq = "1h" if product_type == "reanalysis" else "3h"
187
188
  if timestep_freq is None:
188
189
  timestep_freq = datasource_timestep_freq
189
- if not datalib.validate_timestep_freq(timestep_freq, datasource_timestep_freq):
190
+ if not metsource.validate_timestep_freq(timestep_freq, datasource_timestep_freq):
190
191
  msg = (
191
192
  f"Product {self.product_type} has timestep frequency of {datasource_timestep_freq} "
192
193
  f"and cannot support requested timestep frequency of {timestep_freq}."
193
194
  )
194
195
  raise ValueError(msg)
195
196
 
196
- self.timesteps = datalib.parse_timesteps(time, freq=timestep_freq)
197
+ self.timesteps = metsource.parse_timesteps(time, freq=timestep_freq)
197
198
  if pressure_levels is None:
198
199
  pressure_levels = pressure_levels_at_model_levels(20_000.0, 50_000.0)
199
- self.pressure_levels = datalib.parse_pressure_levels(pressure_levels)
200
- self.variables = datalib.parse_variables(variables, self.pressure_level_variables)
200
+ self.pressure_levels = metsource.parse_pressure_levels(pressure_levels)
201
+ self.variables = metsource.parse_variables(variables, self.pressure_level_variables)
201
202
 
202
203
  def __repr__(self) -> str:
203
204
  base = super().__repr__()
@@ -17,8 +17,9 @@ import xarray as xr
17
17
  from overrides import overrides
18
18
 
19
19
  import pycontrails
20
- from pycontrails.core import cache, datalib
20
+ from pycontrails.core import cache
21
21
  from pycontrails.core.met import MetDataset, MetVariable
22
+ from pycontrails.datalib._met_utils import metsource
22
23
  from pycontrails.datalib.ecmwf.common import ECMWFAPI
23
24
  from pycontrails.datalib.ecmwf.variables import PRESSURE_LEVEL_VARIABLES, SURFACE_VARIABLES
24
25
  from pycontrails.utils import dependencies, iteration, temp
@@ -120,7 +121,7 @@ class HRES(ECMWFAPI):
120
121
 
121
122
  Parameters
122
123
  ----------
123
- time : datalib.TimeInput | None
124
+ time : metsource.TimeInput | None
124
125
  The time range for data retrieval, either a single datetime or (start, end) datetime range.
125
126
  Input must be a datetime-like or tuple of datetime-like
126
127
  (datetime, :class:`pandas.Timestamp`, :class:`numpy.datetime64`)
@@ -129,10 +130,10 @@ class HRES(ECMWFAPI):
129
130
  be assumed to be the nearest synoptic hour: 00, 06, 12, 18.
130
131
  All subsequent times will be downloaded for relative to :attr:`forecast_time`.
131
132
  If None, ``paths`` must be defined and all time coordinates will be loaded from files.
132
- variables : datalib.VariableInput
133
+ variables : metsource.VariableInput
133
134
  Variable name (i.e. "air_temperature", ["air_temperature, relative_humidity"])
134
135
  See :attr:`pressure_level_variables` for the list of available variables.
135
- pressure_levels : datalib.PressureLevelInput, optional
136
+ pressure_levels : metsource.PressureLevelInput, optional
136
137
  Pressure levels for data, in hPa (mbar)
137
138
  Set to -1 for to download surface level parameters.
138
139
  Defaults to -1.
@@ -241,9 +242,9 @@ class HRES(ECMWFAPI):
241
242
 
242
243
  def __init__(
243
244
  self,
244
- time: datalib.TimeInput | None,
245
- variables: datalib.VariableInput,
246
- pressure_levels: datalib.PressureLevelInput = -1,
245
+ time: metsource.TimeInput | None,
246
+ variables: metsource.VariableInput,
247
+ pressure_levels: metsource.PressureLevelInput = -1,
247
248
  paths: str | list[str] | pathlib.Path | list[pathlib.Path] | None = None,
248
249
  cachepath: str | list[str] | pathlib.Path | list[pathlib.Path] | None = None,
249
250
  grid: float = 0.25,
@@ -277,13 +278,13 @@ class HRES(ECMWFAPI):
277
278
  if time is None and paths is None:
278
279
  raise ValueError("Time input is required when paths is None")
279
280
 
280
- self.timesteps = datalib.parse_timesteps(time, freq="1h")
281
- self.pressure_levels = datalib.parse_pressure_levels(
281
+ self.timesteps = metsource.parse_timesteps(time, freq="1h")
282
+ self.pressure_levels = metsource.parse_pressure_levels(
282
283
  pressure_levels, self.supported_pressure_levels
283
284
  )
284
- self.variables = datalib.parse_variables(variables, self.supported_variables)
285
+ self.variables = metsource.parse_variables(variables, self.supported_variables)
285
286
 
286
- self.grid = datalib.parse_grid(grid, [0.1, 0.25, 0.5, 1]) # lat/lon degree resolution
287
+ self.grid = metsource.parse_grid(grid, [0.1, 0.25, 0.5, 1]) # lat/lon degree resolution
287
288
 
288
289
  # "enfo" = ensemble forecast
289
290
  # "oper" = atmospheric model/HRES
@@ -309,12 +310,12 @@ class HRES(ECMWFAPI):
309
310
  if forecast_time_pd.hour % 6:
310
311
  raise ValueError("Forecast hour must be on one of 00, 06, 12, 18")
311
312
 
312
- self.forecast_time = datalib.round_hour(forecast_time_pd.to_pydatetime(), 6)
313
+ self.forecast_time = metsource.round_hour(forecast_time_pd.to_pydatetime(), 6)
313
314
 
314
315
  # if no specific forecast is requested, set the forecast time using timesteps
315
316
  elif self.timesteps:
316
317
  # round first element to the nearest 6 hour time (00, 06, 12, 18 UTC) for forecast_time
317
- self.forecast_time = datalib.round_hour(self.timesteps[0], 6)
318
+ self.forecast_time = metsource.round_hour(self.timesteps[0], 6)
318
319
 
319
320
  # when no forecast_time or time input, forecast_time is defined in _open_and_cache
320
321
 
@@ -28,8 +28,9 @@ import xarray as xr
28
28
  from overrides import overrides
29
29
 
30
30
  import pycontrails
31
- from pycontrails.core import cache, datalib
31
+ from pycontrails.core import cache
32
32
  from pycontrails.core.met import MetDataset, MetVariable
33
+ from pycontrails.datalib._met_utils import metsource
33
34
  from pycontrails.datalib.ecmwf.common import ECMWFAPI
34
35
  from pycontrails.datalib.ecmwf.model_levels import pressure_levels_at_model_levels
35
36
  from pycontrails.datalib.ecmwf.variables import MODEL_LEVEL_VARIABLES
@@ -70,7 +71,7 @@ class HRESModelLevel(ECMWFAPI):
70
71
 
71
72
  Parameters
72
73
  ----------
73
- time : datalib.TimeInput
74
+ time : metsource.TimeInput
74
75
  The time range for data retrieval, either a single datetime or (start, end) datetime range.
75
76
  Input must be datetime-like or tuple of datetime-like
76
77
  (:py:class:`datetime.datetime`, :class:`pandas.Timestamp`, :class:`numpy.datetime64`)
@@ -80,9 +81,9 @@ class HRESModelLevel(ECMWFAPI):
80
81
  If ``forecast_time`` is unspecified, the forecast time will
81
82
  be assumed to be the nearest synoptic hour available in the operational archive (00 or 12).
82
83
  All subsequent times will be downloaded for relative to :attr:`forecast_time`.
83
- variables : datalib.VariableInput
84
+ variables : metsource.VariableInput
84
85
  Variable name (i.e. "t", "air_temperature", ["air_temperature, specific_humidity"])
85
- pressure_levels : datalib.PressureLevelInput, optional
86
+ pressure_levels : metsource.PressureLevelInput, optional
86
87
  Pressure levels for data, in hPa (mbar).
87
88
  To download surface-level parameters, use :class:`pycontrails.datalib.ecmwf.HRES`.
88
89
  Defaults to pressure levels that match model levels at a nominal surface pressure.
@@ -119,9 +120,9 @@ class HRESModelLevel(ECMWFAPI):
119
120
 
120
121
  def __init__(
121
122
  self,
122
- time: datalib.TimeInput,
123
- variables: datalib.VariableInput,
124
- pressure_levels: datalib.PressureLevelInput | None = None,
123
+ time: metsource.TimeInput,
124
+ variables: metsource.VariableInput,
125
+ pressure_levels: metsource.PressureLevelInput | None = None,
125
126
  timestep_freq: str | None = None,
126
127
  grid: float | None = None,
127
128
  forecast_time: DatetimeLike | None = None,
@@ -165,15 +166,15 @@ class HRESModelLevel(ECMWFAPI):
165
166
  raise ValueError(msg)
166
167
  self.levels = levels
167
168
 
168
- forecast_hours = datalib.parse_timesteps(time, freq="1h")
169
+ forecast_hours = metsource.parse_timesteps(time, freq="1h")
169
170
  if forecast_time is None:
170
- self.forecast_time = datalib.round_hour(forecast_hours[0], 12)
171
+ self.forecast_time = metsource.round_hour(forecast_hours[0], 12)
171
172
  else:
172
173
  forecast_time_pd = pd.to_datetime(forecast_time)
173
174
  if (hour := forecast_time_pd.hour) % 12:
174
175
  msg = f"Forecast hour must be one of 00 or 12 but is {hour:02d}."
175
176
  raise ValueError(msg)
176
- self.forecast_time = datalib.round_hour(forecast_time_pd.to_pydatetime(), 12)
177
+ self.forecast_time = metsource.round_hour(forecast_time_pd.to_pydatetime(), 12)
177
178
 
178
179
  last_step = (forecast_hours[-1] - self.forecast_time) / timedelta(hours=1)
179
180
  if last_step > LAST_STEP_6H:
@@ -188,7 +189,7 @@ class HRESModelLevel(ECMWFAPI):
188
189
  )
189
190
  if timestep_freq is None:
190
191
  timestep_freq = datasource_timestep_freq
191
- if not datalib.validate_timestep_freq(timestep_freq, datasource_timestep_freq):
192
+ if not metsource.validate_timestep_freq(timestep_freq, datasource_timestep_freq):
192
193
  msg = (
193
194
  f"Forecast out to step {last_step} "
194
195
  f"has timestep frequency of {datasource_timestep_freq} "
@@ -196,15 +197,15 @@ class HRESModelLevel(ECMWFAPI):
196
197
  )
197
198
  raise ValueError(msg)
198
199
 
199
- self.timesteps = datalib.parse_timesteps(time, freq=timestep_freq)
200
+ self.timesteps = metsource.parse_timesteps(time, freq=timestep_freq)
200
201
  if self.step_offset < 0:
201
202
  msg = f"Selected forecast time {self.forecast_time} is after first timestep."
202
203
  raise ValueError(msg)
203
204
 
204
205
  if pressure_levels is None:
205
206
  pressure_levels = pressure_levels_at_model_levels(20_000.0, 50_000.0)
206
- self.pressure_levels = datalib.parse_pressure_levels(pressure_levels)
207
- self.variables = datalib.parse_variables(variables, self.pressure_level_variables)
207
+ self.pressure_levels = metsource.parse_pressure_levels(pressure_levels)
208
+ self.variables = metsource.parse_variables(variables, self.pressure_level_variables)
208
209
 
209
210
  def __repr__(self) -> str:
210
211
  base = super().__repr__()
@@ -15,13 +15,14 @@ import pandas as pd
15
15
  import xarray as xr
16
16
  from overrides import overrides
17
17
 
18
- from pycontrails.core import datalib, met
18
+ from pycontrails.core import met
19
+ from pycontrails.datalib._met_utils import metsource
19
20
  from pycontrails.datalib.ecmwf.variables import ECMWF_VARIABLES
20
21
  from pycontrails.physics import constants
21
22
  from pycontrails.utils.types import DatetimeLike
22
23
 
23
24
 
24
- class IFS(datalib.MetDataSource):
25
+ class IFS(metsource.MetDataSource):
25
26
  """
26
27
  ECMWF Integrated Forecasting System (IFS) data source.
27
28
 
@@ -31,16 +32,16 @@ class IFS(datalib.MetDataSource):
31
32
 
32
33
  Parameters
33
34
  ----------
34
- time : datalib.TimeInput | None
35
+ time : metsource.TimeInput | None
35
36
  The time range for data retrieval, either a single datetime or (start, end) datetime range.
36
37
  Input must be a single datetime-like or tuple of datetime-like
37
38
  (datetime, :class:`pandas.Timestamp`, :class:`numpy.datetime64`)
38
39
  specifying the (start, end) of the date range, inclusive.
39
40
  If None, all time coordinates will be loaded.
40
- variables : datalib.VariableInput
41
+ variables : metsource.VariableInput
41
42
  Variable name (i.e. "air_temperature", ["air_temperature, relative_humidity"])
42
43
  See :attr:`pressure_level_variables` for the list of available variables.
43
- pressure_levels : datalib.PressureLevelInput, optional
44
+ pressure_levels : metsource.PressureLevelInput, optional
44
45
  Pressure level bounds for data (min, max), in hPa (mbar)
45
46
  Set to -1 for to download surface level parameters.
46
47
  Defaults to -1.
@@ -69,9 +70,9 @@ class IFS(datalib.MetDataSource):
69
70
 
70
71
  def __init__(
71
72
  self,
72
- time: datalib.TimeInput | None,
73
- variables: datalib.VariableInput,
74
- pressure_levels: datalib.PressureLevelInput = -1,
73
+ time: metsource.TimeInput | None,
74
+ variables: metsource.VariableInput,
75
+ pressure_levels: metsource.PressureLevelInput = -1,
75
76
  paths: str | list[str] | pathlib.Path | list[pathlib.Path] | None = None,
76
77
  grid: float | None = None,
77
78
  forecast_path: str | pathlib.Path | None = None,
@@ -89,9 +90,9 @@ class IFS(datalib.MetDataSource):
89
90
  self.forecast_date = pd.to_datetime(forecast_date).to_pydatetime()
90
91
 
91
92
  # parse inputs
92
- self.timesteps = datalib.parse_timesteps(time, freq="3h")
93
- self.pressure_levels = datalib.parse_pressure_levels(pressure_levels, None)
94
- self.variables = datalib.parse_variables(variables, self.supported_variables)
93
+ self.timesteps = metsource.parse_timesteps(time, freq="3h")
94
+ self.pressure_levels = metsource.parse_pressure_levels(pressure_levels, None)
95
+ self.variables = metsource.parse_variables(variables, self.supported_variables)
95
96
 
96
97
  def __repr__(self) -> str:
97
98
  base = super().__repr__()
@@ -221,8 +222,8 @@ class IFS(datalib.MetDataSource):
221
222
  LOG.debug(f"Loading IFS forecast date {date_str}")
222
223
 
223
224
  # load each dataset
224
- xr_kwargs.setdefault("chunks", datalib.DEFAULT_CHUNKS)
225
- xr_kwargs.setdefault("engine", datalib.NETCDF_ENGINE)
225
+ xr_kwargs.setdefault("chunks", metsource.DEFAULT_CHUNKS)
226
+ xr_kwargs.setdefault("engine", metsource.NETCDF_ENGINE)
226
227
  ds_full = xr.open_dataset(path_full, **xr_kwargs)
227
228
  ds_fl = xr.open_dataset(path_fl, **xr_kwargs)
228
229
  ds_surface = xr.open_dataset(path_surface, **xr_kwargs)
@@ -22,7 +22,8 @@ import xarray as xr
22
22
  from overrides import overrides
23
23
 
24
24
  import pycontrails
25
- from pycontrails.core import cache, datalib, met
25
+ from pycontrails.core import cache, met
26
+ from pycontrails.datalib._met_utils import metsource
26
27
  from pycontrails.datalib.gfs.variables import (
27
28
  PRESSURE_LEVEL_VARIABLES,
28
29
  SURFACE_VARIABLES,
@@ -43,12 +44,12 @@ logger = logging.getLogger(__name__)
43
44
  GFS_FORECAST_BUCKET = "noaa-gfs-bdp-pds"
44
45
 
45
46
 
46
- class GFSForecast(datalib.MetDataSource):
47
+ class GFSForecast(metsource.MetDataSource):
47
48
  """GFS Forecast data access.
48
49
 
49
50
  Parameters
50
51
  ----------
51
- time : `datalib.TimeInput`
52
+ time : `metsource.TimeInput`
52
53
  The time range for data retrieval, either a single datetime or (start, end) datetime range.
53
54
  Input must be a single datetime-like or tuple of datetime-like (datetime,
54
55
  :class:`pandas.Timestamp`, :class:`numpy.datetime64`)
@@ -56,10 +57,10 @@ class GFSForecast(datalib.MetDataSource):
56
57
  All times will be downloaded for a single forecast model run nearest to the start time
57
58
  (see :attr:`forecast_time`)
58
59
  If None, ``paths`` must be defined and all time coordinates will be loaded from files.
59
- variables : `datalib.VariableInput`
60
+ variables : `metsource.VariableInput`
60
61
  Variable name (i.e. "temperature", ["temperature, relative_humidity"])
61
62
  See :attr:`pressure_level_variables` for the list of available variables.
62
- pressure_levels : `datalib.PressureLevelInput`, optional
63
+ pressure_levels : `metsource.PressureLevelInput`, optional
63
64
  Pressure levels for data, in hPa (mbar)
64
65
  Set to [-1] for to download surface level parameters.
65
66
  Defaults to [-1].
@@ -132,9 +133,9 @@ class GFSForecast(datalib.MetDataSource):
132
133
 
133
134
  def __init__(
134
135
  self,
135
- time: datalib.TimeInput | None,
136
- variables: datalib.VariableInput,
137
- pressure_levels: datalib.PressureLevelInput = -1,
136
+ time: metsource.TimeInput | None,
137
+ variables: metsource.VariableInput,
138
+ pressure_levels: metsource.PressureLevelInput = -1,
138
139
  paths: str | list[str] | pathlib.Path | list[pathlib.Path] | None = None,
139
140
  grid: float = 0.25,
140
141
  forecast_time: DatetimeLike | None = None,
@@ -175,13 +176,13 @@ class GFSForecast(datalib.MetDataSource):
175
176
  # 3 hourly for 0.5 and 1 degree grid
176
177
  # https://www.nco.ncep.noaa.gov/pmb/products/gfs/
177
178
  freq = "1h" if grid == 0.25 else "3h"
178
- self.timesteps = datalib.parse_timesteps(time, freq=freq)
179
+ self.timesteps = metsource.parse_timesteps(time, freq=freq)
179
180
 
180
- self.pressure_levels = datalib.parse_pressure_levels(
181
+ self.pressure_levels = metsource.parse_pressure_levels(
181
182
  pressure_levels, self.supported_pressure_levels
182
183
  )
183
- self.variables = datalib.parse_variables(variables, self.supported_variables)
184
- self.grid = datalib.parse_grid(grid, (0.25, 0.5, 1))
184
+ self.variables = metsource.parse_variables(variables, self.supported_variables)
185
+ self.grid = metsource.parse_grid(grid, (0.25, 0.5, 1))
185
186
 
186
187
  # note GFS allows unsigned requests (no credentials)
187
188
  # https://stackoverflow.com/questions/34865927/can-i-use-boto3-anonymously/34866092#34866092
@@ -195,12 +196,12 @@ class GFSForecast(datalib.MetDataSource):
195
196
  if forecast_time_pd.hour % 6:
196
197
  raise ValueError("Forecast hour must be on one of 00, 06, 12, 18")
197
198
 
198
- self.forecast_time = datalib.round_hour(forecast_time_pd.to_pydatetime(), 6)
199
+ self.forecast_time = metsource.round_hour(forecast_time_pd.to_pydatetime(), 6)
199
200
 
200
201
  # if no specific forecast is requested, set the forecast time using timesteps
201
202
  else:
202
203
  # round first element to the nearest 6 hour time (00, 06, 12, 18 UTC) for forecast_time
203
- self.forecast_time = datalib.round_hour(self.timesteps[0], 6)
204
+ self.forecast_time = metsource.round_hour(self.timesteps[0], 6)
204
205
 
205
206
  def __repr__(self) -> str:
206
207
  base = super().__repr__()
@@ -16,7 +16,7 @@ from __future__ import annotations
16
16
 
17
17
  import datetime
18
18
  import enum
19
- import io
19
+ import tempfile
20
20
  from collections.abc import Iterable
21
21
 
22
22
  import numpy as np
@@ -35,7 +35,7 @@ except ModuleNotFoundError as exc:
35
35
  name="goes module",
36
36
  package_name="cartopy",
37
37
  module_not_found_error=exc,
38
- pycontrails_optional_package="goes",
38
+ pycontrails_optional_package="sat",
39
39
  )
40
40
 
41
41
  try:
@@ -45,7 +45,7 @@ except ModuleNotFoundError as exc:
45
45
  name="goes module",
46
46
  package_name="gcsfs",
47
47
  module_not_found_error=exc,
48
- pycontrails_optional_package="goes",
48
+ pycontrails_optional_package="sat",
49
49
  )
50
50
 
51
51
 
@@ -535,7 +535,7 @@ class GOES:
535
535
  da_dict = {}
536
536
  for rpath, init_bytes in data.items():
537
537
  channel = _extract_channel_from_rpath(rpath)
538
- ds = xr.open_dataset(io.BytesIO(init_bytes), engine="h5netcdf")
538
+ ds = _load_via_tempfile(init_bytes)
539
539
 
540
540
  da = ds["CMI"]
541
541
  da = da.expand_dims(band_id=ds["band_id"].values)
@@ -551,7 +551,7 @@ class GOES:
551
551
  da = xr.concat(da_dict.values(), dim="band_id")
552
552
 
553
553
  else:
554
- ds = xr.open_dataset(io.BytesIO(data), engine="h5netcdf")
554
+ ds = _load_via_tempfile(data)
555
555
  da = ds["CMI"]
556
556
  da = da.expand_dims(band_id=ds["band_id"].values)
557
557
 
@@ -564,6 +564,13 @@ class GOES:
564
564
  return da
565
565
 
566
566
 
567
+ def _load_via_tempfile(data: bytes) -> xr.Dataset:
568
+ """Load xarray dataset via temporary file."""
569
+ with tempfile.NamedTemporaryFile(buffering=0) as tmp:
570
+ tmp.write(data)
571
+ return xr.load_dataset(tmp.name)
572
+
573
+
567
574
  def _concat_c02(ds1: XArrayType, ds2: XArrayType) -> XArrayType:
568
575
  """Concatenate two datasets with C01 and C02 data."""
569
576
  # Average the C02 data to the C01 resolution