roms-tools 1.7.0__py3-none-any.whl → 2.1.0__py3-none-any.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.
Files changed (124) hide show
  1. roms_tools/__init__.py +2 -1
  2. roms_tools/setup/boundary_forcing.py +246 -146
  3. roms_tools/setup/datasets.py +229 -69
  4. roms_tools/setup/download.py +13 -17
  5. roms_tools/setup/grid.py +777 -614
  6. roms_tools/setup/initial_conditions.py +168 -32
  7. roms_tools/setup/mask.py +115 -0
  8. roms_tools/setup/nesting.py +575 -0
  9. roms_tools/setup/plot.py +218 -63
  10. roms_tools/setup/regrid.py +4 -2
  11. roms_tools/setup/river_forcing.py +125 -29
  12. roms_tools/setup/surface_forcing.py +31 -25
  13. roms_tools/setup/tides.py +29 -14
  14. roms_tools/setup/topography.py +250 -153
  15. roms_tools/setup/utils.py +174 -44
  16. roms_tools/setup/vertical_coordinate.py +5 -16
  17. roms_tools/tests/test_setup/test_boundary_forcing.py +10 -5
  18. roms_tools/tests/test_setup/test_data/grid.zarr/.zattrs +0 -1
  19. roms_tools/tests/test_setup/test_data/grid.zarr/.zmetadata +56 -201
  20. roms_tools/tests/test_setup/test_data/grid.zarr/Cs_r/.zattrs +1 -1
  21. roms_tools/tests/test_setup/test_data/grid.zarr/Cs_w/.zattrs +1 -1
  22. roms_tools/tests/test_setup/test_data/grid.zarr/{layer_depth_rho → sigma_r}/.zarray +2 -6
  23. roms_tools/tests/test_setup/test_data/grid.zarr/sigma_r/.zattrs +7 -0
  24. roms_tools/tests/test_setup/test_data/grid.zarr/sigma_r/0 +0 -0
  25. roms_tools/tests/test_setup/test_data/grid.zarr/sigma_w/.zarray +20 -0
  26. roms_tools/tests/test_setup/test_data/grid.zarr/sigma_w/.zattrs +7 -0
  27. roms_tools/tests/test_setup/test_data/grid.zarr/sigma_w/0 +0 -0
  28. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zattrs +1 -2
  29. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zmetadata +58 -203
  30. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/Cs_r/.zattrs +1 -1
  31. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/Cs_w/.zattrs +1 -1
  32. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/.zattrs +1 -1
  33. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/0.0 +0 -0
  34. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_coarse/0.0 +0 -0
  35. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_rho/0.0 +0 -0
  36. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_u/0.0 +0 -0
  37. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_v/0.0 +0 -0
  38. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_r/.zarray +20 -0
  39. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_r/.zattrs +7 -0
  40. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_r/0 +0 -0
  41. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_w/.zarray +20 -0
  42. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_w/.zattrs +7 -0
  43. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_w/0 +0 -0
  44. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zmetadata +2 -3
  45. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_tracer/.zattrs +1 -2
  46. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_name/.zarray +1 -1
  47. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_name/0 +0 -0
  48. roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/.zmetadata +5 -6
  49. roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_tracer/.zarray +2 -2
  50. roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_tracer/.zattrs +1 -2
  51. roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_tracer/0.0.0 +0 -0
  52. roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/tracer_name/.zarray +2 -2
  53. roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_name/0 +0 -0
  54. roms_tools/tests/test_setup/test_datasets.py +2 -2
  55. roms_tools/tests/test_setup/test_grid.py +110 -12
  56. roms_tools/tests/test_setup/test_initial_conditions.py +2 -1
  57. roms_tools/tests/test_setup/test_nesting.py +489 -0
  58. roms_tools/tests/test_setup/test_river_forcing.py +53 -15
  59. roms_tools/tests/test_setup/test_surface_forcing.py +3 -22
  60. roms_tools/tests/test_setup/test_tides.py +2 -1
  61. roms_tools/tests/test_setup/test_topography.py +106 -1
  62. roms_tools/tests/test_setup/test_validation.py +2 -2
  63. {roms_tools-1.7.0.dist-info → roms_tools-2.1.0.dist-info}/LICENSE +1 -1
  64. {roms_tools-1.7.0.dist-info → roms_tools-2.1.0.dist-info}/METADATA +9 -4
  65. {roms_tools-1.7.0.dist-info → roms_tools-2.1.0.dist-info}/RECORD +85 -108
  66. {roms_tools-1.7.0.dist-info → roms_tools-2.1.0.dist-info}/WHEEL +1 -1
  67. roms_tools/_version.py +0 -2
  68. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_rho/.zarray +0 -24
  69. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_rho/.zattrs +0 -9
  70. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_rho/0.0.0 +0 -0
  71. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_u/.zarray +0 -24
  72. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_u/.zattrs +0 -9
  73. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_u/0.0.0 +0 -0
  74. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_v/.zarray +0 -24
  75. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_v/.zattrs +0 -9
  76. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_v/0.0.0 +0 -0
  77. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_rho/.zattrs +0 -9
  78. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_rho/0.0.0 +0 -0
  79. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_u/.zarray +0 -24
  80. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_u/.zattrs +0 -9
  81. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_u/0.0.0 +0 -0
  82. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_v/.zarray +0 -24
  83. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_v/.zattrs +0 -9
  84. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_v/0.0.0 +0 -0
  85. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/.zarray +0 -24
  86. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/.zattrs +0 -9
  87. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/0.0.0 +0 -0
  88. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_u/.zarray +0 -24
  89. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_u/.zattrs +0 -9
  90. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_u/0.0.0 +0 -0
  91. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_v/.zarray +0 -24
  92. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_v/.zattrs +0 -9
  93. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_v/0.0.0 +0 -0
  94. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/.zarray +0 -24
  95. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/.zattrs +0 -9
  96. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/0.0.0 +0 -0
  97. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/.zarray +0 -24
  98. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/.zattrs +0 -9
  99. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/0.0.0 +0 -0
  100. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/.zarray +0 -24
  101. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/.zattrs +0 -9
  102. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/0.0.0 +0 -0
  103. roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_tracer/0.0.0 +0 -0
  104. roms_tools/tests/test_setup/test_data/river_forcing.zarr/tracer_name/0 +0 -0
  105. roms_tools/tests/test_setup/test_vertical_coordinate.py +0 -91
  106. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/.zattrs +0 -0
  107. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/.zgroup +0 -0
  108. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/abs_time/.zarray +0 -0
  109. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/abs_time/.zattrs +0 -0
  110. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/abs_time/0 +0 -0
  111. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/month/.zarray +0 -0
  112. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/month/.zattrs +0 -0
  113. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/month/0 +0 -0
  114. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_name/.zarray +0 -0
  115. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_name/.zattrs +0 -0
  116. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_name/0 +0 -0
  117. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_time/.zarray +0 -0
  118. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_time/.zattrs +0 -0
  119. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_time/0 +0 -0
  120. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_volume/.zarray +0 -0
  121. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_volume/.zattrs +0 -0
  122. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_volume/0.0 +0 -0
  123. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/tracer_name/.zattrs +0 -0
  124. {roms_tools-1.7.0.dist-info → roms_tools-2.1.0.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,4 @@
1
+ import time
1
2
  import re
2
3
  import xarray as xr
3
4
  from dataclasses import dataclass, field
@@ -15,7 +16,11 @@ from roms_tools.setup.utils import (
15
16
  one_dim_fill,
16
17
  gc_dist,
17
18
  )
18
- from roms_tools.setup.download import download_correction_data, download_river_data
19
+ from roms_tools.setup.download import (
20
+ download_correction_data,
21
+ download_topo,
22
+ download_river_data,
23
+ )
19
24
  from roms_tools.setup.fill import LateralFill
20
25
 
21
26
  # lat-lon datasets
@@ -74,7 +79,7 @@ class Dataset:
74
79
  )
