lair.utils.geo#

Geo-spatial utilities.

Functions

add_extent_map(fig, main_extent, ...[, zorder])

Add an extent map to the figure.

add_lat_ticks(ax, ylims[, labelsize, more_ticks])

Add latitude ticks to the map.

add_latlon_ticks(ax, extent[, x_rotation, ...])

Add latitude and longitude ticks to the map.

add_lon_ticks(ax, xlims[, rotation, ...])

Add longitude ticks to the map.

bbox2extent(bbox)

Bounding box to extent.

bearing(lat1, lon1, lat2, lon2[, deg, final])

clip(data[, bbox, extent, geom, crs])

Clip the data to the given bounds.

cosine_weights(lats)

Calculate cosine weights from latitude.

dms2dd([d, m, s])

Degree-minute-second to decimal degree

earth_radius(lat)

Calculate radius of Earth assuming oblate spheroid defined by WGS84

extent2bbox(extent)

Extent to bounding box.

generate_regular_grid(xmin, xmax, dx, ymin, ...)

Generate a regular grid.

gridcell_area(grid[, R])

Calculate the area of each grid cell in a grid.

gridcell_area_from_latlon(lat, lon[, R])

Calculate the area of each grid cell in a lat-lon grid.

haversine(lat1, lon1, lat2, lon2[, R, deg])

plot_grid(grid[, lw, ax, extent, crs])

Plot a grid.

regrid(data, out_grid[, method])

Regrid data to a new grid.

resample(data, resolution[, regrid_method])

Resample the data to a new resolution.

round_latlon(data, lat_deci, lon_deci[, ...])

Round latitude and longitude values to a specified number of decimal places.

wrap_lons(longitudes[, base, period])

Transform the longitude values to be within the closed interval [base, base + period].

write_rio_crs(data, crs)

Write the CRS and coordinate system to the rioxarray accessor.

Classes

BaseGrid(data, crs, **kwargs)

Base class for working with gridded data.

CRS(crs)

Coordinate Reference System (CRS) class.

lair.utils.geo.bbox2extent(bbox: list[float]) list[float][source]#

Bounding box to extent.

Parameters:
bboxlist[minx, miny, maxx, maxy]

Bounding box

Returns:
list[minx, maxx, miny, maxy]

Extent

lair.utils.geo.extent2bbox(extent: list[float] | tuple[float, float, float, float]) list[float][source]#

Extent to bounding box.

Parameters:
extentlist[minx, maxx, miny, maxy]

Extent

Returns:
list[minx, miny, maxx, maxy]

Bounding box

class lair.utils.geo.CRS(crs: Any)[source]#

Coordinate Reference System (CRS) class.

This class is a wrapper around the pyproj.CRS class, with additional methods for converting to other CRS classes.

See https://pyproj4.github.io/pyproj/stable/crs_compatibility.html#cartopy for more information.

Note

osgeo, fiona, and pycrs conversions have not been implemented.

Attributes

epsg

Get the EPSG code of the CRS.

proj4

Get the PROJ4 string of the CRS.

wkt

Get the WKT string of the CRS.

crs

(pyproj.CRS) Pyproj CRS object

__init__(crs: Any)[source]#
property epsg: int | None#

Get the EPSG code of the CRS.

property proj4: str#

Get the PROJ4 string of the CRS.

property units: str#

Get the units of the CRS.

property wkt: str#

Get the WKT string of the CRS.

to_cartopy() CRS[source]#

Convert to cartopy CRS.

to_rasterio() CRS[source]#

Convert to rasterio CRS.

to_pyproj() CRS[source]#

Convert to pyproj CRS.

lair.utils.geo.dms2dd(d: float = 0.0, m: float = 0.0, s: float = 0.0) float[source]#

Degree-minute-second to decimal degree

Parameters:
dfloat, optional

Degrees, by default 0.0

mfloat, optional

Minutes, by default 0.0

sfloat, optional

Seconds, by default 0.0

Returns:
float

Decimal degrees

Raises:
ValueError

If any of the inputs are not floats

lair.utils.geo.wrap_lons(longitudes: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], base: float = -180.0, period: float | None = 360.0) ndarray[source]#

Transform the longitude values to be within the closed interval [base, base + period].

Parameters:
longitudesArrayLike

One or more longitude values (degrees) to be wrapped.

basefloat, default=-180.0

