Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add grid_bounds_to_polygons #478

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions cf_xarray/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@
from .options import set_options # noqa
from .utils import _get_version

from . import geometry # noqa

#
__version__ = _get_version()
63 changes: 63 additions & 0 deletions cf_xarray/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,3 +585,66 @@ def cf_to_polygons(ds: xr.Dataset):
geoms = np.where(np.diff(offset3) == 1, polygons[offset3[:-1]], multipolygons)

return xr.DataArray(geoms, dims=node_count.dims, coords=node_count.coords)


def grid_to_polygons(ds: xr.Dataset) -> xr.DataArray:
dcherian marked this conversation as resolved.
Show resolved Hide resolved
dcherian marked this conversation as resolved.
Show resolved Hide resolved
"""
Converts the bounds of a regular 2D lat/lon grid to a 2D array of shapely polygons.

Modified from https://notebooksharing.space/view/c6c1f3a7d0c260724115eaa2bf78f3738b275f7f633c1558639e7bbd75b31456.

Parameters
----------
ds : xr.Dataset
Dataset with "latitude" and "longitude" variables as well as their bounds variables.
1D and 2D "latitude" and "longitude" variables are supported. 1D variables will
be broadcast against each other.

Returns
-------
DataArray
DataArray with shapely polygon per grid cell.
"""
import shapely

grid = ds.cf[["latitude", "longitude"]].load()
bounds = grid.cf.bounds
dims = grid.cf.dims

if "latitude" in dims or "longitude" in dims:
# for 1D lat, lon, this allows them to be
# broadcast against each other
grid = grid.reset_coords()

assert "latitude" in bounds
assert "longitude" in bounds
(lon_bounds,) = bounds["longitude"]
(lat_bounds,) = bounds["latitude"]

with xr.set_options(keep_attrs=True):
(points,) = xr.broadcast(grid)

bounds_dim = grid.cf.get_bounds_dim_name("latitude")
points = points.transpose(..., bounds_dim)
lonbnd = points[lon_bounds].data
latbnd = points[lat_bounds].data

if points.sizes[bounds_dim] == 2:
lonbnd = lonbnd[..., [0, 0, 1, 1]]
latbnd = latbnd[..., [0, 1, 1, 0]]

elif points.sizes[bounds_dim] != 4:
raise ValueError(
f"The size of the detected bounds or vertex dimension {bounds_dim} is not 2 or 4."
)

# geopandas needs this
mask = lonbnd[..., 0] >= 180
lonbnd[mask, :] = lonbnd[mask, :] - 360
Comment on lines +641 to +643
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the reason for this is that most geographic data is given in WGS84 which is -180...180 (I may be wrong here). So if you want to compare it to regional polygons it is probably a good idea to wrap the data. Still, it may be confusing to users.

Maybe this could be optional. However, I am not sure what a good name is (In regionmask I call it wrap_lon with the options 180 (= what you do here), 360 and False. That works but there may be better names).


polyarray = shapely.polygons(shapely.linearrings(lonbnd, latbnd))

# 'geometry' is a blessed name in geopandas.
boxes = points[lon_bounds][..., 0].copy(data=polyarray).rename("geometry")

return boxes
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ module=[
"pint",
"matplotlib",
"pytest",
"shapely",
"shapely.geometry",
"shapely.*",
"xarray.core.pycompat",
]
ignore_missing_imports = true
Expand Down
Loading