75
80
  var_names: Dict[str, str]
76
81
  climatology: Optional[bool] = False
77
- use_dask: Optional[bool] = True
82
+ use_dask: Optional[bool] = False
78
83
  apply_post_processing: Optional[bool] = True
79
84
 
80
85
  is_global: bool = field(init=False, repr=False)
@@ -117,6 +122,8 @@ class Dataset:
117
122
 
118
123
  # Make sure that latitude is ascending
119
124
  ds = self.ensure_dimension_is_ascending(ds, dim="latitude")
125
+ # Make sure there are no 360 degree jumps in longitude
126
+ ds = self.ensure_dimension_is_ascending(ds, dim="longitude")
120
127
 
121
128
  if "depth" in self.dim_names:
122
129
  # Make sure that depth is ascending
@@ -126,11 +133,6 @@ class Dataset:
126
133
 
127
134
  # Check whether the data covers the entire globe
128
135
  object.__setattr__(self, "is_global", self.check_if_global(ds))
129
-
130
- # If dataset is global concatenate three copies of field along longitude dimension
131
- if self.is_global:
132
- ds = self.concatenate_longitudes(ds)
133
-
134
136
  object.__setattr__(self, "ds", ds)
135
137
 
136
138
  if self.apply_post_processing:
@@ -289,7 +291,11 @@ class Dataset:
289
291
  ) -> xr.Dataset:
