From 40b7a19115bd5fb5960fac16296287d7cbecca39 Mon Sep 17 00:00:00 2001 From: Bill Ladwig Date: Fri, 11 Jan 2019 16:17:35 -0700 Subject: [PATCH] PEP8 --- src/wrf/geobnds.py | 71 +- src/wrf/interp.py | 940 +++++----- src/wrf/interputils.py | 454 ++--- src/wrf/latlonutils.py | 547 +++--- src/wrf/metadecorators.py | 1841 ++++++++++---------- src/wrf/projection.py | 988 +++++------ src/wrf/projutils.py | 13 +- src/wrf/py3compat.py | 118 +- src/wrf/routines.py | 489 +++--- src/wrf/specialdec.py | 463 +++-- src/wrf/units.py | 440 +++-- src/wrf/util.py | 3451 +++++++++++++++++++------------------ src/wrf/version.py | 1 - 13 files changed, 4865 insertions(+), 4951 deletions(-) diff --git a/src/wrf/geobnds.py b/src/wrf/geobnds.py index 2014b5c..5dff84c 100644 --- a/src/wrf/geobnds.py +++ b/src/wrf/geobnds.py @@ -2,46 +2,47 @@ from __future__ import (absolute_import, division, print_function) from .coordpair import CoordPair + class GeoBounds(object): """A class that stores the geographic boundaries. - - Currently, only corner points are used, specified as the bottom left and - top right corners. Users can specify the corner points directly, or + + Currently, only corner points are used, specified as the bottom left and + top right corners. Users can specify the corner points directly, or specify two-dimensional latitude and longitude arrays and the corner points will be extracted from them. - + Attributes: - + bottom_left (:class:`wrf.CoordPair`): The bottom left coordinate. top_right (:class:`wrf.CoordPair`): The top right coordinate. - + """ def __init__(self, bottom_left=None, top_right=None, lats=None, lons=None): """ Initialize a :class:`wrf.GeoBounds` object. - - Args: - - bottom_left (:class:`wrf.CoordPair`, optional): The lower left - corner. Must also specify *top_right* if used. + + Args: + + bottom_left (:class:`wrf.CoordPair`, optional): The lower left + corner. Must also specify *top_right* if used. Default is None. - - top_right (:class:`wrf.CoordPair`, optional): The upper right - corner. Must also specify *bottom_left* if used. + + top_right (:class:`wrf.CoordPair`, optional): The upper right + corner. Must also specify *bottom_left* if used. Default is None. - - lats (:class:`numpy.ndarray`, optional): An array of at least - two dimensions containing all of the latitude values. Must + + lats (:class:`numpy.ndarray`, optional): An array of at least + two dimensions containing all of the latitude values. Must also specify *lons* if used. Default is None. - - lons (:class:`numpy.ndarray`, optional): An array of at least - two dimensions containing all of the longitude values. Must + + lons (:class:`numpy.ndarray`, optional): An array of at least + two dimensions containing all of the longitude values. Must also specify *lats* if used. Default is None. - + """ if bottom_left is not None and top_right is not None: self.bottom_left = bottom_left self.top_right = top_right - + # Make sure the users set lat/lon coordinates if self.bottom_left.lat is None: raise ValueError("'bottom_left' parameter does not contain a " @@ -56,35 +57,31 @@ class GeoBounds(object): raise ValueError("'top_right' parameter does not contain a" "'lon' attribute") elif lats is not None and lons is not None: - self.bottom_left = CoordPair(lat=lats[0,0], lon=lons[0,0]) - self.top_right = CoordPair(lat=lats[-1,-1], lon=lons[-1,-1]) + self.bottom_left = CoordPair(lat=lats[0, 0], lon=lons[0, 0]) + self.top_right = CoordPair(lat=lats[-1, -1], lon=lons[-1, -1]) else: raise ValueError("must specify either 'bottom_top' and " "'top_right' parameters " "or 'lats' and 'lons' parameters") - + def __repr__(self): - argstr = "{}, {}".format(repr(self.bottom_left), + argstr = "{}, {}".format(repr(self.bottom_left), repr(self.top_right)) - + return "{}({})".format(self.__class__.__name__, argstr) - + class NullGeoBounds(GeoBounds): """An emtpy :class:`wrf.GeoBounds` subclass. - - This is used for initializing arrays of :class:`wrf.GeoBounds`, in - particular when working with moving domains and variables combined with the + + This is used for initializing arrays of :class:`wrf.GeoBounds`, in + particular when working with moving domains and variables combined with the 'join' method. - + """ def __init__(self): """ Initialize a :class:`wrf.NullGeoBounds` object.""" pass - + def __repr__(self): return "{}()".format(self.__class__.__name__) - - - - \ No newline at end of file diff --git a/src/wrf/interp.py b/src/wrf/interp.py index 91ce663..0e004bc 100755 --- a/src/wrf/interp.py +++ b/src/wrf/interp.py @@ -3,7 +3,7 @@ from __future__ import (absolute_import, division, print_function) import numpy as np import numpy.ma as ma -from .extension import (_interpz3d, _vertcross, _interpline, _smooth2d, +from .extension import (_interpz3d, _vertcross, _interpline, _smooth2d, _monotonic, _vintrp, _interpz3d_lev2d) from .metadecorators import set_interp_metadata @@ -20,84 +20,84 @@ from wrf.g_pressure import get_pressure # Note: Extension decorator is good enough to handle left dims @set_interp_metadata("horiz") -def interplevel(field3d, vert, desiredlev, missing=default_fill(np.float64), +def interplevel(field3d, vert, desiredlev, missing=default_fill(np.float64), squeeze=True, meta=True): - """Return the three-dimensional field interpolated to a horizontal plane + """Return the three-dimensional field interpolated to a horizontal plane at the specified vertical level. Args: - - field3d (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A - three-dimensional field to interpolate, with the rightmost + + field3d (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A + three-dimensional field to interpolate, with the rightmost dimensions of nz x ny x nx. - - vert (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A - three-dimensional array for the vertical coordinate, typically + + vert (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A + three-dimensional array for the vertical coordinate, typically pressure or height. This array must have the same dimensionality as *field3d*. - - desiredlev (:obj:`float`, 1D sequence, or :class:`numpy.ndarray`): The - desired vertical level(s). This can be a single value (e.g. 500), - a sequence of values (e.g. [1000, 850, 700, 500, 250]), or a - multidimensional array where the right two dimensions (ny x nx) - must match *field3d*, and any leftmost dimensions match - field3d.shape[:-3] (e.g. planetary boundary layer). + + desiredlev (:obj:`float`, 1D sequence, or :class:`numpy.ndarray`): The + desired vertical level(s). This can be a single value (e.g. 500), + a sequence of values (e.g. [1000, 850, 700, 500, 250]), or a + multidimensional array where the right two dimensions (ny x nx) + must match *field3d*, and any leftmost dimensions match + field3d.shape[:-3] (e.g. planetary boundary layer). Must be in the same units as the *vert* parameter. - - missing (:obj:`float`): The fill value to use for the output. + + missing (:obj:`float`): The fill value to use for the output. Default is :data:`wrf.default_fill(numpy.float64)`. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - meta (:obj:`bool`): Set to False to disable metadata and return - :class:`numpy.ndarray` instead of + + meta (:obj:`bool`): Set to False to disable metadata and return + :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The - interpolated variable. If xarray is enabled and - the *meta* parameter is True, then the result will be an - :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The + interpolated variable. If xarray is enabled and + the *meta* parameter is True, then the result will be an + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + Example: - + Interpolate Geopotential Height to 500 hPa - + .. code-block:: python - + from netCDF4 import Dataset from wrf import getvar, interplevel - + wrfin = Dataset("wrfout_d02_2010-06-13_21:00:00") - + p = getvar(wrfin, "pressure") ht = getvar(wrfin, "z", units="dm") - + ht_500 = interplevel(ht, p, 500.0) - - + + Interpolate Relative Humidity to Boundary Layer Heights - + .. code-block:: python - + from netCDF4 import Dataset from wrf import getvar, interplevel - + wrfin = Dataset("wrfout_d02_2010-06-13_21:00:00") - + rh = getvar(wrfin, "rh") z = getvar(wrfin, "z") pblh = getvar(wrfin, "PBLH") - + rh_pblh = interplevel(rh, p, pblh) - - + + """ - + _desiredlev = np.asarray(desiredlev) if _desiredlev.ndim == 0: _desiredlev = np.array([desiredlev], np.float64) @@ -109,13 +109,13 @@ def interplevel(field3d, vert, desiredlev, missing=default_fill(np.float64), result = _interpz3d(field3d, vert, _desiredlev, missing) else: result = _interpz3d_lev2d(field3d, vert, _desiredlev, missing) - - masked = ma.masked_values (result, missing) - + + masked = ma.masked_values(result, missing) + if not meta: if squeeze: return masked.squeeze() - + return masked @@ -127,154 +127,154 @@ def vertcross(field3d, vert, levels=None, missing=default_fill(np.float64), start_point=None, end_point=None, latlon=False, autolevels=100, cache=None, meta=True): """Return the vertical cross section for a three-dimensional field. - - The cross section is defined by a horizontal line through the domain. - This horizontal line is defined by either including the - *pivot_point* and *angle* parameters, or the *start_point* and - *end_point* parameters. The *pivot_point*, *start_point*, and *end_point* - coordinates can be defined in either x,y or latitude,longitude space. - If latitude,longitude coordinates are used, then a WRF input file or + + The cross section is defined by a horizontal line through the domain. + This horizontal line is defined by either including the + *pivot_point* and *angle* parameters, or the *start_point* and + *end_point* parameters. The *pivot_point*, *start_point*, and *end_point* + coordinates can be defined in either x,y or latitude,longitude space. + If latitude,longitude coordinates are used, then a WRF input file or map projection must also be specified. - - The vertical levels for the cross section are fixed if *levels* is not - specified, and are determined by dividing the vertical coordinate in to - grid boxes of roughly 1% of the maximum vertical distance from top to - bottom. Otherwise, the *levels* argument can be used to specify specific - vertical levels. If all vertical levels are desired, use the raw + + The vertical levels for the cross section are fixed if *levels* is not + specified, and are determined by dividing the vertical coordinate in to + grid boxes of roughly 1% of the maximum vertical distance from top to + bottom. Otherwise, the *levels* argument can be used to specify specific + vertical levels. If all vertical levels are desired, use the raw :meth:`wrf.interp2dxy` function. - + See Also: - + :meth:`wrf.interp2dxy` - + Args: - - field3d (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A - three-dimensional field to interpolate, whose + + field3d (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A + three-dimensional field to interpolate, whose rightmost dimensions are nz x ny x nx. - - vert (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A - three-dimensional variable for the vertical coordinate, typically + + vert (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A + three-dimensional variable for the vertical coordinate, typically pressure or height. This array must have the same dimensionality as *field3d* - + levels (sequence, optional): A sequence of :obj:`float` for the desired - vertical levels in the output array. Must be in the same units - as *vert*. If None, a fixed set of vertical levels is provided. + vertical levels in the output array. Must be in the same units + as *vert*. If None, a fixed set of vertical levels is provided. Default is None. - - missing (:obj:`float`): The fill value to use for the output. + + missing (:obj:`float`): The fill value to use for the output. Default is :data:`wrf.default_fill(numpy.float64)`. - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable, optional): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable, optional): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. This is used - to obtain the map projection when using latitude,longitude + to obtain the map projection when using latitude,longitude coordinates. Default is None. - - timeidx (:obj:`int`, optional): The - desired time index when obtaining map boundary information - from moving nests. This value can be a positive or negative integer. - Only required when *wrfin* is specified and the nest is moving. - Currently, :data:`wrf.ALL_TIMES` is not supported. + + timeidx (:obj:`int`, optional): The + desired time index when obtaining map boundary information + from moving nests. This value can be a positive or negative + integer. Only required when *wrfin* is specified and the nest is + moving. Currently, :data:`wrf.ALL_TIMES` is not supported. Default is 0. - - stagger (:obj:`str`): If using latitude, longitude coordinate pairs - for *start_point*, *end_point*, or *pivot_point*, + + stagger (:obj:`str`): If using latitude, longitude coordinate pairs + for *start_point*, *end_point*, or *pivot_point*, set the appropriate grid staggering type for *field3d*. By default, the mass grid is used. The options are: - + - 'm': Use the mass grid (default). - - 'u': Use the same staggered grid as the u wind component, + - 'u': Use the same staggered grid as the u wind component, which has a staggered west_east (x) dimension. - - 'v': Use the same staggered grid as the v wind component, + - 'v': Use the same staggered grid as the v wind component, which has a staggered south_north (y) dimension. - - projection (:class:`wrf.WrfProj` subclass, optional): The map - projection object to use when working with latitude, longitude - coordinates, and must be specified if *wrfin* is None. Default + + projection (:class:`wrf.WrfProj` subclass, optional): The map + projection object to use when working with latitude, longitude + coordinates, and must be specified if *wrfin* is None. Default is None. - + ll_point (:class:`wrf.CoordPair`, sequence of :class:`wrf.CoordPair`, \ - optional): The lower left latitude, longitude point for your domain, - and must be specified - if *wrfin* is None. If the domain is a moving nest, this should be + optional): The lower left latitude, longitude point for your domain, + and must be specified + if *wrfin* is None. If the domain is a moving nest, this should be a sequence of :class:`wrf.CoordPair`. Default is None. - - pivot_point (:class:`wrf.CoordPair`, optional): A coordinate pair for - the pivot point, which indicates the location through which - the plane will pass. Must also specify *angle*. The coordinate - pair can be in x,y grid coordinates or latitude, longitude - coordinates. If using latitude, longitude coordinates, then - either *wrfin* or *projection* must be specified to obtain the + + pivot_point (:class:`wrf.CoordPair`, optional): A coordinate pair for + the pivot point, which indicates the location through which + the plane will pass. Must also specify *angle*. The coordinate + pair can be in x,y grid coordinates or latitude, longitude + coordinates. If using latitude, longitude coordinates, then + either *wrfin* or *projection* must be specified to obtain the map projection. Default is None. - - angle (:obj:`float`, optional): Only valid for cross sections where - a plane will be plotted through - a given point on the model domain. 0.0 represents a S-N cross - section. 90.0 is a W-E cross section. - - start_point (:class:`wrf.CoordPair`, optional): A coordinate pair - which indicates the start location through which the plane will - pass. Must also specify *end_point*. The coordinate - pair can be in x,y grid coordinates or latitude, longitude - coordinates. If using latitude, longitude coordinates, then - either *wrfin* or *projection* must be specified to obtain the + + angle (:obj:`float`, optional): Only valid for cross sections where + a plane will be plotted through + a given point on the model domain. 0.0 represents a S-N cross + section. 90.0 is a W-E cross section. + + start_point (:class:`wrf.CoordPair`, optional): A coordinate pair + which indicates the start location through which the plane will + pass. Must also specify *end_point*. The coordinate + pair can be in x,y grid coordinates or latitude, longitude + coordinates. If using latitude, longitude coordinates, then + either *wrfin* or *projection* must be specified to obtain the map projection. Default is None. - - end_point (:class:`wrf.CoordPair`, optional): A coordinate pair - which indicates the end location through which the plane will - pass. Must also specify *end_point*. The coordinate - pair can be in x,y grid coordinates or latitude, longitude - coordinates. If using latitude, longitude coordinates, then - either *wrfin* or *projection* must be specified to obtain the + + end_point (:class:`wrf.CoordPair`, optional): A coordinate pair + which indicates the end location through which the plane will + pass. Must also specify *end_point*. The coordinate + pair can be in x,y grid coordinates or latitude, longitude + coordinates. If using latitude, longitude coordinates, then + either *wrfin* or *projection* must be specified to obtain the map projection. Default is None. - - latlon (:obj:`bool`, optional): Set to True to also interpolate the - two-dimensional latitude and longitude coordinates along the same - horizontal line and include this information in the metadata + + latlon (:obj:`bool`, optional): Set to True to also interpolate the + two-dimensional latitude and longitude coordinates along the same + horizontal line and include this information in the metadata (if enabled). This can be helpful for plotting. Default is False. - + Note: - - Currently, *field3d* must be of type :class:`xarray.DataArray` - and contain coordinate information in order to generate the - latitude and longitude coordinates along the line if + + Currently, *field3d* must be of type :class:`xarray.DataArray` + and contain coordinate information in order to generate the + latitude and longitude coordinates along the line if *latlon* is set to True. Otherwise, a warning will be issued, - and the latitude and longitude information will not be + and the latitude and longitude information will not be present. - - autolevels(:obj:`int`, optional): The number of evenly spaced - automatically chosen vertical levels to use when *levels* + + autolevels(:obj:`int`, optional): The number of evenly spaced + automatically chosen vertical levels to use when *levels* is None. Default is 100. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - + Returns: - + :class:`xarray.DataArray` or :class:`numpy.ndarray`: - The interpolated variable. If xarray is enabled and - the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + The interpolated variable. If xarray is enabled and + the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ # Some fields like uvmet have an extra left dimension for the product # type, we'll handle that iteration here. multi = True if field3d.ndim - vert.ndim == 1 else False - + try: xy = cache["xy"] var2dz = cache["var2dz"] @@ -284,31 +284,31 @@ def vertcross(field3d, vert, levels=None, missing=default_fill(np.float64), start_point_xy = None end_point_xy = None pivot_point_xy = None - - if (latlon is True or is_latlon_pair(start_point) or - is_latlon_pair(pivot_point)): - + + if (latlon is True or is_latlon_pair(start_point) or + is_latlon_pair(pivot_point)): + if wrfin is not None: is_moving = is_moving_domain(wrfin) else: is_moving = False - + if timeidx is None: if wrfin is not None: - # Moving nests aren't supported with ALL_TIMES because the - # domain could move outside of the line, which causes + # Moving nests aren't supported with ALL_TIMES because the + # domain could move outside of the line, which causes # crashes or different line lengths. if is_moving: - raise ValueError("Requesting all times with a moving nest " - "is not supported when using lat/lon " - "cross sections because the domain could " - "move outside of the cross section. " - "You must request each time " - "individually.") + raise ValueError("Requesting all times with a moving " + "nest is not supported when using " + "lat/lon cross sections because the " + "domain could move outside of the " + "cross section. You must request " + "each time individually.") else: # Domain not moving, just use 0 _timeidx = 0 - + # If using grid coordinates, then don't care about lat/lon # coordinates. Just use 0. else: @@ -317,48 +317,48 @@ def vertcross(field3d, vert, levels=None, missing=default_fill(np.float64), if is_moving: _timeidx = timeidx else: - # When using non-moving nests, set the time to 0 + # When using non-moving nests, set the time to 0 # to avoid problems downstream _timeidx = 0 - + if pivot_point is not None: if pivot_point.lat is not None and pivot_point.lon is not None: - xy_coords = to_xy_coords(pivot_point, wrfin, _timeidx, + xy_coords = to_xy_coords(pivot_point, wrfin, _timeidx, stagger, projection, ll_point) pivot_point_xy = (xy_coords.x, xy_coords.y) else: pivot_point_xy = (pivot_point.x, pivot_point.y) - + if start_point is not None and end_point is not None: if start_point.lat is not None and start_point.lon is not None: - xy_coords = to_xy_coords(start_point, wrfin, _timeidx, + xy_coords = to_xy_coords(start_point, wrfin, _timeidx, stagger, projection, ll_point) start_point_xy = (xy_coords.x, xy_coords.y) else: start_point_xy = (start_point.x, start_point.y) - + if end_point.lat is not None and end_point.lon is not None: - xy_coords = to_xy_coords(end_point, wrfin, _timeidx, + xy_coords = to_xy_coords(end_point, wrfin, _timeidx, stagger, projection, ll_point) end_point_xy = (xy_coords.x, xy_coords.y) else: end_point_xy = (end_point.x, end_point.y) - - xy, var2dz, z_var2d = get_xy_z_params(to_np(vert), pivot_point_xy, - angle, start_point_xy, + + xy, var2dz, z_var2d = get_xy_z_params(to_np(vert), pivot_point_xy, + angle, start_point_xy, end_point_xy, levels, autolevels) - + if not multi: result = _vertcross(field3d, xy, var2dz, z_var2d, missing) else: outshape = field3d.shape[0:-3] + (z_var2d.shape[0], xy.shape[0]) result = np.empty(outshape, dtype=field3d.dtype) - + for i in py3range(field3d.shape[0]): - result[i,:] = _vertcross(field3d[i,:], xy, var2dz, z_var2d, - missing)[:] - + result[i, :] = _vertcross(field3d[i, :], xy, var2dz, z_var2d, + missing)[:] + return ma.masked_values(result, missing) @@ -366,159 +366,159 @@ def vertcross(field3d, vert, levels=None, missing=default_fill(np.float64), def interpline(field2d, wrfin=None, timeidx=0, stagger=None, projection=None, ll_point=None, pivot_point=None, angle=None, start_point=None, - end_point=None, latlon=False, + end_point=None, latlon=False, cache=None, meta=True): """Return the two-dimensional field interpolated along a line. - - This line is defined by either including the - *pivot_point* and *angle* parameters, or the *start_point* and - *end_point* parameters. The *pivot_point*, *start_point*, and *end_point* - coordinates can be defined in either x,y or latitude,longitude space. - If latitude,longitude coordinates are used, then a WRF input file or + + This line is defined by either including the + *pivot_point* and *angle* parameters, or the *start_point* and + *end_point* parameters. The *pivot_point*, *start_point*, and *end_point* + coordinates can be defined in either x,y or latitude,longitude space. + If latitude,longitude coordinates are used, then a WRF input file or map projection must also be specified. - + Args: - + field2d (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A two-dimensional field. - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable, optional): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable, optional): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. This is used - to obtain the map projection when using latitude,longitude - coordinates. Should not be used when working with x,y + to obtain the map projection when using latitude,longitude + coordinates. Should not be used when working with x,y coordinates. Default is None. - - timeidx (:obj:`int`, optional): The - desired time index when obtaining map boundary information - from moving nests. This value can be a positive or negative integer. - Only required when *wrfin* is specified and the nest is moving. - Currently, :data:`wrf.ALL_TIMES` is not supported. - Default is 0. - - stagger (:obj:`str`): If using latitude, longitude coordinate pairs - for *start_point*, *end_point*, or *pivot_point*, + + timeidx (:obj:`int`, optional): The + desired time index when obtaining map boundary information + from moving nests. This value can be a positive or negative + integer. Only required when *wrfin* is specified and the nest is + moving. Currently, :data:`wrf.ALL_TIMES` is not supported. + Default is 0. + + stagger (:obj:`str`): If using latitude, longitude coordinate pairs + for *start_point*, *end_point*, or *pivot_point*, set the appropriate grid staggering type for *field2d*. By default, the mass grid is used. The options are: - + - 'm': Use the mass grid (default). - - 'u': Use the same staggered grid as the u wind component, + - 'u': Use the same staggered grid as the u wind component, which has a staggered west_east (x) dimension. - - 'v': Use the same staggered grid as the v wind component, + - 'v': Use the same staggered grid as the v wind component, which has a staggered south_north (y) dimension. - - projection (:class:`wrf.WrfProj`, optional): The map - projection object to use when working with latitude, longitude - coordinates, and must be specified if *wrfin* is None. Should - not be used when working with x,y coordinates. Default + + projection (:class:`wrf.WrfProj`, optional): The map + projection object to use when working with latitude, longitude + coordinates, and must be specified if *wrfin* is None. Should + not be used when working with x,y coordinates. Default is None. - + ll_point (:class:`wrf.CoordPair`, sequence of :class:`wrf.CoordPair`, \ - optional): The lower left latitude, longitude point for your domain, - and must be specified - if *wrfin* is None. If the domain is a moving nest, this should be + optional): The lower left latitude, longitude point for your domain, + and must be specified + if *wrfin* is None. If the domain is a moving nest, this should be a sequence of :class:`wrf.CoordPair`. Default is None. - - pivot_point (:class:`wrf.CoordPair`, optional): A coordinate pair for - the pivot point, which indicates the location through which - the plane will pass. Must also specify *angle*. The coordinate - pair can be in x,y grid coordinates or latitude, longitude - coordinates. If using latitude, longitude coordinates, then - either *wrfin* or *projection* must be specified to obtain the + + pivot_point (:class:`wrf.CoordPair`, optional): A coordinate pair for + the pivot point, which indicates the location through which + the plane will pass. Must also specify *angle*. The coordinate + pair can be in x,y grid coordinates or latitude, longitude + coordinates. If using latitude, longitude coordinates, then + either *wrfin* or *projection* must be specified to obtain the map projection. Default is None. - - angle (:obj:`float`, optional): Only valid for cross sections where - a plane will be plotted through - a given point on the model domain. 0.0 represents a S-N cross - section. 90.0 is a W-E cross section. - - start_point (:class:`wrf.CoordPair`, optional): A coordinate pair - which indicates the start location through which the plane will - pass. Must also specify *end_point*. The coordinate - pair can be in x,y grid coordinates or latitude, longitude - coordinates. If using latitude, longitude coordinates, then - either *wrfin* or *projection* must be specified to obtain the + + angle (:obj:`float`, optional): Only valid for cross sections where + a plane will be plotted through + a given point on the model domain. 0.0 represents a S-N cross + section. 90.0 is a W-E cross section. + + start_point (:class:`wrf.CoordPair`, optional): A coordinate pair + which indicates the start location through which the plane will + pass. Must also specify *end_point*. The coordinate + pair can be in x,y grid coordinates or latitude, longitude + coordinates. If using latitude, longitude coordinates, then + either *wrfin* or *projection* must be specified to obtain the map projection. Default is None. - - end_point (:class:`wrf.CoordPair`, optional): A coordinate pair - which indicates the end location through which the plane will - pass. Must also specify *end_point*. The coordinate - pair can be in x,y grid coordinates or latitude, longitude - coordinates. If using latitude, longitude coordinates, then - either *wrfin* or *projection* must be specified to obtain the + + end_point (:class:`wrf.CoordPair`, optional): A coordinate pair + which indicates the end location through which the plane will + pass. Must also specify *end_point*. The coordinate + pair can be in x,y grid coordinates or latitude, longitude + coordinates. If using latitude, longitude coordinates, then + either *wrfin* or *projection* must be specified to obtain the map projection. Default is None. - - latlon (:obj:`bool`, optional): Set to True to also interpolate the - two-dimensional latitude and longitude coordinates along the same - horizontal line and include this information in the metadata + + latlon (:obj:`bool`, optional): Set to True to also interpolate the + two-dimensional latitude and longitude coordinates along the same + horizontal line and include this information in the metadata (if enabled). This can be helpful for plotting. Default is False. - + Note: - - Currently, *field2d* must be of type :class:`xarray.DataArray` - and contain coordinate information in order to generate the - latitude and longitude coordinates along the line if + + Currently, *field2d* must be of type :class:`xarray.DataArray` + and contain coordinate information in order to generate the + latitude and longitude coordinates along the line if *latlon* is set to True. Otherwise, a warning will be issued, - and the latitude and longitude information will not be + and the latitude and longitude information will not be present. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - - + + Returns: - + :class:`xarray.DataArray` or :class:`numpy.ndarray`: - The interpolated variable. If xarray is enabled and - the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + The interpolated variable. If xarray is enabled and + the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - - + + """ - + try: xy = cache["xy"] except (KeyError, TypeError): start_point_xy = None end_point_xy = None pivot_point_xy = None - - if (latlon is True or is_latlon_pair(start_point) or - is_latlon_pair(pivot_point)): - + + if (latlon is True or is_latlon_pair(start_point) or + is_latlon_pair(pivot_point)): + if wrfin is not None: is_moving = is_moving_domain(wrfin) else: is_moving = False - + if timeidx is None: if wrfin is not None: - # Moving nests aren't supported with ALL_TIMES because the - # domain could move outside of the line, which causes + # Moving nests aren't supported with ALL_TIMES because the + # domain could move outside of the line, which causes # crashes or different line lengths. if is_moving: - raise ValueError("Requesting all times with a moving nest " - "is not supported when using a lat/lon " - "line because the domain could " - "move outside of line. " + raise ValueError("Requesting all times with a moving " + "nest is not supported when using a " + "lat/lon line because the domain " + "could move outside of line. " "You must request each time " "individually.") else: # Domain not moving, just use 0 _timeidx = 0 - + # If using grid coordinates, then don't care about lat/lon # coordinates. Just use 0. else: @@ -527,84 +527,84 @@ def interpline(field2d, wrfin=None, timeidx=0, stagger=None, projection=None, if is_moving: _timeidx = timeidx else: - # When using non-moving nests, set the time to 0 + # When using non-moving nests, set the time to 0 # to avoid problems downstream _timeidx = 0 - + if pivot_point is not None: if pivot_point.lat is not None and pivot_point.lon is not None: - xy_coords = to_xy_coords(pivot_point, wrfin, _timeidx, + xy_coords = to_xy_coords(pivot_point, wrfin, _timeidx, stagger, projection, ll_point) pivot_point_xy = (xy_coords.x, xy_coords.y) else: pivot_point_xy = (pivot_point.x, pivot_point.y) - + if start_point is not None and end_point is not None: if start_point.lat is not None and start_point.lon is not None: - xy_coords = to_xy_coords(start_point, wrfin, _timeidx, + xy_coords = to_xy_coords(start_point, wrfin, _timeidx, stagger, projection, ll_point) start_point_xy = (xy_coords.x, xy_coords.y) else: start_point_xy = (start_point.x, start_point.y) - + if end_point.lat is not None and end_point.lon is not None: - xy_coords = to_xy_coords(end_point, wrfin, _timeidx, + xy_coords = to_xy_coords(end_point, wrfin, _timeidx, stagger, projection, ll_point) end_point_xy = (xy_coords.x, xy_coords.y) else: end_point_xy = (end_point.x, end_point.y) - - xy = get_xy(field2d, pivot_point_xy, angle, start_point_xy, + + xy = get_xy(field2d, pivot_point_xy, angle, start_point_xy, end_point_xy) - + return _interpline(field2d, xy) @set_interp_metadata("vinterp") -def vinterp(wrfin, field, vert_coord, interp_levels, extrapolate=False, - field_type=None, log_p=False, timeidx=0, method="cat", +def vinterp(wrfin, field, vert_coord, interp_levels, extrapolate=False, + field_type=None, log_p=False, timeidx=0, method="cat", squeeze=True, cache=None, meta=True): - """Return the field vertically interpolated to the given the type of + """Return the field vertically interpolated to the given the type of surface and a set of new levels. - + Args: wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`,\ or an iterable): - WRF-ARW NetCDF data as a :class:`netCDF4.Dataset`, - :class:`Nio.NioFile` or an iterable sequence of the + WRF-ARW NetCDF data as a :class:`netCDF4.Dataset`, + :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - field (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A + + field (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A three-dimensional field. - - vert_coord (:obj:`str`): A string indicating the vertical coordinate + + vert_coord (:obj:`str`): A string indicating the vertical coordinate type to interpolate to. - - Valid strings are: + + Valid strings are: * 'pressure', 'pres', 'p': pressure [hPa] * 'ght_msl': grid point height msl [km] * 'ght_agl': grid point height agl [km] * 'theta', 'th': potential temperature [K] - * 'theta-e', 'thetae', 'eth': equivalent potential temperature \ - [K] - - interp_levels (sequence): A 1D sequence of vertical levels to - interpolate to. Values must be in the same units as specified + * 'theta-e', 'thetae', 'eth': equivalent potential \ + temperature [K] + + interp_levels (sequence): A 1D sequence of vertical levels to + interpolate to. Values must be in the same units as specified above for the *vert_coord* parameter. - - extrapolate (:obj:`bool`, optional): Set to True to extrapolate - values below ground. This is only performed when *vert_coord* is - a pressure or height type, and the *field_type* is a pressure type - (with height vertical coordinate), a height type (with pressure as - the vertical coordinate), or a temperature type (with either height - or pressure as the vertical coordinate). If those conditions are - not met, or *field_type* is None, then the lowest model level + + extrapolate (:obj:`bool`, optional): Set to True to extrapolate + values below ground. This is only performed when *vert_coord* is + a pressure or height type, and the *field_type* is a pressure type + (with height vertical coordinate), a height type (with pressure as + the vertical coordinate), or a temperature type (with either height + or pressure as the vertical coordinate). If those conditions are + not met, or *field_type* is None, then the lowest model level will be used. Extrapolation is performed using standard atmosphere. - Default is False. - - field_type (:obj:`str`, optional): + Default is False. + + field_type (:obj:`str`, optional): The type of field. Default is None. - + Valid strings are: * 'none': None * 'pressure', 'pres', 'p': pressure [Pa] @@ -615,255 +615,247 @@ def vinterp(wrfin, field, vert_coord, interp_levels, extrapolate=False, * 'tk': temperature [K] * 'theta', 'th': potential temperature [K] * 'theta-e', 'thetae', 'eth': equivalent potential temperature - - log_p (:obj:`bool`, optional): Set to True to use the log of the - vertical coordinate for interpolation. This is mainly intended - for pressure vertical coordinate types, but note that the log - will still be taken for any vertical coordinate type when + + log_p (:obj:`bool`, optional): Set to True to use the log of the + vertical coordinate for interpolation. This is mainly intended + for pressure vertical coordinate types, but note that the log + will still be taken for any vertical coordinate type when this is set to True. Default is False. - + timeidx (:obj:`int`, optional): - The time index to use when extracting auxiallary variables used in + The time index to use when extracting auxiallary variables used in the interpolation. This value must be set to match the same value used when the `field` variable was extracted. Default is 0. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - + Returns: - + :class:`xarray.DataArray` or :class:`numpy.ndarray`: - The interpolated variable. If xarray is enabled and - the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + The interpolated variable. If xarray is enabled and + the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ _key = get_id(wrfin) - + _wrfin = get_iterable(wrfin) - + # Remove case sensitivity field_type = field_type.lower() if field_type is not None else "none" vert_coord = vert_coord.lower() if vert_coord is not None else "none" - - valid_coords = ("pressure", "pres", "p", "ght_msl", + + valid_coords = ("pressure", "pres", "p", "ght_msl", "ght_agl", "theta", "th", "theta-e", "thetae", "eth") - - valid_field_types = ("none", "pressure", "pres", "p", + + valid_field_types = ("none", "pressure", "pres", "p", 'pressure_hpa', 'pres_hpa', 'p_hpa', "z", - "tc", "tk", "theta", "th", "theta-e", "thetae", + "tc", "tk", "theta", "th", "theta-e", "thetae", "eth", "ght", 'z_km', 'ght_km') - - icase_lookup = {"none" : 0, - "p" : 1, - "pres" : 1, - "pressure" : 1, - "p_hpa" : 1, - "pres_hpa" : 1, - "pressure_hpa" : 1, - "z" : 2, - "ght" : 2, - "z_km" : 2, - "ght_km" : 2, - "tc" : 3, - "tk" : 4, - "theta" : 5, - "th" : 5, - "theta-e" : 6, - "thetae" : 6, - "eth" : 6} - - in_unitmap = {"p_hpa" : 1.0/ConversionFactors.PA_TO_HPA, - "pres_hpa" : 1.0/ConversionFactors.PA_TO_HPA, - "pressure_hpa" : 1.0/ConversionFactors.PA_TO_HPA, - "z_km" : 1.0/ConversionFactors.M_TO_KM, - "ght_km" : 1.0/ConversionFactors.M_TO_KM, - - } - - out_unitmap = {"p_hpa" : ConversionFactors.PA_TO_HPA, - "pres_hpa" : ConversionFactors.PA_TO_HPA, - "pressure_hpa" : ConversionFactors.PA_TO_HPA, - "z_km" : ConversionFactors.M_TO_KM, - "ght_km" : ConversionFactors.M_TO_KM, - - } - - # These constants match what's in the fortran code. + + icase_lookup = {"none": 0, + "p": 1, + "pres": 1, + "pressure": 1, + "p_hpa": 1, + "pres_hpa": 1, + "pressure_hpa": 1, + "z": 2, + "ght": 2, + "z_km": 2, + "ght_km": 2, + "tc": 3, + "tk": 4, + "theta": 5, + "th": 5, + "theta-e": 6, + "thetae": 6, + "eth": 6} + + in_unitmap = {"p_hpa": 1.0/ConversionFactors.PA_TO_HPA, + "pres_hpa": 1.0/ConversionFactors.PA_TO_HPA, + "pressure_hpa": 1.0/ConversionFactors.PA_TO_HPA, + "z_km": 1.0/ConversionFactors.M_TO_KM, + "ght_km": 1.0/ConversionFactors.M_TO_KM, + } + + out_unitmap = {"p_hpa": ConversionFactors.PA_TO_HPA, + "pres_hpa": ConversionFactors.PA_TO_HPA, + "pressure_hpa": ConversionFactors.PA_TO_HPA, + "z_km": ConversionFactors.M_TO_KM, + "ght_km": ConversionFactors.M_TO_KM, + } + + # These constants match what's in the fortran code. rgas = Constants.RD ussalr = Constants.USSALR sclht = Constants.SCLHT - + # interp_levels might be a list or tuple, make a numpy array if not isinstance(interp_levels, np.ndarray): interp_levels = np.asarray(interp_levels, np.float64) - + if len(interp_levels) == 0: raise ValueError("'interp_levels' contains no values") - + # Check if field is staggered if is_staggered(_wrfin, field): raise ValueError("Please unstagger field in the vertical") - + # Check for valid coord if vert_coord not in valid_coords: - raise ValueError("'%s' is not a valid vertical " - "coordinate type" % vert_coord) - + raise ValueError("'{}' is not a valid vertical " + "coordinate type".format(vert_coord)) + # Check for valid field type if field_type not in valid_field_types: - raise ValueError("'%s' is not a valid field type" % field_type) - + raise ValueError("'{}' is not a valid field type".format(field_type)) + log_p_int = 1 if log_p else 0 - + icase = 0 extrap = 0 - + if extrapolate: extrap = 1 icase = icase_lookup[field_type] - + # Extract variables - ncvars = extract_vars(_wrfin, timeidx, ("PSFC", "QVAPOR"), + ncvars = extract_vars(_wrfin, timeidx, ("PSFC", "QVAPOR"), method, squeeze, cache, meta=False, _key=_key) - + sfp = ncvars["PSFC"] * ConversionFactors.PA_TO_HPA qv = ncvars["QVAPOR"] - - terht = get_terrain(_wrfin, timeidx, units="m", + + terht = get_terrain(_wrfin, timeidx, units="m", method=method, squeeze=squeeze, cache=cache, meta=False, _key=_key) - tk = get_temp(_wrfin, timeidx, units="k", - method=method, squeeze=squeeze, cache=cache, + tk = get_temp(_wrfin, timeidx, units="k", + method=method, squeeze=squeeze, cache=cache, meta=False, _key=_key) - p = get_pressure(_wrfin, timeidx, units="pa", + p = get_pressure(_wrfin, timeidx, units="pa", method=method, squeeze=squeeze, cache=cache, meta=False, _key=_key) - ght = get_height(_wrfin, timeidx, msl=True, units="m", + ght = get_height(_wrfin, timeidx, msl=True, units="m", method=method, squeeze=squeeze, cache=cache, meta=False, _key=_key) - - smsfp = _smooth2d(sfp, 3, 2.0) + + smsfp = _smooth2d(sfp, 3, 2.0) vcor = 0 - + if vert_coord in ("pressure", "pres", "p"): vcor = 1 vcord_array = p * ConversionFactors.PA_TO_HPA - + elif vert_coord == "ght_msl": vcor = 2 vcord_array = np.exp(-ght/sclht) - + elif vert_coord == "ght_agl": ht_agl = get_height(_wrfin, timeidx, msl=False, units="m", - method=method, squeeze=squeeze, cache=cache, - meta=False, _key=_key) - + method=method, squeeze=squeeze, cache=cache, + meta=False, _key=_key) + vcor = 3 vcord_array = np.exp(-ht_agl/sclht) - + elif vert_coord in ("theta", "th"): - t = get_theta(_wrfin, timeidx, units="k", - method=method, squeeze=squeeze, cache=cache, - meta=False, _key=_key) - - coriolis = extract_vars(_wrfin, timeidx, "F", - method, squeeze, cache, meta=False, + t = get_theta(_wrfin, timeidx, units="k", + method=method, squeeze=squeeze, cache=cache, + meta=False, _key=_key) + + coriolis = extract_vars(_wrfin, timeidx, "F", + method, squeeze, cache, meta=False, _key=_key)["F"] - + vcor = 4 idir = 1 icorsw = 0 delta = 0.01 - + p_hpa = p * ConversionFactors.PA_TO_HPA - + vcord_array = _monotonic(t, p_hpa, coriolis, idir, delta, icorsw) - - # We only extrapolate temperature fields below ground + + # We only extrapolate temperature fields below ground # if we are interpolating to pressure or height vertical surfaces. - - icase = 0 - + + icase = 0 + elif vert_coord in ("theta-e", "thetae", "eth"): vcor = 5 icorsw = 0 idir = 1 delta = 0.01 - - eth = get_eth(_wrfin, timeidx, method=method, squeeze=squeeze, - cache=cache, meta=False, _key=_key) - - coriolis = extract_vars(_wrfin, timeidx, "F", - method, squeeze, cache, meta=False, + + eth = get_eth(_wrfin, timeidx, method=method, squeeze=squeeze, + cache=cache, meta=False, _key=_key) + + coriolis = extract_vars(_wrfin, timeidx, "F", + method, squeeze, cache, meta=False, _key=_key)["F"] - + p_hpa = p * ConversionFactors.PA_TO_HPA - + vcord_array = _monotonic(eth, p_hpa, coriolis, idir, delta, icorsw) # We only extrapolate temperature fields below ground if we are # interpolating to pressure or height vertical surfaces icase = 0 - + # Set the missing value if isinstance(field, ma.MaskedArray): missing = field.fill_value else: missing = default_fill(np.float64) - + if (field.shape != p.shape): raise ValueError("'field' shape does not match other variable shapes. " "Verify that the 'timeidx' parameter matches the " "same value used when extracting the 'field' " "variable.") - - # Some field types are in different units than the Fortran routine + + # Some field types are in different units than the Fortran routine # expects - + conv_factor = in_unitmap.get(field_type) - + if conv_factor is not None: field_ = field * conv_factor else: field_ = field - + res = _vintrp(field_, p, tk, qv, ght, terht, sfp, smsfp, vcord_array, interp_levels, icase, extrap, vcor, log_p_int, missing) - + conv_factor = out_unitmap.get(field_type) - + if conv_factor is not None: res_ = res * conv_factor else: res_ = res - - return ma.masked_values(res_, missing) - - - - - + return ma.masked_values(res_, missing) diff --git a/src/wrf/interputils.py b/src/wrf/interputils.py index 8e04c86..f5cf8d1 100644 --- a/src/wrf/interputils.py +++ b/src/wrf/interputils.py @@ -14,165 +14,164 @@ from .util import pairs_to_latlon def to_positive_idxs(shape, coord): """Return the positive index values. - + This function converts negative index values to positive index values. - + Args: - + shape (indexable sequence): The array shape. - + coord (indexable sequence): The coordinate pair for x and y. - + Returns: - + :obj:`list`: The coordinate values with all positive indexes. - + """ if (coord[-2] >= 0 and coord[-1] >= 0): return coord - - return [x if (x >= 0) else shape[-i-1]+x for (i,x) in enumerate(coord)] + return [x if (x >= 0) else shape[-i-1]+x for (i, x) in enumerate(coord)] -def _calc_xy(xdim, ydim, pivot_point=None, angle=None, - start_point=None, end_point=None): + +def _calc_xy(xdim, ydim, pivot_point=None, angle=None, + start_point=None, end_point=None): """Return the x,y points for the horizontal cross section line. - + Args: - + xdim (:obj:`int`): The x-dimension size. - + ydim (:obj:`int`): The y-dimension size. - - pivot_point (:obj:`tuple` or :obj:`list`, optional): A - :obj:`tuple` or :obj:`list` with two entries, - in the form of [x, y] (or [west_east, south_north]), which - indicates the x,y location through which the plane will pass. + + pivot_point (:obj:`tuple` or :obj:`list`, optional): A + :obj:`tuple` or :obj:`list` with two entries, + in the form of [x, y] (or [west_east, south_north]), which + indicates the x,y location through which the plane will pass. Must also specify `angle`. - - angle (:obj:`float`, optional): Only valid for cross sections where - a plane will be plotted through - a given point on the model domain. 0.0 represents a S-N cross - section. 90.0 is a W-E cross section. - - start_point (:obj:`tuple` or :obj:`list`, optional): A - :obj:`tuple` or :obj:`list` with two entries, in the form of - [x, y] (or [west_east, south_north]), which indicates the start + + angle (:obj:`float`, optional): Only valid for cross sections where + a plane will be plotted through + a given point on the model domain. 0.0 represents a S-N cross + section. 90.0 is a W-E cross section. + + start_point (:obj:`tuple` or :obj:`list`, optional): A + :obj:`tuple` or :obj:`list` with two entries, in the form of + [x, y] (or [west_east, south_north]), which indicates the start x,y location through which the plane will pass. - - end_point (:obj:`tuple` or :obj:`list`, optional): A - :obj:`tuple` or :obj:`list` with two entries, in the form of - [x, y] (or [west_east, south_north]), which indicates the end x,y + + end_point (:obj:`tuple` or :obj:`list`, optional): A + :obj:`tuple` or :obj:`list` with two entries, in the form of + [x, y] (or [west_east, south_north]), which indicates the end x,y location through which the plane will pass. - + Returns: - - :class:`np.ndarray`: A two-dimensional array with the left index - representing each point along the line, and the rightmost dimension + + :class:`np.ndarray`: A two-dimensional array with the left index + representing each point along the line, and the rightmost dimension having two values for the x and y coordinates [0=X, 1=Y]. - + """ # Have a pivot point with an angle to find cross section if pivot_point is not None and angle is not None: xp = pivot_point[-2] yp = pivot_point[-1] - + if xp >= xdim or yp >= ydim: raise ValueError("pivot point {} is outside of domain " "with shape {}".format(pivot_point, - (xdim, ydim))) - - if (angle > 315.0 or angle < 45.0 - or ((angle > 135.0) and (angle < 225.0))): - - #x = y*slope + intercept + (xdim, ydim))) + + if (angle > 315.0 or angle < 45.0 + or ((angle > 135.0) and (angle < 225.0))): + slope = -(360.-angle)/45. - if( angle < 45. ): + if(angle < 45.): slope = angle/45. - if( angle > 135.): + if(angle > 135.): slope = (angle-180.)/45. - + intercept = xp - yp*slope - + # find intersections with domain boundaries y0 = 0. x0 = y0*slope + intercept - - if( x0 < 0.): # intersect outside of left boundary + + if(x0 < 0.): # intersect outside of left boundary x0 = 0. - y0 = (x0 - intercept)/slope - if( x0 > xdim-1): #intersect outside of right boundary + y0 = (x0 - intercept)/slope + if(x0 > xdim-1): # intersect outside of right boundary x0 = xdim-1 - y0 = (x0 - intercept)/slope - y1 = ydim-1. #need to make sure this will be a float? + y0 = (x0 - intercept)/slope + y1 = ydim-1. # need to make sure this will be a float? x1 = y1*slope + intercept - - if( x1 < 0.): # intersect outside of left boundary + + if(x1 < 0.): # intersect outside of left boundary x1 = 0. - y1 = (x1 - intercept)/slope - - if( x1 > xdim-1): # intersect outside of right boundary + y1 = (x1 - intercept)/slope + + if(x1 > xdim-1): # intersect outside of right boundary x1 = xdim-1 - y1 = (x1 - intercept)/slope + y1 = (x1 - intercept)/slope else: # y = x*slope + intercept slope = (90.-angle)/45. - if( angle > 225. ): + if (angle > 225.): slope = (270.-angle)/45. intercept = yp - xp*slope - #find intersections with domain boundaries + # Find intersections with domain boundaries x0 = 0. y0 = x0*slope + intercept - - if( y0 < 0.): # intersect outside of bottom boundary + + if (y0 < 0.): # intersect outside of bottom boundary y0 = 0. - x0 = (y0 - intercept)/slope - - if( y0 > ydim-1): # intersect outside of top boundary + x0 = (y0 - intercept)/slope + + if (y0 > ydim-1): # intersect outside of top boundary y0 = ydim-1 - x0 = (y0 - intercept)/slope - - x1 = xdim-1. # need to make sure this will be a float? + x0 = (y0 - intercept)/slope + + x1 = xdim-1. # need to make sure this will be a float? y1 = x1*slope + intercept - - if( y1 < 0.): # intersect outside of bottom boundary + + if (y1 < 0.): # intersect outside of bottom boundary y1 = 0. - x1 = (y1 - intercept)/slope - - if( y1 > ydim-1):# intersect outside of top boundary + x1 = (y1 - intercept)/slope + + if (y1 > ydim-1): # intersect outside of top boundary y1 = ydim-1 - x1 = (y1 - intercept)/slope + x1 = (y1 - intercept)/slope elif start_point is not None and end_point is not None: x0 = start_point[-2] y0 = start_point[-1] x1 = end_point[-2] y1 = end_point[-1] - + if x0 >= xdim or y0 >= ydim: raise ValueError("start_point {} is outside of domain " "with shape {}".format(start_point, (xdim, ydim))) - + if x1 >= xdim or y1 >= ydim: raise ValueError("end_point {} is outside of domain " "with shape {}".format(end_point, (xdim, ydim))) else: raise ValueError("invalid start/end or pivot/angle arguments") - + dx = x1 - x0 dy = y1 - y0 distance = (dx*dx + dy*dy)**0.5 npts = int(distance) + 1 - - xy = np.zeros((npts,2), "float") + + xy = np.zeros((npts, 2), "float") dx = dx/(npts-1) dy = dy/(npts-1) - + for i in py3range(npts): - xy[i,0] = x0 + i*dx - xy[i,1] = y0 + i*dy - + xy[i, 0] = x0 + i*dx + xy[i, 1] = y0 + i*dy + return xy @@ -180,66 +179,66 @@ def get_xy_z_params(z, pivot_point=None, angle=None, start_point=None, end_point=None, levels=None, autolevels=100): """Return the cross section parameters. - - This function returns the xy horizontal cross section line coordinates, - the xy x z vertical values interpolated along the xy cross section + + This function returns the xy horizontal cross section line coordinates, + the xy x z vertical values interpolated along the xy cross section line, and the fixed vertical levels to be used by the cross section - algorithm (at ~1% increments for the minimum to maximum vertical + algorithm (at ~1% increments for the minimum to maximum vertical span). - + Args: - - z (:class:`numpy.ndarray`): The vertical coordinate, whose rightmost + + z (:class:`numpy.ndarray`): The vertical coordinate, whose rightmost dimensions are bottom_top x south_north x west_east. - - pivot_point (:obj:`tuple` or :obj:`list`, optional): A - :obj:`tuple` or :obj:`list` with two entries, - in the form of [x, y] (or [west_east, south_north]), which - indicates the x,y location through which the plane will pass. + + pivot_point (:obj:`tuple` or :obj:`list`, optional): A + :obj:`tuple` or :obj:`list` with two entries, + in the form of [x, y] (or [west_east, south_north]), which + indicates the x,y location through which the plane will pass. Must also specify `angle`. - - angle (:obj:`float`, optional): Only valid for cross sections where - a plane will be plotted through - a given point on the model domain. 0.0 represents a S-N cross - section. 90.0 is a W-E cross section. - - start_point (:obj:`tuple` or :obj:`list`, optional): A - :obj:`tuple` or :obj:`list` with two entries, in the form of - [x, y] (or [west_east, south_north]), which indicates the start + + angle (:obj:`float`, optional): Only valid for cross sections where + a plane will be plotted through + a given point on the model domain. 0.0 represents a S-N cross + section. 90.0 is a W-E cross section. + + start_point (:obj:`tuple` or :obj:`list`, optional): A + :obj:`tuple` or :obj:`list` with two entries, in the form of + [x, y] (or [west_east, south_north]), which indicates the start x,y location through which the plane will pass. - - end_point (:obj:`tuple` or :obj:`list`, optional): A - :obj:`tuple` or :obj:`list` with two entries, in the form of - [x, y] (or [west_east, south_north]), which indicates the end x,y + + end_point (:obj:`tuple` or :obj:`list`, optional): A + :obj:`tuple` or :obj:`list` with two entries, in the form of + [x, y] (or [west_east, south_north]), which indicates the end x,y location through which the plane will pass. - + levels (sequence): A sequence of :obj:`float` for the desired - vertical levels in the output array. If None, a fixed set of + vertical levels in the output array. If None, a fixed set of vertical levels is provided. Default is None. - - autolevels(:obj:`int`, optional): The number of evenly spaced - automatically chosen vertical levels to use when *levels* + + autolevels(:obj:`int`, optional): The number of evenly spaced + automatically chosen vertical levels to use when *levels* is None. Default is 100. - + Returns: - - :obj:`tuple`: A tuple containing the xy horizontal cross section - coordinates, the vertical values interpolated along the xy cross - section line, and the fixed vertical levels used by the - cross section algorithm at ~1% increments of minimum to maximum + + :obj:`tuple`: A tuple containing the xy horizontal cross section + coordinates, the vertical values interpolated along the xy cross + section line, and the fixed vertical levels used by the + cross section algorithm at ~1% increments of minimum to maximum vertical span. - + """ - + xy = get_xy(z, pivot_point, angle, start_point, end_point) - + # Interp z var2dz = _interp2dxy(z, xy) - + extra_dim_num = z.ndim - 3 - idx1 = tuple([0]*extra_dim_num + [0,0]) - idx2 = tuple([0]*extra_dim_num + [-1,0]) - + idx1 = tuple([0]*extra_dim_num + [0, 0]) + idx2 = tuple([0]*extra_dim_num + [-1, 0]) + if levels is None: # interp to constant z grid if(var2dz[idx1] > var2dz[idx2]): # monotonically decreasing coordinate @@ -255,181 +254,182 @@ def get_xy_z_params(z, pivot_point=None, angle=None, dz = (1.0/autolevels)*z_max z_var2d = np.zeros((autolevels), dtype=z.dtype) z_var2d[0] = z_min - - for i in py3range(1,autolevels): + + for i in py3range(1, autolevels): z_var2d[i] = z_var2d[0] + i*dz else: z_var2d = np.asarray(levels, z.dtype) - + return xy, var2dz, z_var2d -def get_xy(var, pivot_point=None, angle=None, +def get_xy(var, pivot_point=None, angle=None, start_point=None, end_point=None): """Return the x,y points for the horizontal cross section line. - + Args: - + var (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A variable that contains a :attr:`shape` attribute. - - pivot_point (:obj:`tuple` or :obj:`list`, optional): A - :obj:`tuple` or :obj:`list` with two entries, - in the form of [x, y] (or [west_east, south_north]), which - indicates the x,y location through which the plane will pass. + + pivot_point (:obj:`tuple` or :obj:`list`, optional): A + :obj:`tuple` or :obj:`list` with two entries, + in the form of [x, y] (or [west_east, south_north]), which + indicates the x,y location through which the plane will pass. Must also specify `angle`. - - angle (:obj:`float`, optional): Only valid for cross sections where - a plane will be plotted through - a given point on the model domain. 0.0 represents a S-N cross - section. 90.0 is a W-E cross section. - - start_point (:obj:`tuple` or :obj:`list`, optional): A - :obj:`tuple` or :obj:`list` with two entries, in the form of - [x, y] (or [west_east, south_north]), which indicates the start + + angle (:obj:`float`, optional): Only valid for cross sections where + a plane will be plotted through + a given point on the model domain. 0.0 represents a S-N cross + section. 90.0 is a W-E cross section. + + start_point (:obj:`tuple` or :obj:`list`, optional): A + :obj:`tuple` or :obj:`list` with two entries, in the form of + [x, y] (or [west_east, south_north]), which indicates the start x,y location through which the plane will pass. - - end_point (:obj:`tuple` or :obj:`list`, optional): A - :obj:`tuple` or :obj:`list` with two entries, in the form of - [x, y] (or [west_east, south_north]), which indicates the end x,y + + end_point (:obj:`tuple` or :obj:`list`, optional): A + :obj:`tuple` or :obj:`list` with two entries, in the form of + [x, y] (or [west_east, south_north]), which indicates the end x,y location through which the plane will pass. - + Returns: - - :class:`np.ndarray`: A two-dimensional array with the left index - representing each point along the line, and the rightmost dimension + + :class:`np.ndarray`: A two-dimensional array with the left index + representing each point along the line, and the rightmost dimension having two values for the x and y coordinates [0=X, 1=Y]. - + """ if pivot_point is not None: pos_pivot = to_positive_idxs(var.shape[-2:], pivot_point) else: pos_pivot = pivot_point - + if start_point is not None: pos_start = to_positive_idxs(var.shape[-2:], start_point) else: pos_start = start_point - + if end_point is not None: pos_end = to_positive_idxs(var.shape[-2:], end_point) else: pos_end = start_point - + xdim = var.shape[-1] ydim = var.shape[-2] - + xy = _calc_xy(xdim, ydim, pos_pivot, angle, pos_start, pos_end) - + return xy + def to_xy_coords(pairs, wrfin=None, timeidx=0, stagger=None, projection=None, ll_point=None): """Return the coordinate pairs in grid space. - - This function converts latitude,longitude coordinate pairs to + + This function converts latitude,longitude coordinate pairs to x,y coordinate pairs. - + Args: - - pairs (:class:`CoordPair` or sequence): A single coordinate pair or + + pairs (:class:`CoordPair` or sequence): A single coordinate pair or a sequence of coordinate pairs to be converted. - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable, optional): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable, optional): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. This is used - to obtain the map projection when using latitude,longitude - coordinates. Should not be used when working with x,y + to obtain the map projection when using latitude,longitude + coordinates. Should not be used when working with x,y coordinates. Default is None. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index when obtaining map boundary information - from moving nests. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return - all times in the file or sequence. Only required when + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index when obtaining map boundary information + from moving nests. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return + all times in the file or sequence. Only required when *wrfin* is specified and the nest is moving. Default is 0. - - stagger (:obj:`str`): If using latitude, longitude coordinate pairs - for *start_point*, *end_point*, or *pivot_point*, + + stagger (:obj:`str`): If using latitude, longitude coordinate pairs + for *start_point*, *end_point*, or *pivot_point*, set the appropriate grid staggering type for *field2d*. By default, the mass grid is used. The options are: - + - 'm': Use the mass grid (default). - - 'u': Use the same staggered grid as the u wind component, + - 'u': Use the same staggered grid as the u wind component, which has a staggered west_east (x) dimension. - - 'v': Use the same staggered grid as the v wind component, + - 'v': Use the same staggered grid as the v wind component, which has a staggered south_north (y) dimension. - - projection (:class:`wrf.WrfProj`, optional): The map - projection object to use when working with latitude, longitude - coordinates, and must be specified if *wrfin* is None. Default + + projection (:class:`wrf.WrfProj`, optional): The map + projection object to use when working with latitude, longitude + coordinates, and must be specified if *wrfin* is None. Default is None. - + ll_point (:class:`wrf.CoordPair`, sequence of :class:`wrf.CoordPair`, \ - optional): The lower left latitude, longitude point for your domain, - and must be specified - if *wrfin* is None. If the domain is a moving nest, this should be + optional): The lower left latitude, longitude point for your domain, + and must be specified + if *wrfin* is None. If the domain is a moving nest, this should be a sequence of :class:`wrf.CoordPair`. Default is None. - + Returns: - - :class:`wrf.CoordPair` or sequence: The coordinate pair(s) in + + :class:`wrf.CoordPair` or sequence: The coordinate pair(s) in x,y grid coordinates. - + """ - + if (wrfin is None and (projection is None or ll_point is None)): - raise ValueError ("'wrfin' parameter or " - "'projection' and 'll_point' parameters " - "are required") - + raise ValueError("'wrfin' parameter or " + "'projection' and 'll_point' parameters " + "are required") + lat, lon = pairs_to_latlon(pairs) - + if wrfin is not None: - xy_vals = _ll_to_xy(lat, lon, wrfin=wrfin, timeidx=timeidx, - squeeze=True, meta=False, stagger=stagger, as_int=True) - + xy_vals = _ll_to_xy(lat, lon, wrfin=wrfin, timeidx=timeidx, + squeeze=True, meta=False, stagger=stagger, + as_int=True) + else: map_proj = projection.map_proj - + if map_proj == ProjectionTypes.LAT_LON: pole_lat = projection.pole_lat pole_lon = projection.pole_lon - latinc = ((projection.dy*360.0)/2.0 / + latinc = ((projection.dy*360.0)/2.0 / Constants.PI/Constants.WRF_EARTH_RADIUS) - loninc = ((projection.dx*360.0)/2.0 / + loninc = ((projection.dx*360.0)/2.0 / Constants.PI/Constants.WRF_EARTH_RADIUS) else: pole_lat = 90.0 pole_lon = 0.0 latinc = 0.0 loninc = 0.0 - + ll_lat, ll_lon = pairs_to_latlon(ll_point) - xy_vals = _ll_to_xy(lat, lon, meta=False, squeeze=True, + xy_vals = _ll_to_xy(lat, lon, meta=False, squeeze=True, as_int=True, - map_proj=projection.map_proj, - truelat1=projection.truelat1, - truelat2=projection.truelat2, - stand_lon=projection.stand_lon, - ref_lat=ll_lat, - ref_lon=ll_lon, - pole_lat=pole_lat, - pole_lon=pole_lon, - known_x=0, - known_y=0, - dx=projection.dx, - dy=projection.dy, - latinc=latinc, + map_proj=projection.map_proj, + truelat1=projection.truelat1, + truelat2=projection.truelat2, + stand_lon=projection.stand_lon, + ref_lat=ll_lat, + ref_lon=ll_lon, + pole_lat=pole_lat, + pole_lon=pole_lon, + known_x=0, + known_y=0, + dx=projection.dx, + dy=projection.dy, + latinc=latinc, loninc=loninc) - + xy_vals = xy_vals.squeeze() - if xy_vals.ndim == 1: return CoordPair(x=xy_vals[0], y=xy_vals[1]) else: - return [CoordPair(x=xy_vals[0,i], y=xy_vals[1,i]) + return [CoordPair(x=xy_vals[0, i], y=xy_vals[1, i]) for i in py3range(xy_vals.shape[1])] diff --git a/src/wrf/latlonutils.py b/src/wrf/latlonutils.py index 505b204..22191af 100644 --- a/src/wrf/latlonutils.py +++ b/src/wrf/latlonutils.py @@ -6,36 +6,36 @@ import numpy as np from .constants import Constants, ProjectionTypes from .extension import _lltoxy, _xytoll -from .util import (extract_vars, extract_global_attrs, - either, is_moving_domain, is_multi_time_req, - iter_left_indexes, is_mapping, is_multi_file) +from .util import (extract_vars, extract_global_attrs, + either, is_moving_domain, iter_left_indexes, + is_mapping, is_multi_file) from .py3compat import viewkeys, viewitems from .projutils import dict_keys_to_upper - + def _lat_varname(wrfin, stagger): """Return the latitude variable name for the specified stagger type. - + Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - stagger (:obj:`str`): The staggered grid type which is one of the + + stagger (:obj:`str`): The staggered grid type which is one of the following: - + - 'm': Use the mass grid (default). - - 'u': Use the same staggered grid as the u wind component, + - 'u': Use the same staggered grid as the u wind component, which has a staggered west_east (x) dimension. - - 'v': Use the same staggered grid as the v wind component, + - 'v': Use the same staggered grid as the v wind component, which has a staggered south_north (y) dimension. - + Returns: - + :obj:`str`: The latitude variable name. - + """ if stagger is None or stagger.lower() == "m": varname = either("XLAT", "XLAT_M")(wrfin) @@ -43,32 +43,33 @@ def _lat_varname(wrfin, stagger): varname = "XLAT_{}".format(stagger.upper()) else: raise ValueError("invalid 'stagger' value") - + return varname - + + def _lon_varname(wrfin, stagger): """Return the longitude variable name for the specified stagger type. - + Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - stagger (:obj:`str`): The staggered grid type, which is one of the + + stagger (:obj:`str`): The staggered grid type, which is one of the following: - + - 'm': Use the mass grid (default). - - 'u': Use the same staggered grid as the u wind component, + - 'u': Use the same staggered grid as the u wind component, which has a staggered west_east (x) dimension. - - 'v': Use the same staggered grid as the v wind component, + - 'v': Use the same staggered grid as the v wind component, which has a staggered south_north (y) dimension. - + Returns: - + :obj:`str`: The latitude variable name. - + """ if stagger is None or stagger.lower() == "m": varname = either("XLONG", "XLONG_M")(wrfin) @@ -76,64 +77,65 @@ def _lon_varname(wrfin, stagger): varname = "XLONG_{}".format(stagger.upper()) else: raise ValueError("invalid 'stagger' value") - + return varname + def _get_proj_params(wrfin, timeidx, stagger, method, squeeze, cache, _key): """Return the map projection parameters. - + Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. The default is 0. - - stagger (:obj:`str`): The staggered grid type, which is one of the + + stagger (:obj:`str`): The staggered grid type, which is one of the following: - + - 'm': Use the mass grid (default). - - 'u': Use the same staggered grid as the u wind component, + - 'u': Use the same staggered grid as the u wind component, which has a staggered west_east (x) dimension. - - 'v': Use the same staggered grid as the v wind component, + - 'v': Use the same staggered grid as the v wind component, which has a staggered south_north (y) dimension. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - - _key (:obj:`int`, optional): A caching key. This is used for internal + + _key (:obj:`int`, optional): A caching key. This is used for internal purposes only. Default is None. - + Returns: - - + + """ if timeidx is not None: if timeidx < 0: raise ValueError("'timeidx' must be greater than 0") - + attrs = extract_global_attrs(wrfin, attrs=("MAP_PROJ", "TRUELAT1", "TRUELAT2", "STAND_LON", "DX", "DY")) @@ -143,9 +145,9 @@ def _get_proj_params(wrfin, timeidx, stagger, method, squeeze, cache, _key): stdlon = attrs["STAND_LON"] dx = attrs["DX"] dy = attrs["DY"] - + if map_proj == ProjectionTypes.LAT_LON: - pole_attrs = extract_global_attrs(wrfin, attrs=("POLE_LAT", + pole_attrs = extract_global_attrs(wrfin, attrs=("POLE_LAT", "POLE_LON")) pole_lat = pole_attrs["POLE_LAT"] pole_lon = pole_attrs["POLE_LON"] @@ -156,20 +158,20 @@ def _get_proj_params(wrfin, timeidx, stagger, method, squeeze, cache, _key): pole_lon = 0.0 latinc = 0.0 loninc = 0.0 - + latvar = _lat_varname(wrfin, stagger) lonvar = _lon_varname(wrfin, stagger) - + lat_timeidx = timeidx - - is_moving = is_moving_domain(wrfin, latvar=latvar, lonvar=lonvar, + + is_moving = is_moving_domain(wrfin, latvar=latvar, lonvar=lonvar, _key=_key) - + # Only need one file and one time if the domain is not moving if not is_moving: # Always use the 0th time for non-moving domains to avoid problems lat_timeidx = 0 - + if is_multi_file(wrfin): if not is_mapping(wrfin): wrfin = next(iter(wrfin)) # only need one file @@ -177,43 +179,43 @@ def _get_proj_params(wrfin, timeidx, stagger, method, squeeze, cache, _key): first_entry = next(iter(viewkeys(wrfin))) wrfin = wrfin[first_entry] key = _key[first_entry] - return _get_proj_params(wrfin, timeidx, stagger, + return _get_proj_params(wrfin, timeidx, stagger, method, squeeze, cache, key) - + xlat = extract_vars(wrfin, lat_timeidx, (latvar,), method, squeeze, cache, - meta=False, _key=_key)[latvar] + meta=False, _key=_key)[latvar] xlon = extract_vars(wrfin, lat_timeidx, (lonvar,), method, squeeze, cache, - meta=False, _key=_key)[lonvar] - + meta=False, _key=_key)[lonvar] + ref_lat = np.ravel(xlat[..., 0, 0]) ref_lon = np.ravel(xlon[..., 0, 0]) - + # Note: fortran index known_x = 1.0 known_y = 1.0 - + return (map_proj, truelat1, truelat2, stdlon, ref_lat, ref_lon, pole_lat, pole_lon, known_x, known_y, dx, dy, latinc, loninc) - + # known_x and known_y are 0-based def _kwarg_proj_params(**projparams): """Return the map projection parameters. - - This function aggregates the projection parameter keyword + + This function aggregates the projection parameter keyword arguments and also performs sanity checking on them. - + Args: - + **projparams: Projection parameter keyword arguments. - + Returns: - + :obj:`tuple`: The map projection parameters. - + """ projparams = dict_keys_to_upper(projparams) - + map_proj = projparams.get("MAP_PROJ") truelat1 = projparams.get("TRUELAT1") truelat2 = projparams.get("TRUELAT2") @@ -222,164 +224,164 @@ def _kwarg_proj_params(**projparams): ref_lon = projparams.get("REF_LON") pole_lat = projparams.get("POLE_LAT", 90.0) pole_lon = projparams.get("POLE_LON", 0.0) - known_x = projparams.get("KNOWN_X") # Use 0-based - known_y = projparams.get("KNOWN_Y") # Use 0-based - + known_x = projparams.get("KNOWN_X") # Use 0-based + known_y = projparams.get("KNOWN_Y") # Use 0-based + dx = projparams.get("DX") dy = projparams.get("DY") latinc = projparams.get("LATINC") loninc = projparams.get("LONINC") - + # Sanity checks # Required args for all projections - for name, var in viewitems({"MAP_PROJ" : map_proj, - "REF_LAT" : ref_lat, - "REF_LON" : ref_lon, - "KNOWN_X" : known_x, - "KNOWN_Y" : known_y, - "DX" : dx}): + for name, var in viewitems({"MAP_PROJ": map_proj, + "REF_LAT": ref_lat, + "REF_LON": ref_lon, + "KNOWN_X": known_x, + "KNOWN_Y": known_y, + "DX": dx}): if var is None: raise ValueError("'{}' argument required".format(name)) - + # ref_lat and ref_lon are expected to be lists ref_lat = np.ravel(np.asarray([ref_lat])) ref_lon = np.ravel(np.asarray([ref_lon])) - + # Fortran wants 1-based indexing known_x = known_x + 1 known_y = known_y + 1 - - if map_proj in (ProjectionTypes.LAMBERT_CONFORMAL, - ProjectionTypes.POLAR_STEREOGRAPHIC, + + if map_proj in (ProjectionTypes.LAMBERT_CONFORMAL, + ProjectionTypes.POLAR_STEREOGRAPHIC, ProjectionTypes.MERCATOR): if truelat1 is None: raise ValueError("'TRUELAT1' argument required") else: if truelat1 is None: truelat1 = 0.0 - + # Map projection 6 (lat lon) required latinc, loninc, and dy if map_proj == ProjectionTypes.LAT_LON: if latinc is None: raise ValueError("'LATINC' argument required") - + if loninc is None: raise ValueError("'LONINC' argument required") - + if dy is None: raise ValueError("'DY' argument required") else: latinc = 0.0 loninc = 0.0 dy = 0.0 - + return (map_proj, truelat1, truelat2, stdlon, ref_lat, ref_lon, pole_lat, pole_lon, known_x, known_y, dx, dy, latinc, loninc) # Will return 0-based indexes def _ll_to_xy(latitude, longitude, wrfin=None, timeidx=0, - stagger=None, method="cat", squeeze=True, cache=None, - _key=None, as_int=True, **projparams): + stagger=None, method="cat", squeeze=True, cache=None, + _key=None, as_int=True, **projparams): """Return the x,y coordinates for a specified latitude and longitude. - - The *latitude* and *longitude* arguments can be a single value or a + + The *latitude* and *longitude* arguments can be a single value or a sequence of values. - - The leftmost dimension of the returned array represents two different + + The leftmost dimension of the returned array represents two different quantities: - + - return_val[0,...] will contain the X (west_east) values. - return_val[1,...] will contain the Y (south_north) values. - + Args: - - latitude (:obj:`float` or sequence): A single latitude or a sequence + + latitude (:obj:`float` or sequence): A single latitude or a sequence of latitude values to be converted. - - longitude (:obj:`float` or sequence): A single longitude or a sequence + + longitude (:obj:`float` or sequence): A single longitude or a sequence of latitude values to be converted. - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. The default is 0. - - stagger (:obj:`str`): By default, the latitude and longitude are - returned on the mass grid, but a staggered grid can be chosen + + stagger (:obj:`str`): By default, the latitude and longitude are + returned on the mass grid, but a staggered grid can be chosen with the following options: - + - 'm': Use the mass grid (default). - - 'u': Use the same staggered grid as the u wind component, + - 'u': Use the same staggered grid as the u wind component, which has a staggered west_east (x) dimension. - - 'v': Use the same staggered grid as the v wind component, + - 'v': Use the same staggered grid as the v wind component, which has a staggered south_north (y) dimension. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - - _key (:obj:`int`, optional): A caching key. This is used for internal + + _key (:obj:`int`, optional): A caching key. This is used for internal purposes only. Default is None. - - as_int (:obj:`bool`): Set to True to return the x,y values as + + as_int (:obj:`bool`): Set to True to return the x,y values as :obj:`int`, otherwise they will be returned as :obj:`float`. - + **projparams: Map projection keyword arguments to set manually. - + Returns: - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The x,y coordinate value(s) whose leftmost dimension is 2 (0=X, 1=Y). - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ - + if wrfin is not None: (map_proj, truelat1, truelat2, stdlon, ref_lat, ref_lon, - pole_lat, pole_lon, known_x, known_y, dx, dy, latinc, - loninc) = _get_proj_params(wrfin, timeidx, stagger, method, squeeze, + pole_lat, pole_lon, known_x, known_y, dx, dy, latinc, + loninc) = _get_proj_params(wrfin, timeidx, stagger, method, squeeze, cache, _key) else: (map_proj, truelat1, truelat2, stdlon, ref_lat, ref_lon, - pole_lat, pole_lon, known_x, known_y, dx, dy, latinc, - loninc) = _kwarg_proj_params(**projparams) - + pole_lat, pole_lon, known_x, known_y, dx, dy, latinc, + loninc) = _kwarg_proj_params(**projparams) + if isinstance(latitude, Iterable): lats = np.asarray(latitude) lons = np.asarray(longitude) - + # Note: For scalars, this will make a single element array lats = lats.ravel() - + lons = lons.ravel() - + if (lats.size != lons.size): raise ValueError("'latitude' and 'longitude' " "must be the same length") - + if ref_lat.size == 1: outdim = [2, lats.size] extra_dims = [outdim[1]] @@ -387,11 +389,10 @@ def _ll_to_xy(latitude, longitude, wrfin=None, timeidx=0, # Moving domain will have moving ref_lats/ref_lons outdim = [2, ref_lat.size, lats.size] extra_dims = outdim[1:] - + result = np.empty(outdim, np.float64) - + for left_idxs in iter_left_indexes(extra_dims): - #left_and_slice_idxs = left_idxs + (slice(None), ) # Left indexes is a misnomer, since these will be on the right x_idxs = (0,) + left_idxs y_idxs = (1,) + left_idxs @@ -401,149 +402,148 @@ def _ll_to_xy(latitude, longitude, wrfin=None, timeidx=0, else: ref_lat_val = ref_lat[left_idxs[-2]] ref_lon_val = ref_lon[left_idxs[-2]] - + lat = lats[left_idxs[-1]] lon = lons[left_idxs[-1]] - + xy = _lltoxy(map_proj, truelat1, truelat2, stdlon, - ref_lat_val, ref_lon_val, pole_lat, pole_lon, - known_x, known_y, dx, dy, latinc, loninc, - lat, lon) - + ref_lat_val, ref_lon_val, pole_lat, pole_lon, + known_x, known_y, dx, dy, latinc, loninc, + lat, lon) + # Note: comes back from fortran as y,x result[x_idxs] = xy[1] result[y_idxs] = xy[0] - + else: result = np.empty((2,), np.float64) - + fort_out = _lltoxy(map_proj, truelat1, truelat2, stdlon, - ref_lat, ref_lon, pole_lat, pole_lon, - known_x, known_y, dx, dy, latinc, loninc, - latitude, longitude) - + ref_lat, ref_lon, pole_lat, pole_lon, + known_x, known_y, dx, dy, latinc, loninc, + latitude, longitude) + # Note, comes back from fortran as y,x. So, need to swap them. result[0] = fort_out[1] result[1] = fort_out[0] - - + # Make indexes 0-based result = result - 1 - + if as_int: result = np.rint(result).astype(int) - + return result + # X and Y should be 0-based -def _xy_to_ll(x, y, wrfin=None, timeidx=0, stagger=None, - method="cat", squeeze=True, cache=None, _key=None, - **projparams): - +def _xy_to_ll(x, y, wrfin=None, timeidx=0, stagger=None, + method="cat", squeeze=True, cache=None, _key=None, + **projparams): + """Return the latitude and longitude for specified x,y coordinates. - + The *x* and *y* arguments can be a single value or a sequence of values. - - The leftmost dimension of the returned array represents two different + + The leftmost dimension of the returned array represents two different quantities: - + - return_val[0,...] will contain the latitude values. - return_val[1,...] will contain the longitude values. - + Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - x (:obj:`float` or sequence): A single x-coordinate or a sequence + + x (:obj:`float` or sequence): A single x-coordinate or a sequence of x-coordinate values to be converted. - - y (:obj:`float` or sequence): A single y-coordinate or a sequence + + y (:obj:`float` or sequence): A single y-coordinate or a sequence of y-coordinate values to be converted. - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. The default is 0. - - stagger (:obj:`str`): By default, the latitude and longitude are - returned on the mass grid, but a staggered grid can be chosen + + stagger (:obj:`str`): By default, the latitude and longitude are + returned on the mass grid, but a staggered grid can be chosen with the following options: - + - 'm': Use the mass grid (default). - - 'u': Use the same staggered grid as the u wind component, + - 'u': Use the same staggered grid as the u wind component, which has a staggered west_east (x) dimension. - - 'v': Use the same staggered grid as the v wind component, + - 'v': Use the same staggered grid as the v wind component, which has a staggered south_north (y) dimension. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - - _key (:obj:`int`, optional): A caching key. This is used for internal + + _key (:obj:`int`, optional): A caching key. This is used for internal purposes only. Default is None. - + **projparams: Map projection keyword arguments to set manually. - + Returns: - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The - latitude and longitude values whose leftmost dimension is 2 + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The + latitude and longitude values whose leftmost dimension is 2 (0=latitude, 1=longitude). - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ - + if wrfin is not None: (map_proj, truelat1, truelat2, stdlon, ref_lat, ref_lon, - pole_lat, pole_lon, known_x, known_y, dx, dy, latinc, - loninc) = _get_proj_params(wrfin, timeidx, stagger, method, squeeze, + pole_lat, pole_lon, known_x, known_y, dx, dy, latinc, + loninc) = _get_proj_params(wrfin, timeidx, stagger, method, squeeze, cache, _key) else: (map_proj, truelat1, truelat2, stdlon, ref_lat, ref_lon, - pole_lat, pole_lon, known_x, known_y, dx, dy, latinc, - loninc) = _kwarg_proj_params(**projparams) - - + pole_lat, pole_lon, known_x, known_y, dx, dy, latinc, + loninc) = _kwarg_proj_params(**projparams) + if isinstance(x, Iterable): x_arr = np.asarray(x) y_arr = np.asarray(y) - + # Convert 0-based x, y to 1-based x_arr = x_arr + 1 y_arr = y_arr + 1 - + x_arr = x_arr.ravel() - + y_arr = y_arr.ravel() - + if (x_arr.size != y_arr.size): raise ValueError("'x' and 'y' must be the same length") - + if ref_lat.size == 1: outdim = [2, x_arr.size] extra_dims = [outdim[1]] @@ -551,44 +551,37 @@ def _xy_to_ll(x, y, wrfin=None, timeidx=0, stagger=None, # Moving domain will have moving ref_lats/ref_lons outdim = [2, ref_lat.size, x_arr.size] extra_dims = outdim[1:] - + result = np.empty(outdim, np.float64) - + for left_idxs in iter_left_indexes(extra_dims): - #left_and_slice_idxs = left_idxs + (slice(None), ) lat_idxs = (0,) + left_idxs lon_idxs = (1,) + left_idxs - + if ref_lat.size == 1: ref_lat_val = ref_lat[0] ref_lon_val = ref_lon[0] else: ref_lat_val = ref_lat[left_idxs[-2]] ref_lon_val = ref_lon[left_idxs[-2]] - + x_val = x_arr[left_idxs[-1]] y_val = y_arr[left_idxs[-1]] - - ll = _xytoll(map_proj, truelat1, truelat2, stdlon, ref_lat_val, + + ll = _xytoll(map_proj, truelat1, truelat2, stdlon, ref_lat_val, ref_lon_val, pole_lat, pole_lon, known_x, known_y, dx, dy, latinc, loninc, x_val, y_val) - - #result[left_and_slice_idxs] = ll[:] + result[lat_idxs] = ll[0] result[lon_idxs] = ll[1] - + else: # Convert 0-based to 1-based for Fortran x_val = x + 1 y_val = y + 1 - - result = _xytoll(map_proj, truelat1, truelat2, stdlon, ref_lat, ref_lon, - pole_lat, pole_lon, known_x, known_y, dx, dy, latinc, - loninc, x_val, y_val) - - return result - + result = _xytoll(map_proj, truelat1, truelat2, stdlon, ref_lat, + ref_lon, pole_lat, pole_lon, known_x, known_y, + dx, dy, latinc, loninc, x_val, y_val) - - + return result diff --git a/src/wrf/metadecorators.py b/src/wrf/metadecorators.py index ea00d75..0d58908 100644 --- a/src/wrf/metadecorators.py +++ b/src/wrf/metadecorators.py @@ -1,6 +1,6 @@ from __future__ import (absolute_import, division, print_function) import warnings -import wrapt +import wrapt from collections import OrderedDict import numpy as np @@ -8,7 +8,7 @@ import numpy.ma as ma from .extension import _interpline from .util import (extract_vars, either, from_args, arg_location, - is_coordvar, latlon_coordvars, to_np, + is_coordvar, latlon_coordvars, to_np, from_var, iter_left_indexes, is_mapping, is_moving_domain, is_latlon_pair) from .coordpair import CoordPair @@ -18,80 +18,80 @@ from .config import xarray_enabled if xarray_enabled(): from xarray import DataArray - + def copy_and_set_metadata(copy_varname=None, delete_attrs=None, name=None, - remove_dims=None, dimnames=None, + remove_dims=None, dimnames=None, coords=None, **fixed_attrs): """A decorator that sets the metadata for a wrapped function's output. - - Generally, the metadata is copied from the variable specified by - *copy_varname*, with other fixed fields set by the other decorator + + Generally, the metadata is copied from the variable specified by + *copy_varname*, with other fixed fields set by the other decorator arguments. - - The *cache* argument used by most diagnostic routines is supplied by + + The *cache* argument used by most diagnostic routines is supplied by this decorator when the *copy_varname* variable is extracted, in order to prevent the variable from being extracted again by the wrapped function. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Args: - + copy_varname (:obj:`str`, optional): The NetCDF variable name to copy. Default is None. - - delete_attrs (sequence of :obj:`str`, optional): A sequence of key - names to remove from the :attr:`xarray.DataArray.attrs` attribute - in the wrapped function output (after being copied from + + delete_attrs (sequence of :obj:`str`, optional): A sequence of key + names to remove from the :attr:`xarray.DataArray.attrs` attribute + in the wrapped function output (after being copied from *copy_varname*). Default is None. - - name (:obj:`str`): The name to use for the + + name (:obj:`str`): The name to use for the :attr:`xarray.DataArray.name` attribute. - - remove_dims (sequence of :obj:`int`, optional): A sequence of dimension - indexes to be removed from the wrapped function output (after being - copied from *copy_varname*). This is useful when the copy - variable is three dimensional but the wrapped function output - is two dimensional, and you still want to keep the names of + + remove_dims (sequence of :obj:`int`, optional): A sequence of dimension + indexes to be removed from the wrapped function output (after being + copied from *copy_varname*). This is useful when the copy + variable is three dimensional but the wrapped function output + is two dimensional, and you still want to keep the names of the rightmost two dimensions. Default is None. - + dimnames (sequence of :obj:`str`, optional): A sequence of dimension - names in order to manually set the + names in order to manually set the :attr:`xarray.DataArray.dims` attribute. Default is None. - - coords (:obj:`dict`): A mapping of coordinate name to coordinate - value to manually specify the :attr:`xarray.DataArray.coords` + + coords (:obj:`dict`): A mapping of coordinate name to coordinate + value to manually specify the :attr:`xarray.DataArray.coords` attribute. Default is None. - - **fixed_attrs: These keyword arguments are added to the + + **fixed_attrs: These keyword arguments are added to the :attr:`xarray.DataArray.attrs` attribute. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - function output with or without metadata. If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + function output with or without metadata. If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - - + + """ @wrapt.decorator - def func_wrapper(wrapped, instance, args, kwargs): + def func_wrapper(wrapped, instance, args, kwargs): do_meta = from_args(wrapped, ("meta",), *args, **kwargs)["meta"] - + if do_meta is None: do_meta = True - + if not xarray_enabled() or not do_meta: return wrapped(*args, **kwargs) - - argvars = from_args(wrapped, ("wrfin", "timeidx", "method", + + argvars = from_args(wrapped, ("wrfin", "timeidx", "method", "squeeze", "cache", "units", "meta", - "_key"), + "_key"), *args, **kwargs) - + wrfin = argvars["wrfin"] timeidx = argvars["timeidx"] units = argvars["units"] @@ -101,147 +101,145 @@ def copy_and_set_metadata(copy_varname=None, delete_attrs=None, name=None, _key = argvars["_key"] if cache is None: cache = {} - + # Note: can't modify nonlocal var if (callable(copy_varname)): _copy_varname = copy_varname(wrfin) else: _copy_varname = copy_varname - + # Extract the copy_from argument - var_to_copy = None if cache is None else cache.get(_copy_varname, + var_to_copy = None if cache is None else cache.get(_copy_varname, None) - if var_to_copy is None: - var_to_copy = extract_vars(wrfin, timeidx, (_copy_varname,), + var_to_copy = extract_vars(wrfin, timeidx, (_copy_varname,), method, squeeze, cache, meta=True, _key=_key)[_copy_varname] - + # Make a copy so we don't modify a user supplied cache - new_cache = dict(cache) + new_cache = dict(cache) new_cache[_copy_varname] = var_to_copy - + # Don't modify the original args/kargs. The args need to be a list # so it can be modified. new_args, cache_argloc = arg_location(wrapped, "cache", args, kwargs) new_args[cache_argloc] = new_cache - + result = wrapped(*new_args) - + outname = "" outdimnames = list() outcoords = OrderedDict() outattrs = OrderedDict() - + if copy_varname is not None: outname = var_to_copy.name - + if dimnames is not None: outdimnames = dimnames outcoords = coords else: outdimnames += var_to_copy.dims outcoords.update(var_to_copy.coords) - + outattrs.update(var_to_copy.attrs) - + if remove_dims is not None: for dimname in remove_dims: outdimnames.remove(dimname) - + try: del outcoords[dimname] except KeyError: pass - - + if name is not None: outname = name - + if units is not None: outattrs["units"] = units - + for argname, val in viewitems(fixed_attrs): outattrs[argname] = val - + if delete_attrs is not None: for attr in delete_attrs: try: del outattrs[attr] except KeyError: pass - + if isinstance(result, ma.MaskedArray): outattrs["_FillValue"] = result.fill_value outattrs["missing_value"] = result.fill_value - - return DataArray(result, name=outname, coords=outcoords, - dims=outdimnames, attrs=outattrs) - + + return DataArray(result, name=outname, coords=outcoords, + dims=outdimnames, attrs=outattrs) + return func_wrapper -def set_wind_metadata(copy_varname, name, description, - wind_ncvar=False, +def set_wind_metadata(copy_varname, name, description, + wind_ncvar=False, two_d=False, wspd_wdir=False): """A decorator that sets the metadata for a wrapped wind function's output. - + This is a special metadata decorator for working with wind functions, which - include wind extraction routines, uvmet routines, and + include wind extraction routines, uvmet routines, and wind speed / wind direction routines. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Args: - + copy_varname (:obj:`str`, optional): The NetCDF variable name to copy. Default is None. - - name (:obj:`str`): The name to use for the + + name (:obj:`str`): The name to use for the :attr:`xarray.DataArray.name` attribute. - - description (:obj:`str`): The description to use for the 'description' + + description (:obj:`str`): The description to use for the 'description' key in the :attr:`xarray.DataArray.attrs` attribute. - - wind_ncvar (:obj:`bool`, optional): Set to True when the wrapped - function is simply extracting a wind variable (U, V, W) from the - NetCDF file. Set to False for other types of wind algorithms + + wind_ncvar (:obj:`bool`, optional): Set to True when the wrapped + function is simply extracting a wind variable (U, V, W) from the + NetCDF file. Set to False for other types of wind algorithms (uvmet, wspd_wdir, etc). Default is False. - - two_d (:obj:`bool`, optional): Set to True if the wind field is + + two_d (:obj:`bool`, optional): Set to True if the wind field is two-dimensional. Set to False for a three-dimensional wind field. Default is False. - - wspd_wdir (:obj:`bool`): Set to True if the wrapped function is a + + wspd_wdir (:obj:`bool`): Set to True if the wrapped function is a wind speed / wind direction algorithm. Otherwise, set to False. Default is False. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - wind function output with or without metadata. If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + wind function output with or without metadata. If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - - + + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): do_meta = from_args(wrapped, ("meta",), *args, **kwargs)["meta"] - + if do_meta is None: do_meta = True - + if not xarray_enabled() or not do_meta: return wrapped(*args, **kwargs) - - argvars = from_args(wrapped, ("wrfin", "timeidx", "units", + + argvars = from_args(wrapped, ("wrfin", "timeidx", "units", "method", "squeeze", "ten_m", "cache", - "_key"), - *args, **kwargs) + "_key"), + *args, **kwargs) wrfin = argvars["wrfin"] timeidx = argvars["timeidx"] units = argvars["units"] @@ -252,35 +250,33 @@ def set_wind_metadata(copy_varname, name, description, _key = argvars["_key"] if cache is None: cache = {} - + if isinstance(copy_varname, either): _copy_varname = copy_varname(wrfin) else: _copy_varname = copy_varname - - copy_var = extract_vars(wrfin, timeidx, _copy_varname, - method, squeeze, cache, + + copy_var = extract_vars(wrfin, timeidx, _copy_varname, + method, squeeze, cache, meta=True, _key=_key)[_copy_varname] - + # Make a copy so we don't modify a user supplied cache - new_cache = dict(cache) + new_cache = dict(cache) new_cache[_copy_varname] = copy_var - + # Don't modify the original args/kargs. The args need to be a list # so it can be modified. new_args, cache_argloc = arg_location(wrapped, "cache", args, kwargs) new_args[cache_argloc] = new_cache - + result = wrapped(*new_args) - + outcoords = OrderedDict() outattrs = OrderedDict() - + outdimnames = list(copy_var.dims) - #outcoords.update(copy_var.coords) outattrs.update(copy_var.attrs) - - + if wind_ncvar: outcoords.update(copy_var.coords) elif not wspd_wdir: @@ -296,70 +292,70 @@ def set_wind_metadata(copy_varname, name, description, else: outdimnames.insert(0, "wspd_wdir") outattrs["MemoryOrder"] = "XY" - + outcoords["wspd_wdir"] = ["wspd", "wdir"] - - if units is not None: + + if units is not None: outattrs["units"] = units - - # xarray doesn't line up coordinate dimensions based on - # names, it just remembers the index it originally mapped to. - # So, need to rebuild the XLAT, XLONG, coordinates again since the + + # xarray doesn't line up coordinate dimensions based on + # names, it just remembers the index it originally mapped to. + # So, need to rebuild the XLAT, XLONG, coordinates again since the # leftmost index changed. if not wind_ncvar: - for key,dataarray in viewitems(copy_var.coords): + for key, dataarray in viewitems(copy_var.coords): if is_coordvar(key): outcoords[key] = dataarray.dims, to_np(dataarray) elif key == "XTIME": outcoords[key] = dataarray.dims, to_np(dataarray) elif key == "Time": outcoords[key] = to_np(dataarray) - + outname = name outattrs["description"] = description - - return DataArray(result, name=outname, coords=outcoords, - dims=outdimnames, attrs=outattrs) - + + return DataArray(result, name=outname, coords=outcoords, + dims=outdimnames, attrs=outattrs) + return func_wrapper def set_cape_metadata(is2d): """A decorator that sets the metadata for a wrapped CAPE function's output. - + This is a special metadata decorator for working with CAPE functions. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Args: - - is2d (:obj:`bool`): Set to True if the wrapped function is for a + + is2d (:obj:`bool`): Set to True if the wrapped function is for a two-dimensional CAPE routine. Set to False for a three-dimensional CAPE routine. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - CAPE function output with or without metadata. If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + CAPE function output with or without metadata. If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): do_meta = from_args(wrapped, ("meta",), *args, **kwargs)["meta"] - + if do_meta is None: do_meta = True - + if not xarray_enabled() or not do_meta: return wrapped(*args, **kwargs) - - argvars = from_args(wrapped, ("wrfin", "timeidx", "method", "squeeze", - "cache", "_key", "missing"), - *args, **kwargs) + + argvars = from_args(wrapped, ("wrfin", "timeidx", "method", "squeeze", + "cache", "_key", "missing"), + *args, **kwargs) wrfin = argvars["wrfin"] timeidx = argvars["timeidx"] method = argvars["method"] @@ -369,27 +365,27 @@ def set_cape_metadata(is2d): _key = argvars["_key"] if cache is None: cache = {} - + _copy_varname = "P" - copy_var = extract_vars(wrfin, timeidx, _copy_varname, method, squeeze, + copy_var = extract_vars(wrfin, timeidx, _copy_varname, method, squeeze, cache, meta=True, _key=_key)[_copy_varname] - + # Make a copy so we don't modify a user supplied cache - new_cache = dict(cache) + new_cache = dict(cache) new_cache[_copy_varname] = copy_var - + # Don't modify the original args/kargs. The args need to be a list # so it can be modified. new_args, cache_argloc = arg_location(wrapped, "cache", args, kwargs) new_args[cache_argloc] = new_cache - + result = wrapped(*new_args) - + outcoords = OrderedDict() outattrs = OrderedDict() outattrs.update(copy_var.attrs) outdimnames = [None] * result.ndim - + if is2d: # Right dims outdimnames[-2:] = copy_var.dims[-2:] @@ -410,69 +406,67 @@ def set_cape_metadata(is2d): outattrs["units"] = "J kg-1 ; J kg-1" outattrs["MemoryOrder"] = "XYZ" outname = "cape_3d" - + outattrs["_FillValue"] = missing outattrs["missing_value"] = missing - - # xarray doesn't line up coordinate dimensions based on - # names, it just remembers the index it originally mapped to. - # So, need to rebuild the XLAT, XLONG, coordinates again since the + # xarray doesn't line up coordinate dimensions based on + # names, it just remembers the index it originally mapped to. + # So, need to rebuild the XLAT, XLONG, coordinates again since the # leftmost index changed. - for key,dataarray in viewitems(copy_var.coords): + for key, dataarray in viewitems(copy_var.coords): if is_coordvar(key): outcoords[key] = dataarray.dims, to_np(dataarray) elif key == "XTIME": outcoords[key] = dataarray.dims, to_np(dataarray) elif key == "Time": outcoords[key] = to_np(dataarray) - + if is2d: outcoords["mcape_mcin_lcl_lfc"] = ["mcape", "mcin", "lcl", "lfc"] else: outcoords["cape_cin"] = ["cape", "cin"] - - - return DataArray(result, name=outname, coords=outcoords, - dims=outdimnames, attrs=outattrs) - + + return DataArray(result, name=outname, coords=outcoords, + dims=outdimnames, attrs=outattrs) + return func_wrapper def set_cloudfrac_metadata(): - """A decorator that sets the metadata for a wrapped cloud fraction + """A decorator that sets the metadata for a wrapped cloud fraction function's output. - - This is a special metadata decorator for working with cloud fraction + + This is a special metadata decorator for working with cloud fraction functions. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - cloud fraction function output with or without metadata. If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + cloud fraction function output with or without metadata. If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): do_meta = from_args(wrapped, ("meta",), *args, **kwargs)["meta"] - + if do_meta is None: do_meta = True - + if not xarray_enabled() or not do_meta: return wrapped(*args, **kwargs) - - argvars = from_args(wrapped, ("wrfin", "timeidx", "method", "squeeze", - "cache", "_key", "vert_type", - "low_thresh", "mid_thresh", - "high_thresh", "missing"), + + argvars = from_args(wrapped, ("wrfin", "timeidx", "method", "squeeze", + "cache", "_key", "vert_type", + "low_thresh", "mid_thresh", + "high_thresh", "missing"), *args, **kwargs) wrfin = argvars["wrfin"] timeidx = argvars["timeidx"] @@ -485,34 +479,34 @@ def set_cloudfrac_metadata(): mid_thresh = argvars["mid_thresh"] high_thresh = argvars["high_thresh"] missing = argvars["missing"] - + if cache is None: cache = {} - + _copy_varname = "P" - copy_var = extract_vars(wrfin, timeidx, _copy_varname, method, squeeze, + copy_var = extract_vars(wrfin, timeidx, _copy_varname, method, squeeze, cache, meta=True, _key=_key)[_copy_varname] - + # Make a copy so we don't modify a user supplied cache - new_cache = dict(cache) + new_cache = dict(cache) new_cache[_copy_varname] = copy_var - + # Don't modify the original args/kargs. The args need to be a list # so it can be modified. new_args, cache_argloc = arg_location(wrapped, "cache", args, kwargs) new_args[cache_argloc] = new_cache - + result = wrapped(*new_args) - + outcoords = OrderedDict() outattrs = OrderedDict() outattrs.update(copy_var.attrs) outdimnames = [None] * result.ndim - + # For printing units - unitstr = ("Pa" if vert_type.lower() == "pres" + unitstr = ("Pa" if vert_type.lower() == "pres" or vert_type.lower() == "pressure" else "m") - + # For setting the threholds in metdata if vert_type.lower() == "pres" or vert_type.lower() == "pressure": _low_thresh = 97000. if low_thresh is None else low_thresh @@ -522,7 +516,7 @@ def set_cloudfrac_metadata(): _low_thresh = 300. if low_thresh is None else low_thresh _mid_thresh = 2000. if mid_thresh is None else mid_thresh _high_thresh = 6000. if high_thresh is None else high_thresh - + # Right dims outdimnames[-2:] = copy_var.dims[-2:] # Left dims @@ -538,156 +532,156 @@ def set_cloudfrac_metadata(): outattrs["missing_value"] = missing outname = "cloudfrac" - # xarray doesn't line up coordinate dimensions based on - # names, it just remembers the index it originally mapped to. - # So, need to rebuild the XLAT, XLONG, coordinates again since the + # xarray doesn't line up coordinate dimensions based on + # names, it just remembers the index it originally mapped to. + # So, need to rebuild the XLAT, XLONG, coordinates again since the # leftmost index changed. - for key,dataarray in viewitems(copy_var.coords): + for key, dataarray in viewitems(copy_var.coords): if is_coordvar(key): outcoords[key] = dataarray.dims, to_np(dataarray) elif key == "XTIME": outcoords[key] = dataarray.dims, to_np(dataarray) elif key == "Time": outcoords[key] = to_np(dataarray) - + outcoords["low_mid_high"] = ["low", "mid", "high"] - - return DataArray(result, name=outname, coords=outcoords, - dims=outdimnames, attrs=outattrs) - + + return DataArray(result, name=outname, coords=outcoords, + dims=outdimnames, attrs=outattrs) + return func_wrapper def set_latlon_metadata(xy=False): - """A decorator that sets the metadata for a wrapped latlon function's + """A decorator that sets the metadata for a wrapped latlon function's output. - + This is a special metadata decorator for working with latlon functions. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Args: - - xy (:obj:`bool`, optional): Set to True if the wrapped function returns - xy values (ll_to_xy). Set to False if the wrapped function returns + + xy (:obj:`bool`, optional): Set to True if the wrapped function returns + xy values (ll_to_xy). Set to False if the wrapped function returns latlon values (xy_to_ll). Default is False. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - latlon function output with or without metadata. If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + latlon function output with or without metadata. If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): - + argvars = from_args(wrapped, ("wrfin", "meta"), *args, **kwargs) # If it's a mapping, then this is handled as a special case in g_latlon do_meta = (not is_mapping(argvars["wrfin"]) and argvars["meta"]) if do_meta is None: do_meta = True - + if not xarray_enabled() or not do_meta: return wrapped(*args, **kwargs) - + # Set squeeze to False. Squeeze will be handled in here - new_args, squeeze_argloc = arg_location(wrapped, "squeeze", args, + new_args, squeeze_argloc = arg_location(wrapped, "squeeze", args, kwargs) new_args[squeeze_argloc] = False - + result = wrapped(*new_args) - + # Want to preserve the input coordinate pair in metadata if result.ndim == 1: result = result[:, np.newaxis] - + argnames = ["x", "y"] if not xy else ["latitude", "longitude"] argnames.append("squeeze") outname = "latlon" if not xy else "xy" - + if result.ndim == 2: dimnames = (["lat_lon", "idx"] if not xy else ["x_y", "idx"]) else: - dimnames = (["lat_lon", "domain_idx", "idx"] if not xy + dimnames = (["lat_lon", "domain_idx", "idx"] if not xy else ["x_y", "domain_idx", "idx"]) - + argvars = from_args(wrapped, argnames, *args, **kwargs) - + var1 = argvars[argnames[0]] var2 = argvars[argnames[1]] squeeze = argvars["squeeze"] - + arr1 = np.asarray(var1).ravel() arr2 = np.asarray(var2).ravel() - + coords = {} if not xy: - coords["xy_coord"] = (dimnames[-1], [CoordPair(x=x[0], y=x[1]) - for x in zip(arr1, arr2)]) + coords["xy_coord"] = (dimnames[-1], [CoordPair(x=x[0], y=x[1]) + for x in zip(arr1, arr2)]) coords[dimnames[0]] = ["lat", "lon"] else: - coords["latlon_coord"] = (dimnames[-1], [CoordPair(lat=x[0], - lon=x[1]) - for x in zip(arr1, arr2)]) + coords["latlon_coord"] = (dimnames[-1], + [CoordPair(lat=x[0], lon=x[1]) + for x in zip(arr1, arr2)]) coords[dimnames[0]] = ["x", "y"] - + da = DataArray(result, name=outname, dims=dimnames, coords=coords) - + if squeeze: da = da.squeeze() - + return da - + return func_wrapper def set_height_metadata(geopt=False, stag=False): - """A decorator that sets the metadata for a wrapped height function's + """A decorator that sets the metadata for a wrapped height function's output. - + This is a special metadata decorator for working with height functions. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Args: - - geopt (:obj:`bool`, optional): Set to True if the wrapped function - returns geopotential. Set to True if the wrapped function + + geopt (:obj:`bool`, optional): Set to True if the wrapped function + returns geopotential. Set to True if the wrapped function returns geopotential height. Default is False. - - stag (:obj:`bool`, optional): Set to True to use the vertical + + stag (:obj:`bool`, optional): Set to True to use the vertical staggered grid, rather than the mass grid. Default is False. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - height function output with or without metadata. If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + height function output with or without metadata. If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): do_meta = from_args(wrapped, ("meta",), *args, **kwargs)["meta"] - + if do_meta is None: do_meta = True - + if not xarray_enabled() or not do_meta: return wrapped(*args, **kwargs) - - argvars = from_args(wrapped, ("wrfin", "timeidx", "method", - "squeeze", "units", "msl", "cache", - "_key"), - *args, **kwargs) + + argvars = from_args(wrapped, ("wrfin", "timeidx", "method", + "squeeze", "units", "msl", "cache", + "_key"), + *args, **kwargs) wrfin = argvars["wrfin"] timeidx = argvars["timeidx"] units = argvars["units"] @@ -696,10 +690,10 @@ def set_height_metadata(geopt=False, stag=False): msl = argvars["msl"] cache = argvars["cache"] _key = argvars["_key"] - + if cache is None: cache = {} - + is_met_em = False # For height, either copy the met_em GHT variable or copy and modify # pressure (which has the same dims as destaggered height) @@ -707,32 +701,32 @@ def set_height_metadata(geopt=False, stag=False): ht_metadata_varname = either("P", "GHT")(wrfin) else: ht_metadata_varname = either("PH", "GHT")(wrfin) - + if ht_metadata_varname == "GHT": is_met_em = True - - ht_var = extract_vars(wrfin, timeidx, ht_metadata_varname, + + ht_var = extract_vars(wrfin, timeidx, ht_metadata_varname, method, squeeze, cache, meta=True, _key=_key) ht_metadata_var = ht_var[ht_metadata_varname] - + # Make a copy so we don't modify a user supplied cache - new_cache = dict(cache) + new_cache = dict(cache) new_cache[ht_metadata_varname] = ht_metadata_var - + # Don't modify the original args/kargs. The args need to be a list # so it can be modified. new_args, cache_argloc = arg_location(wrapped, "cache", args, kwargs) new_args[cache_argloc] = new_cache - + result = wrapped(*new_args) - + outcoords = OrderedDict() outattrs = OrderedDict() outdimnames = list(ht_metadata_var.dims) outcoords.update(ht_metadata_var.coords) outattrs.update(ht_metadata_var.attrs) - + if geopt: outname = "geopt" outattrs["units"] = "m2 s-2" @@ -742,7 +736,7 @@ def set_height_metadata(geopt=False, stag=False): outattrs["description"] = ("geopotential (vertically " "staggered grid)") else: - outname = "height" if msl else "height_agl" + outname = "height" if msl else "height_agl" outattrs["units"] = units height_type = "MSL" if msl else "AGL" if not stag or is_met_em: @@ -750,88 +744,88 @@ def set_height_metadata(geopt=False, stag=False): "(mass grid)".format(height_type)) else: outattrs["description"] = ("model height - [{}] (vertically " - "staggered grid)".format(height_type)) - - - return DataArray(result, name=outname, - dims=outdimnames, coords=outcoords, attrs=outattrs) + "staggered grid)".format( + height_type)) + + return DataArray(result, name=outname, dims=outdimnames, + coords=outcoords, attrs=outattrs) + return func_wrapper -def _set_horiz_meta(wrapped, instance, args, kwargs): - """A decorator implementation that sets the metadata for a wrapped +def _set_horiz_meta(wrapped, instance, args, kwargs): + """A decorator implementation that sets the metadata for a wrapped horizontal interpolation function. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Args: - - wrapped: The wrapped function which in turns needs to be called by your + + wrapped: The wrapped function which in turns needs to be called by your wrapper function. - - instance: The object to which the wrapped function was bound when it + + instance: The object to which the wrapped function was bound when it was called. - - args: The list of positional arguments supplied when the decorated + + args: The list of positional arguments supplied when the decorated function was called. - - kwargs: The dictionary of keyword arguments supplied when the decorated + + kwargs: The dictionary of keyword arguments supplied when the decorated function was called. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - horiztontal interpolation function output with or without metadata. - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + horiztontal interpolation function output with or without metadata. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + See Also: - + :mod:`wrapt` - - """ - argvars = from_args(wrapped, ("field3d", "vert", "desiredlev", - "missing", "squeeze"), - *args, **kwargs) - + + """ + argvars = from_args(wrapped, ("field3d", "vert", "desiredlev", + "missing", "squeeze"), + *args, **kwargs) + field3d = argvars["field3d"] z = argvars["vert"] desiredlev = argvars["desiredlev"] _desiredlev = np.asarray(desiredlev) missingval = argvars["missing"] squeeze = argvars["squeeze"] - + result = wrapped(*args, **kwargs) - + levsare2d = _desiredlev.ndim >= 2 multiproduct = field3d.ndim - z.ndim == 1 - + # Defaults, in case the data isn't a DataArray outname = None outdimnames = None outcoords = None outattrs = OrderedDict() - + # Get the vertical level units vert_units = None if isinstance(z, DataArray): vert_units = z.attrs.get("units", None) - - + if isinstance(field3d, DataArray): outcoords = OrderedDict() outdimnames = list(field3d.dims) outcoords.update(field3d.coords) - + del outdimnames[-3] - + try: del outcoords[field3d.dims[-3]] except KeyError: - pass # xarray 0.9 - + pass # xarray 0.9 + if not levsare2d: outdimnames.insert(-2, "level") if _desiredlev.ndim == 0: @@ -847,71 +841,71 @@ def _set_horiz_meta(wrapped, instance, args, kwargs): else: d = field3d.dims[0:-3] + field3d.dims[-2:] outcoords["level"] = d, _desiredlev[:] - + outattrs.update(field3d.attrs) outname = "{0}_interp".format(field3d.name) - + else: outname = "field3d_interp" - + outattrs["missing_value"] = missingval outattrs["_FillValue"] = missingval outattrs["vert_units"] = vert_units - + for key in ("MemoryOrder", "description"): try: del outattrs[key] except KeyError: pass - - da = DataArray(result, name=outname, dims=outdimnames, + + da = DataArray(result, name=outname, dims=outdimnames, coords=outcoords, attrs=outattrs) - + return da.squeeze() if squeeze else da def _set_cross_meta(wrapped, instance, args, kwargs): """A decorator implementation that sets the metadata for a wrapped cross \ section interpolation function. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Args: - - wrapped: The wrapped function which in turns needs to be called by your + + wrapped: The wrapped function which in turns needs to be called by your wrapper function. - - instance: The object to which the wrapped function was bound when it + + instance: The object to which the wrapped function was bound when it was called. - - args: The list of positional arguments supplied when the decorated + + args: The list of positional arguments supplied when the decorated function was called. - - kwargs: The dictionary of keyword arguments supplied when the decorated + + kwargs: The dictionary of keyword arguments supplied when the decorated function was called. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - cross section interpolation function output with or without metadata. - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + cross section interpolation function output with or without metadata. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + See Also: - + :mod:`wrapt` - - """ - argvars = from_args(wrapped, ("field3d", "vert", "levels", - "latlon", "missing", + + """ + argvars = from_args(wrapped, ("field3d", "vert", "levels", + "latlon", "missing", "wrfin", "timeidx", "stagger", "projection", "ll_point", "pivot_point", "angle", "start_point", "end_point", "autolevels", - "cache"), - *args, **kwargs) - + "cache"), + *args, **kwargs) + field3d = argvars["field3d"] z = argvars["vert"] levels = argvars["levels"] @@ -928,23 +922,23 @@ def _set_cross_meta(wrapped, instance, args, kwargs): end_point = argvars["end_point"] autolevels = argvars["autolevels"] cache = argvars["cache"] - + start_point_xy = None end_point_xy = None pivot_point_xy = None - - if (inc_latlon is True or is_latlon_pair(start_point) or - is_latlon_pair(pivot_point)): - + + if (inc_latlon is True or is_latlon_pair(start_point) or + is_latlon_pair(pivot_point)): + if wrfin is not None: is_moving = is_moving_domain(wrfin) else: is_moving = False - + if timeidx is None: if wrfin is not None: - # Moving nests aren't supported with ALL_TIMES because the - # domain could move outside of the line, which causes + # Moving nests aren't supported with ALL_TIMES because the + # domain could move outside of the line, which causes # crashes or different line lengths. if is_moving: raise ValueError("Requesting all times with a moving nest " @@ -956,7 +950,7 @@ def _set_cross_meta(wrapped, instance, args, kwargs): else: # Domain not moving, just use 0 _timeidx = 0 - + # If using grid coordinates, then don't care about lat/lon # coordinates. Just use 0. else: @@ -965,36 +959,37 @@ def _set_cross_meta(wrapped, instance, args, kwargs): if is_moving: _timeidx = timeidx else: - # When using non-moving nests, set the time to 0 + # When using non-moving nests, set the time to 0 # to avoid problems downstream _timeidx = 0 - + if pivot_point is not None: if pivot_point.lat is not None and pivot_point.lon is not None: - xy_coords = to_xy_coords(pivot_point, wrfin, _timeidx, + xy_coords = to_xy_coords(pivot_point, wrfin, _timeidx, stagger, projection, ll_point) pivot_point_xy = (xy_coords.x, xy_coords.y) else: pivot_point_xy = (pivot_point.x, pivot_point.y) - + if start_point is not None and end_point is not None: if start_point.lat is not None and start_point.lon is not None: - xy_coords = to_xy_coords(start_point, wrfin, _timeidx, + xy_coords = to_xy_coords(start_point, wrfin, _timeidx, stagger, projection, ll_point) start_point_xy = (xy_coords.x, xy_coords.y) else: start_point_xy = (start_point.x, start_point.y) - + if end_point.lat is not None and end_point.lon is not None: - xy_coords = to_xy_coords(end_point, wrfin, _timeidx, + xy_coords = to_xy_coords(end_point, wrfin, _timeidx, stagger, projection, ll_point) end_point_xy = (xy_coords.x, xy_coords.y) else: end_point_xy = (end_point.x, end_point.y) - + xy, var2dz, z_var2d = get_xy_z_params(to_np(z), pivot_point_xy, angle, - start_point_xy, end_point_xy, levels, autolevels) - + start_point_xy, end_point_xy, + levels, autolevels) + # Make a copy so we don't modify a user supplied cache if cache is not None: new_cache = dict(cache) @@ -1003,170 +998,167 @@ def _set_cross_meta(wrapped, instance, args, kwargs): new_cache["xy"] = xy new_cache["var2dz"] = var2dz new_cache["z_var2d"] = z_var2d - + # Don't modify the original args/kargs. The args need to be a list # so it can be modified. new_args, cache_argloc = arg_location(wrapped, "cache", args, kwargs) new_args[cache_argloc] = new_cache - + result = wrapped(*new_args) - + # Defaults, in case the data isn't a DataArray outname = None outdimnames = None outcoords = None outattrs = OrderedDict() - + # Use XY to set the cross-section metadata - st_x = xy[0,0] - st_y = xy[0,1] - ed_x = xy[-1,0] - ed_y = xy[-1,1] - + st_x = xy[0, 0] + st_y = xy[0, 1] + ed_x = xy[-1, 0] + ed_y = xy[-1, 1] + cross_str = "({0}, {1}) to ({2}, {3})".format(st_x, st_y, ed_x, ed_y) if angle is not None: cross_str += " ; center={0} ; angle={1}".format(pivot_point, angle) - + if isinstance(field3d, DataArray): outcoords = OrderedDict() outdimnames = list(field3d.dims) outcoords.update(field3d.coords) - for i in py3range(-3,0,1): + for i in py3range(-3, 0, 1): outdimnames.remove(field3d.dims[i]) try: del outcoords[field3d.dims[i]] except KeyError: - pass # Xarray 0.9 - - - # Delete any lat,lon coords + pass # Xarray 0.9 + + # Delete any lat,lon coords delkeys = [key for key in viewkeys(outcoords) if is_coordvar(key)] for key in delkeys: del outcoords[key] - + outdimnames.append("vertical") outdimnames.append("cross_line_idx") outattrs.update(field3d.attrs) - + outname = "{0}_cross".format(field3d.name) - + for key in ("MemoryOrder",): try: del outattrs[key] except KeyError: pass - + # Interpolate to get the lat/lon coords, if desired if inc_latlon: latcoordname, loncoordname = latlon_coordvars(field3d.coords) - + if latcoordname is not None and loncoordname is not None: latcoord = field3d.coords[latcoordname] loncoord = field3d.coords[loncoordname] - + if latcoord.ndim == 2: lats = _interpline(latcoord, xy) lons = _interpline(loncoord, xy) - - outcoords["xy_loc"] = ("cross_line_idx", - np.asarray(tuple( - CoordPair(x=xy[i,0], y=xy[i,1], - lat=lats[i], lon=lons[i]) - for i in py3range(xy.shape[-2]))) - ) + + outcoords["xy_loc"] = ("cross_line_idx", np.asarray(tuple( + CoordPair(x=xy[i, 0], y=xy[i, 1], + lat=lats[i], lon=lons[i]) + for i in py3range(xy.shape[-2])))) # Moving domain else: extra_dims = latcoord.shape[0:-2] outdims = extra_dims + xy.shape[-2:-1] - + latlon_loc = np.empty(outdims, np.object_) for left_dims in iter_left_indexes(extra_dims): idxs = left_dims + (slice(None),) lats = _interpline(latcoord[idxs], xy) lons = _interpline(loncoord[idxs], xy) - - latlon_loc[idxs] = np.asarray(tuple( - CoordPair(x=xy[i,0], y=xy[i,1], - lat=lats[i], lon=lons[i]) - for i in py3range(xy.shape[-2])) - )[:] - - + + latlon_loc[idxs] = np.asarray( + tuple(CoordPair( + x=xy[i, 0], y=xy[i, 1], + lat=lats[i], lon=lons[i]) + for i in py3range(xy.shape[-2])))[:] + extra_dimnames = latcoord.dims[0:-2] loc_dimnames = extra_dimnames + ("cross_line_idx",) outcoords["xy_loc"] = (loc_dimnames, latlon_loc) - + else: warnings.warn("'latlon' is set to True, but 'field3d' " - " contains no coordinate information") - outcoords["xy_loc"] = ("cross_line_idx", np.asarray(tuple( - CoordPair(xy[i,0], xy[i,1]) - for i in py3range(xy.shape[-2])))) - - else: - outcoords["xy_loc"] = ("cross_line_idx", np.asarray(tuple( - CoordPair(xy[i,0], xy[i,1]) - for i in py3range(xy.shape[-2])))) - + "contains no coordinate information") + outcoords["xy_loc"] = ("cross_line_idx", + np.asarray(tuple( + CoordPair(xy[i, 0], xy[i, 1]) + for i in py3range(xy.shape[-2])))) + + else: + outcoords["xy_loc"] = ("cross_line_idx", + np.asarray(tuple( + CoordPair(xy[i, 0], xy[i, 1]) + for i in py3range(xy.shape[-2])))) + outcoords["vertical"] = z_var2d[:] - + else: if inc_latlon: warnings.warn("'latlon' is set to True, but 'field3d' is " "not of type xarray.DataArray and contains no " "coordinate information") outname = "field3d_cross" - + outattrs["orientation"] = cross_str outattrs["missing_value"] = missingval outattrs["_FillValue"] = missingval - - return DataArray(result, name=outname, dims=outdimnames, - coords=outcoords, attrs=outattrs) - + + return DataArray(result, name=outname, dims=outdimnames, + coords=outcoords, attrs=outattrs) + def _set_line_meta(wrapped, instance, args, kwargs): - """A decorator implementation that sets the metadata for a wrapped line + """A decorator implementation that sets the metadata for a wrapped line interpolation function. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Args: - - wrapped: The wrapped function which in turns needs to be called by your + + wrapped: The wrapped function which in turns needs to be called by your wrapper function. - - instance: The object to which the wrapped function was bound when it + + instance: The object to which the wrapped function was bound when it was called. - - args: The list of positional arguments supplied when the decorated + + args: The list of positional arguments supplied when the decorated function was called. - - kwargs: The dictionary of keyword arguments supplied when the decorated + + kwargs: The dictionary of keyword arguments supplied when the decorated function was called. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - line interpolation function output with or without metadata. - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + line interpolation function output with or without metadata. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + See Also: - + :mod:`wrapt` - - """ - argvars = from_args(wrapped, ("field2d", - "wrfin", "timeidx", "stagger", "projection", - "ll_point", "pivot_point", "angle", - "start_point", "end_point", "latlon", - "cache"), - *args, **kwargs) - + + """ + argvars = from_args(wrapped, ("field2d", "wrfin", "timeidx", "stagger", + "projection", "ll_point", "pivot_point", + "angle", "start_point", "end_point", + "latlon", "cache"), + *args, **kwargs) + field2d = argvars["field2d"] wrfin = argvars["wrfin"] timeidx = argvars["timeidx"] @@ -1179,26 +1171,26 @@ def _set_line_meta(wrapped, instance, args, kwargs): end_point = argvars["end_point"] inc_latlon = argvars["latlon"] cache = argvars["cache"] - + if cache is None: cache = {} - + start_point_xy = None end_point_xy = None pivot_point_xy = None - - if (inc_latlon is True or is_latlon_pair(start_point) or - is_latlon_pair(pivot_point)): - + + if (inc_latlon is True or is_latlon_pair(start_point) or + is_latlon_pair(pivot_point)): + if wrfin is not None: is_moving = is_moving_domain(wrfin) else: is_moving = False - + if timeidx is None: if wrfin is not None: - # Moving nests aren't supported with ALL_TIMES because the - # domain could move outside of the line, which causes + # Moving nests aren't supported with ALL_TIMES because the + # domain could move outside of the line, which causes # crashes or different line lengths. if is_moving: raise ValueError("Requesting all times with a moving nest " @@ -1210,7 +1202,7 @@ def _set_line_meta(wrapped, instance, args, kwargs): else: # Domain not moving, just use 0 _timeidx = 0 - + # If using grid coordinates, then don't care about lat/lon # coordinates. Just use 0. else: @@ -1219,534 +1211,521 @@ def _set_line_meta(wrapped, instance, args, kwargs): if is_moving: _timeidx = timeidx else: - # When using non-moving nests, set the time to 0 + # When using non-moving nests, set the time to 0 # to avoid problems downstream _timeidx = 0 - - + if pivot_point is not None: if pivot_point.lat is not None and pivot_point.lon is not None: - xy_coords = to_xy_coords(pivot_point, wrfin, _timeidx, + xy_coords = to_xy_coords(pivot_point, wrfin, _timeidx, stagger, projection, ll_point) pivot_point_xy = (xy_coords.x, xy_coords.y) else: - pivot_point_xy = (pivot_point.x, pivot_point.y) - - + pivot_point_xy = (pivot_point.x, pivot_point.y) + if start_point is not None and end_point is not None: if start_point.lat is not None and start_point.lon is not None: - xy_coords = to_xy_coords(start_point, wrfin, _timeidx, + xy_coords = to_xy_coords(start_point, wrfin, _timeidx, stagger, projection, ll_point) start_point_xy = (xy_coords.x, xy_coords.y) else: start_point_xy = (start_point.x, start_point.y) - + if end_point.lat is not None and end_point.lon is not None: - xy_coords = to_xy_coords(end_point, wrfin, _timeidx, + xy_coords = to_xy_coords(end_point, wrfin, _timeidx, stagger, projection, ll_point) end_point_xy = (xy_coords.x, xy_coords.y) else: end_point_xy = (end_point.x, end_point.y) - xy = get_xy(field2d, pivot_point_xy, angle, start_point_xy, end_point_xy) - + # Make a copy so we don't modify a user supplied cache - new_cache = dict(cache) + new_cache = dict(cache) new_cache["xy"] = xy - + # Don't modify the original args/kargs. The args need to be a list # so it can be modified. new_args, cache_argloc = arg_location(wrapped, "cache", args, kwargs) new_args[cache_argloc] = new_cache - + result = wrapped(*new_args) - + # Defaults, in case the data isn't a DataArray outname = None outdimnames = None outcoords = None outattrs = OrderedDict() - + # Use XY to set the cross-section metadata - st_x = xy[0,0] - st_y = xy[0,1] - ed_x = xy[-1,0] - ed_y = xy[-1,1] - + st_x = xy[0, 0] + st_y = xy[0, 1] + ed_x = xy[-1, 0] + ed_y = xy[-1, 1] + cross_str = "({0}, {1}) to ({2}, {3})".format(st_x, st_y, ed_x, ed_y) if angle is not None: - cross_str += " ; center={0} ; angle={1}".format(pivot_point, - angle) - + cross_str += " ; center={0} ; angle={1}".format(pivot_point, angle) + if isinstance(field2d, DataArray): outcoords = OrderedDict() outdimnames = list(field2d.dims) outcoords.update(field2d.coords) - for i in py3range(-2,0,1): + for i in py3range(-2, 0, 1): outdimnames.remove(field2d.dims[i]) try: del outcoords[field2d.dims[i]] except KeyError: - pass # xarray 0.9 - + pass # xarray 0.9 + # Delete any lat,lon coords delkeys = [key for key in viewkeys(outcoords) if is_coordvar(key)] for key in delkeys: del outcoords[key] - + outdimnames.append("line_idx") outattrs.update(field2d.attrs) - + outname = "{0}_line".format(field2d.name) - + for key in ("MemoryOrder",): try: del outattrs[key] except KeyError: pass - + # Interpolate to get the lat/lon coords, if desired if inc_latlon: latcoordname, loncoordname = latlon_coordvars(field2d.coords) - + if latcoordname is not None and loncoordname is not None: latcoord = field2d.coords[latcoordname] loncoord = field2d.coords[loncoordname] - + if latcoord.ndim == 2: lats = _interpline(latcoord, xy) lons = _interpline(loncoord, xy) - - outcoords["xy_loc"] = ("line_idx", - np.asarray(tuple( - CoordPair(x=xy[i,0], y=xy[i,1], - lat=lats[i], lon=lons[i]) - for i in py3range(xy.shape[-2]))) - ) - - # Moving domain + + outcoords["xy_loc"] = ("line_idx", np.asarray(tuple( + CoordPair(x=xy[i, 0], y=xy[i, 1], + lat=lats[i], lon=lons[i]) + for i in py3range(xy.shape[-2])))) + + # Moving domain else: extra_dims = latcoord.shape[0:-2] outdims = extra_dims + xy.shape[-2:-1] - + latlon_loc = np.empty(outdims, np.object_) for left_dims in iter_left_indexes(extra_dims): idxs = left_dims + (slice(None),) lats = _interpline(latcoord[idxs], xy) lons = _interpline(loncoord[idxs], xy) - + latlon_loc[idxs] = np.asarray(tuple( - CoordPair(x=xy[i,0], y=xy[i,1], - lat=lats[i], lon=lons[i]) - for i in py3range(xy.shape[-2])) - )[:] - - + CoordPair(x=xy[i, 0], y=xy[i, 1], + lat=lats[i], lon=lons[i]) + for i in py3range(xy.shape[-2])))[:] + extra_dimnames = latcoord.dims[0:-2] loc_dimnames = extra_dimnames + ("line_idx",) outcoords["xy_loc"] = (loc_dimnames, latlon_loc) - + else: warnings.warn("'latlon' is set to True, but 'field2d' " "contains no coordinate information") - outcoords["xy_loc"] = ("line_idx", np.asarray(tuple( - CoordPair(xy[i,0], xy[i,1]) - for i in py3range(xy.shape[-2])))) - - else: - outcoords["xy_loc"] = ("line_idx", np.asarray(tuple( - CoordPair(xy[i,0], xy[i,1]) - for i in py3range(xy.shape[-2])))) - + outcoords["xy_loc"] = ("line_idx", np.asarray( + tuple(CoordPair(xy[i, 0], xy[i, 1]) + for i in py3range(xy.shape[-2])))) + else: + outcoords["xy_loc"] = ("line_idx", np.asarray( + tuple(CoordPair(xy[i, 0], xy[i, 1]) + for i in py3range(xy.shape[-2])))) else: if inc_latlon: warnings.warn("'latlon' is set to True, but 'field2d' is " "not of type xarray.DataArray and contains no " "coordinate information") outname = "field2d_line" - + outattrs["orientation"] = cross_str - - return DataArray(result, name=outname, dims=outdimnames, - coords=outcoords, attrs=outattrs) - + + return DataArray(result, name=outname, dims=outdimnames, + coords=outcoords, attrs=outattrs) + def _set_vinterp_meta(wrapped, instance, args, kwargs): - """A decorator implementation that sets the metadata for a wrapped + """A decorator implementation that sets the metadata for a wrapped vertical coordinate interpolation function. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Args: - - wrapped: The wrapped function which in turns needs to be called by your + + wrapped: The wrapped function which in turns needs to be called by your wrapper function. - - instance: The object to which the wrapped function was bound when it + + instance: The object to which the wrapped function was bound when it was called. - - args: The list of positional arguments supplied when the decorated + + args: The list of positional arguments supplied when the decorated function was called. - - kwargs: The dictionary of keyword arguments supplied when the decorated + + kwargs: The dictionary of keyword arguments supplied when the decorated function was called. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - vertical coordinate interpolation function output with or without - metadata. - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + vertical coordinate interpolation function output with or without + metadata. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + See Also: - + :mod:`wrapt` - - """ - argvars = from_args(wrapped, ("wrfin", "field", "vert_coord", + + """ + argvars = from_args(wrapped, ("wrfin", "field", "vert_coord", "interp_levels", "extrapolate", "field_type", "log_p", "timeidx", "method", "squeeze", - "cache"), - *args, **kwargs) - + "cache"), + *args, **kwargs) + field = argvars["field"] vert_coord = argvars["vert_coord"] interp_levels = argvars["interp_levels"] field_type = argvars["field_type"] - + result = wrapped(*args, **kwargs) - + # Defaults, in case the data isn't a DataArray outname = None outdimnames = None outcoords = None outattrs = OrderedDict() - - + if isinstance(field, DataArray): outcoords = OrderedDict() outdimnames = list(field.dims) outcoords.update(field.coords) - + outdimnames.remove(field.dims[-3]) try: del outcoords[field.dims[-3]] except KeyError: - pass # xarray 0.9 - + pass # xarray 0.9 + outdimnames.insert(-2, "interp_level") outcoords["interp_level"] = interp_levels outattrs.update(field.attrs) - - + outname = field.name - + else: outname = field_type - + outattrs["vert_interp_type"] = vert_coord - - return DataArray(result, name=outname, dims=outdimnames, - coords=outcoords, attrs=outattrs) - - + + return DataArray(result, name=outname, dims=outdimnames, + coords=outcoords, attrs=outattrs) + + def _set_2dxy_meta(wrapped, instance, args, kwargs): - """A decorator implementation that sets the metadata for a wrapped line + """A decorator implementation that sets the metadata for a wrapped line cross section interpolation function. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Args: - - wrapped: The wrapped function which in turns needs to be called by your + + wrapped: The wrapped function which in turns needs to be called by your wrapper function. - - instance: The object to which the wrapped function was bound when it + + instance: The object to which the wrapped function was bound when it was called. - - args: The list of positional arguments supplied when the decorated + + args: The list of positional arguments supplied when the decorated function was called. - - kwargs: The dictionary of keyword arguments supplied when the decorated + + kwargs: The dictionary of keyword arguments supplied when the decorated function was called. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - line cross section interpolation function output with or without - metadata. - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + line cross section interpolation function output with or without + metadata. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + See Also: - + :mod:`wrapt` - - """ - argvars = from_args(wrapped, ("field3d", "xy"), *args, **kwargs) - + + """ + argvars = from_args(wrapped, ("field3d", "xy"), *args, **kwargs) + field3d = argvars["field3d"] xy = to_np(argvars["xy"]) - + result = wrapped(*args, **kwargs) - + # Use XY to set the cross-section metadata - st_x = xy[0,0] - st_y = xy[0,1] - ed_x = xy[-1,0] - ed_y = xy[-1,1] - - cross_str = "({0},{1}) to ({2},{3})".format(st_x, st_y, - ed_x, ed_y) - + st_x = xy[0, 0] + st_y = xy[0, 1] + ed_x = xy[-1, 0] + ed_y = xy[-1, 1] + + cross_str = "({0},{1}) to ({2},{3})".format(st_x, st_y, ed_x, ed_y) + outname = None outdimnames = None outcoords = None outattrs = OrderedDict() - + # Dims are (...,xy,z) if isinstance(field3d, DataArray): outcoords = OrderedDict() outdimnames = list(field3d.dims) outcoords.update(field3d.coords) - - for i in py3range(-2,0,1): + + for i in py3range(-2, 0, 1): try: del outcoords[field3d.dims[i]] except KeyError: - pass # xarray 0.9 + pass # xarray 0.9 outdimnames.remove(field3d.dims[i]) - + # Need to remove XLAT, XLONG... - delkeys = (key for key,arr in viewitems(field3d.coords) + delkeys = (key for key, arr in viewitems(field3d.coords) if arr.ndim > 1) - + for key in delkeys: del outcoords[key] - + outdimnames.append("line_idx") - #outattrs.update(field3d.attrs) - + desc = field3d.attrs.get("description", None) if desc is not None: outattrs["description"] = desc - + units = field3d.attrs.get("units", None) if units is not None: outattrs["units"] = units - + outname = "{0}_2dxy".format(field3d.name) - - outcoords["xy_loc"] = ("line_idx", [CoordPair(xy[i,0], xy[i,1]) - for i in py3range(xy.shape[-2])]) - + + outcoords["xy_loc"] = ("line_idx", + [CoordPair(xy[i, 0], xy[i, 1]) + for i in py3range(xy.shape[-2])]) + for key in ("MemoryOrder",): try: del outattrs[key] except KeyError: pass - + else: outname = "field3d_2dxy" - + outattrs["orientation"] = cross_str - - return DataArray(result, name=outname, dims=outdimnames, - coords=outcoords, attrs=outattrs) + + return DataArray(result, name=outname, dims=outdimnames, + coords=outcoords, attrs=outattrs) def _set_1d_meta(wrapped, instance, args, kwargs): - """A decorator implementation that sets the metadata for a wrapped 1D + """A decorator implementation that sets the metadata for a wrapped 1D interpolation function. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Args: - - wrapped: The wrapped function which in turns needs to be called by your + + wrapped: The wrapped function which in turns needs to be called by your wrapper function. - - instance: The object to which the wrapped function was bound when it + + instance: The object to which the wrapped function was bound when it was called. - - args: The list of positional arguments supplied when the decorated + + args: The list of positional arguments supplied when the decorated function was called. - - kwargs: The dictionary of keyword arguments supplied when the decorated + + kwargs: The dictionary of keyword arguments supplied when the decorated function was called. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - 1D interpolation function output with or without metadata. - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + 1D interpolation function output with or without metadata. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + See Also: - + :mod:`wrapt` - - """ - argvars = from_args(wrapped, ("field", "z_in", "z_out", "missingval"), - *args, **kwargs) - + + """ + argvars = from_args(wrapped, ("field", "z_in", "z_out", "missingval"), + *args, **kwargs) + field = argvars["field"] z_in = argvars["z_in"] z_out = argvars["z_out"] missingval = argvars["missingval"] - + result = wrapped(*args, **kwargs) - + outname = None outdimnames = None outcoords = None outattrs = OrderedDict() - - # Dims are (...,xy,z) + + # Dims are (..., xy, z) if isinstance(field, DataArray): outcoords = OrderedDict() outdimnames = list(field.dims) - + outdimnames.pop(-1) - + for name in outdimnames: try: outcoords[name] = field.coords[name] except KeyError: continue - + outdimnames.append("z") outname = "{0}_z".format(field.name) outcoords["z"] = z_out - + desc = field.attrs.get("description", None) if desc is not None: outattrs["description"] = desc - + units = field.attrs.get("units", None) if units is not None: outattrs["units"] = units - + else: outname = "field_z" - + outattrs["_FillValue"] = missingval outattrs["missing_value"] = missingval - - return DataArray(result, name=outname, dims=outdimnames, + + return DataArray(result, name=outname, dims=outdimnames, coords=outcoords, attrs=outattrs) - + def _set_xy_meta(wrapped, instance, args, kwargs): - """A decorator implementation that sets the metadata for a wrapped xy line + """A decorator implementation that sets the metadata for a wrapped xy line interpolation function. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Args: - - wrapped: The wrapped function which in turns needs to be called by your + + wrapped: The wrapped function which in turns needs to be called by your wrapper function. - - instance: The object to which the wrapped function was bound when it + + instance: The object to which the wrapped function was bound when it was called. - - args: The list of positional arguments supplied when the decorated + + args: The list of positional arguments supplied when the decorated function was called. - - kwargs: The dictionary of keyword arguments supplied when the decorated + + kwargs: The dictionary of keyword arguments supplied when the decorated function was called. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - xy line interpolation function output with or without metadata. - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + xy line interpolation function output with or without metadata. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + See Also: - + :mod:`wrapt` - - """ - argvars = from_args(wrapped, ("field", "pivot_point", "angle", - "start_point", "end_point"), - *args, **kwargs) - + + """ + argvars = from_args(wrapped, ("field", "pivot_point", "angle", + "start_point", "end_point"), + *args, **kwargs) + field = argvars["field"] pivot_point = argvars["pivot_point"] angle = argvars["angle"] start_point = argvars["start_point"] end_point = argvars["end_point"] - + result = wrapped(*args, **kwargs) - + if isinstance(field, DataArray): outname = "{0}_xy".format(field.name) else: outname = "xy" - + outdimnames = ["line_idx", "x_y"] outcoords = OrderedDict() outattrs = OrderedDict() - + outcoords["x_y"] = ["x", "y"] - + if pivot_point is not None and angle is not None: outattrs["pivot_point"] = pivot_point outattrs["angle"] = angle - + if start_point is not None and end_point is not None: outattrs["start_point"] = start_point outattrs["end_point"] = end_point - - return DataArray(result, name=outname, dims=outdimnames, - coords=outcoords, attrs=outattrs) - - + + return DataArray(result, name=outname, dims=outdimnames, + coords=outcoords, attrs=outattrs) + + def set_interp_metadata(interp_type): - """A decorator that sets the metadata for a wrapped interpolation + """A decorator that sets the metadata for a wrapped interpolation function. - - If the wrapped function's *meta* argument is False, then this decorator + + If the wrapped function's *meta* argument is False, then this decorator returns the wrapped function output without applying the metadata. - + Args: - + interp_type (:obj:`str`): The type of interpolation routine. Choices - are: 'horiz', 'cross', 'line', 'vinterp', '2dxy', '1d', + are: 'horiz', 'cross', 'line', 'vinterp', '2dxy', '1d', 'xy'. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - interpolation function output with or without metadata. - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + interpolation function output with or without metadata. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - - """ + + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): do_meta = from_args(wrapped, ("meta"), *args, **kwargs)["meta"] - + if do_meta is None: do_meta = True - + if not xarray_enabled() or not do_meta: return wrapped(*args, **kwargs) - + if interp_type == "horiz": return _set_horiz_meta(wrapped, instance, args, kwargs) elif interp_type == "cross": @@ -1761,104 +1740,102 @@ def set_interp_metadata(interp_type): return _set_1d_meta(wrapped, instance, args, kwargs) elif interp_type == "xy": return _set_xy_meta(wrapped, instance, args, kwargs) - + return func_wrapper -def set_alg_metadata(alg_ndims, refvarname, +def set_alg_metadata(alg_ndims, refvarname, refvarndims=None, missingarg=None, stagdim=None, stagsubvar=None, units=None, description=None): - """A decorator that sets the metadata for a wrapped raw diagnostic + """A decorator that sets the metadata for a wrapped raw diagnostic function. - + Args: - - alg_ndims (:obj:`int`): The number of dimensions returned by the + + alg_ndims (:obj:`int`): The number of dimensions returned by the wrapped function. - - refvarname (:obj:`str`): The wrapped function argument name for the + + refvarname (:obj:`str`): The wrapped function argument name for the reference variable. - - refvarndims (:obj:`int`, optional): The number of right dimensions for - the reference variable. This paramter is required when the - wrapped function result has less dimensions than reference. + + refvarndims (:obj:`int`, optional): The number of right dimensions for + the reference variable. This paramter is required when the + wrapped function result has less dimensions than reference. Default is None. - - missingarg (:obj:`str`, optional): The wrapped function argument name + + missingarg (:obj:`str`, optional): The wrapped function argument name for the missing value variable. Default is None. - - stagdim (:obj`int`, optional): The staggered dimension for the - reference. This is only needed if the reference variable is + + stagdim (:obj`int`, optional): The staggered dimension for the + reference. This is only needed if the reference variable is on a staggered grid. Default is None. - - stagsubvar (:obj:`str`, optional): The wrapped function argument name - to use to supply the unstaggered dimension name for the staggered - dimension in the reference. This is needed if *stagdim* is not + + stagsubvar (:obj:`str`, optional): The wrapped function argument name + to use to supply the unstaggered dimension name for the staggered + dimension in the reference. This is needed if *stagdim* is not None. It is primarily used for absolute vorticity. Default is None. - - units (:obj:`str`, optional): The units to use if if there is no + + units (:obj:`str`, optional): The units to use if if there is no 'units' argument for the wrapped function. Default is None. - - description (:obj:`str`, optional): A description for the wrapped - algorithm, which is stored in the :attr:`xarray.DataArray.attrs` + + description (:obj:`str`, optional): A description for the wrapped + algorithm, which is stored in the :attr:`xarray.DataArray.attrs` attribute under the 'description' key. Default is None. - - + + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - numerical function output with or without metadata. - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + numerical function output with or without metadata. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): do_meta = from_args(wrapped, ("meta",), *args, **kwargs)["meta"] - + if do_meta is None: do_meta = True - + if not xarray_enabled() or not do_meta: return wrapped(*args, **kwargs) - - + result = wrapped(*args, **kwargs) - + outname = wrapped.__name__ outattrs = OrderedDict() - + # Default dimension names outdims = ["dim_{}".format(i) for i in py3range(result.ndim)] - + if missingarg is not None: - missingval = from_args(wrapped, (missingarg,), + missingval = from_args(wrapped, (missingarg,), *args, **kwargs)[missingarg] else: missingval = None - + if missingval is not None: outattrs["_FillValue"] = missingval outattrs["missing_value"] = missingval result = np.ma.masked_values(result, missingval) - + if units is not None: if isinstance(units, from_var): _units = units(wrapped, *args, **kwargs) if _units is not None: - outattrs["units"] = _units + outattrs["units"] = _units else: outattrs["units"] = units - + else: # Check for a units argument, if not, just ignore _units = from_args(wrapped, ("units",), *args, **kwargs)["units"] if _units is not None: outattrs["units"] = _units - - + if description is not None: if isinstance(description, from_var): desc = description(wrapped, *args, **kwargs) @@ -1866,37 +1843,36 @@ def set_alg_metadata(alg_ndims, refvarname, outattrs["description"] = desc else: outattrs["description"] = description - - + # Copy the dimnames from the reference variable, otherwise, use # the supplied dimnames if refvarname is not None: - refvar = from_args(wrapped, (refvarname,), + refvar = from_args(wrapped, (refvarname,), *args, **kwargs)[refvarname] else: refvar = None - + if stagsubvar is not None: - stagvar = from_args(wrapped, (stagsubvar,), - *args, **kwargs)[stagsubvar] + stagvar = from_args(wrapped, (stagsubvar,), + *args, **kwargs)[stagsubvar] else: stagvar = None - + if isinstance(refvar, DataArray): - + # Copy the right dims outdims[-alg_ndims:] = refvar.dims[-alg_ndims:] - + # Use the stagsubvar if applicable if stagvar is not None and stagdim is not None: outdims[stagdim] = stagvar.dims[stagdim] - - # Left dims + + # Left dims if refvarndims is None: # Used when result and reference are aligned on right if result.ndim > alg_ndims: result_extra = result.ndim - alg_ndims - + for i in py3range(1, result_extra + 1): idx = -alg_ndims - i if -idx <= refvar.ndim: @@ -1905,48 +1881,48 @@ def set_alg_metadata(alg_ndims, refvarname, continue # When reference and result aren't exactly aligned (slp) # (reference is 3D, result is 2D) - else: + else: ref_extra = refvar.ndim - refvarndims ref_left_dimnames = refvar.dims[0:ref_extra] - - for i,dimname in enumerate(ref_left_dimnames[::-1], 1): + + for i, dimname in enumerate(ref_left_dimnames[::-1], 1): if i <= result.ndim: outdims[-alg_ndims - i] = dimname else: continue - + out = DataArray(result, name=outname, dims=outdims, attrs=outattrs) - + return out - + return func_wrapper def set_smooth_metdata(): @wrapt.decorator - def func_wrapper(wrapped, instance, args, kwargs): + def func_wrapper(wrapped, instance, args, kwargs): do_meta = from_args(wrapped, ("meta",), *args, **kwargs)["meta"] - + if do_meta is None: do_meta = True - + if not xarray_enabled() or not do_meta: return wrapped(*args, **kwargs) - - argvars = from_args(wrapped, ("field", "passes", "cenweight"), + + argvars = from_args(wrapped, ("field", "passes", "cenweight"), *args, **kwargs) - + field = argvars["field"] passes = argvars["passes"] cenweight = argvars["cenweight"] - + result = wrapped(*args, **kwargs) - + outname = "smooth2d" outdimnames = ["dim_{}".format(i) for i in py3range(result.ndim)] outcoords = OrderedDict() outattrs = OrderedDict() - + if isinstance(field, DataArray): outname = "smooth_" + field.name outdimnames = list(field.dims) @@ -1954,147 +1930,146 @@ def set_smooth_metdata(): outattrs.update(field.attrs) outattrs["passes"] = passes outattrs["cenweight"] = cenweight - + if isinstance(result, ma.MaskedArray): outattrs["_FillValue"] = result.fill_value outattrs["missing_value"] = result.fill_value - - return DataArray(result, name=outname, coords=outcoords, - dims=outdimnames, attrs=outattrs) - + + return DataArray(result, name=outname, coords=outcoords, + dims=outdimnames, attrs=outattrs) + return func_wrapper - - + + def set_uvmet_alg_metadata(units=None, description="earth rotated u,v", latarg="lat", windarg="u"): - """A decorator that sets the metadata for the wrapped raw UVMET diagnostic + """A decorator that sets the metadata for the wrapped raw UVMET diagnostic function. - + Args: - - units (:obj:`str`, optional): The units to use if if there is no + + units (:obj:`str`, optional): The units to use if if there is no 'units' argument for the wrapped function. Default is None. - - description (:obj:`str`, optional): A description for the wrapped - algorithm, which is stored in the :attr:`xarray.DataArray.attrs` + + description (:obj:`str`, optional): A description for the wrapped + algorithm, which is stored in the :attr:`xarray.DataArray.attrs` attribute under the 'description' key. Default is None. - - latarg (:obj:'str`, optional): The wrapped function argument name for + + latarg (:obj:'str`, optional): The wrapped function argument name for latitude. Default is 'lat'. - - windarg (:obj:`str`, optional): The wrapped function argument name for + + windarg (:obj:`str`, optional): The wrapped function argument name for the u wind component. Default is 'u'. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - UVMET function output with or without metadata. - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + UVMET function output with or without metadata. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): do_meta = from_args(wrapped, ("meta",), *args, **kwargs)["meta"] - + if do_meta is None: do_meta = True - + if not xarray_enabled() or not do_meta: return wrapped(*args, **kwargs) - + result = wrapped(*args, **kwargs) - + # Default dimension names outdims = ["dim_{}".format(i) for i in py3range(result.ndim)] - + outname = "uvmet" outattrs = OrderedDict() - + if units is not None: outattrs["units"] = units else: _units = from_args(wrapped, ("units",), *args, **kwargs)["units"] if _units is not None: outattrs["units"] = _units - + if description is not None: outattrs["description"] = description - + latvar = from_args(wrapped, latarg, *args, **kwargs)[latarg] uvar = from_args(wrapped, windarg, *args, **kwargs)[windarg] - + if isinstance(uvar, DataArray) and isinstance(latvar, DataArray): # Right dims come from latvar outdims[-2:] = latvar.dims[-2:] - + # Left dims come from u-var outdims[1:-2] = uvar.dims[0:-2] - + # Left-most is always u_v outdims[0] = "u_v" outcoords = {} outcoords["u_v"] = ["u", "v"] - + out = DataArray(result, name=outname, dims=outdims, coords=outcoords, attrs=outattrs) - + return out - + return func_wrapper def set_cape_alg_metadata(is2d, copyarg="pres_hpa"): - """A decorator that sets the metadata for the wrapped raw CAPE diagnostic + """A decorator that sets the metadata for the wrapped raw CAPE diagnostic function. - + Args: - - is2d (:obj:`bool`): Set to True for the two-dimensional CAPE + + is2d (:obj:`bool`): Set to True for the two-dimensional CAPE calculation, False for three-dimensional CAPE. - - copyarg (:obj:`str`): The wrapped function argument to use for + + copyarg (:obj:`str`): The wrapped function argument to use for copying dimension names. Default is 'pres_hpa'. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - CAPE function output with or without metadata. - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + CAPE function output with or without metadata. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): do_meta = from_args(wrapped, ("meta",), *args, **kwargs)["meta"] - + if do_meta is None: do_meta = True - + if not xarray_enabled() or not do_meta: return wrapped(*args, **kwargs) - + result = wrapped(*args, **kwargs) - - argvals = from_args(wrapped, (copyarg,"missing"), *args, **kwargs) + + argvals = from_args(wrapped, (copyarg, "missing"), *args, **kwargs) p = argvals[copyarg] missing = argvals["missing"] - + # Note: 2D/3D cape supports using only a single column of data is1d = p.ndim == 1 - + # Need to squeeze the right dimensions for 1D cape if is1d: result = np.squeeze(result) - + # Default dimension names outdims = ["dim_{}".format(i) for i in py3range(result.ndim)] - + outattrs = OrderedDict() - - + if is2d: if is1d: outname = "cape_2d" @@ -2113,8 +2088,7 @@ def set_cape_alg_metadata(is2d, copyarg="pres_hpa"): outattrs["MemoryOrder"] = "Z" outattrs["description"] = "cape; cin" outattrs["units"] = "J kg-1 ; J kg-1" - - + if isinstance(p, DataArray): if is2d: if not is1d: @@ -2130,9 +2104,8 @@ def set_cape_alg_metadata(is2d, copyarg="pres_hpa"): outdims[1:-3] = p.dims[0:-3] else: outdims[1] = p.dims[0] - - - outcoords = {} + + outcoords = {} # Left-most is always cape_cin or cape_cin_lcl_lfc if is2d: outdims[0] = "mcape_mcin_lcl_lfc" @@ -2140,64 +2113,64 @@ def set_cape_alg_metadata(is2d, copyarg="pres_hpa"): else: outdims[0] = "cape_cin" outcoords["cape_cin"] = ["cape", "cin"] - + outattrs["_FillValue"] = missing outattrs["missing_value"] = missing - + out = DataArray(result, name=outname, dims=outdims, coords=outcoords, attrs=outattrs) - + return out - + return func_wrapper def set_cloudfrac_alg_metadata(copyarg="vert"): - """A decorator that sets the metadata for the wrapped raw cloud fraction + """A decorator that sets the metadata for the wrapped raw cloud fraction diagnostic function. - + Args: - - copyarg (:obj:`str`): The wrapped function argument to use for + + copyarg (:obj:`str`): The wrapped function argument to use for copying dimension names. Default is 'vert'. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - cloud fraction function output with or without metadata. - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + cloud fraction function output with or without metadata. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ - + @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): do_meta = from_args(wrapped, ("meta",), *args, **kwargs)["meta"] - + if do_meta is None: do_meta = True - + if not xarray_enabled() or not do_meta: return wrapped(*args, **kwargs) - + result = wrapped(*args, **kwargs) - - argvals = from_args(wrapped, (copyarg, "low_thresh", - "mid_thresh", "high_thresh", - "missing"), - *args, **kwargs) + + argvals = from_args(wrapped, (copyarg, "low_thresh", + "mid_thresh", "high_thresh", + "missing"), + *args, **kwargs) cp = argvals[copyarg] low_thresh = argvals["low_thresh"] mid_thresh = argvals["mid_thresh"] high_thresh = argvals["high_thresh"] missing = argvals["missing"] - + # Default dimension names outdims = ["dim_{}".format(i) for i in py3range(result.ndim)] - + outattrs = OrderedDict() - + outname = "cloudfrac" outattrs["description"] = "low, mid, high clouds" outattrs["units"] = "%" @@ -2207,76 +2180,75 @@ def set_cloudfrac_alg_metadata(copyarg="vert"): outattrs["high_thresh"] = high_thresh outattrs["_FillValue"] = missing outattrs["missing_value"] = missing - + if isinstance(cp, DataArray): # Right dims outdims[-2:] = cp.dims[-2:] # Left dims outdims[1:-2] = cp.dims[0:-3] - - - outcoords = {} + + outcoords = {} # Left-most is always low_mid_high outdims[0] = "low_mid_high" outcoords["low_mid_high"] = ["low", "mid", "high"] - + out = DataArray(result, name=outname, dims=outdims, coords=outcoords, attrs=outattrs) - + return out - + return func_wrapper def set_destag_metadata(): - """A decorator that sets the metadata for the wrapped raw destaggering + """A decorator that sets the metadata for the wrapped raw destaggering function. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped - destaggering function output with or without metadata. - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped + destaggering function output with or without metadata. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ - + @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): do_meta = from_args(wrapped, ("meta",), *args, **kwargs)["meta"] - + if do_meta is None: do_meta = True - + if not xarray_enabled() or not do_meta: return wrapped(*args, **kwargs) - + result = wrapped(*args, **kwargs) - + # Default dimension names outdims = ["dim{}".format(i) for i in py3range(result.ndim)] - - destag_args = from_args(wrapped, ("var", "stagger_dim"), + + destag_args = from_args(wrapped, ("var", "stagger_dim"), *args, **kwargs) var = destag_args["var"] - destag_dim = destag_args["stagger_dim"] - + destag_dim = destag_args["stagger_dim"] + if isinstance(var, DataArray): - + if var.name is not None: outname = "destag_{}".format(var.name) else: outname = "destag_var" - + outattrs = OrderedDict() outattrs.update(var.attrs) - + outattrs["destag_dim"] = destag_dim - + outdims = [] outdims += var.dims - + destag_dim_name = outdims[destag_dim] if destag_dim_name.find("_stag") >= 0: new_dim_name = destag_dim_name.replace("_stag", "") @@ -2286,16 +2258,11 @@ def set_destag_metadata(): else: dim_num = result.ndim + destag_dim new_dim_name = "dim_{}".format(dim_num) - - outdims[destag_dim] = new_dim_name - - out = DataArray(result, name=outname, dims=outdims, attrs=outattrs) - - return out - - return func_wrapper - + outdims[destag_dim] = new_dim_name + out = DataArray(result, name=outname, dims=outdims, attrs=outattrs) + return out + return func_wrapper diff --git a/src/wrf/projection.py b/src/wrf/projection.py index b7cd2d7..021b7f1 100644 --- a/src/wrf/projection.py +++ b/src/wrf/projection.py @@ -10,74 +10,75 @@ from .py3compat import viewitems if cartopy_enabled(): from cartopy import crs - + if basemap_enabled(): from mpl_toolkits.basemap import Basemap - + if pyngl_enabled(): from Ngl import Resources if cartopy_enabled(): class MercatorWithLatTS(crs.Mercator): - """A :class:`cartopy.crs.Mercator` subclass that adds support for + """A :class:`cartopy.crs.Mercator` subclass that adds support for a latitude of true scale parameter. - + See Also: - + :class:`cartopy.crs.Mercator` - + """ def __init__(self, central_longitude=0.0, latitude_true_scale=0.0, - min_latitude=-80.0, + min_latitude=-80.0, max_latitude=84.0, globe=None): """Initialize a :class:`wrf.MercatorWithLatTS` object. - + Args: - - central_longitude (:obj:`float`, optional): The central + + central_longitude (:obj:`float`, optional): The central longitude. Default is 0.0. - - latitude_true_scale (:obj:`float`, optional): The latitude + + latitude_true_scale (:obj:`float`, optional): The latitude of true scale. Default is 0.0. - - min_latitude (:obj:`float`, optional): The maximum southerly + + min_latitude (:obj:`float`, optional): The maximum southerly extent of the projection. Default is -80.0. - - max_latitude (:obj:`float`, optional): The maximum northerly + + max_latitude (:obj:`float`, optional): The maximum northerly extent of the projection. Default is 84.0. - + globe (:class:`cartopy.crs.Globe`, optional): A globe object. If omitted, a default globe is created. - + """ proj4_params = [("proj", "merc"), - ("lon_0", central_longitude), - ("lat_ts", latitude_true_scale), - ("k", 1), - ("units", "m")] + ("lon_0", central_longitude), + ("lat_ts", latitude_true_scale), + ("k", 1), + ("units", "m")] super(crs.Mercator, self).__init__(proj4_params, globe=globe) # Calculate limits. - limits = self.transform_points(crs.Geodetic(), - np.array([-180, 180]) + central_longitude, - np.array([min_latitude, max_latitude])) - - # When using a latitude of true scale, the min/max x-limits get set + limits = self.transform_points( + crs.Geodetic(), + np.array([-180, 180]) + central_longitude, + np.array([min_latitude, max_latitude])) + + # When using a latitude of true scale, the min/max x-limits get set # to the same value, so make sure the left one is negative xlimits = limits[..., 0] - + if math.fabs(xlimits[0] - xlimits[1]) < 1e-6: if xlimits[0] < 0: xlimits[1] = -xlimits[1] else: xlimits[0] = -xlimits[0] - + self._xlimits = tuple(xlimits) self._ylimits = tuple(limits[..., 1]) - + # Compatibility with cartopy >= 0.17 self._x_limits = self._xlimits self._y_limits = self._ylimits @@ -87,75 +88,75 @@ if cartopy_enabled(): def _ismissing(val, islat=True): """Return True if a value is None or out of bounds. - + This function is used to check for invalid latitude/longitude values. - + Args: - + val (numeric): A numeric value. - + islat (:obj:`bool`): Set to False if checking for longitude values. - + Returns: - + :obj:`bool`: True if the value is None, or an out of bounds value. - + """ if islat: if val is None: - return True - + return True + if math.fabs(val) > 90.: return True else: if val is None: - return True - + return True + if math.fabs(val) > 360.: return True - + return False class WrfProj(object): """A base class for storing map projection information from WRF data. - - Subclasses of this type will be stored in the 'projection' attribute + + Subclasses of this type will be stored in the 'projection' attribute entry within a :attr:`xarray.DataArray.attrs` dictionary. This base class - contains the methods required to extract the appropriate projection class - for PyNGL, matplotlib basemap, and cartopy. - + contains the methods required to extract the appropriate projection class + for PyNGL, matplotlib basemap, and cartopy. + Attributes: - + map_proj (:obj:`int`): Model projection integer id. - - truelat1 (:obj:`float`): True latitude 1. - - truelat2 (:obj:`float`): True latitude 2. - + + truelat1 (:obj:`float`): True latitude 1. + + truelat2 (:obj:`float`): True latitude 2. + moad_cen_lat (:obj:`float`): Mother of all domains center latitude. - - stand_lon (:obj:`float`): Standard longitude. - + + stand_lon (:obj:`float`): Standard longitude. + pole_lat (:obj:`float`): The pole latitude. - + pole_lon (:obj:`float`): The pole longitude. - + dx (:obj:`float`): The x grid spacing. - + dy (:obj:`float`): The y grid spacing. - - + + """ def __init__(self, **proj_params): """Initialize a :class:`wrf.WrfProj` object. - + Args: - + **proj_params: Map projection optional keyword arguments, that - have the same names as found in WRF output NetCDF global + have the same names as found in WRF output NetCDF global attributes (case insensitive): - + - 'MAP_PROJ': The map projection type as an integer. - 'TRUELAT1': True latitude 1. - 'TRUELAT2': True latitude 2. @@ -165,356 +166,351 @@ class WrfProj(object): - 'POLE_LON': Pole longitude. """ - + up_proj_params = dict_keys_to_upper(proj_params) - + self.map_proj = up_proj_params.get("MAP_PROJ", None) - - # These indicate the center of the nest/domain, not necessarily the + + # These indicate the center of the nest/domain, not necessarily the # center of the projection self._cen_lat = up_proj_params.get("CEN_LAT", None) self._cen_lon = up_proj_params.get("CEN_LON", None) - + self.truelat1 = up_proj_params.get("TRUELAT1", None) self.truelat2 = (up_proj_params.get("TRUELAT2", None) - if not _ismissing(up_proj_params.get("TRUELAT2", - None)) + if not _ismissing(up_proj_params.get("TRUELAT2", + None)) else None) self.moad_cen_lat = up_proj_params.get("MOAD_CEN_LAT", None) self.stand_lon = up_proj_params.get("STAND_LON", None) self.pole_lat = up_proj_params.get("POLE_LAT", None) self.pole_lon = up_proj_params.get("POLE_LON", None) - + self.dx = up_proj_params.get("DX", None) self.dy = up_proj_params.get("DY", None) - + # Just in case... if self.moad_cen_lat is None: self.moad_cen_lat = self._cen_lat - + if self.stand_lon is None: self.stand_lon = self._cen_lon - - + @staticmethod def _context_equal(x, y, ctx): """Return True if both objects are equal based on the provided context. - + Args: - + x (numeric): A numeric value. - + y (numeric): A numeric value. - + ctx (:class:`decimal.Context`): A decimal Context object. - + Returns: - - :obj:`bool`: True if the values are equal based on the provided + + :obj:`bool`: True if the values are equal based on the provided context, False otherwise. - + """ if x is not None: if y is None: return False - - # Note: The float conversion is because these may come in as + + # Note: The float conversion is because these may come in as # numpy.float32 or numpy.float64, which Decimal does not know # how to handle. - if (Decimal(float(x)).normalize(ctx) != - Decimal(float(y)).normalize(ctx)): + if (Decimal(float(x)).normalize(ctx) != + Decimal(float(y)).normalize(ctx)): return False else: if y is not None: return False - + return True - - + def __eq__(self, other): """Return True if this projection object is the same as *other*. - - Note: WRF can use either floats or doubles, so only going to - guarantee floating point precision equivalence, in case the different + + Note: WRF can use either floats or doubles, so only going to + guarantee floating point precision equivalence, in case the different types are ever compared (or a projection is hand constructed). For WRF domains, 7-digit equivalence should be good enough. - + """ - + if self.map_proj is not None: if self.map_proj != other.map_proj: return False else: if other.map_proj is not None: return False - + # Only checking for floating point equal. ctx = Context(prec=7, rounding=ROUND_HALF_UP) - + return (WrfProj._context_equal(self.truelat1, other.truelat1, ctx) and WrfProj._context_equal(self.truelat2, other.truelat2, ctx) and - WrfProj._context_equal(self.moad_cen_lat, other.moad_cen_lat, - ctx) and - WrfProj._context_equal(self.stand_lon, other.stand_lon, + WrfProj._context_equal(self.moad_cen_lat, other.moad_cen_lat, + ctx) and + WrfProj._context_equal(self.stand_lon, other.stand_lon, ctx) and WrfProj._context_equal(self.pole_lat, other.pole_lat, ctx) and WrfProj._context_equal(self.pole_lon, other.pole_lon, ctx) and WrfProj._context_equal(self.dx, other.dx, ctx) and WrfProj._context_equal(self.dy, other.dy, ctx)) - - + def _basemap(self, geobounds, **kwargs): return None - + def _cf_params(self): return None - + def _cartopy(self): return None - + def _calc_extents(self, geobounds): # Need to modify the extents for the new projection pc = crs.PlateCarree() - xs, ys, _ = self._cartopy().transform_points(pc, - np.array([geobounds.bottom_left.lon, - geobounds.top_right.lon]), - np.array([geobounds.bottom_left.lat, - geobounds.top_right.lat])).T + xs, ys, _ = self._cartopy().transform_points( + pc, + np.array([geobounds.bottom_left.lon, geobounds.top_right.lon]), + np.array([geobounds.bottom_left.lat, geobounds.top_right.lat])).T _xlimits = xs.tolist() _ylimits = ys.tolist() - + return (_xlimits, _ylimits) - - + def _cart_extents(self, geobounds): try: _ = len(geobounds) - except TypeError: # Only a single object + except TypeError: # Only a single object extents = self._calc_extents(geobounds) - else: + else: extents = np.empty(geobounds.shape, np.object) - + for idxs, geobnd_val in np.ndenumerate(geobounds): extents[idxs] = self._calc_extents(geobnd_val) - + return extents - + def _pyngl(self, geobounds): return None - + def _proj4(self): return None - + def _globe(self): - return (None if not cartopy_enabled() + return (None if not cartopy_enabled() else crs.Globe(ellipse=None, semimajor_axis=Constants.WRF_EARTH_RADIUS, semiminor_axis=Constants.WRF_EARTH_RADIUS, nadgrids="@null")) - + def cartopy_xlim(self, geobounds): """Return the x extents in projected coordinates for cartopy. - + Returns: - + :obj:`list`: A pair of [xmin, xmax]. - + See Also: - + :mod:`cartopy`, :mod:`matplotlib` - + """ try: _ = len(geobounds) except TypeError: - x_extents= self._cart_extents(geobounds)[0] + x_extents = self._cart_extents(geobounds)[0] else: extents = self._cart_extents(geobounds) x_extents = np.empty(extents.shape, np.object) - + for idxs, extent in np.ndenumerate(extents): x_extents[idxs] = extent[0] - + return x_extents - + def cartopy_ylim(self, geobounds): """Return the y extents in projected coordinates for cartopy. - + Returns: - + :obj:`list`: A pair of [ymin, ymax]. - + See Also: - + :mod:`cartopy`, :mod:`matplotlib` - + """ try: _ = len(geobounds) except TypeError: - y_extents= self._cart_extents(geobounds)[1] + y_extents = self._cart_extents(geobounds)[1] else: extents = self._cart_extents(geobounds) y_extents = np.empty(extents.shape, np.object) - + for idxs, extent in np.ndenumerate(extents): y_extents[idxs] = extent[1] - + return y_extents - + def __repr__(self): args = ("stand_lon={}, moad_cen_lat={}, " "truelat1={}, truelat2={}, " - "pole_lat={}, pole_lon={}".format(self.stand_lon, + "pole_lat={}, pole_lon={}".format(self.stand_lon, self.moad_cen_lat, self.truelat1, self.truelat2, self.pole_lat, self.pole_lon)) return "{}({})".format(self.__class__.__name__, args) - + def basemap(self, geobounds, **kwargs): - """Return a :class:`matplotlib.mpl_toolkits.basemap.Basemap` object + """Return a :class:`matplotlib.mpl_toolkits.basemap.Basemap` object for the map projection. - + Args: - - geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to + + geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to get the extents. If set to None and using the *var* parameter, - the geobounds will be taken from the variable. If using a + the geobounds will be taken from the variable. If using a file, then the geobounds will be taken from the native grid. - - **kwargs: Keyword arguments for creating a + + **kwargs: Keyword arguments for creating a :class:`matplotlib.mpl_toolkits.basemap.Basemap`. By default, - the domain bounds will be set to the native projection, the - resolution will be set to 'l', and the other projection + the domain bounds will be set to the native projection, the + resolution will be set to 'l', and the other projection parameters will be set by the information in the file. - + Returns: - + :class:`matplotlib.mpl_toolkits.basemap.Basemap`: A Basemap object for the projection. - + See Also: - + :class:`matplotlib.mpl_toolkits.basemap.Basemap` - + """ if not basemap_enabled(): raise RuntimeError("'mpl_toolkits.basemap' is not " "installed or is disabled") - + return self._basemap(geobounds, **kwargs) - + def cartopy(self): - """Return a :class:`cartopy.crs.Projection` subclass for the + """Return a :class:`cartopy.crs.Projection` subclass for the map projection. - + Returns: - - :class:`cartopy.crs.Projection`: A Projection subclass for the + + :class:`cartopy.crs.Projection`: A Projection subclass for the map projection. - + See Also: - + :class:`cartopy.crs.Projection` - + """ if not cartopy_enabled(): raise RuntimeError("'cartopy' is not " "installed or is disabled") return self._cartopy() - + def pyngl(self, geobounds, **kwargs): """Return a :class:`Ngl.Resources` object for the map projection. - + Args: - - geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to + + geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to get the extents. If set to None and using the *var* parameter, - the geobounds will be taken from the variable. If using a + the geobounds will be taken from the variable. If using a file, then the geobounds will be taken from the native grid. - - **kwargs: Additional PyNGL resources to set while creating the + + **kwargs: Additional PyNGL resources to set while creating the :class:`Ngl.Resources` object. - + Returns: - - :class:`Ngl.Resources`: A dict-like object that contains the + + :class:`Ngl.Resources`: A dict-like object that contains the PyNGL resources for the map projection. - + See Also: - - `PyNGL `_ - + + `PyNGL `_ + """ if not pyngl_enabled(): raise RuntimeError("'pyngl' is not " "installed or is disabled") return self._pyngl(geobounds, **kwargs) - + def proj4(self): """Return the PROJ.4 string for the map projection. - + Returns: - + :obj:`str`: A string suitable for use with the PROJ.4 library. - + See Also: - - PROJ.4 `_ - + + PROJ.4 `_ + """ return self._proj4() - + def cf(self): - """Return a dictionary with the NetCDF CF parameters for the + """Return a dictionary with the NetCDF CF parameters for the projection. - + Returns: - - :obj:`dict`: A dictionary with the NetCDF CF parameter names and + + :obj:`dict`: A dictionary with the NetCDF CF parameter names and projection parameter values. - + """ return self._cf_params() - + # Used for 'missing' projection values during the 'join' method class NullProjection(WrfProj): """A :class:`wrf.WrfProj` subclass for empty projections. - - The :class:`NullProjection` is primarily used for creating missing + + The :class:`NullProjection` is primarily used for creating missing projections when using the 'join' method. - + """ def __init__(self): """Initialize a :class:`wrf.NullProjection` object.""" - pass - + pass + def __repr__(self): return "{}()".format(self.__class__.__name__) - - + + class LambertConformal(WrfProj): """A :class:`wrf.WrfProj` subclass for Lambert Conformal Conic projections. - + See Also: - - :class:`wrf.WrfProj`, :class:`wrf.LatLon`, - :class:`wrf.PolarStereographic`, + + :class:`wrf.WrfProj`, :class:`wrf.LatLon`, + :class:`wrf.PolarStereographic`, :class:`Mercator`, :class:`RotatedLatLon` - + """ def __init__(self, **proj_params): """Initialize a :class:`wrf.LambertConformal` object. - + Args: - + **proj_params: Map projection optional keyword arguments, that - have the same names as found in WRF output NetCDF global + have the same names as found in WRF output NetCDF global attributes: - + - 'TRUELAT1': True latitude 1. - 'TRUELAT2': True latitude 2. - 'MOAD_CEN_LAT': Mother of all domains center latitude. @@ -524,122 +520,118 @@ class LambertConformal(WrfProj): """ super(LambertConformal, self).__init__(**proj_params) - + self._std_parallels = [self.truelat1] if self.truelat2 is not None: self._std_parallels.append(self.truelat2) - - + def _cf_params(self): _cf_params = {} - _cf_params["grid_mapping_name"] = "lambert_conformal_conic"; + _cf_params["grid_mapping_name"] = "lambert_conformal_conic" _cf_params["standard_parallel"] = self._std_parallels _cf_params["longitude_of_central_meridian"] = self.stand_lon _cf_params["latitude_of_projection_origin"] = self.moad_cen_lat _cf_params["semi_major_axis"] = Constants.WRF_EARTH_RADIUS - + return _cf_params - - + def _pyngl(self, geobounds, **kwargs): if not pyngl_enabled(): return None - - truelat2 = (self.truelat1 - if _ismissing(self.truelat2) - else self.truelat2) - + + truelat2 = (self.truelat1 if _ismissing(self.truelat2) + else self.truelat2) + _pyngl = Resources() _pyngl.mpProjection = "LambertConformal" _pyngl.mpDataBaseVersion = "MediumRes" _pyngl.mpLambertMeridianF = self.stand_lon _pyngl.mpLambertParallel1F = self.truelat1 _pyngl.mpLambertParallel2F = truelat2 - + _pyngl.mpLimitMode = "Corners" _pyngl.mpLeftCornerLonF = geobounds.bottom_left.lon _pyngl.mpLeftCornerLatF = geobounds.bottom_left.lat _pyngl.mpRightCornerLonF = geobounds.top_right.lon _pyngl.mpRightCornerLatF = geobounds.top_right.lat - + for key, val in viewitems(kwargs): setattr(_pyngl, key, val) - + return _pyngl - - + def _basemap(self, geobounds, **kwargs): if not basemap_enabled(): return None - - local_kwargs = dict(projection = "lcc", - lon_0 = self.stand_lon, - lat_0 = self.moad_cen_lat, - lat_1 = self.truelat1, - lat_2 = self.truelat2, - llcrnrlat = geobounds.bottom_left.lat, - urcrnrlat = geobounds.top_right.lat, - llcrnrlon = geobounds.bottom_left.lon, - urcrnrlon = geobounds.top_right.lon, - rsphere = Constants.WRF_EARTH_RADIUS, - resolution = 'l') + + local_kwargs = dict(projection="lcc", + lon_0=self.stand_lon, + lat_0=self.moad_cen_lat, + lat_1=self.truelat1, + lat_2=self.truelat2, + llcrnrlat=geobounds.bottom_left.lat, + urcrnrlat=geobounds.top_right.lat, + llcrnrlon=geobounds.bottom_left.lon, + urcrnrlon=geobounds.top_right.lon, + rsphere=Constants.WRF_EARTH_RADIUS, + resolution='l') local_kwargs.update(kwargs) - + _basemap = Basemap(**local_kwargs) - + return _basemap - + def _cartopy(self): if not cartopy_enabled(): return None - + # Set cutoff to -30 for NH, +30.0 for SH. cutoff = -30.0 if self.moad_cen_lat >= 0 else 30.0 - + _cartopy = crs.LambertConformal( - central_longitude = self.stand_lon, - central_latitude = self.moad_cen_lat, - standard_parallels = self._std_parallels, - globe = self._globe(), - cutoff = cutoff) - + central_longitude=self.stand_lon, + central_latitude=self.moad_cen_lat, + standard_parallels=self._std_parallels, + globe=self._globe(), + cutoff=cutoff) + return _cartopy - + def _proj4(self): - truelat2 = (self.truelat1 - if _ismissing(self.truelat2) + truelat2 = (self.truelat1 + if _ismissing(self.truelat2) else self.truelat2) - + _proj4 = ("+proj=lcc +units=meters +a={} +b={} +lat_1={} " "+lat_2={} +lat_0={} +lon_0={} +nadgrids=@null".format( - Constants.WRF_EARTH_RADIUS, - Constants.WRF_EARTH_RADIUS, - self.truelat1, - truelat2, - self.moad_cen_lat, - self.stand_lon)) + Constants.WRF_EARTH_RADIUS, + Constants.WRF_EARTH_RADIUS, + self.truelat1, + truelat2, + self.moad_cen_lat, + self.stand_lon)) return _proj4 - - + + class Mercator(WrfProj): """A :class:`wrf.WrfProj` subclass for Mercator projections. - + See Also: - - :class:`wrf.WrfProj`, :class:`wrf.LatLon`, - :class:`wrf.PolarStereographic`, + + :class:`wrf.WrfProj`, :class:`wrf.LatLon`, + :class:`wrf.PolarStereographic`, :class:`RotatedLatLon`, :class:`LambertConformal` - + """ def __init__(self, **proj_params): """Initialize a :class:`wrf.Mercator` object. - + Args: - + **proj_params: Map projection optional keyword arguments, that - have the same names as found in WRF output NetCDF global + have the same names as found in WRF output NetCDF global attributes: - + - 'TRUELAT1': True latitude 1. - 'TRUELAT2': True latitude 2. - 'MOAD_CEN_LAT': Mother of all domains center latitude. @@ -649,117 +641,108 @@ class Mercator(WrfProj): """ super(Mercator, self).__init__(**proj_params) - - self._lat_ts = (None - if self.truelat1 == 0. or _ismissing(self.truelat1) - else self.truelat1) - - self._stand_lon = (0. if _ismissing(self.stand_lon, islat=False) + + self._lat_ts = ( + None if self.truelat1 == 0. or _ismissing(self.truelat1) + else self.truelat1) + + self._stand_lon = (0. if _ismissing(self.stand_lon, islat=False) else self.stand_lon) - - + def _cf_params(self): - _cf_params = {} _cf_params["grid_mapping_name"] = "mercator" _cf_params["longitude_of_projection_origin"] = self.stand_lon _cf_params["standard_parallel"] = self.truelat1 - + return _cf_params - - + def _pyngl(self, geobounds, **kwargs): if not pyngl_enabled(): return None - + _pyngl = Resources() _pyngl.mpProjection = "Mercator" _pyngl.mpDataBaseVersion = "MediumRes" _pyngl.mpCenterLatF = 0.0 _pyngl.mpCenterLonF = self._stand_lon - + _pyngl.mpLimitMode = "Corners" _pyngl.mpLeftCornerLonF = geobounds.bottom_left.lon _pyngl.mpLeftCornerLatF = geobounds.bottom_left.lat _pyngl.mpRightCornerLonF = geobounds.top_right.lon _pyngl.mpRightCornerLatF = geobounds.top_right.lat - + for key, val in viewitems(kwargs): setattr(_pyngl, key, val) - + return _pyngl - - + def _basemap(self, geobounds, **kwargs): if not basemap_enabled(): return None - - local_kwargs = dict(projection = "merc", - lon_0 = self._stand_lon, - lat_0 = self.moad_cen_lat, - lat_ts = self._lat_ts, - llcrnrlat = geobounds.bottom_left.lat, - urcrnrlat = geobounds.top_right.lat, - llcrnrlon = geobounds.bottom_left.lon, - urcrnrlon = geobounds.top_right.lon, - rsphere = Constants.WRF_EARTH_RADIUS, - resolution = "l") + + local_kwargs = dict(projection="merc", + lon_0=self._stand_lon, + lat_0=self.moad_cen_lat, + lat_ts=self._lat_ts, + llcrnrlat=geobounds.bottom_left.lat, + urcrnrlat=geobounds.top_right.lat, + llcrnrlon=geobounds.bottom_left.lon, + urcrnrlon=geobounds.top_right.lon, + rsphere=Constants.WRF_EARTH_RADIUS, + resolution="l") local_kwargs.update(kwargs) - + _basemap = Basemap(**local_kwargs) - + return _basemap - - + def _cartopy(self): if not cartopy_enabled(): return None - + if self._lat_ts == 0.0: - _cartopy = crs.Mercator( - central_longitude = self._stand_lon, - globe = self._globe()) - + _cartopy = crs.Mercator(central_longitude=self._stand_lon, + globe=self._globe()) else: - _cartopy = MercatorWithLatTS( - central_longitude = self._stand_lon, - latitude_true_scale = self._lat_ts, - globe = self._globe()) - + _cartopy = MercatorWithLatTS(central_longitude=self._stand_lon, + latitude_true_scale=self._lat_ts, + globe=self._globe()) + return _cartopy - - + def _proj4(self): - + _proj4 = ("+proj=merc +units=meters +a={} +b={} " "+lon_0={} +lat_ts={} +nadgrids=@null".format( Constants.WRF_EARTH_RADIUS, Constants.WRF_EARTH_RADIUS, self._stand_lon, self._lat_ts)) - + return _proj4 - + + class PolarStereographic(WrfProj): """A :class:`wrf.WrfProj` subclass for Polar Stereographic projections. - + See Also: - - :class:`wrf.WrfProj`, :class:`wrf.LatLon`, - :class:`wrf.RotatedLatLon`, + + :class:`wrf.WrfProj`, :class:`wrf.LatLon`, + :class:`wrf.RotatedLatLon`, :class:`Mercator`, :class:`LambertConformal` - - """ + """ def __init__(self, **proj_params): """Initialize a :class:`wrf.PolarStereographic` object. - + Args: - + **proj_params: Map projection optional keyword arguments, that - have the same names as found in WRF output NetCDF global + have the same names as found in WRF output NetCDF global attributes: - + - 'TRUELAT1': True latitude 1. - 'TRUELAT2': True latitude 2. - 'MOAD_CEN_LAT': Mother of all domains center latitude. @@ -770,11 +753,8 @@ class PolarStereographic(WrfProj): """ super(PolarStereographic, self).__init__(**proj_params) self._hemi = -90. if self.truelat1 < 0 else 90. - self._lat_ts = (None - if _ismissing(self.truelat1) - else self.truelat1) - - + self._lat_ts = (None if _ismissing(self.truelat1) else self.truelat1) + def _cf_params(self): _cf_params = {} _cf_params["grid_mapping_name"] = "polar_stereographic" @@ -782,68 +762,64 @@ class PolarStereographic(WrfProj): self.stand_lon) _cf_params["standard_parallel"] = self.truelat1 _cf_params["latitude_of_projection_origin"] = self._hemi - + return _cf_params - - + def _pyngl(self, geobounds, **kwargs): if not pyngl_enabled(): return None - + _pyngl = Resources() _pyngl.mpProjection = "Stereographic" _pyngl.mpDataBaseVersion = "MediumRes" - + _pyngl.mpCenterLonF = self.stand_lon if self._hemi > 0: _pyngl.mpCenterLatF = 90.0 else: _pyngl.mpCenterLatF = -90.0 - + _pyngl.mpLimitMode = "Corners" _pyngl.mpLeftCornerLonF = geobounds.bottom_left.lon _pyngl.mpLeftCornerLatF = geobounds.bottom_left.lat _pyngl.mpRightCornerLonF = geobounds.top_right.lon _pyngl.mpRightCornerLatF = geobounds.top_right.lat - + for key, val in viewitems(kwargs): setattr(_pyngl, key, val) - + return _pyngl - - + def _basemap(self, geobounds, **kwargs): if not basemap_enabled(): return None - - local_kwargs = dict(projection = "stere", - lon_0 = self.stand_lon, - lat_0 = self._hemi, - lat_ts = self._lat_ts, - llcrnrlat = geobounds.bottom_left.lat, - urcrnrlat = geobounds.top_right.lat, - llcrnrlon = geobounds.bottom_left.lon, - urcrnrlon = geobounds.top_right.lon, - rsphere = Constants.WRF_EARTH_RADIUS, - resolution = "l") + + local_kwargs = dict(projection="stere", + lon_0=self.stand_lon, + lat_0=self._hemi, + lat_ts=self._lat_ts, + llcrnrlat=geobounds.bottom_left.lat, + urcrnrlat=geobounds.top_right.lat, + llcrnrlon=geobounds.bottom_left.lon, + urcrnrlon=geobounds.top_right.lon, + rsphere=Constants.WRF_EARTH_RADIUS, + resolution="l") local_kwargs.update(kwargs) _basemap = Basemap(**local_kwargs) - + return _basemap - - + def _cartopy(self): if not cartopy_enabled(): return None - - _cartopy = crs.Stereographic(central_latitude=self._hemi, - central_longitude=self.stand_lon, - true_scale_latitude=self._lat_ts, - globe=self._globe()) + + _cartopy = crs.Stereographic(central_latitude=self._hemi, + central_longitude=self.stand_lon, + true_scale_latitude=self._lat_ts, + globe=self._globe()) return _cartopy - - + def _proj4(self): _proj4 = ("+proj=stere +units=meters +a={} +b={} " "+lat0={} +lon_0={} +lat_ts={} +nadgrids=@null".format( @@ -852,30 +828,29 @@ class PolarStereographic(WrfProj): self._hemi, self.stand_lon, self._lat_ts)) - + return _proj4 - - + class LatLon(WrfProj): """A :class:`wrf.WrfProj` subclass for Lat Lon projections. - + See Also: - - :class:`wrf.WrfProj`, :class:`wrf.RotatedLatLon`, - :class:`wrf.PolarStereographic`, + + :class:`wrf.WrfProj`, :class:`wrf.RotatedLatLon`, + :class:`wrf.PolarStereographic`, :class:`Mercator`, :class:`LambertConformal` - + """ def __init__(self, **proj_params): """Initialize a :class:`wrf.LatLon` object. - + Args: - + **proj_params: Map projection optional keyword arguments, that - have the same names as found in WRF output NetCDF global + have the same names as found in WRF output NetCDF global attributes: - + - 'TRUELAT1': True latitude 1. - 'TRUELAT2': True latitude 2. - 'MOAD_CEN_LAT': Mother of all domains center latitude. @@ -885,128 +860,124 @@ class LatLon(WrfProj): """ super(LatLon, self).__init__(**proj_params) - - + def _cf_params(self): _cf_params = {} _cf_params["grid_mapping_name"] = "latitude_longitude" return _cf_params - - + def _pyngl(self, geobounds, **kwargs): if not pyngl_enabled(): return None - + _pyngl = Resources() _pyngl.mpProjection = "CylindricalEquidistant" _pyngl.mpDataBaseVersion = "MediumRes" _pyngl.mpCenterLonF = self.stand_lon _pyngl.mpCenterLatF = self.moad_cen_lat - + _pyngl.mpLimitMode = "Corners" _pyngl.mpLeftCornerLonF = geobounds.bottom_left.lon _pyngl.mpLeftCornerLatF = geobounds.bottom_left.lat _pyngl.mpRightCornerLonF = geobounds.top_right.lon _pyngl.mpRightCornerLatF = geobounds.top_right.lat - + for key, val in viewitems(kwargs): setattr(_pyngl, key, val) - + return _pyngl - - + def _basemap(self, geobounds, **kwargs): if not basemap_enabled(): return None - - local_kwargs = dict(projection = "cyl", - lon_0 = self.stand_lon, - lat_0 = self.moad_cen_lat, - llcrnrlat = geobounds.bottom_left.lat, - urcrnrlat = geobounds.top_right.lat, - llcrnrlon = geobounds.bottom_left.lon, - urcrnrlon = geobounds.top_right.lon, - rsphere = Constants.WRF_EARTH_RADIUS, - resolution = "l") - + + local_kwargs = dict(projection="cyl", + lon_0=self.stand_lon, + lat_0=self.moad_cen_lat, + llcrnrlat=geobounds.bottom_left.lat, + urcrnrlat=geobounds.top_right.lat, + llcrnrlon=geobounds.bottom_left.lon, + urcrnrlon=geobounds.top_right.lon, + rsphere=Constants.WRF_EARTH_RADIUS, + resolution="l") + local_kwargs.update(kwargs) _basemap = Basemap(**local_kwargs) - + return _basemap - - + def _cartopy(self): if not cartopy_enabled(): return None - + _cartopy = crs.PlateCarree(central_longitude=self.stand_lon, globe=self._globe()) - + return _cartopy - - + def _cart_extents(self, geobounds): - return ([geobounds.bottom_left.lon, geobounds.top_right.lon], - [geobounds.bottom_left.lat, geobounds.top_right.lat]) - - + return ([geobounds.bottom_left.lon, geobounds.top_right.lon], + [geobounds.bottom_left.lat, geobounds.top_right.lat]) + def _proj4(self): _proj4 = ("+proj=eqc +units=meters +a={} +b={} " - "+lon_0={} +nadgrids=@null".format(Constants.WRF_EARTH_RADIUS, - Constants.WRF_EARTH_RADIUS, - self.stand_lon)) + "+lon_0={} +nadgrids=@null".format( + Constants.WRF_EARTH_RADIUS, + Constants.WRF_EARTH_RADIUS, + self.stand_lon)) return _proj4 + # Notes (may not be correct since this projection confuses me): # Each projection system handles this differently. -# 1) In WRF, if following the WPS instructions, POLE_LON is mainly used to -# determine north or south hemisphere. In other words, it determines if +# 1) In WRF, if following the WPS instructions, POLE_LON is mainly used to +# determine north or south hemisphere. In other words, it determines if # the globe is tipped toward or away from you. -# 2) In WRF, POLE_LAT is always positive, but should be negative in the +# 2) In WRF, POLE_LAT is always positive, but should be negative in the # proj4 based systems when using the southern hemisphere projections. -# 3) In cartopy, pole_longitude is used to describe the dateline, which -# is 180 degrees away from the normal central (standard) longitude -# (e.g. center of the projection), according to the cartopy developer. +# 3) In cartopy, pole_longitude is used to describe the dateline, which +# is 180 degrees away from the normal central (standard) longitude +# (e.g. center of the projection), according to the cartopy developer. # 4) In basemap, lon_0 should be set to the central (standard) longitude. # 5) In either cartopy, basemap or pyngl, I'm not sure that projections with -# a pole_lon not equal to 0 or 180 can be plotted. Hopefully people +# a pole_lon not equal to 0 or 180 can be plotted. Hopefully people # follow the WPS instructions, otherwise I need to see a sample file. -# 6) For items in 3 - 4, the "longitude" (lon_0 or pole_longitude) is -# determined by WRF's +# 6) For items in 3 - 4, the "longitude" (lon_0 or pole_longitude) is +# determined by WRF's # STAND_LON values, with the following calculations based on hemisphere: # BASEMAP: NH: -STAND_LON; SH: 180.0 - STAND_LON # CARTOPY: NH: -STAND_LON - 180.; SH: -STAND_LON # 9) For PYNGL/NCL, you only need to set the center lat and center lon, -# Center lat is the offset of the pole from +/- 90 degrees. Center -# lon is -STAND_LON in NH and 180.0 - STAND_LON in SH. -# 10) It also appears that NetCDF CF has no clear documentation on what +# Center lat is the offset of the pole from +/- 90 degrees. Center +# lon is -STAND_LON in NH and 180.0 - STAND_LON in SH. +# 10) It also appears that NetCDF CF has no clear documentation on what # each parameter means. Going to assume it is the same as basemap, since -# basemap appears to mirror the WMO way of doing things (tilt earth, then +# basemap appears to mirror the WMO way of doing things (tilt earth, then # spin globe). -# 11) Basemap and cartopy produce projections that differ in their extent +# 11) Basemap and cartopy produce projections that differ in their extent # calculations by either using negative values or 0-360 (basemap). For # this reason, the proj4 string for this class will use cartopy's values # to keep things in the -180 to 180, -90 to 90 range. -# 12) This projection makes me sad. +# 12) This projection makes me sad. class RotatedLatLon(WrfProj): """A :class:`wrf.WrfProj` subclass for Rotated Lat Lon projections. - + See Also: - - :class:`wrf.WrfProj`, :class:`wrf.LatLon`, - :class:`wrf.PolarStereographic`, + + :class:`wrf.WrfProj`, :class:`wrf.LatLon`, + :class:`wrf.PolarStereographic`, :class:`Mercator`, :class:`LambertConformal` - + """ def __init__(self, **proj_params): """Initialize a :class:`wrf.RotatedLatLon` object. - + Args: - + **proj_params: Map projection optional keyword arguments, that - have the same names as found in WRF output NetCDF global + have the same names as found in WRF output NetCDF global attributes: - + - 'TRUELAT1': True latitude 1. - 'TRUELAT2': True latitude 2. - 'MOAD_CEN_LAT': Mother of all domains center latitude. @@ -1016,9 +987,9 @@ class RotatedLatLon(WrfProj): """ super(RotatedLatLon, self).__init__(**proj_params) - + # Need to determine hemisphere, typically pole_lon is 0 for southern - # hemisphere, 180 for northern hemisphere. If not, going to have + # hemisphere, 180 for northern hemisphere. If not, going to have # to guess based on other parameters, but hopefully people follow # the WPS instructions and this never happens. self._north = True @@ -1032,100 +1003,95 @@ class RotatedLatLon(WrfProj): else: if self.moad_cen_lat is not None and self.moad_cen_lat < 0.0: # Only probably true - self._north = False - + self._north = False + if self.pole_lat is not None and self.stand_lon is not None: - self._pyngl_cen_lat = (90. - self.pole_lat if self._north - else self.pole_lat - 90.0) - self._pyngl_cen_lon = (-self.stand_lon if self._north - else 180.0 - self.stand_lon) - self._bm_lon_0 = (-self.stand_lon if self._north + self._pyngl_cen_lat = (90. - self.pole_lat if self._north + else self.pole_lat - 90.0) + self._pyngl_cen_lon = (-self.stand_lon if self._north + else 180.0 - self.stand_lon) + self._bm_lon_0 = (-self.stand_lon if self._north else 180.0 - self.stand_lon) - self._bm_cart_pole_lat = (self.pole_lat if self._north - else -self.pole_lat ) - # The important point is that pole longitude is the position - # of the dateline of the new projection, not its central - # longitude (per the creator of cartopy). This is based on + self._bm_cart_pole_lat = (self.pole_lat if self._north + else -self.pole_lat) + # The important point is that pole longitude is the position + # of the dateline of the new projection, not its central + # longitude (per the creator of cartopy). This is based on # how it's handled by agencies like WMO, but not proj4. - self._cart_pole_lon = (-self.stand_lon - 180.0 if self._north - else -self.stand_lon) + self._cart_pole_lon = (-self.stand_lon - 180.0 if self._north + else -self.stand_lon) else: self._pyngl_cen_lat = self.moad_cen_lat self._pyngl_cen_lon = self.stand_lon - self._bm_cart_pole_lat = (90.0 - self.moad_cen_lat if self._north - else -90.0 - self.moad_cen_lat) - self._bm_lon_0 = (-self.stand_lon if self._north + self._bm_cart_pole_lat = (90.0 - self.moad_cen_lat if self._north + else -90.0 - self.moad_cen_lat) + self._bm_lon_0 = (-self.stand_lon if self._north else 180.0 - self.stand_lon) - self._cart_pole_lon = (-self.stand_lon - 180.0 if self._north - else -self.stand_lon) - - - def _cf_params(self): + self._cart_pole_lon = (-self.stand_lon - 180.0 if self._north + else -self.stand_lon) + + def _cf_params(self): _cf_params = {} # Assuming this follows the same guidelines as cartopy _cf_params["grid_mapping_name"] = "rotated_latitude_longitude" _cf_params["grid_north_pole_latitude"] = self._bm_cart_pole_lat _cf_params["grid_north_pole_longitude"] = self.pole_lon _cf_params["north_pole_grid_longitude"] = self._bm_lon_0 - + return _cf_params - - + def _pyngl(self, geobounds, **kwargs): if not pyngl_enabled(): return None - + _pyngl = Resources() _pyngl.mpProjection = "CylindricalEquidistant" _pyngl.mpDataBaseVersion = "MediumRes" _pyngl.mpCenterLatF = self._pyngl_cen_lat _pyngl.mpCenterLonF = self._pyngl_cen_lon - + _pyngl.mpLimitMode = "Corners" _pyngl.mpLeftCornerLonF = geobounds.bottom_left.lon _pyngl.mpLeftCornerLatF = geobounds.bottom_left.lat _pyngl.mpRightCornerLonF = geobounds.top_right.lon _pyngl.mpRightCornerLatF = geobounds.top_right.lat - + for key, val in viewitems(kwargs): setattr(_pyngl, key, val) - + return _pyngl - - + def _basemap(self, geobounds, **kwargs): if not basemap_enabled(): return None - - local_kwargs = dict(projection = "rotpole", - o_lat_p = self._bm_cart_pole_lat, - o_lon_p = self.pole_lon, - llcrnrlat = geobounds.bottom_left.lat, - urcrnrlat = geobounds.top_right.lat, - llcrnrlon = geobounds.bottom_left.lon, - urcrnrlon = geobounds.top_right.lon, - lon_0 = self._bm_lon_0, - rsphere = Constants.WRF_EARTH_RADIUS, - resolution = "l") - + + local_kwargs = dict(projection="rotpole", + o_lat_p=self._bm_cart_pole_lat, + o_lon_p=self.pole_lon, + llcrnrlat=geobounds.bottom_left.lat, + urcrnrlat=geobounds.top_right.lat, + llcrnrlon=geobounds.bottom_left.lon, + urcrnrlon=geobounds.top_right.lon, + lon_0=self._bm_lon_0, + rsphere=Constants.WRF_EARTH_RADIUS, + resolution="l") + local_kwargs.update(kwargs) _basemap = Basemap(**local_kwargs) - + return _basemap - - + def _cartopy(self): if not cartopy_enabled(): return None - - _cartopy = crs.RotatedPole( - pole_longitude=self._cart_pole_lon, - pole_latitude=self._bm_cart_pole_lat, - central_rotated_longitude=( - 180.0 - self.pole_lon), # Probably - globe = self._globe()) + + _cartopy = crs.RotatedPole(pole_longitude=self._cart_pole_lon, + pole_latitude=self._bm_cart_pole_lat, + central_rotated_longitude=( + 180.0 - self.pole_lon), # Probably + globe=self._globe()) return _cartopy - + def _proj4(self): _proj4 = ("+proj=ob_tran +o_proj=latlon " "+a={} +b={} +to_meter={} +o_lon_p={} +o_lat_p={} " @@ -1135,21 +1101,22 @@ class RotatedLatLon(WrfProj): 180.0 - self.pole_lon, self._bm_cart_pole_lat, 180.0 + self._cart_pole_lon)) - + return _proj4 - + + def getproj(**proj_params): """Return a :class:`wrf.WrfProj` subclass. - - This functions serves as a factory function for returning a + + This functions serves as a factory function for returning a :class:`wrf.WrfProj` subclass from the specified map projection parameters. - + Args: - + **proj_params: Map projection optional keyword arguments, that - have the same names as found in WRF output NetCDF global + have the same names as found in WRF output NetCDF global attributes: - + - 'MAP_PROJ': The map projection type as an integer. - 'TRUELAT1': True latitude 1. - 'TRUELAT2': True latitude 2. @@ -1157,16 +1124,15 @@ def getproj(**proj_params): - 'STAND_LON': Standard longitude. - 'POLE_LAT': Pole latitude. - 'POLE_LON': Pole longitude. - + Returns: - - :class:`wrf.WrfProj`: A :class:`wrf.WrfProj` subclass for the + + :class:`wrf.WrfProj`: A :class:`wrf.WrfProj` subclass for the specified map projection parameters. - + """ - up_proj_params = dict_keys_to_upper(proj_params) - + proj_type = up_proj_params.get("MAP_PROJ", 0) if proj_type == ProjectionTypes.LAMBERT_CONFORMAL: return LambertConformal(**proj_params) @@ -1174,15 +1140,13 @@ def getproj(**proj_params): return PolarStereographic(**proj_params) elif proj_type == ProjectionTypes.MERCATOR: return Mercator(**proj_params) - elif (proj_type == ProjectionTypes.ZERO or + elif (proj_type == ProjectionTypes.ZERO or proj_type == ProjectionTypes.LAT_LON): - if (up_proj_params.get("POLE_LAT", None) == 90. - and up_proj_params.get("POLE_LON", None) == 0.): + if (up_proj_params.get("POLE_LAT", None) == 90. + and up_proj_params.get("POLE_LON", None) == 0.): return LatLon(**proj_params) else: return RotatedLatLon(**proj_params) else: # Unknown projection return WrfProj(**proj_params) - - \ No newline at end of file diff --git a/src/wrf/projutils.py b/src/wrf/projutils.py index 5cc2d14..70f2cf1 100644 --- a/src/wrf/projutils.py +++ b/src/wrf/projutils.py @@ -2,16 +2,17 @@ from __future__ import (absolute_import, division, print_function) from .py3compat import viewitems + def dict_keys_to_upper(d): """Return a dictionary with the keys changed to uppercase. - + Args: - + d (:obj:`dict`): A dictionary. - + Returns: - + :obj:`dict`: A dictionary with uppercase keys. - + """ - return {key.upper() : val for key, val in viewitems(d)} + return {key.upper(): val for key, val in viewitems(d)} diff --git a/src/wrf/py3compat.py b/src/wrf/py3compat.py index 0c08366..4777388 100644 --- a/src/wrf/py3compat.py +++ b/src/wrf/py3compat.py @@ -3,18 +3,19 @@ from __future__ import (absolute_import, division, print_function) from sys import version_info from math import floor, copysign + # Dictionary python 2-3 compatibility stuff def viewitems(d): """Return either the items or viewitems method for a dictionary. - + Args: - + d (:obj:`dict`): A dictionary. - + Returns: - + view method: Either the items or viewitems method. - + """ func = getattr(d, "viewitems", None) if func is None: @@ -24,15 +25,15 @@ def viewitems(d): def viewkeys(d): """Return either the keys or viewkeys method for a dictionary. - + Args: - + d (:obj:`dict`): A dictionary. - + Returns: - + view method: Either the keys or viewkeys method. - + """ func = getattr(d, "viewkeys", None) if func is None: @@ -42,32 +43,33 @@ def viewkeys(d): def viewvalues(d): """Return either the values or viewvalues method for a dictionary. - + Args: - + d (:obj:`dict`): A dictionary. - + Returns: - + view method: Either the values or viewvalues method. - + """ func = getattr(d, "viewvalues", None) if func is None: func = d.values return func() + def isstr(s): """Return True if the object is a string type. - + Args: - + s (string): A string (str, unicode, bytes). - + Returns: - + :obj:`bool`: True if the object is a type of string. Otherwise, False. - + """ try: return isinstance(s, basestring) @@ -75,25 +77,25 @@ def isstr(s): return isinstance(s, str) -# Python 2 rounding behavior +# Python 2 rounding behavior def _round2(x, d=None): - """Return the result of Python 2.x rounding, which is to round the number + """Return the result of Python 2.x rounding, which is to round the number to the nearest integer. - - Python 3.x uses banker's rounding, which is not applicable for nearest + + Python 3.x uses banker's rounding, which is not applicable for nearest neighbor approaches with grid boxes. - + Args: - + x (:obj:`float`): A number, usually a float. - - d (:obj:`int`, optional): The number of digits. Default is None, + + d (:obj:`int`, optional): The number of digits. Default is None, which indicates the nearest integer. - + Returns: - + :obj:`float`: The rounded number. - + """ d = 0 if d is None else d p = 10 ** d @@ -101,69 +103,67 @@ def _round2(x, d=None): def py2round(x, d=None): - """Return the result of Python 2.x rounding, which is to round the number + """Return the result of Python 2.x rounding, which is to round the number to the nearest integer. - - Python 3.x uses banker's rounding, which is not applicable for nearest + + Python 3.x uses banker's rounding, which is not applicable for nearest neighbor approaches with grid boxes. - + Args: - + x (:obj:`float`): A number, usually a float. - - d (:obj:`int`, optional): The number of digits. Default is None, + + d (:obj:`int`, optional): The number of digits. Default is None, which indicates the nearest integer. - + Returns: - + :obj:`float`: The rounded number. - + """ if version_info >= (3,): return _round2(x, d) - + return round(x, d) def py3range(*args): """Return the equivalent of the range function in Python 3.x. - + For Python 2.x, this is the same as the xrange function. - + Args: - + *args: The function arguments for range or xrange. - + Returns: - + iterable: An iterable sequence. - + """ if version_info >= (3,): return range(*args) - + return xrange(*args) def ucode(*args, **kwargs): """Return a Python 3.x unicode string. - + For Python 2.x, this is accomplished by using the unicode function. - + Args: - + *args: The function positional arguments for str or unicode. - + **kwargs: The function keyword arguments for str or unicode. - + Returns: - + string: A unicode string. - + """ if version_info >= (3, ): return str(*args, **kwargs) - - return unicode(*args, **kwargs) - + return unicode(*args, **kwargs) diff --git a/src/wrf/routines.py b/src/wrf/routines.py index aef3697..93727a5 100644 --- a/src/wrf/routines.py +++ b/src/wrf/routines.py @@ -2,8 +2,8 @@ from __future__ import (absolute_import, division, print_function) from .util import (get_iterable, is_standard_wrf_var, extract_vars, viewkeys, get_id) -from .g_cape import (get_2dcape, get_3dcape, get_cape2d_only, - get_cin2d_only, get_lcl, get_lfc, get_3dcape_only, +from .g_cape import (get_2dcape, get_3dcape, get_cape2d_only, + get_cin2d_only, get_lcl, get_lfc, get_3dcape_only, get_3dcin_only) from .g_ctt import get_ctt from .g_dbz import get_dbz, get_max_dbz @@ -16,334 +16,337 @@ from .g_pressure import get_pressure, get_pressure_hpa from .g_pw import get_pw from .g_rh import get_rh, get_rh_2m from .g_slp import get_slp -from .g_temp import get_tc, get_eth, get_temp, get_theta, get_tk, get_tv, get_tw +from .g_temp import (get_tc, get_eth, get_temp, get_theta, get_tk, get_tv, + get_tw) from .g_terrain import get_terrain -from .g_uvmet import (get_uvmet, get_uvmet10, get_uvmet10_wspd_wdir, +from .g_uvmet import (get_uvmet, get_uvmet10, get_uvmet10_wspd_wdir, get_uvmet_wspd_wdir, get_uvmet_wspd, get_uvmet_wdir, get_uvmet10_wspd, get_uvmet10_wdir) from .g_vorticity import get_avo, get_pvo -from .g_wind import (get_destag_wspd_wdir, get_destag_wspd_wdir10, - get_u_destag, get_v_destag, get_w_destag, - get_destag_wspd, get_destag_wdir, get_destag_wspd10, - get_destag_wdir10) +from .g_wind import (get_destag_wspd_wdir, get_destag_wspd_wdir10, + get_u_destag, get_v_destag, get_w_destag, + get_destag_wspd, get_destag_wdir, get_destag_wspd10, + get_destag_wdir10) from .g_times import get_times, get_xtimes from .g_cloudfrac import (get_cloudfrac, get_low_cloudfrac, get_mid_cloudfrac, get_high_cloudfrac) -# func is the function to call. kargs are required arguments that should +# func is the function to call. kargs are required arguments that should # not be altered by the user -_FUNC_MAP = {"cape2d" : get_2dcape, - "cape3d" : get_3dcape, - "dbz" : get_dbz, - "maxdbz" : get_max_dbz, - "dp" : get_dp, - "dp2m" : get_dp_2m, - "height" : get_height, - "geopt" : get_geopt, - "srh" : get_srh, - "uhel" : get_uh, - "omega" : get_omega, - "pw" : get_pw, - "rh" : get_rh, - "rh2m" : get_rh_2m, - "slp" : get_slp, - "theta" : get_theta, - "temp" : get_temp, - "tk" : get_tk, - "tc" : get_tc, - "theta_e" : get_eth, - "tv" : get_tv, - "twb" : get_tw, - "terrain" : get_terrain, - "times" : get_times, - "xtimes" : get_xtimes, - "uvmet" : get_uvmet, - "uvmet10" : get_uvmet10, - "avo" : get_avo, - "pvo" : get_pvo, - "ua" : get_u_destag, - "va" : get_v_destag, - "wa" : get_w_destag, - "lat" : get_lat, - "lon" : get_lon, - "pressure" : get_pressure_hpa, - "pres" : get_pressure, - "wspd_wdir" : get_destag_wspd_wdir, - "wspd_wdir10" : get_destag_wspd_wdir10, - "uvmet_wspd_wdir" : get_uvmet_wspd_wdir, - "uvmet10_wspd_wdir" : get_uvmet10_wspd_wdir, - "ctt" : get_ctt, - "cloudfrac" : get_cloudfrac, - "geopt_stag" : get_stag_geopt, - "zstag" : get_stag_height, +_FUNC_MAP = {"cape2d": get_2dcape, + "cape3d": get_3dcape, + "dbz": get_dbz, + "maxdbz": get_max_dbz, + "dp": get_dp, + "dp2m": get_dp_2m, + "height": get_height, + "geopt": get_geopt, + "srh": get_srh, + "uhel": get_uh, + "omega": get_omega, + "pw": get_pw, + "rh": get_rh, + "rh2m": get_rh_2m, + "slp": get_slp, + "theta": get_theta, + "temp": get_temp, + "tk": get_tk, + "tc": get_tc, + "theta_e": get_eth, + "tv": get_tv, + "twb": get_tw, + "terrain": get_terrain, + "times": get_times, + "xtimes": get_xtimes, + "uvmet": get_uvmet, + "uvmet10": get_uvmet10, + "avo": get_avo, + "pvo": get_pvo, + "ua": get_u_destag, + "va": get_v_destag, + "wa": get_w_destag, + "lat": get_lat, + "lon": get_lon, + "pressure": get_pressure_hpa, + "pres": get_pressure, + "wspd_wdir": get_destag_wspd_wdir, + "wspd_wdir10": get_destag_wspd_wdir10, + "uvmet_wspd_wdir": get_uvmet_wspd_wdir, + "uvmet10_wspd_wdir": get_uvmet10_wspd_wdir, + "ctt": get_ctt, + "cloudfrac": get_cloudfrac, + "geopt_stag": get_stag_geopt, + "zstag": get_stag_height, # Diagnostics below are extracted from multi-product diagnostics - "cape2d_only" : get_cape2d_only, - "cin2d_only" : get_cin2d_only, - "lcl" : get_lcl, - "lfc" : get_lfc, - "cape3d_only" : get_3dcape_only, + "cape2d_only": get_cape2d_only, + "cin2d_only": get_cin2d_only, + "lcl": get_lcl, + "lfc": get_lfc, + "cape3d_only": get_3dcape_only, "cin3d_only": get_3dcin_only, - "uvmet_wspd" : get_uvmet_wspd, - "uvmet_wdir" : get_uvmet_wdir, - "uvmet10_wspd" : get_uvmet10_wspd, - "uvmet10_wdir" : get_uvmet10_wdir, - "wspd" : get_destag_wspd, - "wdir" : get_destag_wdir, - "wspd10" : get_destag_wspd10, - "wdir10" : get_destag_wdir10, - "low_cloudfrac" : get_low_cloudfrac, - "mid_cloudfrac" : get_mid_cloudfrac, - "high_cloudfrac" : get_high_cloudfrac + "uvmet_wspd": get_uvmet_wspd, + "uvmet_wdir": get_uvmet_wdir, + "uvmet10_wspd": get_uvmet10_wspd, + "uvmet10_wdir": get_uvmet10_wdir, + "wspd": get_destag_wspd, + "wdir": get_destag_wdir, + "wspd10": get_destag_wspd10, + "wdir10": get_destag_wdir10, + "low_cloudfrac": get_low_cloudfrac, + "mid_cloudfrac": get_mid_cloudfrac, + "high_cloudfrac": get_high_cloudfrac } - -_VALID_KARGS = {"cape2d" : ["missing"], - "cape3d" : ["missing"], - "dbz" : ["do_variant", "do_liqskin"], - "maxdbz" : ["do_variant", "do_liqskin"], - "dp" : ["units"], - "dp2m" : ["units"], - "height" : ["msl", "units"], - "geopt" : [], - "srh" : ["top"], - "uhel" : ["bottom", "top"], - "omega" : [], - "pw" : [], - "rh" : [], - "rh2m" : [], - "slp" : ["units"], - "temp" : ["units"], - "tk" : [], - "tc" : [], - "theta" : ["units"], - "theta_e" : ["units"], - "tv" : ["units"], - "twb" : ["units"], - "terrain" : ["units"], - "times" : [], - "xtimes" : [], - "uvmet" : ["units"], - "uvmet10" : ["units"], - "avo" : [], - "pvo" : [], - "ua" : ["units"], - "va" : ["units"], - "wa" : ["units"], - "lat" : [], - "lon" : [], - "pres" : ["units"], - "pressure" : ["units"], - "wspd_wdir" : ["units"], - "wspd_wdir10" : ["units"], - "uvmet_wspd_wdir" : ["units"], - "uvmet10_wspd_wdir" : ["units"], - "ctt" : ["fill_nocloud", "missing", "opt_thresh", "units"], - "cloudfrac" : ["vert_type", "low_thresh", - "mid_thresh", "high_thresh"], - "geopt_stag" : [], - "zstag" : ["msl", "units"], - "cape2d_only" : ["missing"], - "cin2d_only" : ["missing"], - "lcl" : ["missing"], - "lfc" : ["missing"], - "cape3d_only" : ["missing"], - "cin3d_only": ["missing"], - "uvmet_wspd" : ["units"], - "uvmet_wdir" : ["units"], - "uvmet10_wspd" : ["units"], - "uvmet10_wdir" : ["units"], - "wspd" : ["units"], - "wdir" : ["units"], - "wspd10" : ["units"], - "wdir10" : ["units"], - "low_cloudfrac" : ["vert_type", "low_thresh", - "mid_thresh", "high_thresh"], - "mid_cloudfrac" : ["vert_type", "low_thresh", - "mid_thresh", "high_thresh"], - "high_cloudfrac" : ["vert_type", "low_thresh", - "mid_thresh", "high_thresh"], - "default" : [] - } - -_ALIASES = {"cape_2d" : "cape2d", - "cape_3d" : "cape3d", - "eth" : "theta_e", - "mdbz" : "maxdbz", - "geopotential" : "geopt", - "helicity" : "srh", - "latitude" : "lat", - "longitude" : "lon", - "omg" : "omega", - "p" : "pres", - "rh2" : "rh2m", + +_VALID_KARGS = {"cape2d": ["missing"], + "cape3d": ["missing"], + "dbz": ["do_variant", "do_liqskin"], + "maxdbz": ["do_variant", "do_liqskin"], + "dp": ["units"], + "dp2m": ["units"], + "height": ["msl", "units"], + "geopt": [], + "srh": ["top"], + "uhel": ["bottom", "top"], + "omega": [], + "pw": [], + "rh": [], + "rh2m": [], + "slp": ["units"], + "temp": ["units"], + "tk": [], + "tc": [], + "theta": ["units"], + "theta_e": ["units"], + "tv": ["units"], + "twb": ["units"], + "terrain": ["units"], + "times": [], + "xtimes": [], + "uvmet": ["units"], + "uvmet10": ["units"], + "avo": [], + "pvo": [], + "ua": ["units"], + "va": ["units"], + "wa": ["units"], + "lat": [], + "lon": [], + "pres": ["units"], + "pressure": ["units"], + "wspd_wdir": ["units"], + "wspd_wdir10": ["units"], + "uvmet_wspd_wdir": ["units"], + "uvmet10_wspd_wdir": ["units"], + "ctt": ["fill_nocloud", "missing", "opt_thresh", "units"], + "cloudfrac": ["vert_type", "low_thresh", + "mid_thresh", "high_thresh"], + "geopt_stag": [], + "zstag": ["msl", "units"], + "cape2d_only": ["missing"], + "cin2d_only": ["missing"], + "lcl": ["missing"], + "lfc": ["missing"], + "cape3d_only": ["missing"], + "cin3d_only": ["missing"], + "uvmet_wspd": ["units"], + "uvmet_wdir": ["units"], + "uvmet10_wspd": ["units"], + "uvmet10_wdir": ["units"], + "wspd": ["units"], + "wdir": ["units"], + "wspd10": ["units"], + "wdir10": ["units"], + "low_cloudfrac": ["vert_type", "low_thresh", + "mid_thresh", "high_thresh"], + "mid_cloudfrac": ["vert_type", "low_thresh", + "mid_thresh", "high_thresh"], + "high_cloudfrac": ["vert_type", "low_thresh", + "mid_thresh", "high_thresh"], + "default": [] + } + +_ALIASES = {"cape_2d": "cape2d", + "cape_3d": "cape3d", + "eth": "theta_e", + "mdbz": "maxdbz", + "geopotential": "geopt", + "helicity": "srh", + "latitude": "lat", + "longitude": "lon", + "omg": "omega", + "p": "pres", + "rh2": "rh2m", "z": "height", - "ter" : "terrain", - "updraft_helicity" : "uhel", - "td" : "dp", - "td2" : "dp2m", - "cfrac" : "cloudfrac", - "wspd_wdir_uvmet" : "uvmet_wspd_wdir", - "wspd_wdir_uvmet10" : "uvmet10_wspd_wdir", - "th" : "theta", - "low_cfrac" : "low_cloudfrac", - "mid_cfrac" : "mid_cloudfrac", - "high_cfrac" : "high_cloudfrac", - "wspd_uvmet" : "uvmet_wspd" , - "wdir_uvmet" : "uvmet_wdir" , - "wspd_uvmet10" : "uvmet10_wspd" , - "wdir_uvmet10" : "uvmet10_wdir" , + "ter": "terrain", + "updraft_helicity": "uhel", + "td": "dp", + "td2": "dp2m", + "cfrac": "cloudfrac", + "wspd_wdir_uvmet": "uvmet_wspd_wdir", + "wspd_wdir_uvmet10": "uvmet10_wspd_wdir", + "th": "theta", + "low_cfrac": "low_cloudfrac", + "mid_cfrac": "mid_cloudfrac", + "high_cfrac": "high_cloudfrac", + "wspd_uvmet": "uvmet_wspd", + "wdir_uvmet": "uvmet_wdir", + "wspd_uvmet10": "uvmet10_wspd", + "wdir_uvmet10": "uvmet10_wdir", } - + + class ArgumentError(Exception): def __init__(self, msg): self.msg = msg - + def __str__(self): return self.msg - + + def _undo_alias(alias): actual = _ALIASES.get(alias, None) if actual is None: return alias else: return actual - + + def _check_kargs(var, kargs): for arg in viewkeys(kargs): if arg not in _VALID_KARGS[var]: if var != "default": raise ValueError("'{}' is an invalid keyword " - "argument for '{}'".format(arg, var)) + "argument for '{}'".format(arg, var)) else: raise ValueError("'{}' is an invalid keyword " "argument".format(arg)) - - -def getvar(wrfin, varname, timeidx=0, - method="cat", squeeze=True, cache=None, meta=True, + + +def getvar(wrfin, varname, timeidx=0, + method="cat", squeeze=True, cache=None, meta=True, **kwargs): - + """Returns basic diagnostics from the WRF ARW model output. - + A table of all available diagnostics is below. - + .. include:: ../../_templates/product_table.txt Args: wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - + varname (:obj:`str`) : The variable name. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. The default is 0. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - - **kwargs: Optional keyword arguments for certain diagnostics. + + **kwargs: Optional keyword arguments for certain diagnostics. See table above. - - + + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - - + + Raises: - :class:`ValueError`: Raised when an invalid diagnostic type or + :class:`ValueError`: Raised when an invalid diagnostic type or keyword argument is passed to the routine. :class:`FortranError`: Raised when a problem occurs during a Fortran calculation. - + See Also: - + :class:`numpy.ndarray`, :class:`xarray.DataArray` - - + + Examples: Using netCDF4 - + .. code-block:: python - + from netCDF4 import Dataset from wrf import getvar - + wrfnc = Dataset("wrfout_d02_2010-06-13_21:00:00") slp = getvar(wrfnc, "slp") - + Using PyNIO - + .. code-block:: python - + from Nio import open_file from wrf import getvar - + wrfnc = open_file("wrfout_d02_2010-06-13_21:00:00"+".nc", "r") slp = getvar(wrfnc, "slp") - + Using Iterables: - + .. code-block:: python - + import os from netCDF4 import Dataset from wrf import getvar - + filedir = "/path/to/wrf/files" - wrfin = [Dataset(f) for f in os.listdir(filedir) + wrfin = [Dataset(f) for f in os.listdir(filedir) if f.startswith("wrfout_d02_")] - + uvmet = getvar(wrfin, "uvmet", timeidx=3, units="kt") - - + + """ - + _key = get_id(wrfin) - + wrfin = get_iterable(wrfin) - + if is_standard_wrf_var(wrfin, varname) and varname != "Times": _check_kargs("default", kwargs) - return extract_vars(wrfin, timeidx, varname, + return extract_vars(wrfin, timeidx, varname, method, squeeze, cache, meta, _key)[varname] elif varname == "Times": varname = "times" # Diverting to the get_times routine - + actual_var = _undo_alias(varname) if actual_var not in _VALID_KARGS: - raise ValueError("'%s' is not a valid variable name" % (varname)) - + raise ValueError("'{}' is not a valid variable name".format(varname)) + _check_kargs(actual_var, kwargs) - - return _FUNC_MAP[actual_var](wrfin, timeidx, method, squeeze, cache, + + return _FUNC_MAP[actual_var](wrfin, timeidx, method, squeeze, cache, meta, _key, **kwargs) - diff --git a/src/wrf/specialdec.py b/src/wrf/specialdec.py index 3845da2..39ee0e1 100644 --- a/src/wrf/specialdec.py +++ b/src/wrf/specialdec.py @@ -2,7 +2,7 @@ from __future__ import (absolute_import, division, print_function) import numpy as np -import wrapt +import wrapt from .util import iter_left_indexes, to_np from .py3compat import py3range @@ -14,28 +14,28 @@ if xarray_enabled(): def uvmet_left_iter(alg_dtype=np.float64): - """A decorator to handle iterating over the leftmost dimensions for the + """A decorator to handle iterating over the leftmost dimensions for the uvmet diagnostic. - + For example, if a wrapped function works with three-dimensional arrays, but - the variables include a 4th leftmost dimension for 'Time', this decorator + the variables include a 4th leftmost dimension for 'Time', this decorator will iterate over all times, call the 3D Fortran routine, and aggregate the results in to a 4D output array. - - It is also important to note that the final output array is allocated - first, and then views are passed to the wrapped function so that values + + It is also important to note that the final output array is allocated + first, and then views are passed to the wrapped function so that values do not need to get copied in to the final output array. - + Args: - - alg_dtype (:class:`np.dtype` or :obj:`str`): The numpy data type used + + alg_dtype (:class:`np.dtype` or :obj:`str`): The numpy data type used in the wrapped function. - + Returns: - - :class:`numpy.ndarray`: The aggregated uvmet output array that includes + + :class:`numpy.ndarray`: The aggregated uvmet output array that includes all extra leftmost dimensions. - + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): @@ -43,64 +43,62 @@ def uvmet_left_iter(alg_dtype=np.float64): v = args[1] lat = args[2] lon = args[3] - cen_long = args[4] + cen_long = args[4] cone = args[5] - + orig_dtype = u.dtype - + lat_lon_fixed = False if lat.ndim == 2: lat_lon_fixed = True - + if lon.ndim == 2 and not lat_lon_fixed: raise ValueError("'lat' and 'lon' shape mismatch") - + num_left_dims_u = u.ndim - 2 num_left_dims_lat = lat.ndim - 2 - + if (num_left_dims_lat > num_left_dims_u): raise ValueError("number of 'lat' dimensions is greater than 'u'") - + if lat_lon_fixed: - mode = 0 # fixed lat/lon + mode = 0 # fixed lat/lon else: if num_left_dims_u == num_left_dims_lat: - mode = 1 # lat/lon same as u + mode = 1 # lat/lon same as u else: - mode = 2 # probably 3D with 2D lat/lon plus Time - + mode = 2 # probably 3D with 2D lat/lon plus Time + has_missing = False u_arr = to_np(u) - + v_arr = to_np(v) - - umissing = default_fill(np.float64) + + umissing = default_fill(np.float64) if isinstance(u_arr, np.ma.MaskedArray): has_missing = True umissing = u_arr.fill_value - - vmissing = default_fill(np.float64) + + vmissing = default_fill(np.float64) if isinstance(v_arr, np.ma.MaskedArray): has_missing = True vmissing = v_arr.fill_value - + uvmetmissing = umissing - + is_stag = 0 if (u.shape[-1] != lat.shape[-1] or u.shape[-2] != lat.shape[-2]): is_stag = 1 # Sanity check if (v.shape[-1] == lat.shape[-1] or v.shape[-2] == lat.shape[-2]): raise ValueError("u is staggered but v is not") - + if (v.shape[-1] != lat.shape[-1] or v.shape[-2] != lat.shape[-2]): is_stag = 1 # Sanity check if (u.shape[-1] == lat.shape[-1] or u.shape[-2] == lat.shape[-2]): raise ValueError("v is staggered but u is not") - - - + # No special left side iteration, return the function result if (num_left_dims_u == 0): return wrapped(u, v, lat, lon, cen_long, cone, isstag=is_stag, @@ -109,44 +107,43 @@ def uvmet_left_iter(alg_dtype=np.float64): # Initial output is time,nz,2,ny,nx to create contiguous views outdims = u.shape[0:num_left_dims_u] - extra_dims = tuple(outdims) # Copy the left-most dims for iteration - + extra_dims = tuple(outdims) # Copy the left-most dims for iteration + outdims += (2,) - + outdims += lat.shape[-2:] - + outview_array = np.empty(outdims, alg_dtype) - + # Final Output moves the u_v dimension to left side output_dims = (2,) output_dims += extra_dims output_dims += lat.shape[-2:] output = np.empty(output_dims, orig_dtype) - + for left_idxs in iter_left_indexes(extra_dims): left_and_slice_idxs = left_idxs + (slice(None),) - + if mode == 0: lat_left_and_slice = (slice(None),) elif mode == 1: lat_left_and_slice = left_and_slice_idxs elif mode == 2: # Only need the left-most - lat_left_and_slice = tuple(left_idx - for left_idx in left_idxs[0:num_left_dims_lat]) - + lat_left_and_slice = tuple( + left_idx for left_idx in left_idxs[0:num_left_dims_lat]) + u_output_idxs = (0,) + left_idxs + (slice(None),) v_output_idxs = (1,) + left_idxs + (slice(None),) u_view_idxs = left_idxs + (0, slice(None)) - v_view_idxs = left_idxs + (1, slice(None)) - - + v_view_idxs = left_idxs + (1, slice(None)) + new_u = u[left_and_slice_idxs] new_v = v[left_and_slice_idxs] new_lat = lat[lat_left_and_slice] new_lon = lon[lat_left_and_slice] outview = outview_array[left_and_slice_idxs] - + # Skip the possible empty/missing arrays for the join method skip_missing = False for arg in (new_u, new_v, new_lat, new_lon): @@ -154,70 +151,69 @@ def uvmet_left_iter(alg_dtype=np.float64): if arg.mask.all(): output[u_output_idxs] = uvmetmissing output[v_output_idxs] = uvmetmissing - + skip_missing = True has_missing = True - + if skip_missing: continue - + # Call the numerical routine result = wrapped(new_u, new_v, new_lat, new_lon, cen_long, cone, - isstag=is_stag, has_missing=has_missing, - umissing=umissing, vmissing=vmissing, + isstag=is_stag, has_missing=has_missing, + umissing=umissing, vmissing=vmissing, uvmetmissing=uvmetmissing, outview=outview) - - # Make sure the result is the same data as what got passed in + + # Make sure the result is the same data as what got passed in # Can delete this once everything works - if (result.__array_interface__["data"][0] != - outview.__array_interface__["data"][0]): + if (result.__array_interface__["data"][0] != + outview.__array_interface__["data"][0]): raise RuntimeError("output array was copied") - + output[u_output_idxs] = ( outview_array[u_view_idxs].astype(orig_dtype)) output[v_output_idxs] = ( outview_array[v_view_idxs].astype(orig_dtype)) - + if has_missing: output = np.ma.masked_values(output, uvmetmissing) - + return output - + return func_wrapper - def cape_left_iter(alg_dtype=np.float64): - """A decorator to handle iterating over the leftmost dimensions for the + """A decorator to handle iterating over the leftmost dimensions for the cape diagnostic. - + For example, if a wrapped function works with three-dimensional arrays, but - the variables include a 4th leftmost dimension for 'Time', this decorator + the variables include a 4th leftmost dimension for 'Time', this decorator will iterate over all times, call the 3D Fortran routine, and aggregate the results in to a 4D output array. - - It is also important to note that the final output array is allocated - first, and then views are passed to the wrapped function so that values + + It is also important to note that the final output array is allocated + first, and then views are passed to the wrapped function so that values do not need to get copied in to the final output array. - + Args: - - alg_dtype (:class:`np.dtype` or :obj:`str`): The numpy data type used + + alg_dtype (:class:`np.dtype` or :obj:`str`): The numpy data type used in the wrapped function. - + Returns: - - :class:`numpy.ndarray`: The aggregated cape output array that includes + + :class:`numpy.ndarray`: The aggregated cape output array that includes all extra leftmost dimensions. - + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): # The cape calculations use an ascending vertical pressure coordinate - + new_args = list(args) new_kwargs = dict(kwargs) - + p_hpa = args[0] tk = args[1] qv = args[2] @@ -227,19 +223,19 @@ def cape_left_iter(alg_dtype=np.float64): missing = args[6] i3dflag = args[7] ter_follow = args[8] - + is2d = i3dflag == 0 # Note: This should still work with DataArrays is1d = np.isscalar(sfp) or np.size(sfp) == 1 - + # Make sure sfp and terrain are regular floats for 1D case # This should also work with DataArrays if is1d: ter = float(ter) - sfp = float(sfp) - + sfp = float(sfp) + orig_dtype = p_hpa.dtype - + if not is1d: # Need to order in ascending pressure order flip = False @@ -247,48 +243,48 @@ def cape_left_iter(alg_dtype=np.float64): top_idxs = list(bot_idxs) top_idxs[-3] = -1 top_idxs = tuple(top_idxs) - + if p_hpa[bot_idxs] > p_hpa[top_idxs]: flip = True - p_hpa = np.ascontiguousarray(p_hpa[...,::-1,:,:]) - tk = np.ascontiguousarray(tk[...,::-1,:,:]) - qv = np.ascontiguousarray(qv[...,::-1,:,:]) - ht = np.ascontiguousarray(ht[...,::-1,:,:]) + p_hpa = np.ascontiguousarray(p_hpa[..., ::-1, :, :]) + tk = np.ascontiguousarray(tk[..., ::-1, :, :]) + qv = np.ascontiguousarray(qv[..., ::-1, :, :]) + ht = np.ascontiguousarray(ht[..., ::-1, :, :]) new_args[0] = p_hpa new_args[1] = tk new_args[2] = qv new_args[3] = ht - + num_left_dims = p_hpa.ndim - 3 else: # Need to order in ascending pressure order flip = False - + if p_hpa[0] > p_hpa[-1]: flip = True p_hpa = np.ascontiguousarray(p_hpa[::-1]) tk = np.ascontiguousarray(tk[::-1]) qv = np.ascontiguousarray(qv[::-1]) ht = np.ascontiguousarray(ht[::-1]) - + # Need to make 3D views for the fortran code. # Going to make these fortran ordered, since the f_contiguous and - # c_contiguous flags are broken in numpy 1.11 (always false). This - # should work across all numpy versions. + # c_contiguous flags are broken in numpy 1.11 (always false). This + # should work across all numpy versions. new_args[0] = p_hpa.reshape((1, 1, p_hpa.shape[0]), order='F') new_args[1] = tk.reshape((1, 1, tk.shape[0]), order='F') new_args[2] = qv.reshape((1, 1, qv.shape[0]), order='F') new_args[3] = ht.reshape((1, 1, ht.shape[0]), order='F') - new_args[4] = np.full((1,1), ter, orig_dtype) - new_args[5] = np.full((1,1), sfp, orig_dtype) - + new_args[4] = np.full((1, 1), ter, orig_dtype) + new_args[5] = np.full((1, 1), sfp, orig_dtype) + num_left_dims = 0 - + # No special left side iteration, build the output from the cape,cin # result if (num_left_dims == 0): cape, cin = wrapped(*new_args, **new_kwargs) - + output_dims = (2,) if not is1d: output_dims += p_hpa.shape[-3:] @@ -296,26 +292,26 @@ def cape_left_iter(alg_dtype=np.float64): output_dims += (p_hpa.shape[0], 1, 1) output = np.empty(output_dims, orig_dtype) - + if flip and not is2d: - output[0,:] = cape[::-1,:,:] - output[1,:] = cin[::-1,:,:] + output[0, :] = cape[::-1, :, :] + output[1, :] = cin[::-1, :, :] else: - output[0,:] = cape[:] - output[1,:] = cin[:] - + output[0, :] = cape[:] + output[1, :] = cin[:] + return output # Initial output is ...,cape_cin,nz,ny,nx to create contiguous views outdims = p_hpa.shape[0:num_left_dims] - extra_dims = tuple(outdims) # Copy the left-most dims for iteration - - outdims += (2,) # cape_cin - + extra_dims = tuple(outdims) # Copy the left-most dims for iteration + + outdims += (2,) # cape_cin + outdims += p_hpa.shape[-3:] - + outview_array = np.empty(outdims, alg_dtype) - + # Create the output array where the leftmost dim is the product type output_dims = (2,) output_dims += extra_dims @@ -326,14 +322,14 @@ def cape_left_iter(alg_dtype=np.float64): left_and_slice_idxs = left_idxs + (slice(None),) cape_idxs = left_idxs + (0, slice(None)) cin_idxs = left_idxs + (1, slice(None)) - + cape_output_idxs = (0,) + left_idxs + (slice(None),) cin_output_idxs = (1,) + left_idxs + (slice(None),) - view_cape_reverse_idxs = left_idxs + (0, slice(None,None,-1), + view_cape_reverse_idxs = left_idxs + (0, slice(None, None, -1), slice(None)) - view_cin_reverse_idxs = left_idxs + (1, slice(None,None,-1), + view_cin_reverse_idxs = left_idxs + (1, slice(None, None, -1), slice(None)) - + new_args[0] = p_hpa[left_and_slice_idxs] new_args[1] = tk[left_and_slice_idxs] new_args[2] = qv[left_and_slice_idxs] @@ -342,9 +338,9 @@ def cape_left_iter(alg_dtype=np.float64): new_args[5] = sfp[left_and_slice_idxs] capeview = outview_array[cape_idxs] cinview = outview_array[cin_idxs] - + # Skip the possible empty/missing arrays for the join method - # Note: Masking handled by cape.py or computation.py, so only + # Note: Masking handled by cape.py or computation.py, so only # supply the fill values here. skip_missing = False for arg in (new_args[0:6]): @@ -356,25 +352,24 @@ def cape_left_iter(alg_dtype=np.float64): else: output[cape_output_idxs] = missing output[cin_output_idxs] = missing - + skip_missing = True - + if skip_missing: continue - + # Call the numerical routine new_kwargs["capeview"] = capeview new_kwargs["cinview"] = cinview - + cape, cin = wrapped(*new_args, **new_kwargs) - - # Make sure the result is the same data as what got passed in + + # Make sure the result is the same data as what got passed in # Can delete this once everything works - if (cape.__array_interface__["data"][0] != - capeview.__array_interface__["data"][0]): + if (cape.__array_interface__["data"][0] != + capeview.__array_interface__["data"][0]): raise RuntimeError("output array was copied") - - + if flip and not is2d: output[cape_output_idxs] = ( outview_array[view_cape_reverse_idxs].astype(orig_dtype)) @@ -382,82 +377,82 @@ def cape_left_iter(alg_dtype=np.float64): outview_array[view_cin_reverse_idxs].astype(orig_dtype)) else: output[cape_output_idxs] = ( - outview_array[cape_idxs].astype(orig_dtype)) + outview_array[cape_idxs].astype(orig_dtype)) output[cin_output_idxs] = ( - outview_array[cin_idxs].astype(orig_dtype)) - + outview_array[cin_idxs].astype(orig_dtype)) + return output - + return func_wrapper def cloudfrac_left_iter(alg_dtype=np.float64): - """A decorator to handle iterating over the leftmost dimensions for the + """A decorator to handle iterating over the leftmost dimensions for the cloud fraction diagnostic. - + For example, if a wrapped function works with three-dimensional arrays, but - the variables include a 4th leftmost dimension for 'Time', this decorator + the variables include a 4th leftmost dimension for 'Time', this decorator will iterate over all times, call the 3D Fortran routine, and aggregate the results in to a 4D output array. - - It is also important to note that the final output array is allocated - first, and then views are passed to the wrapped function so that values + + It is also important to note that the final output array is allocated + first, and then views are passed to the wrapped function so that values do not need to get copied in to the final output array. - + Args: - - alg_dtype (:class:`np.dtype` or :obj:`str`): The numpy data type used + + alg_dtype (:class:`np.dtype` or :obj:`str`): The numpy data type used in the wrapped function. - + Returns: - - :class:`numpy.ndarray`: The aggregated cloud fraction output array + + :class:`numpy.ndarray`: The aggregated cloud fraction output array that includes all extra leftmost dimensions. - + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): new_args = list(args) new_kwargs = dict(kwargs) - + vert = args[0] rh = args[1] - + num_left_dims = vert.ndim - 3 orig_dtype = vert.dtype - - # No special left side iteration, build the output from the + + # No special left side iteration, build the output from the # low, mid, high results. if (num_left_dims == 0): low, mid, high = wrapped(*new_args, **new_kwargs) - + output_dims = (3,) output_dims += vert.shape[-2:] output = np.empty(output_dims, orig_dtype) - - output[0,:] = low[:] - output[1,:] = mid[:] - output[2,:] = high[:] - + + output[0, :] = low[:] + output[1, :] = mid[:] + output[2, :] = high[:] + return output - - # Initial output is ...,low_mid_high,nz,ny,nx to create contiguous views + # Initial output is ...,low_mid_high,nz,ny,nx to create contiguous + # views outdims = vert.shape[0:num_left_dims] - extra_dims = tuple(outdims) # Copy the left-most dims for iteration - - outdims += (3,) # low_mid_high - + extra_dims = tuple(outdims) # Copy the left-most dims for iteration + + outdims += (3,) # low_mid_high + outdims += vert.shape[-2:] - + outview_array = np.empty(outdims, alg_dtype) - + # Create the output array where the leftmost dim is the cloud type output_dims = (3,) output_dims += extra_dims output_dims += vert.shape[-2:] output = np.empty(output_dims, orig_dtype) - + has_missing = False missing = default_fill(np.float64) for left_idxs in iter_left_indexes(extra_dims): @@ -465,16 +460,16 @@ def cloudfrac_left_iter(alg_dtype=np.float64): low_idxs = left_idxs + (0, slice(None)) mid_idxs = left_idxs + (1, slice(None)) high_idxs = left_idxs + (2, slice(None)) - + low_output_idxs = (0,) + left_idxs + (slice(None),) mid_output_idxs = (1,) + left_idxs + (slice(None),) high_output_idxs = (2,) + left_idxs + (slice(None),) - + new_args[0] = vert[left_and_slice_idxs] new_args[1] = rh[left_and_slice_idxs] - + # Skip the possible empty/missing arrays for the join method - # Note: Masking handled by cloudfrac.py or computation.py, so only + # Note: Masking handled by cloudfrac.py or computation.py, so only # supply the fill values here. skip_missing = False for arg in (new_args[0:2]): @@ -483,41 +478,41 @@ def cloudfrac_left_iter(alg_dtype=np.float64): output[low_output_idxs] = missing output[mid_output_idxs] = missing output[high_output_idxs] = missing - + skip_missing = True has_missing = True - + if skip_missing: continue - + lowview = outview_array[low_idxs] midview = outview_array[mid_idxs] highview = outview_array[high_idxs] - + new_kwargs["lowview"] = lowview new_kwargs["midview"] = midview new_kwargs["highview"] = highview - + low, mid, high = wrapped(*new_args, **new_kwargs) - - # Make sure the result is the same data as what got passed in + + # Make sure the result is the same data as what got passed in # Can delete this once everything works - if (low.__array_interface__["data"][0] != - lowview.__array_interface__["data"][0]): + if (low.__array_interface__["data"][0] != + lowview.__array_interface__["data"][0]): raise RuntimeError("output array was copied") - + output[low_output_idxs] = ( - outview_array[low_idxs].astype(orig_dtype)) + outview_array[low_idxs].astype(orig_dtype)) output[mid_output_idxs] = ( - outview_array[mid_idxs].astype(orig_dtype)) + outview_array[mid_idxs].astype(orig_dtype)) output[high_output_idxs] = ( - outview_array[high_idxs].astype(orig_dtype)) - + outview_array[high_idxs].astype(orig_dtype)) + if has_missing: output = np.ma.masked_values(output, missing) - + return output - + return func_wrapper @@ -526,95 +521,94 @@ def interplevel_left_iter(is2dlev, alg_dtype=np.float64): def func_wrapper(wrapped, instance, args, kwargs): new_args = list(args) new_kwargs = dict(kwargs) - + field3d = args[0] z = args[1] levels = args[2] - + num_left_dims = z.ndim - 3 orig_dtype = field3d.dtype left_dims = z.shape[0:num_left_dims] multiproduct = True if field3d.ndim - z.ndim == 1 else False - - # No special left side iteration, build the output from the + + # No special left side iteration, build the output from the # low, mid, high results. if (num_left_dims == 0): if multiproduct: if not is2dlev: - outshape = (field3d.shape[0:-3] + levels.shape + + outshape = (field3d.shape[0:-3] + levels.shape + field3d.shape[-2:]) else: outshape = (field3d.shape[0:-3] + field3d.shape[-2:]) - + output = np.empty(outshape, dtype=alg_dtype) for i in py3range(field3d.shape[0]): - new_args[0] = field3d[i,:] - new_kwargs["outview"] = output[i,:] + new_args[0] = field3d[i, :] + new_kwargs["outview"] = output[i, :] _ = wrapped(*new_args, **new_kwargs) else: output = wrapped(*args, **kwargs) return output - + if multiproduct: outdims = field3d.shape[0:1] + left_dims else: outdims = left_dims - + extra_dims = tuple(outdims) - + if not is2dlev: outdims += levels.shape - + outdims += z.shape[-2:] - + outview_array = np.empty(outdims, alg_dtype) for left_idxs in iter_left_indexes(extra_dims): - + field_out_slice_idxs = left_idxs + (slice(None),) - + if multiproduct: z_slice_idxs = left_idxs[1:] + (slice(None),) else: z_slice_idxs = left_idxs + (slice(None),) - - + new_args[0] = field3d[field_out_slice_idxs] new_args[1] = z[z_slice_idxs] - + if is2dlev: if levels.ndim > 2: new_args[2] = levels[z_slice_idxs] - + new_kwargs["outview"] = outview_array[field_out_slice_idxs] - + _ = wrapped(*new_args, **new_kwargs) - + output = outview_array.astype(orig_dtype) - + return output - + return func_wrapper - + def check_cape_args(): """A decorator to check that the cape_3d arguments are valid. - + An exception is raised when an invalid argument is found. - + Returns: - + None - + Raises: - + :class:`ValueError`: Raised when an invalid argument is detected. - + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): - + p_hpa = args[0] tk = args[1] qv = args[2] @@ -624,11 +618,11 @@ def check_cape_args(): missing = args[6] i3dflag = args[7] ter_follow = args[8] - + is2d = False if i3dflag != 0 else True - is1d = ((np.isscalar(sfp) or np.size(sfp) == 1) or + is1d = ((np.isscalar(sfp) or np.size(sfp) == 1) or (np.isscalar(ter) or np.size(ter) == 1)) - + if not (p_hpa.shape == tk.shape == qv.shape == ht.shape): raise ValueError("arguments 0, 1, 2, 3 must be the same shape") @@ -641,41 +635,41 @@ def check_cape_args(): if np.size(ter) != np.size(sfp): raise ValueError("arguments 4 and 5 must both be scalars or " "both be arrays") - + # Only need to test p_hpa since we assured args 0-3 have same ndim if p_hpa.ndim != 1: raise ValueError("arguments 0-3 " "must be 1-dimensional when " "arguments 4 and 5 are scalars") - + return wrapped(*args, **kwargs) - + return func_wrapper def check_interplevel_args(is2dlev): """A decorator to check that the interplevel arguments are valid. - + An exception is raised when an invalid argument is found. - + Returns: - + None - + Raises: - + :class:`ValueError`: Raised when an invalid argument is detected. - + """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): - + field3d = args[0] z = args[1] levels = args[2] - + multiproduct = True if (field3d.ndim - z.ndim) == 1 else False - + if not multiproduct: if field3d.shape != z.shape: raise ValueError("arguments 0 and 1 must have the same shape") @@ -683,16 +677,15 @@ def check_interplevel_args(is2dlev): if field3d.shape[1:] != z.shape: raise ValueError("argument 0 and 1 must have same rightmost " "dimensions") - + if is2dlev: if levels.ndim != 2: - if (levels.shape[0:-2] != z.shape[0:-3] or - levels.shape[-2:] != z.shape[-2:]): + if (levels.shape[0:-2] != z.shape[0:-3] or + levels.shape[-2:] != z.shape[-2:]): raise ValueError("argument 1 and 2 must have " "the same leftmost and rightmost " "dimensions") - + return wrapped(*args, **kwargs) - - return func_wrapper + return func_wrapper diff --git a/src/wrf/units.py b/src/wrf/units.py index 11420cf..54d0e2d 100755 --- a/src/wrf/units.py +++ b/src/wrf/units.py @@ -4,31 +4,31 @@ from .constants import Constants, ConversionFactors def _apply_conv_fact(var, vartype, var_unit, dest_unit): - """Return the variable converted to different units using a conversion + """Return the variable converted to different units using a conversion factor. - + Args: - - var (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A + + var (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A variable. - - vartype (:obj:`str`): The type of variable. Choices are: 'wind', + + vartype (:obj:`str`): The type of variable. Choices are: 'wind', 'pressure', 'temp', or 'height'. - + var_unit (:obj:`str`): The variable's current units. - + dest_unit (:obj:`str`): The desired units. - + Returns: - + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The variable in - the desired units. - + the desired units. + """ if var_unit == dest_unit: return var - - # Note, case where var_unit and dest_unit are base unit, should be + + # Note, case where var_unit and dest_unit are base unit, should be # handled above if var_unit == _BASE_UNITS[vartype]: return var*(_CONV_FACTORS[vartype]["to_dest"][dest_unit]) @@ -36,62 +36,62 @@ def _apply_conv_fact(var, vartype, var_unit, dest_unit): if dest_unit == _BASE_UNITS[vartype]: return var*(_CONV_FACTORS[vartype]["to_base"][var_unit]) else: - return var*(_CONV_FACTORS[vartype]["to_base"][var_unit] * - _CONV_FACTORS[vartype]["to_dest"][dest_unit]) + return var*(_CONV_FACTORS[vartype]["to_base"][var_unit] * + _CONV_FACTORS[vartype]["to_dest"][dest_unit]) def _to_kelvin(var, var_unit): """Return the variable in Kelvin. - + Args: - - var (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A + + var (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A variable. - + var_unit (:obj:`str`): The variable's current units. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The variable in + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The variable in Kelvin. - + """ if var_unit == "c": return var + Constants.CELKEL elif var_unit == "f": return (var - 32.0) * (5.0/9.0) + Constants.CELKEL - - + + def _k_to_c(var): """Return the variable in Celsius. - + Args: - - var (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A + + var (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A variable in units of Kelvin. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The variable in + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The variable in Celsius. - + """ return var - Constants.CELKEL def _k_to_f(var): """Return the variable in Fahrenheit. - + Args: - - var (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A + + var (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A variable in units of Kelvin. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The variable in + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The variable in Fahrenheit. - + """ return 1.8 * _k_to_c(var) + 32.0 @@ -99,25 +99,25 @@ def _k_to_f(var): def _apply_temp_conv(var, var_unit, dest_unit): """Return the variable converted to different units using a temperature conversion algorithm. - + Args: - - var (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A + + var (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A variable. - + var_unit (:obj:`str`): The variable's current units. - + dest_unit (:obj:`str`): The desired units. - + Returns: - + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The variable in - the desired units. - + the desired units. + """ if dest_unit == var_unit: return var - + if var_unit != _BASE_UNITS["temp"]: tk = _to_kelvin(var, var_unit) if dest_unit == _BASE_UNITS["temp"]: @@ -126,247 +126,237 @@ def _apply_temp_conv(var, var_unit, dest_unit): return (_TEMP_CONV_METHODS[dest_unit])(tk) else: return (_TEMP_CONV_METHODS[dest_unit])(var) - + # A mapping of unit names to their dictionary key names -_UNIT_ALIASES = {"mps" : "m s-1", - "m/s" : "m s-1", - "ms-1" : "m s-1", - "meters_per_second" : "m s-1", - "metres_per_second" : "m s-1", - "knots" : "kt", - "knot" : "kt", - "kts" : "kt", - "kn" : "kt", - "miles_per_hour" : "mi h-1", - "mih-1" : "mi h-1", - "mph" : "mi h-1", - "mi/h" : "mi h-1", - "kmph" : "km h-1", - "kmh-1" : "km h-1", - "km/h" : "km h-1", - "kilometers_per_hour" : "km h-1", - "kilometres_per_hour" : "km h-1", - "ft/s" : "ft s-1", - "ft/sec" : "ft s-1", - "fps" : "ft s-1", - "fs-1" : "ft s-1", - "feet_per_second" : "ft s-1", - - "pascal" : "pa", - "pascals" : "pa", - "hecto_pascal" : "hpa", - "hecto_pascals" : "hpa", - "millibar" : "mb", - "millibars" : "mb", - "mbar" : "mb", - - "kelvin" : "k", - "degree_kelvin" : "k", - "degrees_kelvin" : "k", - "degree_k" : "k", - "degrees_k" : "k", - "degreek" : "k", - "degreesk" : "k", - "degk" : "k", - "degsk" : "k", - "deg_k" : "k", - "degs_k" : "k", - "deg k" : "k", - "degs k" : "k", - - "celsius" : "c", - "degree_celsius" : "c", - "degrees_celsius" : "c", - "degree_c" : "c", - "degrees_c" : "c", - "degreec" : "c", - "degreesc" : "c", - "degc" : "c", - "degsc" : "c", - "deg_c" : "c", - "degs_c" : "c", - "deg c" : "c", - "degs c" : "c", - - "fahrenheit" : "f", - "degree_fahrenheit" : "f", - "degrees_fahrenheit" : "f", - "degree_f" : "f", - "degrees_f" : "f", - "degreef" : "f", - "degreesf" : "f", - "degf" : "f", - "degsf" : "f", - "deg_f" : "f", - "degs_f" : "f", - "deg f" : "f", - "degs f" : "f", - - "meter" : "m", - "meters" : "m", - "metre" : "m", - "metres" : "m", - "kilometer" : "km", - "kilometers" : "km", - "dekameter" : "dm", - "dekameters" : "dm", - "decameter" : "dm", - "decameters" : "dm", - "dekametre" : "dm", - "dekametres" : "dm", - "decametre" : "dm", - "decametres" : "dm", - "dam" : "dm", - "dkm" : "dm", - "feet" : "ft", - "foot" : "ft", - "mile" : "mi", - "miles" : "mi" - - } +_UNIT_ALIASES = {"mps": "m s-1", + "m/s": "m s-1", + "ms-1": "m s-1", + "meters_per_second": "m s-1", + "metres_per_second": "m s-1", + "knots": "kt", + "knot": "kt", + "kts": "kt", + "kn": "kt", + "miles_per_hour": "mi h-1", + "mih-1": "mi h-1", + "mph": "mi h-1", + "mi/h": "mi h-1", + "kmph": "km h-1", + "kmh-1": "km h-1", + "km/h": "km h-1", + "kilometers_per_hour": "km h-1", + "kilometres_per_hour": "km h-1", + "ft/s": "ft s-1", + "ft/sec": "ft s-1", + "fps": "ft s-1", + "fs-1": "ft s-1", + "feet_per_second": "ft s-1", + "pascal": "pa", + "pascals": "pa", + "hecto_pascal": "hpa", + "hecto_pascals": "hpa", + "millibar": "mb", + "millibars": "mb", + "mbar": "mb", + "kelvin": "k", + "degree_kelvin": "k", + "degrees_kelvin": "k", + "degree_k": "k", + "degrees_k": "k", + "degreek": "k", + "degreesk": "k", + "degk": "k", + "degsk": "k", + "deg_k": "k", + "degs_k": "k", + "deg k": "k", + "degs k": "k", + "celsius": "c", + "degree_celsius": "c", + "degrees_celsius": "c", + "degree_c": "c", + "degrees_c": "c", + "degreec": "c", + "degreesc": "c", + "degc": "c", + "degsc": "c", + "deg_c": "c", + "degs_c": "c", + "deg c": "c", + "degs c": "c", + "fahrenheit": "f", + "degree_fahrenheit": "f", + "degrees_fahrenheit": "f", + "degree_f": "f", + "degrees_f": "f", + "degreef": "f", + "degreesf": "f", + "degf": "f", + "degsf": "f", + "deg_f": "f", + "degs_f": "f", + "deg f": "f", + "degs f": "f", + "meter": "m", + "meters": "m", + "metre": "m", + "metres": "m", + "kilometer": "km", + "kilometers": "km", + "dekameter": "dm", + "dekameters": "dm", + "decameter": "dm", + "decameters": "dm", + "dekametre": "dm", + "dekametres": "dm", + "decametre": "dm", + "decametres": "dm", + "dam": "dm", + "dkm": "dm", + "feet": "ft", + "foot": "ft", + "mile": "mi", + "miles": "mi" + } # A mapping of unit types to the avaible units -_VALID_UNITS = {"wind" : ["m s-1", "kt", "mi h-1", "km h-1", "ft s-1"], - "pressure" : ["pa", "hpa", "mb", "torr", "mmhg", "atm"], - "temp" : ["k", "f", "c"], - "height" : ["m", "km", "dm", "ft", "mi"] - } +_VALID_UNITS = {"wind": ["m s-1", "kt", "mi h-1", "km h-1", "ft s-1"], + "pressure": ["pa", "hpa", "mb", "torr", "mmhg", "atm"], + "temp": ["k", "f", "c"], + "height": ["m", "km", "dm", "ft", "mi"] + } # Conversion factor map for wind from base units -_WIND_BASE_FACTORS = {"kt" : ConversionFactors.MPS_TO_KTS, - "km h-1" : ConversionFactors.MPS_TO_KMPH, - "mi h-1" : ConversionFactors.MPS_TO_MPH, - "ft s-1" : ConversionFactors.MPS_TO_FPS - } +_WIND_BASE_FACTORS = {"kt": ConversionFactors.MPS_TO_KTS, + "km h-1": ConversionFactors.MPS_TO_KMPH, + "mi h-1": ConversionFactors.MPS_TO_MPH, + "ft s-1": ConversionFactors.MPS_TO_FPS + } # Conversion factor map to base units -_WIND_TOBASE_FACTORS = {"kt" : 1.0/ConversionFactors.MPS_TO_KTS, - "km h-1" : 1.0/ConversionFactors.MPS_TO_KMPH, - "mi h-1" : 1.0/ConversionFactors.MPS_TO_MPH, - "ft s-1" : 1.0/ConversionFactors.MPS_TO_FPS +_WIND_TOBASE_FACTORS = {"kt": 1.0/ConversionFactors.MPS_TO_KTS, + "km h-1": 1.0/ConversionFactors.MPS_TO_KMPH, + "mi h-1": 1.0/ConversionFactors.MPS_TO_MPH, + "ft s-1": 1.0/ConversionFactors.MPS_TO_FPS } # Conversion factor map for pressure from base units -_PRES_BASE_FACTORS = {"hpa" : ConversionFactors.PA_TO_HPA, - "mb" : ConversionFactors.PA_TO_HPA, - "torr" : ConversionFactors.PA_TO_TORR, - "mmhg" : ConversionFactors.PA_TO_MMHG, - "atm" : ConversionFactors.PA_TO_ATM +_PRES_BASE_FACTORS = {"hpa": ConversionFactors.PA_TO_HPA, + "mb": ConversionFactors.PA_TO_HPA, + "torr": ConversionFactors.PA_TO_TORR, + "mmhg": ConversionFactors.PA_TO_MMHG, + "atm": ConversionFactors.PA_TO_ATM } # Conversion factor map for pressure to base units -_PRES_TOBASE_FACTORS = {"hpa" : 1.0/ConversionFactors.PA_TO_HPA, - "mb" : 1.0/ConversionFactors.PA_TO_HPA, - "torr" : 1.0/ConversionFactors.PA_TO_TORR, - "mmhg" : 1.0/ConversionFactors.PA_TO_MMHG, - "atm" : 1.0/ConversionFactors.PA_TO_ATM +_PRES_TOBASE_FACTORS = {"hpa": 1.0/ConversionFactors.PA_TO_HPA, + "mb": 1.0/ConversionFactors.PA_TO_HPA, + "torr": 1.0/ConversionFactors.PA_TO_TORR, + "mmhg": 1.0/ConversionFactors.PA_TO_MMHG, + "atm": 1.0/ConversionFactors.PA_TO_ATM } # Conversion factor map for height from base units -_HEIGHT_BASE_FACTORS = {"km" : ConversionFactors.M_TO_KM, - "dm" : ConversionFactors.M_TO_DM, - "ft" : ConversionFactors.M_TO_FT, - "mi" : ConversionFactors.M_TO_MILES +_HEIGHT_BASE_FACTORS = {"km": ConversionFactors.M_TO_KM, + "dm": ConversionFactors.M_TO_DM, + "ft": ConversionFactors.M_TO_FT, + "mi": ConversionFactors.M_TO_MILES } # Conversion factor map for height to base units -_HEIGHT_TOBASE_FACTORS = {"km" : 1.0/ConversionFactors.M_TO_KM, - "dm" : 1.0/ConversionFactors.M_TO_DM, - "ft" : 1.0/ConversionFactors.M_TO_FT, - "mi" : 1.0/ConversionFactors.M_TO_MILES - } +_HEIGHT_TOBASE_FACTORS = {"km": 1.0/ConversionFactors.M_TO_KM, + "dm": 1.0/ConversionFactors.M_TO_DM, + "ft": 1.0/ConversionFactors.M_TO_FT, + "mi": 1.0/ConversionFactors.M_TO_MILES + } # Mapping of unit type to base unit type -_BASE_UNITS = {"wind" : "m s-1", - "pressure" : "pa", - "temp" : "k", - "height" : "m" +_BASE_UNITS = {"wind": "m s-1", + "pressure": "pa", + "temp": "k", + "height": "m" } # A mapping of unit type to a mapping of to/from base conversion factors -_CONV_FACTORS = {"wind" : {"to_dest" : _WIND_BASE_FACTORS, - "to_base" : _WIND_TOBASE_FACTORS}, - "pressure" : {"to_dest" : _PRES_BASE_FACTORS, - "to_base" : _PRES_TOBASE_FACTORS}, - "height" : {"to_dest" : _HEIGHT_BASE_FACTORS, - "to_base" : _HEIGHT_TOBASE_FACTORS} +_CONV_FACTORS = {"wind": {"to_dest": _WIND_BASE_FACTORS, + "to_base": _WIND_TOBASE_FACTORS}, + "pressure": {"to_dest": _PRES_BASE_FACTORS, + "to_base": _PRES_TOBASE_FACTORS}, + "height": {"to_dest": _HEIGHT_BASE_FACTORS, + "to_base": _HEIGHT_TOBASE_FACTORS} } # A mapping of temperature type to the conversion function -_TEMP_CONV_METHODS = {"c" : _k_to_c, - "f" : _k_to_f +_TEMP_CONV_METHODS = {"c": _k_to_c, + "f": _k_to_f } + def dealias_and_clean_unit(unit): """Return the properly cleaned and dealiased unit name. - + Args: - + unit (:obj:`str`): The unit name. - + Returns: - + :obj:`str`: A unit name suitable for dictionary key lookups. - + """ cleaned_unit = " ".join(unit.lower().split()) dealiased = _UNIT_ALIASES.get(cleaned_unit, None) - + return cleaned_unit if dealiased is None else dealiased - + def check_units(unit, unit_type): """Raise an exception if the unit name is invalid. - + Args: - + unit (:obj:`str`): The unit name. - + unit_type (:obj:`str`): The type of unit. - + Returns: - + None - + Raises: - + :class:`ValueError`: Raised when the unit name is invalid. - + """ u_cleaned = dealias_and_clean_unit(unit) if u_cleaned not in _VALID_UNITS[unit_type]: - raise ValueError("invalid unit type '%s'" % unit) + raise ValueError("invalid unit type '{}'".format(unit)) def do_conversion(var, vartype, var_unit, dest_unit): """Return the variable converted to different units. - + Args: - - var (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A + + var (:class:`xarray.DataArray` or :class:`numpy.ndarray`): A variable. - - vartype (:obj:`str`): The type of variable. Choices are: 'wind', + + vartype (:obj:`str`): The type of variable. Choices are: 'wind', 'pressure', 'temp', or 'height'. - + var_unit (:obj:`str`): The variable's current units. - + dest_unit (:obj:`str`): The desired units. - + Returns: - + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The variable in - the desired units. - + the desired units. + """ - u_cleaned = dealias_and_clean_unit(dest_unit) + u_cleaned = dealias_and_clean_unit(dest_unit) if vartype != "temp": return _apply_conv_fact(var, vartype, var_unit.lower(), u_cleaned) else: return _apply_temp_conv(var, var_unit.lower(), u_cleaned) - - - - - diff --git a/src/wrf/util.py b/src/wrf/util.py index 0aaf94d..d8f6567 100644 --- a/src/wrf/util.py +++ b/src/wrf/util.py @@ -14,7 +14,7 @@ try: except ImportError: from inspect import getargspec -try: +try: from inspect import getargvalues except ImportError: from inspect import getgeneratorlocals @@ -41,215 +41,215 @@ if xarray_enabled(): from xarray import DataArray -_COORD_PAIR_MAP = {"XLAT" : ("XLAT", "XLONG"), - "XLONG" : ("XLAT", "XLONG"), - "XLAT_M" : ("XLAT_M", "XLONG_M"), - "XLONG_M" : ("XLAT_M", "XLONG_M"), - "XLAT_U" : ("XLAT_U", "XLONG_U"), - "XLONG_U" : ("XLAT_U", "XLONG_U"), - "XLAT_V" : ("XLAT_V", "XLONG_V"), - "XLONG_V" : ("XLAT_V", "XLONG_V"), - "CLAT" : ("CLAT", "CLONG"), - "CLONG" : ("CLAT", "CLONG")} +_COORD_PAIR_MAP = {"XLAT": ("XLAT", "XLONG"), + "XLONG": ("XLAT", "XLONG"), + "XLAT_M": ("XLAT_M", "XLONG_M"), + "XLONG_M": ("XLAT_M", "XLONG_M"), + "XLAT_U": ("XLAT_U", "XLONG_U"), + "XLONG_U": ("XLAT_U", "XLONG_U"), + "XLAT_V": ("XLAT_V", "XLONG_V"), + "XLONG_V": ("XLAT_V", "XLONG_V"), + "CLAT": ("CLAT", "CLONG"), + "CLONG": ("CLAT", "CLONG")} _COORD_VARS = ("XLAT", "XLONG", "XLAT_M", "XLONG_M", "XLAT_U", "XLONG_U", "XLAT_V", "XLONG_V", "CLAT", "CLONG") -_LAT_COORDS = ("XLAT", "XLAT_M", "XLAT_U", "XLAT_V", "CLAT") +_LAT_COORDS = ("XLAT", "XLAT_M", "XLAT_U", "XLAT_V", "CLAT") -_LON_COORDS = ("XLONG", "XLONG_M", "XLONG_U","XLONG_V", "CLONG") +_LON_COORDS = ("XLONG", "XLONG_M", "XLONG_U", "XLONG_V", "CLONG") _TIME_COORD_VARS = ("XTIME",) def is_time_coord_var(varname): """Return True if the input variable name is a time coordinate. - + Args: - + varname (:obj:`str`): The input variable name. - + Returns: - - :obj:`bool`: True if the input variable is a time coordinate, + + :obj:`bool`: True if the input variable is a time coordinate, otherwise False. - + """ return varname in _TIME_COORD_VARS def get_coord_pairs(coord_varname): - """Return a :obj:`tuple` for the variable names of the coordinate pair used + """Return a :obj:`tuple` for the variable names of the coordinate pair used for the 2D curvilinear coordinate variable. - - For example, the 'XLAT' variable will have coordinate variables of - ('XLAT', 'XLONG') since the 'XLAT' variable itself is two-dimensional. - + + For example, the 'XLAT' variable will have coordinate variables of + ('XLAT', 'XLONG') since the 'XLAT' variable itself is two-dimensional. + Args: - + coord_varname (:obj:`str`): The coordinate variable name. - + Returns: - - :obj:`bool`: True if the time index is :data:`wrf.ALL_TIMES` or + + :obj:`bool`: True if the time index is :data:`wrf.ALL_TIMES` or :obj:`None`, otherwise False. - + """ return _COORD_PAIR_MAP[coord_varname] def is_multi_time_req(timeidx): - """Return True if the requested time index is for :data:`wrf.ALL_TIMES` or + """Return True if the requested time index is for :data:`wrf.ALL_TIMES` or :obj:`None`. - + Args: - - timeidx (:obj:`int`, :data:`wrf.ALL_TIMES`, or :obj:`None`): The + + timeidx (:obj:`int`, :data:`wrf.ALL_TIMES`, or :obj:`None`): The requested time index. - + Returns: - - :obj:`bool`: True if the time index is :data:`wrf.ALL_TIMES` or + + :obj:`bool`: True if the time index is :data:`wrf.ALL_TIMES` or :obj:`None`, otherwise False. - + """ return timeidx is None def is_multi_file(wrfin): """Return True if the input argument is an iterable. - + Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - + Returns: - + :obj:`bool`: True if the input is an iterable. False if the input is a single NetCDF file object. - + """ return (isinstance(wrfin, Iterable) and not isstr(wrfin)) def has_time_coord(wrfnc): - """Return True if the input file or sequence contains the time + """Return True if the input file or sequence contains the time coordinate variable. - + The time coordinate is named 'XTIME'. - + Args: - - wrfnc (:class:`netCDF4.Dataset` or :class:`Nio.NioFile`): A single + + wrfnc (:class:`netCDF4.Dataset` or :class:`Nio.NioFile`): A single NetCDF file object. - + Returns: - - :obj:`bool`: True if the netcdf file contains the time coordinate + + :obj:`bool`: True if the netcdf file contains the time coordinate variable, False otherwise. - + """ return "XTIME" in wrfnc.variables def is_mapping(wrfin): """Return True if the input file or sequence is a mapping type. - + Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - + Returns: - + :obj:`bool`: True if the input is a mapping type, False otherwise. - + """ return isinstance(wrfin, Mapping) def _generator_copy(gen): """Return a copy of a generator. - + This function instantiates a new generator object using the arguments passed to the original. - + Args: - + gen (:class:`types.GeneratorType`): A generator object. - + Note: - + In order for this to work correctly, the generator cannot modify the original construction arguments. - + Returns: - + :class:`types.GeneratorType`: A copy of the generator object. - + """ funcname = gen.__name__ - - # This is for generator expressions. Only solution is to tee the + + # This is for generator expressions. Only solution is to tee the # original generator. if funcname == "": return tee(gen) - + try: argvals = getargvalues(gen.gi_frame) except NameError: argvals = getgeneratorlocals(gen) module = getmodule(gen.gi_frame) - + if module is not None: try: try: - argd = {key:argvals.locals[key] for key in argvals.args} + argd = {key: argvals.locals[key] for key in argvals.args} res = module.get(funcname)(**argd) except AttributeError: res = getattr(module, funcname)(**argd) - except: + except Exception: # This is the old way it used to work, but it looks like this was - # fixed by Python. + # fixed by Python. try: res = module.get(funcname)(**argvals.locals) except AttributeError: - res = getattr(module, funcname)(**argvals.locals) + res = getattr(module, funcname)(**argvals.locals) else: # Created in jupyter or the python interpreter import __main__ - + try: - argd = {key:argvals.locals[key] for key in argvals.args} + argd = {key: argvals.locals[key] for key in argvals.args} res = getattr(__main__, funcname)(**argd) - except: - # This was the old way it used to work, but appears to have + except Exception: + # This was the old way it used to work, but appears to have # been fixed by Python. res = getattr(__main__, funcname)(**argvals.locals) - + return res def test(): - q = [1,2,3] + q = [1, 2, 3] for i in q: yield i - - + + class TestGen(object): def __init__(self, count=3): self._total = count self._i = 0 - + def __iter__(self): return self - + def next(self): if self._i >= self._total: raise StopIteration @@ -257,232 +257,232 @@ class TestGen(object): val = self._i self._i += 1 return val - + # Python 3 def __next__(self): return self.next() def latlon_coordvars(ncvars): - """Return the first found latitude and longitude coordinate names from a + """Return the first found latitude and longitude coordinate names from a NetCDF variable dictionary. - + This function searches the dictionary structure for NetCDF variables and returns the first found latitude and longitude coordinate names (typically 'XLAT' and 'XLONG'). - + Args: - + ncvars (:obj:`dict`): A NetCDF variable dictionary object. - + Returns: - - :obj:`tuple`: The latitude and longitude coordinate name pairs as + + :obj:`tuple`: The latitude and longitude coordinate name pairs as (lat_coord_name, lon_coord_name). - + """ lat_coord = None lon_coord = None - + for name in _LAT_COORDS: if name in viewkeys(ncvars): lat_coord = name break - + for name in _LON_COORDS: if name in viewkeys(ncvars): lon_coord = name break - + return lat_coord, lon_coord def is_coordvar(varname): """Returns True if the variable is a coordinate variable. - + Args: - + varname (:obj:`str`): The variable name. - + Returns: - - :obj:`bool`: True if the variable is a coordinate variable, + + :obj:`bool`: True if the variable is a coordinate variable, otherwise False. - + """ return varname in _COORD_VARS class IterWrapper(Iterable): """A wrapper class for generators and custom iterable classes that returns - a new iterator to the start of the sequence when + a new iterator to the start of the sequence when :meth:`IterWrapper.__iter__` is called. - - If the wrapped object is a generator, a copy of the generator is - constructed and returned when :meth:`IterWrapper.__iter__` is called. - If the wrapped object is a custom type, then the :meth:`copy.copy` is - called and a new instance is returned. In both cases, the original + + If the wrapped object is a generator, a copy of the generator is + constructed and returned when :meth:`IterWrapper.__iter__` is called. + If the wrapped object is a custom type, then the :meth:`copy.copy` is + called and a new instance is returned. In both cases, the original iterable object is unchanged. - + Note: - + Do not increment the wrapped iterable outside of this wrapper. - + """ def __init__(self, wrapped): """Initialize an :class:`wrf.IterWrapper` object. - + Args: - - wrapped (an iterable object): Any iterable object that contains the + + wrapped (an iterable object): Any iterable object that contains the *__iter__* method. - + """ self._wrapped = wrapped - + def __iter__(self): - """Return an iterator to the start of the sequence. - + """Return an iterator to the start of the sequence. + Returns: - + An iterator to the start of the sequence. - + """ if isinstance(self._wrapped, GeneratorType): - + gen_copy = _generator_copy(self._wrapped) # If a tuple comes back, then this is a generator expression, # so store the first tee'd item, then return the other if isinstance(gen_copy, tuple): self._wrapped = gen_copy[0] return gen_copy[1] - + return gen_copy else: obj_copy = copy(self._wrapped) return obj_copy.__iter__() - - + + def get_iterable(wrfseq): - """Returns a resettable iterable object. - - In this context, resettable means that when :meth:`object.__iter__` + """Returns a resettable iterable object. + + In this context, resettable means that when :meth:`object.__iter__` is called, the iterable returned always points to the first element in the sequence, similar to how the list and tuple behave. - + Args: - - wrfseq (iterable): An iterable type, which includes lists, tuples, + + wrfseq (iterable): An iterable type, which includes lists, tuples, dictionaries, generators, and user-defined classes. - + Returns: - + iterable: A resettable iterator object. - + """ if not is_multi_file(wrfseq): return wrfseq else: if not is_mapping(wrfseq): - + if isinstance(wrfseq, (list, tuple, IterWrapper)): return wrfseq else: - return IterWrapper(wrfseq) # generator/custom iterable class - + return IterWrapper(wrfseq) # generator/custom iterable class + else: if isinstance(wrfseq, dict): return wrfseq else: - return dict(wrfseq) # generator/custom iterable dict class - - + return dict(wrfseq) # generator/custom iterable dict class + + # Helper to extract masked arrays from DataArrays that convert to NaN def to_np(array): - """Return the :class:`numpy.ndarray` contained in an + """Return the :class:`numpy.ndarray` contained in an :class:`xarray.DataArray` instance. - - If the :class:`xarray.DataArray` instance does not contain a *_FillValue* - or *missing_value* attribute, then this routine simply returns the - :attr:`xarray.DataArray.values` attribute. If the - :class:`xarray.DataArray` object contains a *_FillValue* or *missing_value* - attribute, then this routine returns a :class:`numpy.ma.MaskedArray` - instance, where the NaN values (used by xarray to represent missing data) + + If the :class:`xarray.DataArray` instance does not contain a *_FillValue* + or *missing_value* attribute, then this routine simply returns the + :attr:`xarray.DataArray.values` attribute. If the + :class:`xarray.DataArray` object contains a *_FillValue* or *missing_value* + attribute, then this routine returns a :class:`numpy.ma.MaskedArray` + instance, where the NaN values (used by xarray to represent missing data) are replaced with the fill value. - - If the object passed in to this routine is not an + + If the object passed in to this routine is not an :class:`xarray.DataArray` instance, then this routine simply returns the - passed in object. This is useful in situations where you do not know - if you have an :class:`xarray.DataArray` or a :class:`numpy.ndarray` and - simply want a :class:`numpy.ndarray` returned. + passed in object. This is useful in situations where you do not know + if you have an :class:`xarray.DataArray` or a :class:`numpy.ndarray` and + simply want a :class:`numpy.ndarray` returned. Args: - + array (:class:`xarray.DataArray`, :class:`numpy.ndarray`, or any \ - object): Can be any object type, but is generally + object): Can be any object type, but is generally used with :class:`xarray.DataArray` or :class:`numpy.ndarray`. - + Returns: - - :class:`numpy.ndarray` or :class:`numpy.ma.MaskedArray`: The - extracted array or the *array* object if *array* is not a + + :class:`numpy.ndarray` or :class:`numpy.ma.MaskedArray`: The + extracted array or the *array* object if *array* is not a :class:`xarray.DataArray` object.. - + """ try: fill_value = array.attrs["_FillValue"] except AttributeError: - result = array # Not a DataArray + result = array # Not a DataArray except KeyError: - result = array.values # Does not have missing values + result = array.values # Does not have missing values else: result = ma.masked_invalid(array.values, copy=False) result.set_fill_value(fill_value) - + return result # Helper utilities for metadata class either(object): - """A callable class that determines which variable is present in the - file. - + """A callable class that determines which variable is present in the + file. + This is used in situations where the same variable type has different names - depending on the type of file used. For example, in a WRF output file, + depending on the type of file used. For example, in a WRF output file, 'P' is used for pressure, whereas in a met_em file, pressure is named - 'PRES'. - + 'PRES'. + Methods: - + __call__(wrfin): Return the variable that is present in the file. - + Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` - or an iterable sequence of the aforementioned types. - + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + or an iterable sequence of the aforementioned types. + Returns: - + :obj:`str`: The variable name that is present in the file. - + Attributes: - + varnames (sequence): A sequence of possible variable names. - + """ def __init__(self, *varnames): """Initialize an :class:`either` object. - + Args: - + *varnames (sequence): A sequence of possible variable names. - + """ self.varnames = varnames - + def __call__(self, wrfin): if is_multi_file(wrfin): if not is_mapping(wrfin): @@ -490,71 +490,71 @@ class either(object): else: entry = wrfin[next(iter(viewkeys(wrfin)))] return self(entry) - + for varname in self.varnames: if varname in wrfin.variables: return varname - + raise ValueError("{} are not valid variable names".format( - self.varnames)) - - + self.varnames)) + + # This should look like: # [(0, (-3,-2)), # variable 1 # (1, -1)] # variable 2 class combine_dims(object): - """A callable class that mixes dimension sizes from different function - arguments. - - This callable object is used for determining the output size for the - extension module functions. The class is initialized with a sequence of - pairs, where the first value is the function argument index. The second - value is the dimension index(es) to use. The second value can be a + """A callable class that mixes dimension sizes from different function + arguments. + + This callable object is used for determining the output size for the + extension module functions. The class is initialized with a sequence of + pairs, where the first value is the function argument index. The second + value is the dimension index(es) to use. The second value can be a single integer or a sequence if multiple dimensions are used. - + Methods: - + __call__(*args): Return a tuple with the combined dimension sizes. - + Args: - - *args: The function arguments from which to extract the + + *args: The function arguments from which to extract the dimensions sizes. - + Returns: - + :obj:`tuple`: The shape for the combined dimensions. - + Attributes: - - pairs (sequence): A sequence representing how to combine the + + pairs (sequence): A sequence representing how to combine the dimensions. - + Example: - + .. code-block:: python - + # Take the -3, and -2 dimension sizes from argument 0 # Then take the -1 dimension size from argument 1 pairs = [(0, (-3, -2), (1, -1)] - + combo = combine_dims(pairs) - + """ - + def __init__(self, pairs): """Initialize a :class:`combine_dims` object. - + Args: - - pairs (sequence): A sequence where each element is a pair - (:obj:`tuple`), with the first element being the function + + pairs (sequence): A sequence where each element is a pair + (:obj:`tuple`), with the first element being the function argument index and the second value being either an integer or sequence for the dimension size indexes to use. - + """ self.pairs = pairs - + def __call__(self, *args): result = [] for pair in self.pairs: @@ -565,170 +565,170 @@ class combine_dims(object): result.append(var.shape[dimidx]) else: result.append(var.shape[dimidxs]) - + return tuple(result) - - + + class from_var(object): """A callable class that retrieves attributes from the function argument. - - If the function argument is not of type :class:`xarray.DataArray`, then + + If the function argument is not of type :class:`xarray.DataArray`, then None will be returned. - + It is assumed that the function has been wrapped using the :mod:`wrapt` module. - + Methods: - + __call__(wrapped, *args, **kwargs): Return the attribute found in \ the function arguments. - + Args: wrapped: The wrapped function, as used by the :mod:`wrapt` module. - + *args: The function arguments. - + **kwargs: The function keyword arguments. - + Returns: - + :obj:`object`: The requested attribute. - + Attributes: - + varname (:obj:`str`): The variable name. - + attribute (:obj:`str`): The attribute name. - + """ def __init__(self, varname, attribute): """Initialize a :class:`from_var` object. - + Args: - + varname (:obj:`str`): The variable name. - + attribute (:obj:`str`): The attribute name. - + """ self.varname = varname self.attribute = attribute - + def __call__(self, wrapped, *args, **kwargs): vard = from_args(wrapped, (self.varname,), *args, **kwargs) - + var = None if vard is not None: var = vard[self.varname] - + try: - return var.attrs.get(self.attribute, None) + return var.attrs.get(self.attribute, None) except AttributeError: return None - + def _corners_moved(wrfnc, first_ll_corner, first_ur_corner, latvar, lonvar): """Return True if the corner points have moved. - + This function is used to test for a moving domain, since the WRF output does not set any flags in the file for this. The test will be performed for all time steps in the NetCDF file. Args: - - wrfnc (:class:`netCDF4.Dataset` or :class:`Nio.NioFile`): A single + + wrfnc (:class:`netCDF4.Dataset` or :class:`Nio.NioFile`): A single NetCDF file object. - + first_ll_corner (:obj:`tuple`): A (latitude, longitude) pair for the lower left corner found in the initial file. - + first_ur_corner (:obj:`tuple`): A (latitude, longitude) pair for the upper right corner found in the initial file. - + latvar (:obj:`str`): The latitude variable name to use. - + lonvar (:obj:`str`: The longitude variable name to use. - - + + Returns: - + :obj:`bool`: True if the corner points have moved, False otherwise. - + """ lats = wrfnc.variables[latvar][:] lons = wrfnc.variables[lonvar][:] - + # Need to check all times for i in py3range(lats.shape[-3]): - start_idxs = [0]*len(lats.shape) # PyNIO does not support ndim + start_idxs = [0] * len(lats.shape) # PyNIO does not support ndim start_idxs[-3] = i start_idxs = tuple(start_idxs) - - end_idxs = [-1]*len(lats.shape) + + end_idxs = [-1] * len(lats.shape) end_idxs[-3] = i end_idxs = tuple(end_idxs) - - if (first_ll_corner[0] != lats[start_idxs] or - first_ll_corner[1] != lons[start_idxs] or - first_ur_corner[0] != lats[end_idxs] or - first_ur_corner[1] != lons[end_idxs]): + + if (first_ll_corner[0] != lats[start_idxs] + or first_ll_corner[1] != lons[start_idxs] + or first_ur_corner[0] != lats[end_idxs] + or first_ur_corner[1] != lons[end_idxs]): return True - + return False -def is_moving_domain(wrfin, varname=None, latvar=either("XLAT", "XLAT_M"), - lonvar=either("XLONG", "XLONG_M"), _key=None): - +def is_moving_domain(wrfin, varname=None, latvar=either("XLAT", "XLAT_M"), + lonvar=either("XLONG", "XLONG_M"), _key=None): + """Return True if the domain is a moving nest. - + This function is used to test for a moving domain, since the WRF output does not set any flags in the file for this. The test will be performed for all files in any sequences and across all times in each file. - - This result is cached internally, so this potentially lengthy check is + + This result is cached internally, so this potentially lengthy check is only done one time for any given *wrfin* parameter. Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - varname (:obj:`str`, optional): A specific NetCDF variable to test, + + varname (:obj:`str`, optional): A specific NetCDF variable to test, which must contain a 'coordinates' attribute. If unspecified, - The *latvar* and *lonvar* parameters are used. Default is None. - + The *latvar* and *lonvar* parameters are used. Default is None. + first_ur_corner (:obj:`tuple`): A (latitude, longitude) pair for the upper right corner found in the initial file. - - latvar (:obj:`str` or :class:`either`, optional): The latitude variable + + latvar (:obj:`str` or :class:`either`, optional): The latitude variable name. Default is :class:`either('XLAT', 'XLAT_M')`. - - lonvar (:obj:`str` or :class:`either`, optional): The latitude variable + + lonvar (:obj:`str` or :class:`either`, optional): The latitude variable name. Default is :class:`either('XLAT', 'XLAT_M')`. - - _key (:obj:`int`, optional): A caching key. This is used for internal + + _key (:obj:`int`, optional): A caching key. This is used for internal purposes only. Default is None. - + Returns: - + :obj:`bool`: True if the domain is a moving nest, False otherwise. - + """ - + if isinstance(latvar, either): latvar = latvar(wrfin) - + if isinstance(lonvar, either): lonvar = lonvar(wrfin) - + # In case it's just a single file if not is_multi_file(wrfin): wrfin = [wrfin] - + # Slow, but safe. Compare the corner points to the first item and see # any move. User iterator protocol in case wrfin is not a list/tuple. if not is_mapping(wrfin): @@ -740,7 +740,7 @@ def is_moving_domain(wrfin, varname=None, latvar=either("XLAT", "XLAT_M"), entry = wrfin[dict_key] key = _key[dict_key] if _key is not None else None return is_moving_domain(entry, varname, latvar, lonvar, key) - + # The long way of checking all lat/lon corner points. Doesn't appear # to be a shortcut in the netcdf files. if varname is not None: @@ -752,7 +752,7 @@ def is_moving_domain(wrfin, varname=None, latvar=either("XLAT", "XLAT_M"), else: coord_names = coord_str.decode().split() except AttributeError: - # Variable doesn't have a coordinates attribute, use the + # Variable doesn't have a coordinates attribute, use the # arguments lon_coord = lonvar lat_coord = latvar @@ -761,45 +761,45 @@ def is_moving_domain(wrfin, varname=None, latvar=either("XLAT", "XLAT_M"), if name in _LAT_COORDS: lat_coord = name continue - + if name in _LON_COORDS: lon_coord = name continue else: lon_coord = lonvar lat_coord = latvar - + # See if there is a cached value product = "is_moving_{}_{}".format(lat_coord, lon_coord) moving = get_cached_item(_key, product) if moving is not None: return moving - + # Need to search all the files lats = first_wrfnc.variables[lat_coord][:] lons = first_wrfnc.variables[lon_coord][:] - + zero_idxs = [0]*len(lats.shape) # PyNIO doesn't have ndim last_idxs = list(zero_idxs) last_idxs[-2:] = [-1]*2 - + zero_idxs = tuple(zero_idxs) last_idxs = tuple(last_idxs) - + lat0 = lats[zero_idxs] lat1 = lats[last_idxs] lon0 = lons[zero_idxs] lon1 = lons[last_idxs] - + ll_corner = (lat0, lon0) ur_corner = (lat1, lon1) - - # Need to check if the first file is moving, might be a single + + # Need to check if the first file is moving, might be a single # file with multiple times if _corners_moved(first_wrfnc, ll_corner, ur_corner, lat_coord, lon_coord): cache_item(_key, product, True) return True - + # Now check any additional files while True: try: @@ -807,14 +807,14 @@ def is_moving_domain(wrfin, varname=None, latvar=either("XLAT", "XLAT_M"), except StopIteration: break else: - if _corners_moved(wrfnc, ll_corner, ur_corner, + if _corners_moved(wrfnc, ll_corner, ur_corner, lat_coord, lon_coord): - + cache_item(_key, product, True) return True cache_item(_key, product, False) - + return False @@ -822,80 +822,80 @@ def _get_global_attr(wrfnc, attr): """Return the global attribute. Args: - - wrfnc (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`): A single + + wrfnc (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`): A single WRF NetCDF file object. - - attr (:obj:`str`): The attribute name. - + + attr (:obj:`str`): The attribute name. + Returns: - + :obj:`object`: The global attribute. - + """ val = getattr(wrfnc, attr, None) - + # PyNIO puts single values in to an array if isinstance(val, np.ndarray): if len(val) == 1: - return val[0] + return val[0] return val - - + + def extract_global_attrs(wrfin, attrs): """Return the global attribute(s). - + If the *wrfin* parameter is a sequence, then only the first element is used, so the entire sequence must have the same global attributes. Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - attrs (:obj:`str`, sequence): The attribute name - or a sequence of attribute names. - + + attrs (:obj:`str`, sequence): The attribute name + or a sequence of attribute names. + Returns: - + :obj:`dict`: A mapping of attribute_name to value. - + """ if isstr(attrs): attrlist = [attrs] else: attrlist = attrs - + multifile = is_multi_file(wrfin) - + if multifile: if not is_mapping(wrfin): wrfin = next(iter(wrfin)) else: entry = wrfin[next(iter(viewkeys(wrfin)))] return extract_global_attrs(entry, attrs) - - return {attr:_get_global_attr(wrfin, attr) for attr in attrlist} + + return {attr: _get_global_attr(wrfin, attr) for attr in attrlist} def extract_dim(wrfin, dim): """Return the dimension size for the specified dimension name. Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - + dim (:obj:`str`): The dimension name. - + Returns: - + :obj:`int`: The dimension size. - + """ if is_multi_file(wrfin): if not is_mapping(wrfin): @@ -903,94 +903,95 @@ def extract_dim(wrfin, dim): else: entry = wrfin[next(iter(viewkeys(wrfin)))] return extract_dim(entry, dim) - + d = wrfin.dimensions[dim] if not isinstance(d, int): try: - return len(d) #netCDF4 - except TypeError: #scipy.io.netcdf - # Scipy can't handled unlimited dimensions, so now we have to + return len(d) # netCDF4 + except TypeError: # scipy.io.netcdf + # Scipy can't handled unlimited dimensions, so now we have to # figure it out try: s = wrfin.variables["P"].shape - return s[-4] - except: + except Exception: raise ValueError("unsupported NetCDF reader") - - return d # PyNIO - - + else: + return s[-4] + + return d # PyNIO + + def _combine_dict(wrfdict, varname, timeidx, method, meta, _key): """Return an array object from a mapping input. - - The resulting array is the combination of all sequences associated with + + The resulting array is the combination of all sequences associated with each key in the dictionary. The leftmost dimension will be the keys. The - rightmost dimensions are the dimensions for the aggregated sequences of - arrays, using either the 'cat' or 'join' *method* parameter value. + rightmost dimensions are the dimensions for the aggregated sequences of + arrays, using either the 'cat' or 'join' *method* parameter value. If dictionaries are nested, then the outermost dictionary keys will be the leftmost dimension, followed by each subsequent dictionary's keys. - - If the order of the keys (and leftmost dimension ordering) matters, it is - recommended that an :class:`OrderedDict` be used instead of a normal - dictionary. Otherwise, the leftmost dimension will be ordered by the + + If the order of the keys (and leftmost dimension ordering) matters, it is + recommended that an :class:`OrderedDict` be used instead of a normal + dictionary. Otherwise, the leftmost dimension will be ordered by the iteration order. - + Args: - - wrfdict (mapping): A mapping of key to an array or + + wrfdict (mapping): A mapping of key to an array or key to a sequence of arrays. - + varname (:obj:`str`) : The variable name. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. The default is 0. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - - _key (:obj:`int`, optional): Cache key for the coordinate variables. + + _key (:obj:`int`, optional): Cache key for the coordinate variables. This is used for internal purposes only. Default is None. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ keynames = [] - numkeys = len(wrfdict) - + numkeys = len(wrfdict) + key_iter = iter(viewkeys(wrfdict)) first_key = next(key_iter) keynames.append(first_key) - + is_moving = is_moving_domain(wrfdict, varname, _key=_key) - + _cache_key = _key[first_key] if _key is not None else None - - first_array = _extract_var(wrfdict[first_key], varname, - timeidx, is_moving=is_moving, method=method, - squeeze=False, cache=None, meta=meta, - _key=_cache_key) - + + first_array = _extract_var(wrfdict[first_key], varname, + timeidx, is_moving=is_moving, method=method, + squeeze=False, cache=None, meta=meta, + _key=_cache_key) + # Create the output data numpy array based on the first array outdims = [numkeys] outdims += first_array.shape outdata = np.empty(outdims, first_array.dtype) - outdata[0,:] = first_array[:] - + outdata[0, :] = first_array[:] + idx = 1 while True: try: @@ -1000,22 +1001,22 @@ def _combine_dict(wrfdict, varname, timeidx, method, meta, _key): else: keynames.append(key) _cache_key = _key[key] if _key is not None else None - vardata = _extract_var(wrfdict[key], varname, timeidx, - is_moving=is_moving, method=method, + vardata = _extract_var(wrfdict[key], varname, timeidx, + is_moving=is_moving, method=method, squeeze=False, cache=None, meta=meta, _key=_cache_key) - + if outdata.shape[1:] != vardata.shape: raise ValueError("data sequences must have the " - "same size for all dictionary keys") - outdata[idx,:] = to_np(vardata)[:] + "same size for all dictionary keys") + outdata[idx, :] = to_np(vardata)[:] idx += 1 - + if xarray_enabled() and meta: outname = str(first_array.name) # Note: assumes that all entries in dict have same coords outcoords = OrderedDict(first_array.coords) - + # First find and store all the existing key coord names/values # This is applicable only if there are nested dictionaries. key_coordnames = [] @@ -1023,90 +1024,88 @@ def _combine_dict(wrfdict, varname, timeidx, method, meta, _key): existing_cnt = 0 while True: key_coord_name = "key_{}".format(existing_cnt) - + if key_coord_name not in first_array.dims: break - + key_coordnames.append(key_coord_name) coord_vals.append(to_np(first_array.coords[key_coord_name])) - + existing_cnt += 1 - + # Now add the key coord name and values for THIS dictionary. - # Put the new key_n name at the bottom, but the new values will + # Put the new key_n name at the bottom, but the new values will # be at the top to be associated with key_0 (left most). This - # effectively shifts the existing 'key_n' coordinate values to the - # right one dimension so *this* dicionary's key coordinate values + # effectively shifts the existing 'key_n' coordinate values to the + # right one dimension so *this* dicionary's key coordinate values # are at 'key_0'. key_coordnames.append(key_coord_name) coord_vals.insert(0, keynames) - + # make it so that key_0 is leftmost outdims = key_coordnames + list(first_array.dims[existing_cnt:]) - - + # Create the new 'key_n', value pairs for coordname, coordval in zip(key_coordnames, coord_vals): outcoords[coordname] = coordval - - + outattrs = OrderedDict(first_array.attrs) - - outarr = DataArray(outdata, name=outname, coords=outcoords, + + outarr = DataArray(outdata, name=outname, coords=outcoords, dims=outdims, attrs=outattrs) else: outarr = outdata - + return outarr def _find_coord_names(coords): - """Return the coordinate variables names found in a + """Return the coordinate variables names found in a :attr:`xarray.DataArray.coords` mapping. - + Args: - + coords (mapping): A :attr:`xarray.DataArray.coords` mapping object. - + Returns: - + :obj:`tuple`: The latitude, longitude, and xtime variable names used in the coordinate mapping. - + """ try: lat_coord = [name for name in _COORD_VARS[0::2] if name in coords][0] except IndexError: lat_coord = None - + try: lon_coord = [name for name in _COORD_VARS[1::2] if name in coords][0] except IndexError: lon_coord = None - + try: xtime_coord = [name for name in _TIME_COORD_VARS if name in coords][0] except IndexError: xtime_coord = None - + return lat_coord, lon_coord, xtime_coord def _find_max_time_size(wrfseq): - """Return the maximum number of times found in a sequence of + """Return the maximum number of times found in a sequence of WRF files. - + Args: - + wrfseq (sequence): A sequence of WRF NetCDF file objects. - + Returns: - + :obj:`int`: The maximum number of times found in a file. - + """ wrf_iter = iter(wrfseq) - + max_times = 0 while True: try: @@ -1116,12 +1115,12 @@ def _find_max_time_size(wrfseq): else: t = extract_dim(wrfnc, "Time") max_times = t if t >= max_times else max_times - + return max_times def _get_coord_names(wrfin, varname): - + # Need only the first real file if is_multi_file(wrfin): if not is_mapping(wrfin): @@ -1131,28 +1130,28 @@ def _get_coord_names(wrfin, varname): return _get_coord_names(entry, varname) else: wrfnc = wrfin - + lat_coord = None lon_coord = None time_coord = None - + var = wrfnc.variables[varname] - - # WRF variables will have a coordinates attribute. MET_EM files have + + # WRF variables will have a coordinates attribute. MET_EM files have # a stagger attribute which indicates the coordinate variable. try: # WRF files coord_attr = getattr(var, "coordinates") except AttributeError: - + if is_coordvar(varname): # Coordinate variable (most likely XLAT or XLONG) lat_coord, lon_coord = get_coord_pairs(varname) time_coord = None - + if has_time_coord(wrfnc): time_coord = "XTIME" - + elif is_time_coord_var(varname): lon_coord = None lat_coord = None @@ -1164,7 +1163,7 @@ def _get_coord_names(wrfin, varname): except AttributeError: lon_coord = None lat_coord = None - + # Let's just check for xlat and xlong in this case if "XLAT" in wrfnc.variables: lat_coord = "XLAT" @@ -1173,12 +1172,12 @@ def _get_coord_names(wrfin, varname): # For met_em files, use the stagger name to get the lat/lon var lat_coord = "XLAT_{}".format(stag_attr) lon_coord = "XLONG_{}".format(stag_attr) - + # If this coord name is missing, it might be an old WRF file if lat_coord not in wrfnc.variables: lat_coord = None lon_coord = None - + if "XLAT" in wrfnc.variables: lat_coord = "XLAT" lon_coord = "XLONG" @@ -1187,67 +1186,67 @@ def _get_coord_names(wrfin, varname): coord_names = coord_attr.split() else: coord_names = coord_attr.decode().split() - + for name in coord_names: if name in _LAT_COORDS: lat_coord = name continue - + if name in _LON_COORDS: lon_coord = name continue - + if name in _TIME_COORD_VARS: time_coord = name continue - + if time_coord is not None: # Make sure the time variable wasn't removed try: _ = wrfnc.variables[time_coord] except KeyError: time_coord = None - + return lat_coord, lon_coord, time_coord - -def _build_data_array(wrfnc, varname, timeidx, is_moving_domain, is_multifile, + +def _build_data_array(wrfnc, varname, timeidx, is_moving_domain, is_multifile, _key): - """Return a :class:`xarray.DataArray` object for the desired variable in - a single NetCDF file object. - + """Return a :class:`xarray.DataArray` object for the desired variable in + a single NetCDF file object. + Args: - - wrfnc (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`): A single + + wrfnc (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`): A single WRF NetCDF file object. - + varname (:obj:`str`) : The variable name. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. The default is 0. - - is_moving_domain (:obj:`bool`): A boolean type that indicates if the + + is_moving_domain (:obj:`bool`): A boolean type that indicates if the NetCDF file object came from a moving nest. - + is_multifile (:obj:`bool`): A boolean type that indicates if the NetCDF file object came from a sequence. - - _key (:obj:`int`, optional): Cache key for the coordinate variables. + + _key (:obj:`int`, optional): Cache key for the coordinate variables. This is used for internal purposes only. Default is None. - + Returns: - + :class:`xarray.DataArray`: An array object that contains metadata. - + """ - + # Note: wrfnc is always a single netcdf file object - # is_moving_domain and is_multifile are arguments indicating if the + # is_moving_domain and is_multifile are arguments indicating if the # single file came from a sequence, and if that sequence is has a moving - # domain. Both arguments are used mainly for coordinate extraction and + # domain. Both arguments are used mainly for coordinate extraction and # caching. multitime = is_multi_time_req(timeidx) time_idx_or_slice = timeidx if not multitime else slice(None) @@ -1256,45 +1255,44 @@ def _build_data_array(wrfnc, varname, timeidx, is_moving_domain, is_multifile, data = var[time_idx_or_slice, :] else: data = var[time_idx_or_slice] - + # Want to preserve the time dimension if not multitime: if len(var.shape) > 1: data = data[np.newaxis, :] else: data = data[np.newaxis] - + attrs = OrderedDict() for dkey, val in viewitems(var.__dict__): # scipy.io adds these but don't want them - if dkey in ("data", "_shape", "_size", "_typecode", "_attributes", - "maskandscale", "dimensions"): + if dkey in ("data", "_shape", "_size", "_typecode", "_attributes", + "maskandscale", "dimensions"): continue - + _dkey = dkey if isinstance(dkey, str) else dkey.decode() if isstr(val): _val = val else: if isinstance(val, bytes): - _val = val.decode() # scipy.io.netcdf + _val = val.decode() # scipy.io.netcdf else: _val = val - + attrs[_dkey] = _val - + dimnames = var.dimensions[-data.ndim:] - + lat_coord = lon_coord = time_coord = None - + try: if dimnames[-2] == "south_north" and dimnames[-1] == "west_east": lat_coord, lon_coord, time_coord = _get_coord_names(wrfnc, varname) except IndexError: pass - - + coords = OrderedDict() - + # Handle lat/lon coordinates and projection information if available if lon_coord is not None and lat_coord is not None: # Using a cache for coordinate variables so the extraction only happens @@ -1303,124 +1301,124 @@ def _build_data_array(wrfnc, varname, timeidx, is_moving_domain, is_multifile, lon_coord_valkey = lon_coord + "_val" lat_coord_dimkey = lat_coord + "_dim" lat_coord_valkey = lat_coord + "_val" - + lon_coord_dims = get_cached_item(_key, lon_coord_dimkey) lon_coord_vals = get_cached_item(_key, lon_coord_valkey) if lon_coord_dims is None or lon_coord_vals is None: lon_var = wrfnc.variables[lon_coord] lon_coord_dims = lon_var.dimensions lon_coord_vals = lon_var[:] - + # Only cache here if the domain is not moving, otherwise # caching is handled by cat/join if not is_moving_domain: cache_item(_key, lon_coord_dimkey, lon_coord_dims) cache_item(_key, lon_coord_valkey, lon_coord_vals) - + lat_coord_dims = get_cached_item(_key, lat_coord_dimkey) lat_coord_vals = get_cached_item(_key, lat_coord_valkey) if lat_coord_dims is None or lat_coord_vals is None: lat_var = wrfnc.variables[lat_coord] lat_coord_dims = lat_var.dimensions lat_coord_vals = lat_var[:] - + # Only cache here if the domain is not moving, otherwise # caching is done in cat/join if not is_moving_domain: cache_item(_key, lat_coord_dimkey, lat_coord_dims) cache_item(_key, lat_coord_valkey, lat_coord_vals) - + time_coord_vals = None if time_coord is not None: # If not from a multifile sequence, then cache the time # coordinate. Otherwise, handled in cat/join/ if not is_multifile: time_coord_vals = get_cached_item(_key, time_coord) - + if time_coord_vals is None: time_coord_vals = wrfnc.variables[time_coord][:] - + if not is_multifile: cache_item(_key, time_coord, time_coord_vals) else: time_coord_vals = wrfnc.variables[time_coord][:] - + if multitime: if is_moving_domain: # Special case with a moving domain in a multi-time file, # otherwise the projection parameters don't change coords[lon_coord] = lon_coord_dims, lon_coord_vals coords[lat_coord] = lat_coord_dims, lat_coord_vals - + else: - coords[lon_coord] = (lon_coord_dims[1:], - lon_coord_vals[0,:]) - coords[lat_coord] = (lat_coord_dims[1:], - lat_coord_vals[0,:]) - + coords[lon_coord] = (lon_coord_dims[1:], + lon_coord_vals[0, :]) + coords[lat_coord] = (lat_coord_dims[1:], + lat_coord_vals[0, :]) + if time_coord is not None: coords[time_coord] = (lon_coord_dims[0], time_coord_vals) else: - coords[lon_coord] = (lon_coord_dims[1:], - lon_coord_vals[timeidx,:]) - coords[lat_coord] = (lat_coord_dims[1:], - lat_coord_vals[timeidx,:]) - + coords[lon_coord] = (lon_coord_dims[1:], + lon_coord_vals[timeidx, :]) + coords[lat_coord] = (lat_coord_dims[1:], + lat_coord_vals[timeidx, :]) + if time_coord is not None: - coords[time_coord] = (lon_coord_dims[0], + coords[time_coord] = (lon_coord_dims[0], [time_coord_vals[timeidx]]) - + proj_params = get_proj_params(wrfnc) proj = getproj(**proj_params) attrs["projection"] = proj - + if dimnames[0] == "Time": t = extract_times(wrfnc, timeidx, meta=False, do_xtime=False) if not multitime: t = [t] coords[dimnames[0]] = t - + data_array = DataArray(data, name=varname, dims=dimnames, coords=coords, attrs=attrs) - + return data_array def _find_forward(wrfseq, varname, timeidx, is_moving, meta, _key): - """Find and return the array object within a sequence for a specific time + """Find and return the array object within a sequence for a specific time index. - + Args: - - wrfseq (iterable): An iterable type, which includes lists, tuples, + + wrfseq (iterable): An iterable type, which includes lists, tuples, dictionaries, generators, and user-defined classes. - + varname (:obj:`str`) : The variable name. - + timeidx (:obj:`int`): The desired time index. Must be positive. - - is_moving (:obj:`bool`): A boolean type that indicates if the + + is_moving (:obj:`bool`): A boolean type that indicates if the sequence is a moving nest. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - - _key (:obj:`int`, optional): Cache key for the coordinate variables. + + _key (:obj:`int`, optional): Cache key for the coordinate variables. This is used for internal purposes only. Default is None. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ wrf_iter = iter(wrfseq) comboidx = 0 - + while True: try: wrfnc = next(wrf_iter) @@ -1428,12 +1426,12 @@ def _find_forward(wrfseq, varname, timeidx, is_moving, meta, _key): break else: numtimes = extract_dim(wrfnc, "Time") - + if timeidx < comboidx + numtimes: filetimeidx = timeidx - comboidx - + if meta: - return _build_data_array(wrfnc, varname, filetimeidx, + return _build_data_array(wrfnc, varname, filetimeidx, is_moving, True, _key) else: var = wrfnc.variables[varname] @@ -1443,57 +1441,57 @@ def _find_forward(wrfseq, varname, timeidx, is_moving, meta, _key): else: result = var[filetimeidx] return result[np.newaxis] # So that nosqueeze works - + else: comboidx += numtimes - + raise IndexError("timeidx {} is out of bounds".format(timeidx)) def _find_reverse(wrfseq, varname, timeidx, is_moving, meta, _key): - """Find and return the array object within a sequence for a specific time + """Find and return the array object within a sequence for a specific time index. - + The sequence is searched in reverse. - + Args: - - wrfseq (iterable): An iterable type, which includes lists, tuples, + + wrfseq (iterable): An iterable type, which includes lists, tuples, dictionaries, generators, and user-defined classes. - + varname (:obj:`str`) : The variable name. - + timeidx (:obj:`int`): The desired time index. Must be negative. - - is_moving (:obj:`bool`): A boolean type that indicates if the + + is_moving (:obj:`bool`): A boolean type that indicates if the sequence is a moving nest. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - - _key (:obj:`int`, optional): Cache key for the coordinate variables. + + _key (:obj:`int`, optional): Cache key for the coordinate variables. This is used for internal purposes only. Default is None. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ - + try: revwrfseq = reversed(wrfseq) except TypeError: revwrfseq = reversed(list(wrfseq)) - + wrf_iter = iter(revwrfseq) revtimeidx = -timeidx - 1 comboidx = 0 - + while True: try: wrfnc = next(wrf_iter) @@ -1501,195 +1499,195 @@ def _find_reverse(wrfseq, varname, timeidx, is_moving, meta, _key): break else: numtimes = extract_dim(wrfnc, "Time") - + if revtimeidx < comboidx + numtimes: - # Finds the "forward" sequence index, then counts that - # number back from the back of the ncfile times, + # Finds the "forward" sequence index, then counts that + # number back from the back of the ncfile times, # since the ncfile needs to be iterated backwards as well. filetimeidx = numtimes - (revtimeidx - comboidx) - 1 - + if meta: - return _build_data_array(wrfnc, varname, filetimeidx, + return _build_data_array(wrfnc, varname, filetimeidx, is_moving, True, _key) else: result = wrfnc.variables[varname][filetimeidx, :] - return result[np.newaxis, :] # So that nosqueeze works + return result[np.newaxis, :] # So that nosqueeze works else: comboidx += numtimes - + raise IndexError("timeidx {} is out of bounds".format(timeidx)) def _find_arr_for_time(wrfseq, varname, timeidx, is_moving, meta, _key): - """Find and return the array object within a sequence for a specific time + """Find and return the array object within a sequence for a specific time index. - + The sequence is searched in forward or reverse based on the time index chosen. - + Args: - - wrfseq (iterable): An iterable type, which includes lists, tuples, + + wrfseq (iterable): An iterable type, which includes lists, tuples, dictionaries, generators, and user-defined classes. - + varname (:obj:`str`) : The variable name. - + timeidx (:obj:`int`): The desired time index. - - is_moving (:obj:`bool`): A boolean type that indicates if the + + is_moving (:obj:`bool`): A boolean type that indicates if the sequence is a moving nest. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - - _key (:obj:`int`, optional): Cache key for the coordinate variables. + + _key (:obj:`int`, optional): Cache key for the coordinate variables. This is used for internal purposes only. Default is None. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ if timeidx >= 0: return _find_forward(wrfseq, varname, timeidx, is_moving, meta, _key) else: return _find_reverse(wrfseq, varname, timeidx, is_moving, meta, _key) - + def _cat_files(wrfseq, varname, timeidx, is_moving, squeeze, meta, _key): """Return an array object from a sequence of files using the concatenate method. - - The concatenate method aggregates all files in the sequence along the - 'Time' dimension, which will be the leftmost dimension. No sorting is + + The concatenate method aggregates all files in the sequence along the + 'Time' dimension, which will be the leftmost dimension. No sorting is performed, so all files in the sequence must be sorted prior to calling this method. - - + + Args: - - wrfseq (iterable): An iterable type, which includes lists, tuples, + + wrfseq (iterable): An iterable type, which includes lists, tuples, dictionaries, generators, and user-defined classes. - + varname (:obj:`str`) : The variable name. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. The default is 0. - - is_moving (:obj:`bool`): A boolean type that indicates if the + + is_moving (:obj:`bool`): A boolean type that indicates if the sequence is a moving nest. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - - _key (:obj:`int`, optional): Cache key for the coordinate variables. + + _key (:obj:`int`, optional): Cache key for the coordinate variables. This is used for internal purposes only. Default is None. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ if is_moving is None: is_moving = is_moving_domain(wrfseq, varname, _key=_key) - + file_times = extract_times(wrfseq, ALL_TIMES, meta=False, do_xtime=False) - - multitime = is_multi_time_req(timeidx) - - # For single times, just need to find the ncfile and appropriate + + multitime = is_multi_time_req(timeidx) + + # For single times, just need to find the ncfile and appropriate # time index, and return that array if not multitime: return _find_arr_for_time(wrfseq, varname, timeidx, is_moving, meta, _key) - #time_idx_or_slice = timeidx if not multitime else slice(None) - # If all times are requested, need to build a new array and cat together # all of the arrays in the sequence wrf_iter = iter(wrfseq) - + if xarray_enabled() and meta: - first_var = _build_data_array(next(wrf_iter), varname, + first_var = _build_data_array(next(wrf_iter), varname, ALL_TIMES, is_moving, True, _key) else: first_var = (next(wrf_iter)).variables[varname][:] - + outdims = [len(file_times)] - + # Making a new time dim, so ignore this one outdims += first_var.shape[1:] - + outdata = np.empty(outdims, first_var.dtype) - + numtimes = first_var.shape[0] startidx = 0 endidx = numtimes - + if first_var.ndim > 1: outdata[startidx:endidx, :] = first_var[:] else: outdata[startidx:endidx] = first_var[:] - + if xarray_enabled() and meta: latname, lonname, timename = _find_coord_names(first_var.coords) - + timecached = False latcached = False loncached = False - + outxtimes = None outlats = None outlons = None - + timekey = timename+"_cat" if timename is not None else None latkey = latname + "_cat" if latname is not None else None lonkey = lonname + "_cat" if lonname is not None else None - + if timename is not None: outxtimes = get_cached_item(_key, timekey) if outxtimes is None: outxtimes = np.empty(outdims[0]) - outxtimes[startidx:endidx] = to_np(first_var.coords[timename][:]) + outxtimes[startidx:endidx] = to_np( + first_var.coords[timename][:]) else: timecached = True - + if is_moving: - outcoorddims = outdims[0:1] + outdims[-2:] - + outcoorddims = outdims[0:1] + outdims[-2:] + if latname is not None: # Try to pull from the coord cache outlats = get_cached_item(_key, latkey) if outlats is None: outlats = np.empty(outcoorddims, first_var.dtype) - outlats[startidx:endidx, :] = to_np(first_var.coords[latname][:]) + outlats[startidx:endidx, :] = to_np( + first_var.coords[latname][:]) else: latcached = True - + if lonname is not None: outlons = get_cached_item(_key, lonkey) if outlons is None: outlons = np.empty(outcoorddims, first_var.dtype) - outlons[startidx:endidx, :] = to_np(first_var.coords[lonname][:]) + outlons[startidx:endidx, :] = to_np( + first_var.coords[lonname][:]) else: loncached = True - - + startidx = endidx while True: try: @@ -1698,100 +1696,100 @@ def _cat_files(wrfseq, varname, timeidx, is_moving, squeeze, meta, _key): break else: vardata = wrfnc.variables[varname][:] - + numtimes = vardata.shape[0] - + endidx = startidx + numtimes - + if vardata.ndim > 1: outdata[startidx:endidx, :] = vardata[:] else: outdata[startidx:endidx] = vardata[:] - + if xarray_enabled() and meta: if timename is not None and not timecached: xtimedata = wrfnc.variables[timename][:] outxtimes[startidx:endidx] = xtimedata[:] - + if is_moving: if latname is not None and not latcached: latdata = wrfnc.variables[latname][:] outlats[startidx:endidx, :] = latdata[:] - + if lonname is not None and not loncached: londata = wrfnc.variables[lonname][:] outlons[startidx:endidx, :] = londata[:] - + startidx = endidx - + if xarray_enabled() and meta: - + # Cache the coords if applicable - if not latcached and outlats is not None: + if not latcached and outlats is not None: cache_item(_key, latkey, outlats) if not loncached and outlons is not None: cache_item(_key, lonkey, outlons) if not timecached and outxtimes is not None: cache_item(_key, timekey, outxtimes) - + outname = first_var.name outattrs = OrderedDict(first_var.attrs) outcoords = OrderedDict(first_var.coords) outdimnames = list(first_var.dims) - + if "Time" not in outdimnames: outdimnames.insert(0, "Time") - + if not multitime: file_times = [file_times[timeidx]] - + outcoords[outdimnames[0]] = file_times - + outcoords["datetime"] = outdimnames[0], file_times - + if timename is not None: outxtimes = outxtimes[:] outcoords[timename] = outdimnames[0], outxtimes - + # If the domain is moving, need to create the lat/lon coords # since they can't be copied if is_moving: outlatdims = [outdimnames[0]] + outdimnames[-2:] - + if latname is not None: outlats = outlats[:] outcoords[latname] = outlatdims, outlats if lonname is not None: outlons = outlons[:] outcoords[lonname] = outlatdims, outlons - + outdata = outdata[:] - - outarr = DataArray(outdata, name=outname, coords=outcoords, + + outarr = DataArray(outdata, name=outname, coords=outcoords, dims=outdimnames, attrs=outattrs) - + else: outdata = outdata[:] outarr = outdata - + return outarr def _get_numfiles(wrfseq): """Return the number of files in the sequence. - + This function will first try to call the builtin :meth:`len` function, but if that fails, the entire squence will be iterated over and counted. - + Args: - - wrfseq (iterable): An iterable type, which includes lists, tuples, + + wrfseq (iterable): An iterable type, which includes lists, tuples, dictionaries, generators, and user-defined classes. - + Returns: - + :obj:`int`: The number of files in the sequence. - + """ try: return len(wrfseq) @@ -1803,59 +1801,59 @@ def _get_numfiles(wrfseq): def _join_files(wrfseq, varname, timeidx, is_moving, meta, _key): """Return an array object from a sequence of files using the join method. - - The join method creates a new leftmost dimension for the file/sequence - index. In situations where there are multiple files with multiple times, + + The join method creates a new leftmost dimension for the file/sequence + index. In situations where there are multiple files with multiple times, and the last file contains less times than the previous files, the - remaining arrays will be arrays filled with missing values. There are + remaining arrays will be arrays filled with missing values. There are checks in place within the wrf-python algorithms to look for these missing arrays, but be careful when calling compiled routines outside of wrf-python. - - In general, join is rarely used, so the concatenate method should be used + + In general, join is rarely used, so the concatenate method should be used for most cases. - + Args: - - wrfseq (iterable): An iterable type, which includes lists, tuples, + + wrfseq (iterable): An iterable type, which includes lists, tuples, dictionaries, generators, and user-defined classes. - + varname (:obj:`str`) : The variable name. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. The default is 0. - - is_moving (:obj:`bool`): A boolean type that indicates if the + + is_moving (:obj:`bool`): A boolean type that indicates if the sequence is a moving nest. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - - _key (:obj:`int`, optional): Cache key for the coordinate variables. + + _key (:obj:`int`, optional): Cache key for the coordinate variables. This is used for internal purposes only. Default is None. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ if is_moving is None: is_moving = is_moving_domain(wrfseq, varname, _key=_key) multitime = is_multi_time_req(timeidx) numfiles = _get_numfiles(wrfseq) maxtimes = _find_max_time_size(wrfseq) - + time_idx_or_slice = timeidx if not multitime else slice(None) file_times_less_than_max = False file_idx = 0 @@ -1864,83 +1862,83 @@ def _join_files(wrfseq, varname, timeidx, is_moving, meta, _key): wrf_iter = iter(wrfseq) wrfnc = next(wrf_iter) numtimes = extract_dim(wrfnc, "Time") - + if xarray_enabled() and meta: - first_var = _build_data_array(wrfnc, varname, ALL_TIMES, is_moving, + first_var = _build_data_array(wrfnc, varname, ALL_TIMES, is_moving, True, _key) - time_coord = np.full((numfiles, maxtimes), np.datetime64("NaT"), + time_coord = np.full((numfiles, maxtimes), np.datetime64("NaT"), "datetime64[ns]") time_coord[file_idx, 0:numtimes] = first_var.coords["Time"][:] else: first_var = wrfnc.variables[varname][:] - + if numtimes < maxtimes: file_times_less_than_max = True - - # Out dimensions will be the number of files, maxtimes, then the + + # Out dimensions will be the number of files, maxtimes, then the # non-time shapes from the first variable outdims = [numfiles] outdims += [maxtimes] outdims += first_var.shape[1:] - + # For join, always need to start with full masked values outdata = np.full(outdims, default_fill(first_var.dtype), first_var.dtype) if first_var.ndim > 1: outdata[file_idx, 0:numtimes, :] = first_var[:] else: outdata[file_idx, 0:numtimes] = first_var[:] - + # Create the secondary coordinate arrays if xarray_enabled() and meta: latname, lonname, timename = _find_coord_names(first_var.coords) outcoorddims = outdims[0:2] + outdims[-2:] - + timecached = False latcached = False loncached = False - + outxtimes = None outlats = None outlons = None - + timekey = timename+"_join" if timename is not None else None latkey = latname + "_join" if latname is not None else None lonkey = lonname + "_join" if lonname is not None else None - + if timename is not None: outxtimes = get_cached_item(_key, timekey) if outxtimes is None: - outxtimes = np.full(outdims[0:2], - default_fill(first_var.dtype), + outxtimes = np.full(outdims[0:2], + default_fill(first_var.dtype), first_var.dtype) outxtimes[file_idx, 0:numtimes] = first_var.coords[timename][:] else: timecached = True - + if is_moving: if latname is not None: outlats = get_cached_item(_key, latkey) if outlats is None: - outlats = np.full(outcoorddims, - default_fill(first_var.dtype), + outlats = np.full(outcoorddims, + default_fill(first_var.dtype), first_var.dtype) outlats[file_idx, 0:numtimes, :] = ( first_var.coords[latname][:]) else: latcached = True - + if lonname is not None: outlons = get_cached_item(_key, lonkey) if outlons is None: - outlons = np.full(outcoorddims, - default_fill(first_var.dtype), + outlons = np.full(outcoorddims, + default_fill(first_var.dtype), first_var.dtype) outlons[file_idx, 0:numtimes, :] = ( first_var.coords[lonname][:]) else: loncached = True - - file_idx=1 + + file_idx = 1 while True: try: wrfnc = next(wrf_iter) @@ -1951,83 +1949,83 @@ def _join_files(wrfseq, varname, timeidx, is_moving, meta, _key): if numtimes < maxtimes: file_times_less_than_max = True outvar = wrfnc.variables[varname][:] - + if not multitime: outvar = outvar[np.newaxis, :] - - if outvar.ndim > 1: + + if outvar.ndim > 1: outdata[file_idx, 0:numtimes, :] = outvar[:] else: outdata[file_idx, 0:numtimes] = outvar[:] - + if xarray_enabled() and meta: # For join, the times are a function of fileidx - file_times = extract_times(wrfnc, ALL_TIMES, meta=False, + file_times = extract_times(wrfnc, ALL_TIMES, meta=False, do_xtime=False) - time_coord[file_idx, 0:numtimes] = np.asarray(file_times, - "datetime64[ns]")[:] - + time_coord[file_idx, 0:numtimes] = np.asarray( + file_times, "datetime64[ns]")[:] + if timename is not None and not timecached: xtimedata = wrfnc.variables[timename][:] outxtimes[file_idx, 0:numtimes] = xtimedata[:] - + if is_moving: if latname is not None and not latcached: latdata = wrfnc.variables[latname][:] outlats[file_idx, 0:numtimes, :] = latdata[:] - + if lonname is not None and not loncached: londata = wrfnc.variables[lonname][:] outlons[file_idx, 0:numtimes, :] = londata[:] - + # Need to update coords here file_idx += 1 - + # If any of the output files contain less than the max number of times, - # then a mask array is needed to flag all the missing arrays with + # then a mask array is needed to flag all the missing arrays with # missing values if file_times_less_than_max: outdata = np.ma.masked_values(outdata, default_fill(outdata.dtype)) - + if xarray_enabled() and meta: # Cache the coords if applicable - if not latcached and outlats is not None: + if not latcached and outlats is not None: cache_item(_key, latkey, outlats) if not loncached and outlons is not None: cache_item(_key, lonkey, outlons) if not timecached and outxtimes is not None: cache_item(_key, timekey, outxtimes) - + outname = first_var.name outcoords = OrderedDict(first_var.coords) outattrs = OrderedDict(first_var.attrs) # New dimensions outdimnames = ["file"] + list(first_var.dims) outcoords["file"] = [i for i in py3range(numfiles)] - + # Time needs to be multi dimensional, so use the default dimension del outcoords["Time"] - + time_coord = time_coord[:, time_idx_or_slice] if not multitime: time_coord = time_coord[:, np.newaxis] outcoords["datetime"] = outdimnames[0:2], time_coord - + if isinstance(outdata, np.ma.MaskedArray): outattrs["_FillValue"] = default_fill(outdata.dtype) outattrs["missing_value"] = default_fill(outdata.dtype) - + if timename is not None: outxtimes = outxtimes[:, time_idx_or_slice] if not multitime: outxtimes = outxtimes[:, np.newaxis] outcoords[timename] = outdimnames[0:2], outxtimes[:] - + # If the domain is moving, need to create the lat/lon coords # since they can't be copied if is_moving: outlatdims = outdimnames[0:2] + outdimnames[-2:] - + if latname is not None: outlats = outlats[:, time_idx_or_slice, :] if not multitime: @@ -2038,159 +2036,159 @@ def _join_files(wrfseq, varname, timeidx, is_moving, meta, _key): if not multitime: outlons = outlons[:, np.newaxis, :] outcoords[lonname] = outlatdims, outlons - + if not multitime: outdata = outdata[:, timeidx, :] outdata = outdata[:, np.newaxis, :] - - outarr = DataArray(outdata, name=outname, coords=outcoords, + + outarr = DataArray(outdata, name=outname, coords=outcoords, dims=outdimnames, attrs=outattrs) - + else: if not multitime: outdata = outdata[:, timeidx, :] outdata = outdata[:, np.newaxis, :] - + outarr = outdata - + return outarr def combine_files(wrfin, varname, timeidx, is_moving=None, - method="cat", squeeze=True, meta=True, + method="cat", squeeze=True, meta=True, _key=None): - """Combine and return an array object for the sequence of WRF output + """Combine and return an array object for the sequence of WRF output files. - + Two aggregation methodologies are available to combine the sequence: - - - 'cat': Concatenate the files along the 'Time' dimension. The Time - dimension will be the leftmost dimension. No sorting is performed, - so files must be properly ordered in the sequence prior to calling + + - 'cat': Concatenate the files along the 'Time' dimension. The Time + dimension will be the leftmost dimension. No sorting is performed, + so files must be properly ordered in the sequence prior to calling this function. - - - 'join': Join the files by creating a new leftmost dimension for the - file index. In situations where there are multiple files with - multiple times, and the last file contains less times than the - previous files, the remaining arrays will be arrays filled with - missing values. There are checks in place within the wrf-python - algorithms to look for these missing arrays, but be careful when + + - 'join': Join the files by creating a new leftmost dimension for the + file index. In situations where there are multiple files with + multiple times, and the last file contains less times than the + previous files, the remaining arrays will be arrays filled with + missing values. There are checks in place within the wrf-python + algorithms to look for these missing arrays, but be careful when calling compiled routines outside of wrf-python. - - + + Args: - - wrfin (iterable): An iterable type, which includes lists, tuples, + + wrfin (iterable): An iterable type, which includes lists, tuples, dictionaries, generators, and user-defined classes. - + varname (:obj:`str`) : The variable name. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. The default is 0. - - is_moving (:obj:`bool`): A boolean type that indicates if the + + is_moving (:obj:`bool`): A boolean type that indicates if the sequence is a moving nest. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - - _key (:obj:`int`, optional): Cache key for the coordinate variables. + + _key (:obj:`int`, optional): Cache key for the coordinate variables. This is used for internal purposes only. Default is None. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ - + # Handles generators, single files, lists, tuples, custom classes wrfseq = get_iterable(wrfin) - + # Dictionary is unique if is_mapping(wrfseq): outarr = _combine_dict(wrfseq, varname, timeidx, method, meta, _key) elif method.lower() == "cat": - outarr = _cat_files(wrfseq, varname, timeidx, is_moving, + outarr = _cat_files(wrfseq, varname, timeidx, is_moving, squeeze, meta, _key) elif method.lower() == "join": outarr = _join_files(wrfseq, varname, timeidx, is_moving, meta, _key) else: raise ValueError("method must be 'cat' or 'join'") - + return outarr.squeeze() if squeeze else outarr -def _extract_var(wrfin, varname, timeidx, is_moving, +def _extract_var(wrfin, varname, timeidx, is_moving, method, squeeze, cache, meta, _key): """Extract a variable from a NetCDF file object or a sequence of NetCDF file objects. - + Args: - - wrfin (iterable): An iterable type, which includes lists, tuples, + + wrfin (iterable): An iterable type, which includes lists, tuples, dictionaries, generators, and user-defined classes. - + varname (:obj:`str`) : The variable name. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. The default is 0. - - is_moving (:obj:`bool`): A boolean type that indicates if the + + is_moving (:obj:`bool`): A boolean type that indicates if the sequence is a moving nest. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - - _key (:obj:`int`, optional): Cache key for the coordinate variables. + + _key (:obj:`int`, optional): Cache key for the coordinate variables. This is used for internal purposes only. Default is None. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is - enabled and the *meta* parameter is True, then the result will be a - :class:`xarray.DataArray` object. Otherwise, the result will be a + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: If xarray is + enabled and the *meta* parameter is True, then the result will be a + :class:`xarray.DataArray` object. Otherwise, the result will be a :class:`numpy.ndarray` object with no metadata. - + """ if cache is not None: @@ -2201,16 +2199,16 @@ def _extract_var(wrfin, varname, timeidx, is_moving, else: if not meta: return to_np(cache_var) - + return cache_var - + multitime = is_multi_time_req(timeidx) multifile = is_multi_file(wrfin) - + if is_time_coord_var(varname): - return extract_times(wrfin, timeidx, method, squeeze, cache, + return extract_times(wrfin, timeidx, method, squeeze, cache, meta, do_xtime=True) - + if not multifile: if xarray_enabled() and meta: if is_moving is None: @@ -2219,334 +2217,332 @@ def _extract_var(wrfin, varname, timeidx, is_moving, multifile, _key) else: if not multitime: - result = wrfin.variables[varname][timeidx,:] - result = result[np.newaxis, :] # So that no squeeze works + result = wrfin.variables[varname][timeidx, :] + result = result[np.newaxis, :] # So that no squeeze works else: result = wrfin.variables[varname][:] else: # Squeeze handled in this routine, so just return it - return combine_files(wrfin, varname, timeidx, is_moving, + return combine_files(wrfin, varname, timeidx, is_moving, method, squeeze, meta, _key) - + return result.squeeze() if squeeze else result -def extract_vars(wrfin, timeidx, varnames, method="cat", squeeze=True, +def extract_vars(wrfin, timeidx, varnames, method="cat", squeeze=True, cache=None, meta=True, _key=None): """Extract variables from a NetCDF file object or a sequence of NetCDF file objects. - + Args: - - wrfin (iterable): An iterable type, which includes lists, tuples, + + wrfin (iterable): An iterable type, which includes lists, tuples, dictionaries, generators, and user-defined classes. - + varnames (sequence of :obj:`str`) : A sequence of variable names. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. The default is 0. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of :class:`xarray.DataArray`. Default is True. - - _key (:obj:`int`, optional): Cache key for the coordinate variables. + + _key (:obj:`int`, optional): Cache key for the coordinate variables. This is used for internal purposes only. Default is None. - + Returns: - - :obj:`dict`: A mapping of variable name to an array object. If xarray - is enabled and the *meta* parameter is True, then the array object will - be a :class:`xarray.DataArray` object. Otherwise, the array object + + :obj:`dict`: A mapping of variable name to an array object. If xarray + is enabled and the *meta* parameter is True, then the array object will + be a :class:`xarray.DataArray` object. Otherwise, the array object will be a :class:`numpy.ndarray` object with no metadata. - + """ if isstr(varnames): varlist = [varnames] else: varlist = varnames - - return {var:_extract_var(wrfin, var, timeidx, None, - method, squeeze, cache, meta, _key) + + return {var: _extract_var(wrfin, var, timeidx, None, + method, squeeze, cache, meta, _key) for var in varlist} def npbytes_to_str(var): """Return a :obj:`bytes` object for the raw character array. - + Args: - + var (:class:`numpy.ndarray`): An array of characters. - + Returns: - + :obj:`bytes`: A string of bytes. - + """ return (bytes(c).decode("utf-8") for c in var[:]) def _make_time(timearr): """Return a :class:`datetime.datetime` object for the array of characters. - + Args: - + timearr (:class:`numpy.ndarray`): An array of characters. - + Returns: - + :class:`datetime.datetime`: A datetime object. - + """ try: - return dt.datetime.strptime("".join(npbytes_to_str(timearr)), - "%Y-%m-%d_%H:%M:%S") + return dt.datetime.strptime("".join(npbytes_to_str(timearr)), + "%Y-%m-%d_%H:%M:%S") except ValueError: return np.datetime64("NaT") def _file_times(wrfin, do_xtime): """Yield a time object for the times found in a sequence of files. - - If *do_xtime* to True, a :class:`datetime.datetime` object is yielded. + + If *do_xtime* to True, a :class:`datetime.datetime` object is yielded. Otherwise, a :obj:`float` object is yielded. - + Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - do_xtime (:obj:`bool`): Set to True to parse the 'XTIME' variable + + do_xtime (:obj:`bool`): Set to True to parse the 'XTIME' variable instead of the 'Times' variable. - + Yields: - - :class:`datetime.datetime` or :obj:`float`: A - :class:`datetime.datetime` object if *do_xtime* is False, + + :class:`datetime.datetime` or :obj:`float`: A + :class:`datetime.datetime` object if *do_xtime* is False, otherwise a :obj:`float`. - + """ if not do_xtime: - times = wrfin.variables["Times"][:,:] + times = wrfin.variables["Times"][:, :] for i in py3range(times.shape[0]): - yield _make_time(times[i,:]) + yield _make_time(times[i, :]) else: xtimes = wrfin.variables["XTIME"][:] for i in py3range(xtimes.shape[0]): yield xtimes[i] - + def _extract_time_map(wrfin, timeidx, do_xtime, meta=False): """Return a mapping of key to a sequence of time objects. - + This function is used when *wrfin* is a mapping. - + Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. - - do_xtime (:obj:`bool`): Set to True to parse the 'XTIME' variable + + do_xtime (:obj:`bool`): Set to True to parse the 'XTIME' variable instead of the 'Times' variable. - + meta (:obj:`bool`, optional): Set to False to disable metadata. - + Returns: - - :obj:`dict`: A mapping of key to a sequence of time objects. If + + :obj:`dict`: A mapping of key to a sequence of time objects. If *meta* is True, the sequence will be of type :class:`xarray.DataArray`, otherwise the sequence is :class:`numpy.ndarray`. - + """ - return {key : extract_times(wrfseq, timeidx, do_xtime, meta) + return {key: extract_times(wrfseq, timeidx, do_xtime, meta) for key, wrfseq in viewitems(wrfin)} - -def extract_times(wrfin, timeidx, method="cat", squeeze=True, cache=None, + +def extract_times(wrfin, timeidx, method="cat", squeeze=True, cache=None, meta=False, do_xtime=False): - + """Return a sequence of time objects. - - If *do_xtime* is False, the 'XTIME' variable is used and each time object - is a :obj:`float`. Otherwise, the 'Times' variable is used, and each - time object is a :class:`datetime.datetime` object. - + + If *do_xtime* is False, the 'XTIME' variable is used and each time object + is a :obj:`float`. Otherwise, the 'Times' variable is used, and each + time object is a :class:`datetime.datetime` object. + Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - + meta (:obj:`bool`, optional): Set to False to disable metadata. - - do_xtime (:obj:`bool`): Set to True to parse the 'XTIME' variable + + do_xtime (:obj:`bool`): Set to True to parse the 'XTIME' variable instead of the 'Times' variable. Default is False. - + Returns: - - :class:`xarray.DataArray` or :class:`numpy.ndarray`: A sequence of time - objects. If *meta* is True, the sequence will be of type - :class:`xarray.DataArray`, otherwise the sequence is + + :class:`xarray.DataArray` or :class:`numpy.ndarray`: A sequence of time + objects. If *meta* is True, the sequence will be of type + :class:`xarray.DataArray`, otherwise the sequence is :class:`numpy.ndarray`. - + """ if is_mapping(wrfin): return _extract_time_map(wrfin, timeidx, do_xtime) - + multitime = is_multi_time_req(timeidx) multi_file = is_multi_file(wrfin) if not multi_file: wrf_list = [wrfin] else: wrf_list = wrfin - + dt = "datetime64[ns]" if not do_xtime else np.float64 - fill_value = (np.datetime64('NaT') if not do_xtime else + fill_value = (np.datetime64('NaT') if not do_xtime else default_fill(np.float64)) - + try: if method.lower() == "cat": - time_list = [file_time - for wrf_file in wrf_list + time_list = [file_time + for wrf_file in wrf_list for file_time in _file_times(wrf_file, do_xtime)] time_arr = np.asarray(time_list, dtype=dt) - + elif method.lower() == "join": - time_list = [[file_time + time_list = [[file_time for file_time in _file_times(wrf_file, do_xtime)] for wrf_file in wrf_list] - + num_rows = len(time_list) num_cols = len(time_list[0]) - + time_arr = np.full((num_rows, num_cols), fill_value, dtype=dt) - for i,row in enumerate(time_list): + for i, row in enumerate(time_list): if len(row) == num_cols: - time_arr[i,:] = row[:] + time_arr[i, :] = row[:] else: - for j,val in enumerate(row): - time_arr[i,j] = val - + for j, val in enumerate(row): + time_arr[i, j] = val + time_arr = ma.masked_values(time_arr, fill_value) - + else: raise ValueError("invalid method argument '{}'".format(method)) except KeyError: - return None # Thrown for pre-3.7 XTIME not existing - + return None # Thrown for pre-3.7 XTIME not existing + if xarray_enabled() and meta: outattrs = OrderedDict() outcoords = None - + if method.lower() == "cat": outdimnames = ["Time"] else: outdimnames = ["fileidx", "Time"] outattrs["missing_value"] = fill_value outattrs["_FillValue"] = fill_value - + if not do_xtime: outname = "times" outattrs["description"] = "model times [np.datetime64]" - + else: ncfile = next(iter(wrf_list)) var = ncfile.variables["XTIME"] outattrs.update(var.__dict__) - + outname = "XTIME" - - - outarr = DataArray(time_arr, name=outname, coords=outcoords, + + outarr = DataArray(time_arr, name=outname, coords=outcoords, dims=outdimnames, attrs=outattrs) - else: outarr = time_arr - + if not multitime: return outarr[timeidx] - + return outarr - - + + def is_standard_wrf_var(wrfin, varname): - """Return True if the variable is a standard WRF variable and not a + """Return True if the variable is a standard WRF variable and not a diagnostic. - + If *wrfin* is a sequence, only the first file is used. - + Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - + varname (:obj:`str`): The variable name. - + Returns: - + :obj:`bool`: True if the variable is a standard WRF variable, otherwise False. - + """ multifile = is_multi_file(wrfin) if multifile: @@ -2555,272 +2551,273 @@ def is_standard_wrf_var(wrfin, varname): else: entry = wrfin[next(iter(viewkeys(wrfin)))] return is_standard_wrf_var(entry, varname) - + return varname in wrfin.variables def is_staggered(wrfin, var): """Return True if the variable is on a staggered grid. - + Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - var (array): An array object which contains a :attr:`shape` + + var (array): An array object which contains a :attr:`shape` attribute. - + Returns: - - :obj:`bool`: True if the variable is on a staggered grid, otherwise + + :obj:`bool`: True if the variable is on a staggered grid, otherwise False. - + """ - + we = extract_dim(wrfin, "west_east") sn = extract_dim(wrfin, "south_north") bt = extract_dim(wrfin, "bottom_top") - + if (var.shape[-1] != we or var.shape[-2] != sn or var.shape[-3] != bt): return True - + return False def get_left_indexes(var, expected_dims): """Returns a tuple for the extra leftmost dimension sizes. - - For example, if an algorithm expects a 3 dimensional variable, but the - variable includes an additional left dimension for Time, and + + For example, if an algorithm expects a 3 dimensional variable, but the + variable includes an additional left dimension for Time, and this Time dimension has 3 values, then this function will return (3,). - + Args: - - var (array): An array object that contains the :attr:`ndim` + + var (array): An array object that contains the :attr:`ndim` and :attr:`shape` attributes. - + expected_dims (:obj:`int`): The expected number of dimensions (usually for a computational algorithm). - + Returns: - + :obj:`tuple`: The shape for the extra leftmost dimensions. - + """ extra_dim_num = var.ndim - expected_dims - + if (extra_dim_num == 0): return [] - - return var.shape[0:extra_dim_num] + + return var.shape[0:extra_dim_num] def iter_left_indexes(dims): """Yield the iteration tuples for a sequence of dimensions sizes. - + For example, if *dims* is (2,2), then this will yield: - + (0,0), (0,1), (1,0), (1,1) - + This is primarily used to iterate over the leftmost index values. - + Args: - + dims (indexable sequence): A sequence of dimension sizes. - + Yields: - + :obj:`tuple`: The leftmost indexing iteration sizes. - + """ arg = [py3range(dim) for dim in dims] for idxs in product(*arg): yield idxs - - + + def get_right_slices(var, right_ndims, fixed_val=0): - """Return an indexing tuple where the left dimensions are held to a + """Return an indexing tuple where the left dimensions are held to a fixed value and the right dimensions are set to slice objects. - - For example, if *var* is a 5D variable, and the desired indexing sequence - for a numpy array is (0,0,0,:,:), then *right_ndims* should be set to 2 + + For example, if *var* is a 5D variable, and the desired indexing sequence + for a numpy array is (0,0,0,:,:), then *right_ndims* should be set to 2 and *fixed_val* set to 0. - + Args: - + var (:class:`numpy.ndarray`): A numpy array. - + right_ndims (:obj:`int`): The number of right dimensions to be sliced. - + fixed_val (:obj:`int`): The value to hold the left dimensions to. - + Returns: - - :obj:`tuple`: An indexing tuple that can be used to index a + + :obj:`tuple`: An indexing tuple that can be used to index a :class:`numpy.ndarray`. - + """ extra_dim_num = var.ndim - right_ndims if extra_dim_num == 0: return [slice(None)] * right_ndims - - return tuple([fixed_val]*extra_dim_num + + + return tuple([fixed_val]*extra_dim_num + [slice(None)]*right_ndims) -def get_proj_params(wrfin):#, timeidx=0, varname=None): - """Return a tuple of latitude, longitude, and projection parameters from +def get_proj_params(wrfin): + """Return a tuple of latitude, longitude, and projection parameters from a WRF output file object or a sequence of WRF output file objects. - + Args: - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` or an iterable sequence of the aforementioned types. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. Default is 0. - - varname (:obj:`str`, optional): The variable name to extract the - coordinate variable names from. Default is None, which will + + varname (:obj:`str`, optional): The variable name to extract the + coordinate variable names from. Default is None, which will use the default coordinate variable names ('XLAT', 'XLONG'). - + Returns: - - :obj:`tuple`: A tuple of the latitude coordinate variable, - longitude coordinate, and global projection attributes. - - """ - proj_params = extract_global_attrs(wrfin, attrs=("MAP_PROJ", - "CEN_LAT", "CEN_LON", - "TRUELAT1", "TRUELAT2", - "MOAD_CEN_LAT", "STAND_LON", - "POLE_LAT", "POLE_LON", - "DX", "DY")) - + + :obj:`tuple`: A tuple of the latitude coordinate variable, + longitude coordinate, and global projection attributes. + + """ + proj_params = extract_global_attrs(wrfin, + attrs=("MAP_PROJ", + "CEN_LAT", "CEN_LON", + "TRUELAT1", "TRUELAT2", + "MOAD_CEN_LAT", "STAND_LON", + "POLE_LAT", "POLE_LON", + "DX", "DY")) + return proj_params - + def from_args(func, argnames, *args, **kwargs): """Return a mapping of argument name to value for the called function. - - This function parses the function \*args and \*\*kwargs to obtain the \ - desired argument value. If the argument has not been passed in, the value + + This function parses the function args and kwargs to obtain the + desired argument value. If the argument has not been passed in, the value is taken from the default keyword argument value. - + This func is usually called from within a decorator. - + Note: - - This function currently does not work with functions that contain - \*args or \*\*kwargs arguments. - + + This function currently does not work with functions that contain + variable length args or kwargs arguments. + Args: - - func (function): The function to examine (usually the function that is + + func (function): The function to examine (usually the function that is wrapped). - + argnames (iterable): An iterable sequence of argument names. - + *args: The positional arguments. - + **kwargs: The keyword arguments. - + Returns: - + :obj:`dict`: A mapping of argument name to argument value. - + """ if isstr(argnames): arglist = [argnames] else: arglist = argnames - + result = OrderedDict() for argname in arglist: arg_loc = arg_location(func, argname, args, kwargs) - + if arg_loc is not None: - result[argname] = arg_loc[0][arg_loc[1]] + result[argname] = arg_loc[0][arg_loc[1]] else: result[argname] = None - + return result def _args_to_list2(func, args, kwargs): """Return all of the function arguments, including defaults, as a list. - - The result can then be passed to the function via *result. This version + + The result can then be passed to the function via *result. This version uses :meth:`inspect.argspec`, so is only applicable for Python 2.7. - + Note: - - This function currently does not work with functions that contain - *args or **kwargs arguments. - + + This function currently does not work with functions that contain + variable length args or kwargs arguments. + Args: - - func (function): The function to examine (usually the function + + func (function): The function to examine (usually the function that is wrapped). - + args (:obj:`tuple`): The positional arguments. - + kwargs (:obj:`dict`): The keyword arguments. - + Returns: - + :obj:`list`: A list of all argument values, including defaults. - + """ - argspec = getargspec(func) - + argspec = getargspec(func) + # Build the full tuple with defaults filled in outargs = [None]*len(argspec.args) if argspec.defaults is not None: - for i,default in enumerate(argspec.defaults[::-1], 1): + for i, default in enumerate(argspec.defaults[::-1], 1): outargs[-i] = default - + # Add the supplied args - for i,arg in enumerate(args): + for i, arg in enumerate(args): outargs[i] = arg - - # Fill in the supplied kargs - for argname,val in viewitems(kwargs): + + # Fill in the supplied kargs + for argname, val in viewitems(kwargs): argidx = argspec.args.index(argname) outargs[argidx] = val - + return outargs -# For Python 3.4, this will run the BoundArguments.apply_defaults method that +# For Python 3.4, this will run the BoundArguments.apply_defaults method that # was introduced in Python 3.5. def _apply_defaults(bound): """Set default values for missing arguments. - + For variable-positional arguments (*args) the default is an empty tuple. - + For variable-keyword arguments (**kwargs) the default is an empty dict. - + Args: - + bound (:class:`inspect.BoundArguments`): A BoundArguments object. - + Returns: - + None - + """ arguments = bound.arguments - + new_arguments = [] for name, param in bound._signature.parameters.items(): try: @@ -2837,34 +2834,34 @@ def _apply_defaults(bound): # Signature.bind_partial(). continue new_arguments.append((name, val)) - + bound.arguments = OrderedDict(new_arguments) - - + + def _args_to_list3(func, args, kwargs): """Return all of the function arguments, including defaults, as a list. - - The result can then be passed to the function via *result. This version + + The result can then be passed to the function via *result. This version uses :meth:`inspect.signature`, so is only applicable for Python 3.4+. - + Note: - - This function currently does not work with functions that contain - *args or **kwargs arguments. - + + This function currently does not work with functions that contain + variable length args or kwargs arguments. + Args: - - func (function): The function to examine (usually the function + + func (function): The function to examine (usually the function that is wrapped). - + args (:obj:`tuple`): The positional arguments. - + kwargs (:obj:`dict`): The keyword arguments. - + Returns: - + :obj:`list`: A list of all argument values, including defaults. - + """ sig = signature(func) bound = sig.bind(*args, **kwargs) @@ -2872,180 +2869,194 @@ def _args_to_list3(func, args, kwargs): bound.apply_defaults() except AttributeError: _apply_defaults(bound) - + return [x for x in bound.arguments.values()] - - + + # Note: Doesn't allow for **kwargs or *args def args_to_list(func, args, kwargs): """Return all of the function arguments, including defaults, as a list. - + The result can then be passed to the function via *result*. - + Note: - - This function currently does not work with functions that contain - \*args or \*\*kwargs arguments. - + + This function currently does not work with functions that contain + variable length args or kwargs arguments. + Args: - - func (function): The function to examine (usually the function + + func (function): The function to examine (usually the function that is wrapped). - + args (:obj:`tuple`): The positional arguments. - + kwargs (:obj:`dict`): The keyword arguments. - + Returns: - + :obj:`list`: A list of all argument values, including defaults. - + """ if version_info > (3,): _args_to_list = _args_to_list3 else: _args_to_list = _args_to_list2 - + return _args_to_list(func, args, kwargs) - + def _arg_location2(func, argname, args, kwargs): - """Return the function arguments as a single list along with the + """Return the function arguments as a single list along with the index within that list for a specified argument name. - - This function parses the args, kargs and signature looking for the - location of *argname*, and returns a list containing all arguments, along + + This function parses the args, kargs and signature looking for the + location of *argname*, and returns a list containing all arguments, along with the argument location in that list. - - This function requires :meth:`inspect.getargspec`, so it is only + + This function requires :meth:`inspect.getargspec`, so it is only applicable for Python 2.7. - + Args: - - func (function): The function to examine (usually the function + + func (function): The function to examine (usually the function that is wrapped). - + argname (:obj:`str`): The argument name to locate. - + args (:obj:`tuple`): The positional arguments. - + kwargs (:obj:`dict`): The keyword arguments. - + Returns: - + :obj:`tuple`: A tuple containing the list of all argument values along with the index for location of *argname*. - + """ argspec = getargspec(func) - + list_args = _args_to_list2(func, args, kwargs) - + # Return the new sequence and location if argname not in argspec.args and argname not in kwargs: return None - + result_idx = argspec.args.index(argname) - + return list_args, result_idx def _arg_location3(func, argname, args, kwargs): - """Return the function arguments as a single list along with the + """Return the function arguments as a single list along with the index within that list for a specified argument name. - - This function parses the args, kargs and signature looking for the - location of *argname*, and returns a list containing all arguments, along + + This function parses the args, kargs and signature looking for the + location of *argname*, and returns a list containing all arguments, along with the argument location in that list. - - This function requires :meth:`inspect.signature`, so it is only + + This function requires :meth:`inspect.signature`, so it is only applicable for Python 3.4 and higher. - + Args: - - func (function): The function to examine (usually the function + + func (function): The function to examine (usually the function that is wrapped). - + argname (:obj:`str`): The argument name to locate. - + args (:obj:`tuple`): The positional arguments. - + kwargs (:obj:`dict`): The keyword arguments. - + Returns: - + :obj:`tuple`: A tuple containing the list of all argument values along with the index for location of *argname*. - + """ sig = signature(func) params = list(sig.parameters.keys()) - + list_args = _args_to_list3(func, args, kwargs) - + try: - result_idx = params.index(argname) + result_idx = params.index(argname) except ValueError: return None - + return list_args, result_idx - - + + def arg_location(func, argname, args, kwargs): - """Return the function arguments as a single list along with the + """Return the function arguments as a single list along with the index within that list for a specified argument name. - - This function parses the args, kargs and signature looking for the - location of *argname*, and returns a list containing all arguments, along + + This function parses the args, kargs and signature looking for the + location of *argname*, and returns a list containing all arguments, along with the argument location in that list. - + Args: - - func (function): The function to examine (usually the function + + func (function): The function to examine (usually the function that is wrapped). - + argname (:obj:`str`): The argument name to locate. - + args (:obj:`tuple`): The positional arguments. - + kwargs (:obj:`dict`): The keyword arguments. - + Returns: - + :obj:`tuple`: A tuple containing the list of all argument values along with the index for location of *argname*. - + """ if version_info > (3,): _arg_location = _arg_location3 else: _arg_location = _arg_location2 - + return _arg_location(func, argname, args, kwargs) def psafilepath(): """Return the full path to the 'psadilookup.dat' file. - - The 'psadilookup.dat' file contains the lookup table for the cape + + The 'psadilookup.dat' file contains the lookup table for the cape routines. - + Returns: - + :obj:`str`: The full path to the 'psadilookup.dat' file. - + """ return os.path.join(os.path.dirname(__file__), "data", "psadilookup.dat") def get_filepath(obj): - + """Return the file path for the specified object. + + This is used to return the file path for a netcdf object. If the + particular object does not have the appropriate file path information, + then one is created based on the timestep in the file. + + Args: + + obj: An object. + + Returns: + + :obj:`str`: A string for a file path. + + """ try: path = obj.filepath() except AttributeError: try: path = obj.file.path - except: + except AttributeError: # Let's make up a filename from the first file time found = False times = extract_times(obj, None, meta=False, do_xtime=False) @@ -3053,122 +3064,123 @@ def get_filepath(obj): path = "wrfout_{}".format(str(t)) found = True break - - if not found: + + if not found: raise ValueError("file contains no path information") - + return path + def get_id(obj, prefix=''): """Return the cache id. - + The cache id is used as a caching key for various routines. If the - object type is a mapping, then the result will also be a + object type is a mapping, then the result will also be a mapping of each key to the object id for the value. - + Args: - + obj (:obj:`object`): Any object type. - + prefix (:obj:`str`): A string to help with recursive calls. - + Returns: - - :obj:`int` or :obj:`dict`: If the *obj* parameter is not a mapping, - then the object id is returned. Otherwise, a mapping of each + + :obj:`int` or :obj:`dict`: If the *obj* parameter is not a mapping, + then the object id is returned. Otherwise, a mapping of each key to the object id for the value is returned. - + """ if not is_multi_file(obj): return hash(prefix + get_filepath(obj)) - - # For sequences, the hashing string will be the list ID and the + + # For sequences, the hashing string will be the list ID and the # path for the first file in the sequence if not is_mapping(obj): _obj = get_iterable(obj) _next = next(iter(_obj)) return get_id(_next, prefix + str(id(obj))) - + # For each key in the mapping, recursively call get_id until # until a non-mapping is found - return {key : get_id(val, prefix) for key,val in viewitems(obj)} + return {key: get_id(val, prefix) for key, val in viewitems(obj)} def geo_bounds(var=None, wrfin=None, varname=None, timeidx=0, method="cat", - squeeze=True, cache=None): + squeeze=True, cache=None): """Return the geographic boundaries for the variable or file(s). - + When using a :class:`xarray.DataArray` as the *var* parameter, the variable - must contain latitude and longitude coordinates. If these coordinate - dimensions are greater than two dimensions, then an array of - :class:`wrf.GeoBounds` objects will be returned with the same shape as the + must contain latitude and longitude coordinates. If these coordinate + dimensions are greater than two dimensions, then an array of + :class:`wrf.GeoBounds` objects will be returned with the same shape as the leftmost dimensions of the coordinate arrays. - - When using a WRF file, or sequence of WRF files, by supplying the - *wrfin* parameter, an array of :class:`wrf.GeoBounds` objects will be - returned if the domain is moving and :data:`wrf.ALL_TIMES` is selected as - the *timeidx* parameter when using *wrfin*. Otherwise, a single + + When using a WRF file, or sequence of WRF files, by supplying the + *wrfin* parameter, an array of :class:`wrf.GeoBounds` objects will be + returned if the domain is moving and :data:`wrf.ALL_TIMES` is selected as + the *timeidx* parameter when using *wrfin*. Otherwise, a single :class:`wrf.GeoBounds` object is returned. - + Args: - - var (:class:`xarray.DataArray`, optional): A :class:`xarray.DataArray` + + var (:class:`xarray.DataArray`, optional): A :class:`xarray.DataArray` variable that includes latitude,longitude coordinate information. If not used, then *wrfin* must be provided. - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable, optional): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` - or an iterable sequence of the aforementioned types. If not used, + iterable, optional): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + or an iterable sequence of the aforementioned types. If not used, then *var* must be provided. - + varname (:obj:`str`, optional): If using *wrfin*, then this will be the - variable name to use to determine the geobounds. The variable - can be a coordinate variable, or a regular variable that contains - coordinate attributes. If None, - then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables + variable name to use to determine the geobounds. The variable + can be a coordinate variable, or a regular variable that contains + coordinate attributes. If None, + then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables will be used. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index when *wrfin* is not None. This value can be a - positive integer, negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return - all times in the file or sequence. Default is 0. This value is + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index when *wrfin* is not None. This value can be a + positive integer, negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return + all times in the file or sequence. Default is 0. This value is ignored when *var* is used. - - method (:obj:`str`, optional): The aggregation method to use for - sequences when *wrfin* is not None. Must be either 'cat' or - 'join'. 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences when *wrfin* is not None. Must be either 'cat' or + 'join'. 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Only used when *wrfin* is used. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using large sequences of files. Only used when *wrfin* is used. Default is None. - + Returns: - + :class:`wrf.GeoBounds`: The domain geographic bounds. - + """ - + if var is None and wrfin is None: raise ValueError("'var' or 'wrfin' parameter is required") - + # Getting lat/lon from xarray coordinates if var is not None: if not xarray_enabled(): raise ValueError("xarray is not installed or is disabled") - + is_moving = None try: var_coords = var.coords @@ -3187,17 +3199,17 @@ def geo_bounds(var=None, wrfin=None, varname=None, timeidx=0, method="cat", except KeyError: raise ValueError("'var' object does not contain a longitude " "coordinate") - + # Getting lat/lon from the file elif wrfin is not None: _key = get_id(wrfin) - is_moving = is_moving_domain(wrfin, varname=varname, - latvar=either("XLAT", "XLAT_M"), - lonvar=either("XLONG", "XLONG_M"), + is_moving = is_moving_domain(wrfin, varname=varname, + latvar=either("XLAT", "XLAT_M"), + lonvar=either("XLONG", "XLONG_M"), _key=_key) if varname is not None: if xarray_enabled(): - var = extract_vars(wrfin, timeidx, varname, method, squeeze, + var = extract_vars(wrfin, timeidx, varname, method, squeeze, cache, meta=True, _key=_key)[varname] return geo_bounds(var) else: @@ -3205,94 +3217,95 @@ def geo_bounds(var=None, wrfin=None, varname=None, timeidx=0, method="cat", else: lat_coord = either("XLAT", "XLAT_M")(wrfin) lon_coord = either("XLONG", "XLONG_M")(wrfin) - - # If requesting all times but the domain isn't moving, just + + # If requesting all times but the domain isn't moving, just # extract one time _timeidx = timeidx if timeidx is None and not is_moving: _timeidx = 0 - - coord_data = extract_vars(wrfin, _timeidx, (lat_coord, lon_coord), - method, squeeze, cache, meta=False, + + coord_data = extract_vars(wrfin, _timeidx, (lat_coord, lon_coord), + method, squeeze, cache, meta=False, _key=_key) lats = coord_data[lat_coord] lons = coord_data[lon_coord] - + # Moving domains if lats.ndim > 2: # Requesting all times, works for 'cat' and 'join' data # and always for xarray data extra_dims = lats.shape[0:-2] out_geo = np.full(extra_dims, NullGeoBounds(), np.object) - + for left_idxs in iter_left_indexes(extra_dims): latlon_idx = left_idxs + (slice(None),) out_geo[left_idxs] = GeoBounds(lats=lats[latlon_idx], lons=lons[latlon_idx]) return out_geo - + # Non-moving domains - return GeoBounds(lats=lats, lons=lons) + return GeoBounds(lats=lats, lons=lons) + -def _get_wrf_proj_geobnds(var, wrfin, varname, timeidx, method, squeeze, +def _get_wrf_proj_geobnds(var, wrfin, varname, timeidx, method, squeeze, cache): """Return the :class:`wrf.WrfProj` subclass and :class:`wrf.GeoBounds`. - + Args: - - var (:class:`xarray.DataArray`): A :class:`xarray.DataArray` + + var (:class:`xarray.DataArray`): A :class:`xarray.DataArray` variable that includes latitude,longitude coordinate information. If not used, then *wrfin* must be provided. - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` - or an iterable sequence of the aforementioned types. If not used, + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + or an iterable sequence of the aforementioned types. If not used, then *var* must be provided. - + varname (:obj:`str`): If using *wrfin*, then this will be the - variable name to use to determine the geobounds. The variable - can be a coordinate variable, or a regular variable that contains - coordinate attributes. If None, - then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables + variable name to use to determine the geobounds. The variable + can be a coordinate variable, or a regular variable that contains + coordinate attributes. If None, + then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables will be used. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. Default is 0. - - method (:obj:`str`): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - + Returns: - - :obj:`tuple`: A tuple of :class:`wrf.WrfProj` + + :obj:`tuple`: A tuple of :class:`wrf.WrfProj` and :class:`wrf.GeoBounds` - + """ # Using a variable if var is not None: if not xarray_enabled(): raise ValueError("xarray is not installed or is disabled") - + geobnds = geo_bounds(var) try: wrf_proj = var.attrs["projection"] @@ -3301,123 +3314,123 @@ def _get_wrf_proj_geobnds(var, wrfin, varname, timeidx, method, squeeze, "information") else: geobnds = geo_bounds(wrfin=wrfin, varname=varname, timeidx=timeidx, - method=method, cache=cache) + method=method, cache=cache) proj_params = get_proj_params(wrfin) wrf_proj = getproj(**proj_params) - + return wrf_proj, geobnds -def _get_proj_obj(ob_type, var, wrfin, varname, timeidx, method, squeeze, +def _get_proj_obj(ob_type, var, wrfin, varname, timeidx, method, squeeze, cache, **kwargs): """Return the desired mapping object for the plotting type. - + Args: - + ob_type (:obj:`str`): Must be 'cartopy', 'basemap', or 'pyngl'. - - var (:class:`xarray.DataArray`): A :class:`xarray.DataArray` + + var (:class:`xarray.DataArray`): A :class:`xarray.DataArray` variable that includes latitude,longitude coordinate information. If not used, then *wrfin* must be provided. - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` - or an iterable sequence of the aforementioned types. If not used, + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + or an iterable sequence of the aforementioned types. If not used, then *var* must be provided. - + varname (:obj:`str`): If using *wrfin*, then this will be the - variable name to use to determine the geobounds. The variable - can be a coordinate variable, or a regular variable that contains - coordinate attributes. If None, - then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables + variable name to use to determine the geobounds. The variable + can be a coordinate variable, or a regular variable that contains + coordinate attributes. If None, + then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables will be used. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. Default is 0. - - method (:obj:`str`): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - + **kwargs: Additional keyword arguments for the plotting type. - + Returns: - - mapping object: Will be either :class:`cartopy.crs.Projection`, - :class:`matplotlib.mpl_toolkits.basemap.Basemap` or + + mapping object: Will be either :class:`cartopy.crs.Projection`, + :class:`matplotlib.mpl_toolkits.basemap.Basemap` or :class:`Ngl.Resources`. - + """ - - wrf_proj, geobnds = _get_wrf_proj_geobnds(var, wrfin, varname, timeidx, + + wrf_proj, geobnds = _get_wrf_proj_geobnds(var, wrfin, varname, timeidx, method, squeeze, cache) - + if ob_type == "cartopy": proj_obj = wrf_proj.cartopy() elif ob_type == "basemap": try: _ = len(geobnds) - except TypeError: # Only a single object + except TypeError: # Only a single object proj_obj = wrf_proj.basemap(geobnds, **kwargs) - else: + else: proj_obj = np.empty(geobnds.shape, np.object) - + for idxs, geobnd_val in np.ndenumerate(geobnds): proj_obj[idxs] = wrf_proj.basemap(geobnd_val, **kwargs) elif ob_type == "pyngl": try: _ = len(geobnds) - except TypeError: # Only a single object + except TypeError: # Only a single object proj_obj = wrf_proj.pyngl(geobnds, **kwargs) - else: + else: proj_obj = np.empty(geobnds.shape, np.object) - + for idxs, geobnd_val in np.ndenumerate(geobnds): proj_obj[idxs] = wrf_proj.pyngl(geobnd_val, **kwargs) - + return proj_obj def latlon_coords(var, as_np=False): - """Return the latitude and longitude coordinates from a + """Return the latitude and longitude coordinates from a :class:`xarray.DataArray` object. - + Args: - + var (:class:`xarray.DataArray`): A variable. - - as_np (:obj:`bool`): Set to True to return the coordinates as - :class:`numpy.ndarray` objects instead of + + as_np (:obj:`bool`): Set to True to return the coordinates as + :class:`numpy.ndarray` objects instead of :class:`xarray.DataArray` objects. - + Returns: - + :obj:`tuple`: The latitude and longitude coordinate variables. - + """ - + if not xarray_enabled(): raise ValueError("xarray is not installed or is disabled") - + try: var_coords = var.coords except AttributeError: @@ -3435,395 +3448,397 @@ def latlon_coords(var, as_np=False): except KeyError: raise ValueError("'var' object does not contain a longitude " "coordinate") - + if as_np: return to_np(lats), to_np(lons) - + return lats, lons - - + + def get_cartopy(var=None, wrfin=None, varname=None, timeidx=0, method="cat", - squeeze=True, cache=None): - """Return a :class:`cartopy.crs.Projection` subclass for the + squeeze=True, cache=None): + """Return a :class:`cartopy.crs.Projection` subclass for the map projection. - + Args: - - var (:class:`xarray.DataArray`, optional): A :class:`xarray.DataArray` + + var (:class:`xarray.DataArray`, optional): A :class:`xarray.DataArray` variable that includes latitude,longitude coordinate information. If not used, then *wrfin* must be provided. - - geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to + + geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to get the extents. If set to None and using the *var* parameter, - the geobounds will be taken from the variable. If using a + the geobounds will be taken from the variable. If using a file, then the geobounds will be taken from the native grid. - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable, optional): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` - or an iterable sequence of the aforementioned types. If not used, + iterable, optional): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + or an iterable sequence of the aforementioned types. If not used, then *var* must be provided. - + varname (:obj:`str`, optional): If using *wrfin*, then this will be the - variable name to use to determine the geobounds. The variable - can be a coordinate variable, or a regular variable that contains - coordinate attributes. If None, - then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables + variable name to use to determine the geobounds. The variable + can be a coordinate variable, or a regular variable that contains + coordinate attributes. If None, + then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables will be used. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. Default is 0. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - + Returns: - - :class:`cartopy.crs.Projection`: A Projection subclass for the + + :class:`cartopy.crs.Projection`: A Projection subclass for the map projection. - + See Also: - + :class:`cartopy.crs.Projection` - + """ - return _get_proj_obj("cartopy", var, wrfin, varname, timeidx, method, + return _get_proj_obj("cartopy", var, wrfin, varname, timeidx, method, squeeze, cache) - + def get_basemap(var=None, wrfin=None, varname=None, timeidx=0, method="cat", - squeeze=True, cache=None, **kwargs): - """Return a :class:`matplotlib.mpl_toolkits.basemap.Basemap` object + squeeze=True, cache=None, **kwargs): + """Return a :class:`matplotlib.mpl_toolkits.basemap.Basemap` object for the map projection. - + Args: - - var (:class:`xarray.DataArray`, optional): A :class:`xarray.DataArray` + + var (:class:`xarray.DataArray`, optional): A :class:`xarray.DataArray` variable that includes latitude,longitude coordinate information. If not used, then *wrfin* must be provided. - - geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to + + geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to get the extents. If set to None and using the *var* parameter, - the geobounds will be taken from the variable. If using a + the geobounds will be taken from the variable. If using a file, then the geobounds will be taken from the native grid. - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable, optional): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` - or an iterable sequence of the aforementioned types. If not used, + iterable, optional): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + or an iterable sequence of the aforementioned types. If not used, then *var* must be provided. - + varname (:obj:`str`, optional): If using *wrfin*, then this will be the - variable name to use to determine the geobounds. The variable - can be a coordinate variable, or a regular variable that contains - coordinate attributes. If None, - then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables + variable name to use to determine the geobounds. The variable + can be a coordinate variable, or a regular variable that contains + coordinate attributes. If None, + then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables will be used. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. Default is 0. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - - **kwargs: Keyword arguments for creating a + + **kwargs: Keyword arguments for creating a :class:`matplotlib.mpl_toolkits.basemap.Basemap`. By default, - the domain bounds will be set to the native projection, the - resolution will be set to 'l', and the other projection + the domain bounds will be set to the native projection, the + resolution will be set to 'l', and the other projection parameters will be set by the information in the file. - + Returns: - - :class:`cartopy.crs.Projection`: A Projection subclass for the + + :class:`cartopy.crs.Projection`: A Projection subclass for the map projection. - + Returns: - + :class:`matplotlib.mpl_toolkits.basemap.Basemap`: A Basemap object for the projection. - + See Also: - + :class:`matplotlib.mpl_toolkits.basemap.Basemap` - + """ - return _get_proj_obj("basemap", var, wrfin, varname, timeidx, method, + return _get_proj_obj("basemap", var, wrfin, varname, timeidx, method, squeeze, cache, **kwargs) - + def get_pyngl(var=None, wrfin=None, varname=None, timeidx=0, method="cat", squeeze=True, cache=None, **kwargs): """Return a :class:`Ngl.Resources` object for the map projection. - + Args: - - var (:class:`xarray.DataArray`, optional): A :class:`xarray.DataArray` + + var (:class:`xarray.DataArray`, optional): A :class:`xarray.DataArray` variable that includes latitude,longitude coordinate information. If not used, then *wrfin* must be provided. - - geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to + + geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to get the extents. If set to None and using the *var* parameter, - the geobounds will be taken from the variable. If using a + the geobounds will be taken from the variable. If using a file, then the geobounds will be taken from the native grid. - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable, optional): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` - or an iterable sequence of the aforementioned types. If not used, + iterable, optional): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + or an iterable sequence of the aforementioned types. If not used, then *var* must be provided. - + varname (:obj:`str`, optional): If using *wrfin*, then this will be the - variable name to use to determine the geobounds. The variable - can be a coordinate variable, or a regular variable that contains - coordinate attributes. If None, - then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables + variable name to use to determine the geobounds. The variable + can be a coordinate variable, or a regular variable that contains + coordinate attributes. If None, + then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables will be used. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. Default is 0. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - - **kwargs: Additional PyNGL resources to set while creating the + + **kwargs: Additional PyNGL resources to set while creating the :class:`Ngl.Resources` object. - + Returns: - - :class:`Ngl.Resources`: A dict-like object that contains the + + :class:`Ngl.Resources`: A dict-like object that contains the PyNGL resources for the map projection. - + See Also: - - `PyNGL `_ + + `PyNGL `_ """ - return _get_proj_obj("pyngl", var, wrfin, varname, timeidx, method, + return _get_proj_obj("pyngl", var, wrfin, varname, timeidx, method, squeeze, cache) - -def cartopy_xlim(var=None, geobounds=None, wrfin=None, varname=None, timeidx=0, + +def cartopy_xlim(var=None, geobounds=None, wrfin=None, varname=None, timeidx=0, method="cat", squeeze=True, cache=None): """Return the x-axis limits in the projected coordinates. - - For some map projections, like :class`wrf.RotatedLatLon`, the - :meth:`cartopy.GeoAxes.set_extent` method does not work correctly. This + + For some map projections, like :class`wrf.RotatedLatLon`, the + :meth:`cartopy.GeoAxes.set_extent` method does not work correctly. This method is equivalent to: - + .. code-block:: python - + pc = crs.PlateCarree() xs, ys, _ = self._cartopy().transform_points(pc, - np.array([geobounds.bottom_left.lon, + np.array([geobounds.bottom_left.lon, geobounds.top_right.lon]), np.array([geobounds.bottom_left.lat, geobounds.top_right.lat])).T - - + + _xlimits = xs.tolist() _ylimits = ys.tolist() - + return (_xlimits, _ylimits)[0] - + Args: - - var (:class:`xarray.DataArray`, optional): A :class:`xarray.DataArray` + + var (:class:`xarray.DataArray`, optional): A :class:`xarray.DataArray` variable that includes latitude,longitude coordinate information. If not used, then *wrfin* must be provided. - - geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to + + geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to get the extents. If set to None and using the *var* parameter, - the geobounds will be taken from the variable. If using a + the geobounds will be taken from the variable. If using a file, then the geobounds will be taken from the native grid. - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable, optional): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` - or an iterable sequence of the aforementioned types. If not used, + iterable, optional): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + or an iterable sequence of the aforementioned types. If not used, then *var* must be provided. - + varname (:obj:`str`, optional): If using *wrfin*, then this will be the - variable name to use to determine the geobounds. The variable - can be a coordinate variable, or a regular variable that contains - coordinate attributes. If None, - then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables + variable name to use to determine the geobounds. The variable + can be a coordinate variable, or a regular variable that contains + coordinate attributes. If None, + then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables will be used. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. Default is 0. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - + Returns: - + :obj:`list`: A list of [start_x, end_x] in the projected coordinate system. - + """ - wrf_proj, native_geobnds = _get_wrf_proj_geobnds(var, wrfin, varname, - timeidx, method, squeeze, cache) + wrf_proj, native_geobnds = _get_wrf_proj_geobnds(var, wrfin, varname, + timeidx, method, squeeze, + cache) if geobounds is not None: return wrf_proj.cartopy_xlim(geobounds) return wrf_proj.cartopy_xlim(native_geobnds) - - -def cartopy_ylim(var=None, geobounds=None, wrfin=None, varname=None, timeidx=0, + + +def cartopy_ylim(var=None, geobounds=None, wrfin=None, varname=None, timeidx=0, method="cat", squeeze=True, cache=None): """Return the y-axis limits in the projected coordinates. - - For some map projections, like :class`wrf.RotatedLatLon`, the - :meth:`cartopy.GeoAxes.set_extent` method does not work correctly. This + + For some map projections, like :class`wrf.RotatedLatLon`, the + :meth:`cartopy.GeoAxes.set_extent` method does not work correctly. This method is equivalent to: - + .. code-block:: python - + pc = crs.PlateCarree() xs, ys, _ = self._cartopy().transform_points(pc, - np.array([geobounds.bottom_left.lon, + np.array([geobounds.bottom_left.lon, geobounds.top_right.lon]), np.array([geobounds.bottom_left.lat, geobounds.top_right.lat])).T - - + + _xlimits = xs.tolist() _ylimits = ys.tolist() - + return (_xlimits, _ylimits)[1] - + Args: - - var (:class:`xarray.DataArray`, optional): A :class:`xarray.DataArray` + + var (:class:`xarray.DataArray`, optional): A :class:`xarray.DataArray` variable that includes latitude,longitude coordinate information. If not used, then *wrfin* must be provided. - - geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to + + geobounds (:class:`wrf.GeoBounds`, optional): The geobounds to get the extents. If set to None and using the *var* parameter, - the geobounds will be taken from the variable. If using a + the geobounds will be taken from the variable. If using a file, then the geobounds will be taken from the native grid. - + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable, optional): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` - or an iterable sequence of the aforementioned types. If not used, + iterable, optional): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + or an iterable sequence of the aforementioned types. If not used, then *var* must be provided. - + varname (:obj:`str`, optional): If using *wrfin*, then this will be the - variable name to use to determine the geobounds. The variable - can be a coordinate variable, or a regular variable that contains - coordinate attributes. If None, - then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables + variable name to use to determine the geobounds. The variable + can be a coordinate variable, or a regular variable that contains + coordinate attributes. If None, + then the 'XLAT', 'XLAT_M', 'XLONG', 'XLONG_M' variables will be used. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. Default is 0. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. Default is None. - + Returns: - + :obj:`list`: A list of [start_y, end_y] in the projected coordinate system. - + """ - wrf_proj, native_geobnds = _get_wrf_proj_geobnds(var, wrfin, varname, - timeidx, method, squeeze, cache) + wrf_proj, native_geobnds = _get_wrf_proj_geobnds(var, wrfin, varname, + timeidx, method, squeeze, + cache) if geobounds is not None: return wrf_proj.cartopy_ylim(geobounds) @@ -3832,79 +3847,79 @@ def cartopy_ylim(var=None, geobounds=None, wrfin=None, varname=None, timeidx=0, def ll_points(lat, lon): """Return the lower left latitude and longitude point(s). - + This functions extracts the lower left corner points and returns the result - as either a single :class:`CoordPair` object, or a list of + as either a single :class:`CoordPair` object, or a list of :class:`CoordPair` objects. - + This is primarily used for testing or constructing the corner point objects from the XLAT and XLONG variables. - + Args: - - lat (:class:`xarray.DataArray` or :class:`numpy.ndarray`): The latitude + + lat (:class:`xarray.DataArray` or :class:`numpy.ndarray`): The latitude array. Must be at least two dimensions. - - lon (:class:`xarray.DataArray` or :class:`numpy.ndarray`): The + + lon (:class:`xarray.DataArray` or :class:`numpy.ndarray`): The longitude array. Must be at least two dimensions. - + Returns: - - :class:`wrf.CoordPair` or :obj:`list`: A single :class:`wrf.CoordPair` + + :class:`wrf.CoordPair` or :obj:`list`: A single :class:`wrf.CoordPair` object or a list of :class:`wrf.CoordPair` objects. - + """ - latvals = np.ravel(to_np(lat)[...,0,0]) - lonvals = np.ravel(to_np(lon)[...,0,0]) - + latvals = np.ravel(to_np(lat)[..., 0, 0]) + lonvals = np.ravel(to_np(lon)[..., 0, 0]) + if latvals.shape[0] == 1: return CoordPair(lat=float(latvals), lon=float(lonvals)) else: - return [CoordPair(lat=latvals[i], lon=lonvals[i]) + return [CoordPair(lat=latvals[i], lon=lonvals[i]) for i in py3range(latvals.shape[0])] def pairs_to_latlon(pairs): """Return latitude and longitude arrays from a sequence of \ :class:`wrf.CoordPair` objects. - - This function converts a sequence of :class:`wrf.CoordPair` objects into - lists of latitude and longitude points. If the *pairs* argument is a - single :class:`wrf.CoordPair` object, then a single latitude and + + This function converts a sequence of :class:`wrf.CoordPair` objects into + lists of latitude and longitude points. If the *pairs* argument is a + single :class:`wrf.CoordPair` object, then a single latitude and longitude value is returned. - + Args: - - pairs (:class:`wrf.CoordPair` or sequence): A single + + pairs (:class:`wrf.CoordPair` or sequence): A single :class:`wrf.CoordPair` or sequence of :class:`wrf.CoordPair`. - + Returns: - - :obj:`tuple`: A tuple of (lat, lon), where lat and lon are single - values or lists of values. - + + :obj:`tuple`: A tuple of (lat, lon), where lat and lon are single + values or lists of values. + """ - + if isinstance(pairs, CoordPair): return (pairs.lat, pairs.lon) else: lats = [pair.lat for pair in pairs] lons = [pair.lon for pair in pairs] - + return lats, lons - + def is_latlon_pair(pair): """Return True if the :class:`wrf.CoordPair` is a lat/lon pair - + Args: - + pair (:class:`wrf.CoordPair`): A single :class:`wrf.CoordPair` object. - + Returns: - + :obj:`bool`: True if the pair is a lat/lon pair. - + """ if pair is not None: return (pair.lat is not None and pair.lon is not None) diff --git a/src/wrf/version.py b/src/wrf/version.py index 1794957..67bc602 100644 --- a/src/wrf/version.py +++ b/src/wrf/version.py @@ -1,2 +1 @@ __version__ = "1.3.0" -