The start limit (degrees) of the closed interval.

periodfloat, default=360.0

The end limit (degrees) of the closed interval expressed as a length from the base.

Returns:
ndarray

The transformed longitude values.

Notes

lair.utils.geo.add_lat_ticks(ax: Axes, ylims: list[float], labelsize: int | None = None, more_ticks: int = 0) None[source]#

Add latitude ticks to the map.

Parameters:
axplt.Axes

Axes object

ylimslist[float]

Latitude limits

labelsizeint, optional

Font size of the labels, by default None

more_ticksint, optional

Number of additional ticks, by default 0

Returns:
None
lair.utils.geo.add_lon_ticks(ax: Axes, xlims: list[float], rotation: int = 0, labelsize: int | None = None, more_ticks: int = 0) None[source]#

Add longitude ticks to the map.

Parameters:
axplt.Axes

Axes object

xlimslist[float]

Longitude limits

rotationint, optional

Rotation of the labels, by default 0

labelsizeint, optional

Font size of the labels, by default None

more_ticksint, optional

Number of additional ticks, by default 0

Returns:
None
lair.utils.geo.add_latlon_ticks(ax: Axes, extent: list[float], x_rotation: int = 0, labelsize: int | None = None, more_lon_ticks: int = 0, more_lat_ticks: int = 0) None[source]#

Add latitude and longitude ticks to the map.

Parameters:
axplt.Axes

Axes object

extentlist[float]

Extent of the map. [minx, maxx, miny, maxy]

x_rotationint, optional

Rotation of the longitude labels, by default 0

labelsizeint, optional

Font size of the labels, by default None

more_lon_ticksint, optional

Number of additional longitude ticks, by default 0

more_lat_ticksint, optional

Number of additional latitude ticks, by default 0

Returns:
None
lair.utils.geo.add_extent_map(fig: matplotlib.figure.Figure, main_extent: list[float], main_extent_crs: CRS, extent_map_rect: list[float], extent_map_extent: list[float], extent_map_crs: CRS, color: str, linewidth: int, zorder: int | None = None) Axes[source]#

Add an extent map to the figure.

TODO This needs better naming and documentation.

Parameters:
figmatplotlib.figure.Figure

Figure object

main_extentlist[float]

Extent of the main map

main_extent_crsccrs.CRS

CRS of the main extent

extent_map_rectlist[float]

Rectangle of the extent map

extent_map_extentlist[float]

Extent of the extent map

extent_map_crsccrs.CRS

CRS of the extent map

colorstr

Color of the extent map

linewidthint

Line width of the extent map

zorderint, optional

Zorder of the extent map, by default None

Returns:
plt.Axes

Axes object of the extent map

class lair.utils.geo.BaseGrid(data, crs, **kwargs)[source]#

Base class for working with gridded data.

This class is a wrapper around xarray DataArray and Dataset objects, with additional methods for clipping, regridding, resampling, and reprojection. All operations are performed inplace, but return the grid object for chaining.

__init__(data, crs, **kwargs)[source]#
copy() Self[source]#

Create a copy of the grid.

Returns:
BaseGrid

The copied grid.

property gridcell_area: DataArray#

Calculate the grid cell area in km^2.

Returns:
xr.DataArray

The grid cell area.

clip(bbox: tuple[float, float, float, float] | None = None, extent: tuple[float, float, float, float] | None = None, geom: Polygon | None = None, crs: Any = None, **kwargs: Any) Self[source]#

Clip the data to the given bounds. Mopdifies the data in place. .. note:

The result can be slightly different between supplying a geom and a bbox/extent.
Clipping with a geom seems to be exclusive of the bounds,
while clipping with a bbox/extent seems to be inclusive of the bounds.
Parameters:
bboxtuple[minx, miny, maxx, maxy]

The bounding box to clip the data to.

extenttuple[minx, maxx, miny, maxy]

The extent to clip the data to.

geomshapely.Polygon

The geometry to clip the data to.

crsAny

The CRS of the input geometries. If not provided, the CRS of the data is used.

inplacebool, optional

Whether to modify the data in place, by default False.

kwargsAny

Additional keyword arguments to pass to the rioxarray clip method.

Returns:
BaseGrid

The clipped grid

regrid(out_grid: Dataset, method: Literal['bilinear', 'conservative', 'conservative_normed', 'nearest_s2d', 'nearest_d2s', 'patch'] = 'bilinear') Self[source]#