290
292
  """Ensure that the specified dimension in the dataset is in ascending order.
291
293
 
292
- If the values along the specified dimension are in descending order, this function reverses the order of the dimension to make it ascending.
294
+ This function checks the order of values along the specified dimension. If they
295
+ are in descending order, it reverses the dimension to make it ascending. For
296
+ the "longitude" dimension, if it has a discontinuity (e.g., [0, 180][-180, 0]),
297
+ the function adjusts values to eliminate the 360-degree jump, transforming
298
+ the range into a continuous [0, 360) span.
293
299
 
294
300
  Parameters
295
301
  ----------
@@ -303,14 +309,23 @@ class Dataset:
303
309
  -------
304
310
  xr.Dataset
305
311
  A new `xarray.Dataset` with the specified dimension in ascending order.
306
- If the dimension was already in ascending order, the original dataset is returned unchanged.
307
- If the dimension was in descending order, the dataset is returned with the dimension reversed.
312
+ - If the dimension was already in ascending order, the original dataset is returned unchanged.
313
+ - If the dimension was in descending order, the dataset is returned with the dimension reversed.
314
+ - If the dimension is "longitude" with a discontinuity (e.g., [0, 180][-180, 0]), the values are adjusted to eliminate the 360-degree jump.
308
315
  """
309
- # Make sure that latitude is ascending
316
+ # Check if the dimension is in descending order and reverse if needed
310
317
  diff = np.diff(ds[self.dim_names[dim]])
311
318
  if np.all(diff < 0):
312
319
  ds = ds.isel(**{self.dim_names[dim]: slice(None, None, -1)})
313
320
 
321
+ # Check for a discontinuity in longitude and adjust values if present
322
+ elif np.any(diff < 0) and dim == "longitude":
323
+ ds[self.dim_names[dim]] = xr.where(
324
+ ds[self.dim_names[dim]] < 0,
325
+ ds[self.dim_names[dim]] + 360,
326
+ ds[self.dim_names[dim]],
327
+ )
328
+
314
329
  return ds
315
330
 
316
331
  def infer_horizontal_resolution(self, ds: xr.Dataset):
@@ -364,43 +379,68 @@ class Dataset:
364
379
 
365
380
  return is_global
366
381
 
367
- def concatenate_longitudes(self, ds):
368
- """
369
- Concatenates the field three times: with longitudes shifted by -360, original longitudes, and shifted by +360.
382
+ def concatenate_longitudes(self, ds, end="upper", verbose=False):
383
+ """Concatenates fields in dataset twice along the longitude dimension.
370
384
 
