Skip to content

Commit 6862d06

Browse files
authored
Merge pull request #187 from xylar/honor-cf-bnds
Use CF bounds for grid-cell corners where available
2 parents 93a37fe + da74f8d commit 6862d06

12 files changed

Lines changed: 638 additions & 14 deletions

ci/recipe/meta.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% set name = "pyremap" %}
2-
{% set version = "2.3.0" %}
2+
{% set version = "2.4.0" %}
33
{% set python_min = "3.10" %}
44

55
package:

docs/developer_guide/api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ This page provides an auto-generated summary of the pyremap API.
1313
:toctree: generated/
1414
1515
16+
get_corners_1d
17+
get_corners_2d
1618
interp_extrap_corner
1719
interp_extrap_corners_2d
1820

docs/mesh_descriptors/lat_lon_2d_grid_descriptor.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,24 @@ The `LatLon2DGridDescriptor` class is used for grids where latitude and longitud
1515
- `read`: Reads a 2D latitude-longitude grid from a file.
1616
- `to_scrip`: Converts the grid to a SCRIP file.
1717

18+
## Grid-Cell Corners
19+
As with {py:class}`LatLonGridDescriptor <pyremap.LatLonGridDescriptor>`,
20+
`read()` uses the CF `bounds` of the latitude and longitude variables to find
21+
grid-cell corners when they are available. For 2D coordinates, the bounds
22+
give the 4 vertices of each cell:
23+
```
24+
double lat(y, x) ;
25+
lat:units = "degrees_north" ;
26+
lat:bounds = "lat_bnds" ;
27+
double lat_bnds(y, x, nv) ;
28+
```
29+
CF does not say which vertex comes first or which direction the 4 vertices are
30+
traversed in, so pyremap works this out from the bounds themselves. Both
31+
latitude and longitude must have bounds, and neighboring cells must share
32+
vertices, since the grid is described by 2D arrays of corners. Otherwise,
33+
corners are interpolated and extrapolated from cell centers and a warning is
34+
raised.
35+
1836
## Example
1937
```python
2038
from pyremap import LatLon2DGridDescriptor

docs/mesh_descriptors/lat_lon_grid_descriptor.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,24 @@ The `LatLonGridDescriptor` class is used to describe a regular latitude-longitud
1616
- `create`: Creates a latitude-longitude grid programmatically.
1717
- `to_scrip`: Converts the grid to a SCRIP file.
1818

19+
## Grid-Cell Corners
20+
Remapping needs the corners of each grid cell, not just the cell centers.
21+
When `read()` is used, corners come from the CF `bounds` attribute of the
22+
latitude and longitude variables if it is present:
23+
```
24+
double lat(lat) ;
25+
lat:units = "degrees_north" ;
26+
lat:bounds = "lat_bnds" ;
27+
double lat_bnds(lat, nbnd) ;
28+
```
29+
The bounds must describe contiguous cells (the upper edge of each cell is the
30+
lower edge of the next), since a 1D lat/lon grid is described by 1D arrays of
31+
corners. If the `bounds` attribute is missing, points to a variable that is
32+
not in the file, has the wrong shape, or describes cells with gaps or overlaps
33+
between them, corners are instead interpolated between cell centers and
34+
extrapolated at the ends of the grid, and a warning is raised in all but the
35+
first of these cases.
36+
1937
## Example
2038
```python
2139
from pyremap import LatLonGridDescriptor

docs/mesh_descriptors/projection_grid_descriptor.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ The `ProjectionGridDescriptor` class describes grids defined by map projections.
1515
- `create`: Creates a projection grid programmatically.
1616
- `to_scrip`: Converts the grid to a SCRIP file.
1717

