Skip to content

Commit

Permalink
Update cf_convert_between_lon_frames
Browse files Browse the repository at this point in the history
- fixed new force option
- added test
  • Loading branch information
sol1105 committed Nov 1, 2024
1 parent 0bd7e04 commit 457f16c
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 5 deletions.
41 changes: 41 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
Version History
===============

v0.14.1 (unreleased)
--------------------

New Features
^^^^^^^^^^^^
* Added new methods to `clisops.core.regrid.Grid`
* Added possibility to apply land or ocean mask if present in the file
* Adapted method from ESMF to detect smashed cells
* Masking degenerate (i.e. collapsed and smashed) cells
* Dropping lat/lon bounds if an integrity check fails
* Added a few attributes to the `Grid` object
* `clisops.ops.regrid`: added option to request a land/sea mask for the output grid
* `clisops.utils.dataset_utils`
* Added function `determine_lon_lat_range` to determine the min. and max. lat and lon values
* Added function `fix_unmasked_missing_values_lon_lat` to identify and mask yet unmasked missing values in lat and lon arrays
* Added `force` parameter to `cf_convert_between_lon_frames`

Bug Fixes
^^^^^^^^^
* `clisops.utils.dataset_utils`
* Fixed issue in `cf_convert_between_lon_frames` causing the longitude frame to not be adjusted in case of NaNs in the longitude array
* Addressed issues in `generate_bounds_curvilinear`
* latitudes are now clipped above 90 or below -90 degrees north
* longitudes are converted to longitude frame -180, 180
* longitude bounds are adjusted at the Greenwich meridian or anti meridian to avoid grid cells wrapping once or more times around the globe
* Bounds are generated significantly faster due to making use of index slicing and `numpy.vectorize`

Breaking Changes
^^^^^^^^^^^^^^^^
* Adapted functions from `roocs_utils.xarray_utils.xarray_utils` into `clisops.utils.dataset_utils`
* `get_coord_by_type` now returns the name of the coordinate variable and not the coordinate variable
* `get_coord_by_type` optionally returns a list with further matches for the coordinate variable
* `get_coord_by_type` does no longer raise an exception when more than one coordinate variable matches the requested type
* `get_coord_by_type` raises `ValueError` instead of `Exception` when the coordinate type is unknown
* `detect_coordinate` raises `KeyError` instead of `AttributeError` if no coordinate could be detected
* `detect_gridtype` raises `ValueError` for unsupported grid types rather than `InvalidParameterValue` and `Exception`
* `clisops.core.regrid`
* `Grid.detect_coordinate`: raises `KeyError` instead of `AttributeError` if no coordinate could be detected
* `clisops.ops.regrid`
* `Regrid._calculate`: issues `UserWarning` instead of letting `clisops.core.Weights.__init__` raise an `Exception` when input and output grid are alike

v0.14.0 (2024-10-03)
--------------------

Expand Down
23 changes: 18 additions & 5 deletions clisops/utils/dataset_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ def cf_convert_between_lon_frames(ds_in, lon_interval, force=False):
ds[lon] = ds[lon].where(ds[lon] <= 180, ds[lon] - 360.0)
if lon_bnds:
ds[lon_bnds] = ds[lon_bnds].where(
ds[lon_bnds] > 180, ds[lon_bnds] - 360.0
ds[lon_bnds] <= 180, ds[lon_bnds] - 360.0
)
elif low > 0 and lon_min < 0:
ds[lon] = ds[lon].where(ds[lon] >= 0, ds[lon] + 360.0)
Expand Down Expand Up @@ -867,7 +867,14 @@ def cf_convert_between_lon_frames(ds_in, lon_interval, force=False):
# interval does not include 180°-meridian: convert interval to [-180,180]
else:
if low >= 0:
low, high = _convert_interval_between_lon_frames(low, high)
if not force:
low, high = _convert_interval_between_lon_frames(low, high)
else:
ds[lon] = ds[lon].where(ds[lon] >= 0, ds[lon] + 360.0)
if lon_bnds:
ds[lon_bnds] = ds[lon_bnds].where(
ds[lon_bnds] >= 0, ds[lon_bnds] + 360.0
)
return ds, low, high

# [0 ... 360]
Expand All @@ -884,9 +891,15 @@ def cf_convert_between_lon_frames(ds_in, lon_interval, force=False):
)
# interval negative
else:
low, high = _convert_interval_between_lon_frames(low, high)
return ds, low, high

if not force:
low, high = _convert_interval_between_lon_frames(low, high)
return ds, low, high
else:
ds[lon] = ds[lon].where(ds[lon] <= 180, ds[lon] - 360.0)
if lon_bnds:
ds[lon_bnds] = ds[lon_bnds].where(
ds[lon_bnds] <= 180, ds[lon_bnds] - 360.0
)
# 1D coordinate variable: Sort, since order might no longer be ascending / descending
if gridtype == "regular_lat_lon":
ds = ds.sortby(lon)
Expand Down
29 changes: 29 additions & 0 deletions tests/test_dataset_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,35 @@ def test_convert_lon_frame_bounds():
assert lu == 350.0


def test_convert_lon_frame_force():
"""Test the force option of function cf_convert_between_lon_frames"""
# Load tutorial dataset defined on [200,330]
ds = xr.tutorial.open_dataset("air_temperature")
assert ds["lon"].min().item() == 200.0
assert ds["lon"].max().item() == 330.0

# Convert to other lon frame
conv, ll, lu = clidu.cf_convert_between_lon_frames(ds, (-180, 180))

assert conv["lon"].values[0] == -160.0
assert conv["lon"].values[-1] == -30.0

# Convert only lon_interval
conv, ll, lu = clidu.cf_convert_between_lon_frames(ds, (-180, -10))

assert conv["lon"].min().item() == 200.0
assert conv["lon"].max().item() == 330.0
assert ll == 180.0
assert lu == 350.0

# The same, but forcing the conversion of the longitudes
conv, ll, lu = clidu.cf_convert_between_lon_frames(ds, (-180, -10), force=True)
assert conv["lon"].min().item() == -160.0
assert conv["lon"].max().item() == -30.0
assert ll == -180.0
assert lu == -10.0


def test_convert_lon_frame_shifted_bounds(mini_esgf_data):
with xr.open_dataset(mini_esgf_data["CMIP6_GFDL_EXTENT"], use_cftime=True) as ds:

Expand Down

0 comments on commit 457f16c

Please sign in to comment.