371
385
  Parameters
372
386
  ----------
373
- field : xr.DataArray
374
- The field to be concatenated.
387
+ ds: xr.Dataset
388
+ The dataset to be concatenated. The longitude dimension must be present in this dataset.
389
+ end : str, optional
390
+ Specifies which end to shift the longitudes.
391
+ Options are:
392
+ - "lower": shifts longitudes by -360 degrees and concatenates to the lower end.
393
+ - "upper": shifts longitudes by +360 degrees and concatenates to the upper end.
394
+ - "both": shifts longitudes by -360 degrees and 360 degrees and concatenates to both ends.
395
+ Default is "upper".
396
+ verbose : bool, optional
397
+ If True, print message if dataset is concatenated along longitude dimension.
398
+ Defaults to False.
375
399
 
376
400
  Returns
377
401
  -------
378
- xr.DataArray
379
- The concatenated field, with the longitude dimension extended.
402
+ ds_concatenated : xr.Dataset
403
+ The concatenated dataset.
404
+ """
380
405
 
381
- Notes
382
- -----
383
- Concatenating three times may be overkill in most situations, but it is safe. Alternatively, we could refactor
384
- to figure out whether concatenating on the lower end, upper end, or at all is needed.
406
+ if verbose:
407
+ start_time = time.time()
385
408
 
386
- """
387
409
  ds_concatenated = xr.Dataset()
388
410
 
389
411
  lon = ds[self.dim_names["longitude"]]
390
- lon_minus360 = lon - 360
391
- lon_plus360 = lon + 360
392
- lon_concatenated = xr.concat(
393
- [lon_minus360, lon, lon_plus360], dim=self.dim_names["longitude"]
394
- )
412
+ if end == "lower":
413
+ lon_minus360 = lon - 360
414
+ lon_concatenated = xr.concat(
415
+ [lon_minus360, lon], dim=self.dim_names["longitude"]
416
+ )
395
417
 
396
- ds_concatenated[self.dim_names["longitude"]] = lon_concatenated
418
+ elif end == "upper":
419
+ lon_plus360 = lon + 360
420
+ lon_concatenated = xr.concat(
421
+ [lon, lon_plus360], dim=self.dim_names["longitude"]
422
+ )
397
423
 
398
- for var in self.var_names.values():
424
+ elif end == "both":
425
+ lon_minus360 = lon - 360
426
+ lon_plus360 = lon + 360
427
+ lon_concatenated = xr.concat(
428
+ [lon_minus360, lon, lon_plus360], dim=self.dim_names["longitude"]
429
+ )
430
+
431
+ for var in ds.data_vars:
399
432
  if self.dim_names["longitude"] in ds[var].dims:
400
433
  field = ds[var]
401
- field_concatenated = xr.concat(
402
- [field, field, field], dim=self.dim_names["longitude"]
403
- )
434
+
435
+ if end == "both":
436
+ field_concatenated = xr.concat(
437
+ [field, field, field], dim=self.dim_names["longitude"]
438
+ )
439
+ else:
440
+ field_concatenated = xr.concat(
441
+ [field, field], dim=self.dim_names["longitude"]
442
+ )
443
+
404
444
  if self.use_dask:
