Repository: regionmask/regionmask

Issue:
convert radian units to degree automatically
our unstructured ICON grid is stored in radian units, see https://gist.github.com/aaronspring/1b45cf22f80360ddc4ef13af1cadec63, but so far `regionmask` assumes `lon` and `lat` in degrees.

This is how I convert. `np.rad2deg` is also possible.

```python
ds.coords['clon'].attrs,ds.coords['clat'].attrs
({'standard_name': 'longitude',
  'long_name': 'center longitude',
  'units': 'radian',
  'bounds': 'clon_bnds'},
 {'standard_name': 'latitude',
  'long_name': 'center latitude',
  'units': 'radian',
  'bounds': 'clat_bnds'})

def rad_to_deg(ds):
    """Convert radian units to deg."""
    import numpy as np
    #ds.coords.compute()
    with xr.set_options(keep_attrs=True):
        for c in ds.coords:
            if 'units' in ds[c].attrs:
                if ds[c].attrs['units'] == 'radian':
                    print(f'convert {c} from rad to deg')
                    ds[c] = ds[c]* 180./np.pi
                    ds[c].attrs['units'] = 'degrees'
            elif 'bnds' in c:
                print(f'convert {c} from rad to deg')
                ds[c] = ds[c]* 180./np.pi
                ds[c].attrs['units'] = 'degrees'
    return ds
```

The user could do that manually before using `regionmask`. But `regionmask` could also check for radian units itself.

Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/regionmask/regionmask/pull/362?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=regionmask) Report
> Merging [#362](https://codecov.io/gh/regionmask/regionmask/pull/362?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=regionmask) (d8cf396) into [main](https://codecov.io/gh/regionmask/regionmask/commit/e5416cc03027c69a0b1a451860c4028d43dadc21?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=regionmask) (e5416cc) will **decrease** coverage by `0.07%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##             main     #362      +/-   ##
==========================================
- Coverage   98.64%   98.57%   -0.08%     
==========================================
  Files          15       15              
  Lines        1328     1331       +3     
==========================================
+ Hits         1310     1312       +2     
- Misses         18       19       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| bare-minimum | `91.42% <100.00%> (+0.01%)` | :arrow_up: |
| min-all-deps | `?` | |
| unittests | `98.57% <100.00%> (-0.08%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=regionmask#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/regionmask/regionmask/pull/362?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=regionmask) | Coverage Δ | |
|---|---|---|
| [regionmask/core/mask.py](https://codecov.io/gh/regionmask/regionmask/pull/362/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=regionmask#diff-cmVnaW9ubWFzay9jb3JlL21hc2sucHk=) | `100.00% <100.00%> (ø)` | |
| [regionmask/defined\_regions/\_natural\_earth.py](https://codecov.io/gh/regionmask/regionmask/pull/362/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=regionmask#diff-cmVnaW9ubWFzay9kZWZpbmVkX3JlZ2lvbnMvX25hdHVyYWxfZWFydGgucHk=) | `96.20% <0.00%> (-0.48%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=regionmask).

aaronspring: Sounds good to me. Not aware of more

Patch:
diff --git a/docs/whats_new.rst b/docs/whats_new.rst
index 9ddd47b..6f8a24a 100644
--- a/docs/whats_new.rst
+++ b/docs/whats_new.rst
@@ -19,6 +19,9 @@ Breaking Changes
 Enhancements
 ~~~~~~~~~~~~
 
+- The masking functions (e.g. :py:meth:`Regions.mask`) now warn if the `units` of the
+  coordinates(``lat.attrs["units"]`` ) are given as "radians" (:issue:`279`).
+
 Deprecations
 ~~~~~~~~~~~~
 
diff --git a/regionmask/core/mask.py b/regionmask/core/mask.py
index 47802b6..7260c31 100644
--- a/regionmask/core/mask.py
+++ b/regionmask/core/mask.py
@@ -145,10 +145,17 @@ def _mask(
     # have to do this before np.asarray
     is_unstructured = False
     if isinstance(lon, xr.DataArray) and isinstance(lat, xr.DataArray):
-        if len(lon.dims) == 1 and len(lat.dims) == 1:
+        if lon.ndim == 1 and lat.ndim == 1:
             if lon.name != lon.dims[0] and lat.name != lat.dims[0]:
                 is_unstructured = True
 
+        has_radians = any(c.attrs.get("units") == "radian" for c in (lon, lat))
+        if has_radians and wrap_lon is not False:
+            warnings.warn(
+                "lon or lat is given as 'radian' (see the 'units' attrs). Should they "
+                "be converted to degree?"
+            )
+
     lon = np.asarray(lon)
     lat = np.asarray(lat)
 


Test patch:
diff --git a/regionmask/tests/test_mask.py b/regionmask/tests/test_mask.py
index f389bb6..831ecb4 100644
--- a/regionmask/tests/test_mask.py
+++ b/regionmask/tests/test_mask.py
@@ -18,7 +18,7 @@ from regionmask.core.mask import (
 )
 from regionmask.core.utils import _wrapAngle, create_lon_lat_dataarray_from_bounds
 
-from . import has_pygeos, requires_pygeos
+from . import assert_no_warnings, has_pygeos, requires_pygeos
 from .utils import (
     dummy_ds,
     dummy_region,
@@ -497,6 +497,29 @@ def test_mask_3D_obj(lon_name, lat_name, drop, method):
     xr.testing.assert_equal(result, expected)
 
 
+@pytest.mark.parametrize("meth", ["mask", "mask_3D"])
+def test_mask_warn_radian(meth):
+
+    lon = dummy_ds.lon.copy()
+    lat = dummy_ds.lat.copy()
+    mask_func = getattr(dummy_region, meth)
+
+    with assert_no_warnings():
+        mask_func(lon, lat)
+
+    lon.attrs["units"] = "radian"
+    with pytest.warns(UserWarning, match="given as 'radian'"):
+        mask_func(lon, lat)
+
+    lat.attrs["units"] = "radian"
+    with pytest.warns(UserWarning, match="given as 'radian'"):
+        mask_func(lon, lat)
+
+    # no warnings with 'wrap_lon=False'
+    with assert_no_warnings():
+        mask_func(lon, lat, wrap_lon=False)
+
+
 # =============================================================================
 # =============================================================================
 # =============================================================================