Regrid the data to a new grid. Uses xesmf for regridding. Modifies the data in place.

Note

At present, xesmf only supports regridding lat-lon grids. self.data must be on a lat-lon grid. Possibly could use xesmf.frontend.BaseRegridder to regrid to a generic grid.

Warning

xarray.Dataset.cf.add_bounds is known to have issues, including near the 180th meridian. Care should be taken when using this method, especially with global datasets.

Parameters:
out_gridxr.DataArray

The new grid to resample to. Must be a lat-lon grid.

methodstr, optional

The regridding method, by default ‘bilinear’.

Returns:
BaseGrid

The regridded grid

resample(resolution: float | tuple[float, float], regrid_method: Literal['bilinear', 'conservative', 'conservative_normed', 'nearest_s2d', 'nearest_d2s', 'patch'] = 'bilinear') Self[source]#

Resample the data to a new resolution. Modifies the data in place.

Parameters:
resolutionfloat | tuple[x_res, y_res]

The new resolution in degrees. If a single value is provided, the resolution is assumed to be the same in both dimensions.

regrid_methodstr, optional

The regridding method, by default ‘bilinear’.

Returns:
BaseGrid

The resampled grid

reproject(resolution: float | tuple[float, float], regrid_method: Literal['bilinear', 'conservative', 'conservative_normed', 'nearest_s2d', 'nearest_d2s', 'patch'] = 'bilinear') Self[source]#

Reproject the data to a lat lon rectilinear grid.

Parameters:
resolutionfloat | tuple[x_res, y_res]

The new resolution in degrees. If a single value is provided, the resolution is assumed to be the same in both dimensions.

regrid_methodstr, optional

The regridding method, by default ‘bilinear’.

Returns:
BaseGrid

The reprojected grid

lair.utils.geo.clip(data: DataArray | Dataset, bbox: list[float] | tuple[float, float, float, float] | None = None, extent: list[float] | tuple[float, float, float, float] | None = None, geom: Polygon | list[Polygon] | None = None, crs: Any = 'EPSG:4326', **kwargs: Any) DataArray | Dataset[source]#

Clip the data to the given bounds.

Note

The result can be slightly different between supplying a geom and a bbox/extent. Clipping with a geom seems to be exclusive of the outer bounds, while clipping with a bbox/extent seems to be inclusive of the outer bounds.

Parameters:
dataxr.DataArray | xr.Dataset

The data to clip.

bboxtuple[minx, miny, maxx, maxy]

The bounding box to clip the data to.

extenttuple[minx, maxx, miny, maxy]

The extent to clip the data to.

geomshapely.Polygon

The geometry or geometries to clip the data to.

crsAny, optional

The CRS of the input geometries. Default is ‘EPSG:4326’.

kwargsAny

Additional keyword arguments to pass to the rioxarray clip method.

Returns:
xr.DataArray | xr.Dataset

The clipped data.

lair.utils.geo.gridcell_area(grid: DataArray | Dataset, R: float | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | None = None) DataArray[source]#

Calculate the area of each grid cell in a grid.

Note

For lat-lon grids, xesmf.utils.grid_area is used to calculate the area, which requires the radius of the earth in kilometers. If the radius of the earth is not provided, it will be calculated based on the latitude.

Parameters:
gridxr.DataArray | xr.Dataset

Grid data. rioxarray coords must be set.

Rfloat, optional

Radius of earth in kilometers, by default calculated based on the latitude.

Returns:
np.ndarray | xr.DataArray

grid-cell area in square-kilometers

lair.utils.geo.plot_grid(grid: DataArray | Dataset, lw: float = 1, ax: Axes | None = None, extent: list[float] | None = None, crs: CRS | None = None, **kwargs: Any) Axes[source]#

Plot a grid.

Parameters:
gridxr.DataArray | xr.Dataset

The grid to plot.

axplt.Axes, optional

Axes object to plot to, by default None.

extentlist[minx, maxx, miny, maxy], optional

Extent of the plot, by default None.

crsccrs.CRS, optional

CRS of the plot, by default None.

kwargsAny

Additional keyword arguments to pass to the pcol or pcolormesh method.

Returns:
plt.Axes

Axes object

lair.utils.geo.generate_regular_grid(xmin: float, xmax: float, dx: float, ymin: float, ymax: float, dy: float, x_label='x', y_label='y', chunks: dict | None = None) DataArray[source]#