405
445
  field_concatenated = field_concatenated.chunk(
406
446
  {self.dim_names["longitude"]: -1}
@@ -410,6 +450,13 @@ class Dataset:
410
450
  else:
411
451
  ds_concatenated[var] = ds[var]
412
452
 
453
+ ds_concatenated[self.dim_names["longitude"]] = lon_concatenated
454
+
455
+ if verbose:
456
+ logging.info(
457
+ f"Concatenating the data along the longitude dimension: {time.time() - start_time:.3f} seconds"
458
+ )
459
+
413
460
  return ds_concatenated
414
461
 
415
462
  def post_process(self):
@@ -423,7 +470,9 @@ class Dataset:
423
470
  """
424
471
  pass
425
472
 
426
- def choose_subdomain(self, target_coords, buffer_points=20, return_copy=False):
473
+ def choose_subdomain(
474
+ self, target_coords, buffer_points=20, return_copy=False, verbose=False
475
+ ):
427
476
  """Selects a subdomain from the xarray Dataset based on specified target
428
477
  coordinates, extending the selection by a defined buffer. Adjusts longitude
429
478
  ranges as necessary to accommodate the dataset's expected range and handles
@@ -440,6 +489,9 @@ class Dataset:
440
489
  return_subdomain : bool, optional
441
490
  If True, returns the subset of the original dataset representing the chosen
442
491
  subdomain. If False, assigns the subset to `self.ds`. Defaults to False.
492
+ verbose : bool, optional
493
+ If True, print message if dataset is concatenated along longitude dimension.
494
+ Defaults to False.
443
495
 
444
496
  Returns
445
497
  -------
@@ -462,9 +514,43 @@ class Dataset:
462
514
 
463
515
  margin = self.resolution * buffer_points
464
516
 
465
- if not self.is_global:
517
+ # Select the subdomain in latitude direction (so that we have to concatenate fewer latitudes below if concatenation is necessary)
518
+ subdomain = self.ds.sel(
519
+ **{
520
+ self.dim_names["latitude"]: slice(lat_min - margin, lat_max + margin),
521
+ }
522
+ )
523
+ lon = subdomain[self.dim_names["longitude"]]
524
+
525
+ if self.is_global:
526
+ # Concatenate only if necessary
527
+ if lon_max + margin > lon.max():
528
+ # See if shifting by +360 degrees helps
529
+ if (lon_min - margin > (lon + 360).min()) and (
530
+ lon_max + margin < (lon + 360).max()
531
+ ):
532
+ subdomain[self.dim_names["longitude"]] = lon + 360
533
+ lon = subdomain[self.dim_names["longitude"]]
534
+ else:
535
+ subdomain = self.concatenate_longitudes(
536
+ subdomain, end="upper", verbose=verbose
537
+ )
538
+ lon = subdomain[self.dim_names["longitude"]]
539
+ if lon_min - margin < lon.min():
540
+ # See if shifting by -360 degrees helps
541
+ if (lon_min - margin > (lon - 360).min()) and (
542
+ lon_max + margin < (lon - 360).max()
543
+ ):
544
+ subdomain[self.dim_names["longitude"]] = lon - 360
545
+ lon = subdomain[self.dim_names["longitude"]]
546
+ else:
547
+ subdomain = self.concatenate_longitudes(
548
+ subdomain, end="lower", verbose=verbose
549
+ )
550
+ lon = subdomain[self.dim_names["longitude"]]
551
+
552
+ else:
466
553
  # Adjust longitude range if needed to match the expected range
467
- lon = self.ds[self.dim_names["longitude"]]
468
554
  if not target_coords["straddle"]:
469
555
  if lon.min() < -180:
470
556
  if lon_max + margin > 0:
@@ -484,12 +570,9 @@ class Dataset:
484
570
  if lon_min - margin < 0:
485
571
  lon_min += 360
486
572
  lon_max += 360
487
-
488
- # Select the subdomain
489
-
490
- subdomain = self.ds.sel(
573
+ # Select the subdomain in longitude direction
574
+ subdomain = subdomain.sel(
491
575
  **{
492
- self.dim_names["latitude"]: slice(lat_min - margin, lat_max + margin),
493
576
  self.dim_names["longitude"]: slice(lon_min - margin, lon_max + margin),
494
577
  }
495
578
  )
@@ -1286,7 +1369,7 @@ class ERA5Correction(Dataset):
1286
1369
 
1287
1370
  super().__post_init__()
1288
1371
 
1289
- def choose_subdomain(self, coords, straddle: bool):
1372
+ def choose_subdomain(self, target_coords, straddle: bool):
1290
1373
  """Converts longitude values in the dataset if necessary and selects a subdomain
1291
1374
  based on the specified coordinates.
1292
1375
 
@@ -1295,7 +1378,7 @@ class ERA5Correction(Dataset):
1295
1378
 
1296
1379
  Parameters
1297
1380
  ----------
1298
- coords : dict
1381
+ target_coords : dict
1299
1382
  A dictionary specifying the target coordinates for selecting the subdomain. Keys should correspond to the
1300
1383
  dimension names of the dataset (e.g., latitude and longitude), and values should be the desired ranges or
1301
1384
  specific coordinate values.
@@ -1314,38 +1397,122 @@ class ERA5Correction(Dataset):
1314
1397
  - The dataset (`self.ds`) is updated in place to reflect the chosen subdomain.
1315
1398
  """
1316
1399
 
1317
- lon = self.ds[self.dim_names["longitude"]]
1318
-
1319
- if not self.is_global:
1320
- if lon.min().values < 0 and not straddle:
1321
- # Convert from [-180, 180] to [0, 360]
1322
- self.ds[self.dim_names["longitude"]] = xr.where(lon < 0, lon + 360, lon)
1400
+ # Select the subdomain in latitude direction (so that we have to concatenate fewer latitudes below if concatenation is performed)
1401
+ subdomain = self.ds.sel({self.dim_names["latitude"]: target_coords["lat"]})
1323
1402
 
1324
- if lon.max().values > 180 and straddle:
1325
- # Convert from [0, 360] to [-180, 180]
1326
- self.ds[self.dim_names["longitude"]] = xr.where(
1327
- lon > 180, lon - 360, lon
1328
- )
1403
+ if self.is_global:
1404
+ # Always concatenate because computational overhead should be managable for 1/4 degree ERA5 resolution
1405
+ subdomain = self.concatenate_longitudes(
1406
+ subdomain, end="both", verbose=False
1407
+ )
1329
1408
 
1330
- # Select the subdomain based on the specified latitude and longitude ranges
1331
- subdomain = self.ds.sel(**coords)
1409
+ # Select the subdomain in longitude direction
1410
+ subdomain = subdomain.sel({self.dim_names["longitude"]: target_coords["lon"]})
1332
1411
 
1333
1412
  # Check if the selected subdomain contains the specified latitude and longitude values
1334
- if not subdomain[self.dim_names["latitude"]].equals(
1335
- coords[self.dim_names["latitude"]]
1336
- ):
1413
+ if not subdomain[self.dim_names["latitude"]].equals(target_coords["lat"]):
1337
1414
  raise ValueError(
1338
1415
  "The correction dataset does not contain all specified latitude values."
1339
1416
  )
1340
- if not subdomain[self.dim_names["longitude"]].equals(
1341
- coords[self.dim_names["longitude"]]
1342
- ):
1417
+ if not subdomain[self.dim_names["longitude"]].equals(target_coords["lon"]):
1343
1418
  raise ValueError(
1344
1419
  "The correction dataset does not contain all specified longitude values."
1345
1420
  )
1346
1421
  object.__setattr__(self, "ds", subdomain)
1347
1422
 
1348
1423
 
1424
+ @dataclass(frozen=True, kw_only=True)
1425
+ class ETOPO5Dataset(Dataset):
1426
+ """Represents topography data on the original grid from the ETOPO5 dataset.
1427
+
1428
+ Parameters
1429
+ ----------
1430
+ filename : str, optional
1431
+ The path to the ETOPO5 dataset file. If not provided, the dataset will be downloaded
1432
+ automatically via the `pooch` library.
1433
+ var_names : Dict[str, str], optional
1434
+ Dictionary of variable names required in the dataset. Defaults to:
1435
+ {
1436
+ "topo": "topo",
1437
+ }
1438
+ dim_names : Dict[str, str], optional
1439
+ Dictionary specifying the names of dimensions in the dataset. Defaults to:
1440
+ {"longitude": "lon", "latitude": "lat"}.
1441
+
1442
+ Attributes
1443
+ ----------
1444
+ ds : xr.Dataset
1445
+ The xarray Dataset containing the ETOPO5 data, loaded from the specified file.
1446
+ """
1447
+
1448
+ filename: str = field(default_factory=lambda: download_topo("etopo5.nc"))
1449
+ var_names: Dict[str, str] = field(
1450
+ default_factory=lambda: {
1451
+ "topo": "topo",
1452
+ }
1453
+ )
1454
+ dim_names: Dict[str, str] = field(
1455
+ default_factory=lambda: {"longitude": "lon", "latitude": "lat"}
1456
+ )
1457
+ ds: xr.Dataset = field(init=False, repr=False)
1458
+
1459
+ def clean_up(self, ds: xr.Dataset) -> xr.Dataset:
1460
+ """Assign lat and lon as coordinates.
1461
+
1462
+ Parameters
1463
+ ----------
1464
+ ds : xr.Dataset
1465
+ The input dataset.
1466
+
1467
+ Returns
1468
+ -------
1469
+ ds : xr.Dataset
1470
+ A cleaned `xarray.Dataset` with updated coordinates.
1471
+ """
1472
+ ds = ds.assign_coords(
1473
+ {
1474
+ "lon": ds["topo_lon"],
1475
+ "lat": ds["topo_lat"],
1476
+ }
1477
+ )
1478
+ return ds
1479
+
1480
+
1481
+ @dataclass(frozen=True, kw_only=True)
1482
+ class SRTM15Dataset(Dataset):
1483
+ """Represents topography data on the original grid from the SRTM15 dataset.
1484
+
1485
+ Parameters
1486
+ ----------
1487
+ filename : str
1488
+ The path to the SRTM15 dataset file.
1489
+ var_names : Dict[str, str], optional
1490
+ Dictionary of variable names required in the dataset. Defaults to:
1491
+ {
1492
+ "topo": "z",
1493
+ }
1494
+ dim_names : Dict[str, str], optional
1495
+ Dictionary specifying the names of dimensions in the dataset. Defaults to:
1496
+ {"longitude": "lon", "latitude": "lat"}.
1497
+
1498
+ Attributes
1499
+ ----------
1500
+ ds : xr.Dataset
1501
+ The xarray Dataset containing the SRTM15 data, loaded from the specified file.
1502
+ """
1503
+
1504
+ filename: str
1505
+ var_names: Dict[str, str] = field(
1506
+ default_factory=lambda: {
1507
+ "topo": "z",
1508
+ }
1509
+ )
1510
+ dim_names: Dict[str, str] = field(
1511
+ default_factory=lambda: {"longitude": "lon", "latitude": "lat"}
1512
+ )
1513
+ ds: xr.Dataset = field(init=False, repr=False)
1514
+
1515
+
1349
1516
  # river datasets
1350
1517
  @dataclass(frozen=True, kw_only=True)
1351
1518
  class RiverDataset:
@@ -1414,13 +1581,6 @@ class RiverDataset:
1414
1581
  -------
1415
1582
  ds : xr.Dataset
1416
1583
  The loaded xarray Dataset containing the forcing data.
1417
-
1418
- Raises
1419
- ------
1420
- FileNotFoundError
1421
- If the specified file does not exist.
1422
- ValueError
1423
- If a list of files is provided but self.dim_names["time"] is not available or use_dask=False.
1424
1584
  """
1425
1585
  ds = _load_data(
1426
1586
  self.filename, self.dim_names, use_dask=False, decode_times=False
@@ -1,5 +1,4 @@
1
1
  import pooch
2
- import xarray as xr
3
2
 
4
3
  # Create a Pooch object to manage the global topography data
5
4
  topo_data = pooch.create(
@@ -19,7 +18,6 @@ correction_data = pooch.create(
19
18
  base_url="https://github.com/CWorthy-ocean/roms-tools-data/raw/main/",
20
19
  # The registry specifies the files that can be fetched
21
20
  registry={
22
- "etopo5.nc": "sha256:23600e422d59bbf7c3666090166a0d468c8ee16092f4f14e32c4e928fbcd627b",
23
21
  "SSR_correction.nc": "sha256:a170c1698e6cc2765b3f0bb51a18c6a979bc796ac3a4c014585aeede1f1f8ea0",
24
22
  },
25
23
  )
@@ -50,6 +48,7 @@ pup_test_data = pooch.create(
50
48
  "ERA5_global_test_data.nc": "8ed177ab64c02caf509b9fb121cf6713f286cc603b1f302f15f3f4eb0c21dc4f",
51
49
  "TPXO_global_test_data.nc": "457bfe87a7b247ec6e04e3c7d3e741ccf223020c41593f8ae33a14f2b5255e60",
52
50
  "TPXO_regional_test_data.nc": "11739245e2286d9c9d342dce5221e6435d2072b50028bef2e86a30287b3b4032",
51
+ "CESM_BGC_coarse_global_clim.nc": "20806e4e99285d6de168d3236e2d9245f4e9106474b1464beaa266a73e6ef79f",
53
52
  "CESM_BGC_2012.nc": "e374d5df3c1be742d564fd26fd861c2d40af73be50a432c51d258171d5638eb6",
54
53
  "CESM_regional_test_data_one_time_slice.nc": "43b578ecc067c85f95d6b97ed7b9dc8da7846f07c95331c6ba7f4a3161036a17",
55
54
  "CESM_regional_test_data_climatology.nc": "986a200029d9478fd43e6e4a8bc43e8a8f4407554893c59b5fcc2e86fd203272",
@@ -58,36 +57,33 @@ pup_test_data = pooch.create(
58
57
  "CESM_surface_global_test_data_climatology.nc": "a072757110c6f7b716a98f867688ef4195a5966741d2f368201ac24617254e35",
59
58
  "CESM_surface_global_test_data.nc": "874106ffbc8b1b220db09df1551bbb89d22439d795b4d1e5a24ee775e9a7bf6e",
60
59
  "grid_created_with_matlab.nc": "fd537ef8159fabb18e38495ec8d44e2fa1b7fb615fcb1417dd4c0e1bb5f4e41d",
60
+ "etopo5_coarsened_and_shifted.nc": "9a5cb4b38c779d22ddb0ad069b298b9722db34ca85a89273eccca691e89e6f96",
61
+ "srtm15_coarsened.nc": "48bc8f4beecfdca9c192b13f4cbeef1455f49d8261a82563aaec5757e100dff9",
61
62
  },
62
63
  )
63
64
 
64
65
 
65
- def fetch_topo(topography_source: str) -> xr.Dataset:
66
- """Load the global topography data as an xarray Dataset.
66
+ def download_topo(filename: str) -> str:
67
+ """Download simple topography file.
67
68
 
68
69
  Parameters
69
70
  ----------
70
- topography_source : str
71
- The source of the topography data to be loaded. Available options:
72
- - "ETOPO5"
71
+ filename : str
72
+ The name of the test data file to be downloaded. Available options:
73
+ - "etopo5.nc"
73
74
 
74
75
  Returns
75
76
  -------
76
- xr.Dataset
77
- The global topography data as an xarray Dataset.
77
+ str
78
+ The path to the downloaded test data file.
78
79
  """
79
- # Mapping from user-specified topography options to corresponding filenames in the registry
80
- topo_dict = {"ETOPO5": "etopo5.nc"}
81
-
82
80
  # Fetch the file using Pooch, downloading if necessary
83
- fname = topo_data.fetch(topo_dict[topography_source])
81
+ fname = topo_data.fetch(filename)
84
82
 
85
- # Load the dataset using xarray and return it
86
- ds = xr.open_dataset(fname)
87
- return ds
83
+ return fname
88
84
 
89
85
 
90
- def download_river_data(filename: str) -> xr.Dataset:
86
+ def download_river_data(filename: str) -> str:
91
87
  """Download river data file.
92
88
 
93
89
  Parameters