Source code for ants.utils.coord

# (C) Crown Copyright, Met Office. All rights reserved.
#
# This file is part of ANTS and is released under the BSD 3-Clause license.
# See LICENSE.txt in the root of the repository for full licensing details.
import ants.utils
import iris
import numpy as np


[docs] def set_crs(coord, axis=None, crs=None): """ Set coord coordinate system. Set coordinate system of the coordinate, correcting and populating metadata where possible. Parameters ---------- coord : :class:`iris.coords.Coord` Coordinate object to infer a suitable coordinate_system. axis : :obj:`str`, optional The desired coordinate axis. If not specified, it will be guessed, see :func:`iris.util.guess_coord_axis`. crs : :class:`iris.coord_systems.CoordSystem`, optional Defaults to a UM Sphere where unspecified and undefined by the coord (ants.coord_systems.UM_SPHERE). Returns ------- None Inplace operation """ def populate_crs(coord, axis, crs, override): metadata = ["standard_name", "units"] for meta in metadata: if not override: if getattr(coord, meta) and getattr(coord, meta) != getattr( getattr(crs, axis), meta ): msg = "Conflicting {}, cannot set inferred crs" raise RuntimeError(msg.format(meta)) setattr(coord, meta, getattr(getattr(crs, axis), meta)) coord.coord_system = crs.crs if axis is None: axis = iris.util.guess_coord_axis(coord) override = True if crs is None: if coord.coord_system is None: crs = ants.coord_systems.UM_SPHERE # Do not override metadata as we are guessing the CRS. override = False else: crs = coord.coord_system if not isinstance(crs, ants.coord_systems.CFCRS): crs = ants.coord_systems.CFCRS(crs) populate_crs(coord, axis, crs, override)
[docs] def guess_bounds(coord, strict=True): """ Guess bounds wrapper around the iris guess bounds functionality. Additional capability from iris includes sensible guessing of latitude bounds to ensure they remain contiguous and guessing of time bounds for any calendar where points are in the middle of a month. Parameters ---------- coord : :class:`iris.coords.Coord` Iris coordinate in which to guess its bounds. strict : bool Define whether an existing bounds on the coordinate should raise an exception (True - default iris/ants behaviour). When strict is False, coordinates with only one point should continue without failure. Raises ------ ValueError Raised if guessing time coordinate bounds isn't possible. Currently, guessing time bounds is only supported for the case where points are the middle of each month. """ def guess_time_bounds(coord): dates = coord.units.num2date(coord.points) lower_bounds = [] upper_bounds = [] for date in dates: if date.month == 12: lyear = date.year uyear = date.year + 1 lmonth = 12 umonth = 1 else: lyear = uyear = date.year lmonth = date.month umonth = date.month + 1 lower_bounds.append(date.__class__(lyear, lmonth, 1, 0, 0)) upper_bounds.append(date.__class__(uyear, umonth, 1, 0, 0)) bounds = coord.units.date2num(np.array([lower_bounds, upper_bounds]).T) contiguous = ants.utils.ndarray.allclose(bounds[1:, 0], bounds[:-1, 1]) pointsismean = ants.utils.ndarray.allclose(bounds.mean(axis=1), coord.points) if not contiguous or not pointsismean: msg = ( "Unsupported time coordinate for guess_bounds." "time bounds can only be guessed where the points " "are the middle of each month." ) raise ValueError(msg) coord.bounds = bounds if not strict: if coord.has_bounds() or coord.points.size < 2: return if coord.units.is_time_reference(): guess_time_bounds(coord) else: coord.guess_bounds() if "latitude" in coord.name().lower(): bounds = coord.bounds.copy() bounds[bounds > 90.0] = 90.0 bounds[bounds < -90.0] = -90.0 coord.bounds = bounds
[docs] def relaxed_equality(coord1, coord2): """ Return whether the provided coordinate is equal to the other provided. Equality is performed with some tolerance as defined by ants.config.TOLERANCE, however in the case of bounds, a more relaxed independent arbitrary tolerance specified. Where bounds are present on one but not both coordinates, a temporary guess is made of these bounds where possible and a comparison made. Parameters ---------- coord1 : :class:`iris.coords.Coord` coord2 : :class:`iris.coords.Coord` Returns ------- : bool True if coord1 == coord2. False if coord1 != coord2 """ result = coord1.attributes.keys() == coord2.attributes.keys() if result: result = coord1.points.shape == coord2.points.shape if result: result = coord1.is_compatible(coord2) if result: result = ants.utils.ndarray.allclose(coord1.points, coord2.points) if not np.isscalar(result): result = result.all() tot_bounds = sum([coord1.has_bounds(), coord2.has_bounds()]) if result and tot_bounds != 0: if tot_bounds == 1: # The bounds on one of the coordinates is not present. Attempt to # guess them for the comparison. coord1 = coord1.copy() coord2 = coord2.copy() guess_bounds(coord1, strict=False) guess_bounds(coord2, strict=False) # We do not check tolerance against the globally specified as bounds # are so often derived. result = np.allclose(coord1.bounds, coord2.bounds) return result
def _get_limits(coord): """ Finds the minimum and maximum values of a coord bounds, without assuming an order for the input Parameters ---------- coord Returns ------- A tuple containing the minimum and maximum bounds """ # Assumes points are within the range defined by the bounds. values = coord.points if coord.has_bounds(): values = coord.bounds minimum = np.min(values) maximum = np.max(values) return minimum, maximum