Generate a regular grid. Grid points are cell centers.

Parameters:
xminfloat

x value of the left edge of the grid

xmaxfloat

x value of the right edge of the grid

dxfloat

x resolution

yminfloat

y value of the bottom edge of the grid

ymaxfloat

y value of the top edge of the grid

dyfloat

y resolution

x_labelstr, optional

Name of the x coordinate, by default ‘x’

y_labelstr, optional

Name of the y coordinate, by default ‘y’

Returns:
xr.DataArray

The generated grid

lair.utils.geo.regrid(data: DataArray | Dataset, out_grid: Dataset, method: Literal['bilinear', 'conservative', 'conservative_normed', 'nearest_s2d', 'nearest_d2s', 'patch'] = 'bilinear') DataArray | Dataset[source]#

Regrid data to a new grid. Uses xesmf for regridding.

Note

At present, xesmf only supports regridding lat-lon grids. self.data must be on a lat-lon grid. Possibly could use xesmf.frontend.BaseRegridder to regrid to a generic grid.

Warning

xarray.Dataset.cf.add_bounds is known to have issues, including near the 180th meridian. Care should be taken when using this method, especially with global datasets.

Parameters:
dataxr.DataArray | xr.Dataset

The data to regrid.

out_gridxr.DataArray

The new grid to resample to. Must be a lat-lon grid.

methodstr, optional

The regridding method, by default ‘bilinear’.

Returns:
xr.DataArray | xr.Dataset

The regridded data.

lair.utils.geo.resample(data: DataArray | Dataset, resolution: float | tuple[float, float], regrid_method: Literal['bilinear', 'conservative', 'conservative_normed', 'nearest_s2d', 'nearest_d2s', 'patch'] = 'bilinear') DataArray | Dataset[source]#

Resample the data to a new resolution. Modifies the data in place.

Parameters:
dataxr.DataArray | xr.Dataset

The data to resample.

resolutionfloat | tuple[x_res, y_res]

The new resolution in degrees. If a single value is provided, the resolution is assumed to be the same in both dimensions.

regrid_methodstr, optional

The regridding method, by default ‘bilinear’.

Returns:
xr.DataArray | xr.Dataset

The resampled data.

lair.utils.geo.round_latlon(data: DataArray | Dataset, lat_deci: int, lon_deci: int, lat_dim: str = 'lat', lon_dim: str = 'lon') Dataset[source]#

Round latitude and longitude values to a specified number of decimal places.

Parameters:
dataxr.DataArray | xr.Dataset

The data with lat/lon coordinates to round.

lat_deciint

Number of decimal places to round latitude values to.

lon_deciint

Number of decimal places to round longitude values to.

lat_dimstr, optional

Name of the latitude dimension, by default ‘lat’.

lon_dimstr, optional

Name of the longitude dimension, by default ‘lon’.

Returns:
xr.DataArray | xr.Dataset

The data with rounded lat/lon coordinates.

lair.utils.geo.write_rio_crs(data: DataArray | Dataset, crs: Any) DataArray | Dataset[source]#

Write the CRS and coordinate system to the rioxarray accessor.

Parameters:
dataDataArray | Dataset

The data to write the CRS to.

crsAny

The CRS to write to the data.

Returns:
DataArray | Dataset

The data with the CRS written to the rioxarray accessor.

lair.utils.geo.cosine_weights(lats: ndarray) ndarray[source]#

Calculate cosine weights from latitude.

Parameters:
latsnp.ndarray

Latitude values

Returns:
np.ndarray

Cosine weighting

Examples

>>> ds: xr.Dataset
>>> weights = cosine_weighting(ds.lat)
>>> ds_weighted = ds.weighted(weights)
lair.utils.geo.earth_radius(lat: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]) Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes][source]#

Calculate radius of Earth assuming oblate spheroid defined by WGS84

Parameters:
latarray-like

latitudes in degrees

Returns:
array-like

vector of radius in kilometers

Notes

lair.utils.geo.gridcell_area_from_latlon(lat: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], lon: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], R: float | None = None) ndarray[source]#

Calculate the area of each grid cell in a lat-lon grid.

Parameters:
latArrayLike

Latitude array

lonArrayLike

Longitude array

Returns:
np.ndarray

Grid-cell area in square-kilometers