Source code for octant.grid

# -*- coding: utf-8 -*-
"""Operations on geographical grid."""
import numpy as np

from .params import EARTH_RADIUS


[docs]def cell_centres(bounds, bound_position=0.5): """ Calculate coordinate cell centres. Inspired by SciTools iris package. Parameters ---------- bounds: numpy.array One-dimensional array of cell boundaries of shape (M,) bound_position: bool, optional The desired position of the bounds relative to the position of the points. Returns ------- centres: numpy.array Array of shape (M+1,) Examples -------- >>> a = np.arange(-1, 3., 1.) >>> a array([-1, 0, 1, 2]) >>> cell_centres(a) array([-0.5, 0.5, 1.5]) See Also -------- octant.grid.cell_bounds """ assert bounds.ndim == 1, "Only 1D points are allowed" deltas = np.diff(bounds) * bound_position centres = bounds[:-1] + deltas return centres
[docs]def cell_bounds(points, bound_position=0.5): """ Calculate coordinate cell boundaries. Inspired by SciTools iris package. Parameters ---------- points: numpy.array One-dimensional array of uniformy spaced values of shape (M,) bound_position: bool, optional The desired position of the bounds relative to the position of the points. Returns ------- bounds: numpy.array Array of shape (M+1,) Examples -------- >>> a = np.arange(-1, 2.5, 0.5) >>> a array([-1. , -0.5, 0. , 0.5, 1. , 1.5, 2. ]) >>> cell_bounds(a) array([-1.25, -0.75, -0.25, 0.25, 0.75, 1.25, 1.75, 2.25]) See Also -------- octant.grid.cell_centres """ assert points.ndim == 1, "Only 1D points are allowed" diffs = np.diff(points) assert (diffs == diffs[0]).all(), "The function only works for uniformly spaced points" delta = diffs[0] * bound_position bounds = np.concatenate([[points[0] - delta], points + delta]) return bounds
def _iris_guess_bounds(points, bound_position=0.5): """Simplified function from iris.coord.Coord.""" diffs = np.diff(points) diffs = np.insert(diffs, 0, diffs[0]) diffs = np.append(diffs, diffs[-1]) min_bounds = points - diffs[:-1] * bound_position max_bounds = points + diffs[1:] * (1 - bound_position) return np.array([min_bounds, max_bounds]).transpose() def _quadrant_area(radian_lat_bounds, radian_lon_bounds, r_planet): """ Calculate spherical segment areas. Taken from iris library. Area weights are calculated for each lat/lon cell as: .. math:: r_{planet}^2 (lon_1 - lon_0) ( sin(lat_1) - sin(lat_0)) The resulting array will have a shape of *(radian_lat_bounds.shape[0], radian_lon_bounds.shape[0])* The calculations are done at 64 bit precision and the returned array will be of type numpy.float64. Parameters ---------- radian_lat_bounds: numpy.array Array of latitude bounds (radians) of shape (N, 2) radian_lon_bounds: numpy.array Array of longitude bounds (radians) of shape (N, 2) r_planet: float Radius of the planet (currently assumed spherical) """ # ensure pairs of bounds if ( radian_lat_bounds.shape[-1] != 2 or radian_lon_bounds.shape[-1] != 2 or radian_lat_bounds.ndim != 2 or radian_lon_bounds.ndim != 2 ): raise ValueError("Bounds must be [n,2] array") # fill in a new array of areas radius_sqr = r_planet ** 2 radian_lat_64 = radian_lat_bounds.astype(np.float64) radian_lon_64 = radian_lon_bounds.astype(np.float64) ylen = np.sin(radian_lat_64[:, 1]) - np.sin(radian_lat_64[:, 0]) xlen = radian_lon_64[:, 1] - radian_lon_64[:, 0] areas = radius_sqr * np.outer(ylen, xlen) # we use abs because backwards bounds (min > max) give negative areas. return np.abs(areas)
[docs]def grid_cell_areas(lon1d, lat1d, r_planet=EARTH_RADIUS): """Simplified iris function to calculate grid cell areas.""" lon_bounds_radian = np.deg2rad(_iris_guess_bounds(lon1d)) lat_bounds_radian = np.deg2rad(_iris_guess_bounds(lat1d)) area = _quadrant_area(lat_bounds_radian, lon_bounds_radian, r_planet) return area