18+
## Grid-Cell Corners
19+
`read()` uses the CF `bounds` of the `x` and `y` variables to find the corners
20+
of each grid cell in projection space when they are available and describe
21+
contiguous cells. Otherwise, corners are interpolated between cell centers
22+
and extrapolated at the ends of the grid. Corners are transformed from
23+
projection space to latitude and longitude by `to_scrip()`.
24+
1825
## Example
1926
```python
2027
from pyremap import ProjectionGridDescriptor

pyremap/descriptor/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636
from pyremap.descriptor.projection_grid_descriptor import (
3737
ProjectionGridDescriptor as ProjectionGridDescriptor,
3838
)
39+
from pyremap.descriptor.utility import (
40+
get_corners_1d as get_corners_1d,
41+
)
42+
from pyremap.descriptor.utility import (
43+
get_corners_2d as get_corners_2d,
44+
)
3945
from pyremap.descriptor.utility import (
4046
interp_extrap_corner as interp_extrap_corner,
4147
)

pyremap/descriptor/lat_lon_2d_grid_descriptor.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from pyremap.descriptor.utility import (
1919
add_history,
2020
expand_scrip,
21-
interp_extrap_corners_2d,
21+
get_corners_2d,
2222
round_res,
2323
unwrap_corners,
2424
)
@@ -87,6 +87,11 @@ def read(
8787
"""
8888
Read the lat-lon grid from a file with the given lat/lon var names.
8989
90+
Grid-cell corners come from the CF ``bounds`` of the latitude and
91+
longitude variables if they are available and neighboring cells share
92+
vertices. Otherwise, corners are interpolated and extrapolated from
93+
the cell centers.
94+
9095
Parameters
9196
----------
9297
filename : str, optional
@@ -127,9 +132,10 @@ def read(
127132
else:
128133
descriptor.units = 'radians'
129134

130-
# interp/extrap corners
131-
descriptor.lon_corner = interp_extrap_corners_2d(descriptor.lon)
132-
descriptor.lat_corner = interp_extrap_corners_2d(descriptor.lat)
135+
# use CF bounds if available, otherwise interp/extrap corners
136+
descriptor.lat_corner, descriptor.lon_corner = get_corners_2d(
137+
ds, lat_var_name, lon_var_name
138+
)
133139

134140
descriptor._set_coords(
135141
lat_var_name,

pyremap/descriptor/lat_lon_grid_descriptor.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from pyremap.descriptor.utility import (
1919
add_history,
2020
expand_scrip,
21-
interp_extrap_corner,
21+
get_corners_1d,
2222
round_res,
2323
unwrap_corners,
2424
)
@@ -121,6 +121,11 @@ def read(
121121
"""
122122
Read the lat-lon grid from a file with the given lat/lon var names.
123123
124+
Grid-cell corners come from the CF ``bounds`` of the latitude and
125+
longitude variables if they are available and describe contiguous
126+
cells. Otherwise, corners are interpolated and extrapolated from the
127+
cell centers.
128+
124129
Parameters
125130
----------
126131
filename : str, optional
@@ -157,9 +162,9 @@ def read(
157162
else:
158163
descriptor.units = 'radians'
159164

160-
# interp/extrap corners
161-
descriptor.lon_corner = interp_extrap_corner(descriptor.lon)
162-
descriptor.lat_corner = interp_extrap_corner(descriptor.lat)
165+
# use CF bounds if available, otherwise interp/extrap corners
166+
descriptor.lon_corner = get_corners_1d(ds, lon_var_name)
167+
descriptor.lat_corner = get_corners_1d(ds, lat_var_name)
163168

164169
descriptor._set_coords(
165170
lat_var_name,

pyremap/descriptor/projection_grid_descriptor.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from pyremap.descriptor.utility import (
2020
add_history,
2121
expand_scrip,
22+
get_corners_1d,
2223
interp_extrap_corner,
2324
unwrap_corners,
2425
)
@@ -98,7 +99,11 @@ def read(
9899
"""
99100
Given a grid file with x and y coordinates defining the axes of the
100101
logically rectangular grid, read in the x and y coordinates and
101-
interpolate/extrapolate to locate corners.
102+
locate the corners.
103+
104+
Corners come from the CF ``bounds`` of the x and y variables if they
105+
are available and describe contiguous cells. Otherwise, corners are
106+
interpolated and extrapolated from the cell centers.
102107
103108
Parameters
104109
----------
@@ -135,9 +140,9 @@ def read(
135140
ds[y_var_name].dims[0],
136141
)
137142

138-
# interp/extrap corners
139-
descriptor.x_corner = interp_extrap_corner(descriptor.x)
140-
descriptor.y_corner = interp_extrap_corner(descriptor.y)
143+
# use CF bounds if available, otherwise interp/extrap corners
144+
descriptor.x_corner = get_corners_1d(ds, x_var_name)
145+
descriptor.y_corner = get_corners_1d(ds, y_var_name)
141146

142147
descriptor.history = add_history(ds=ds)
143148
return descriptor

0 commit comments

Comments
 (0)