forked from 3rdparty/wrf-python
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							1674 lines
						
					
					
						
							55 KiB
						
					
					
				
			
		
		
	
	
							1674 lines
						
					
					
						
							55 KiB
						
					
					
				| from __future__ import (absolute_import, division, print_function,  | |
|                         unicode_literals) | |
|  | |
| import os | |
| from sys import version_info | |
| from copy import copy | |
| from collections import Iterable, Mapping, OrderedDict | |
| from itertools import product | |
| from types import GeneratorType | |
| import datetime as dt | |
| from math import floor, copysign | |
| from inspect import getmodule | |
|  | |
| try: | |
|     from inspect import signature | |
| except ImportError: | |
|     from inspect import getargspec | |
|  | |
| try:     | |
|     from inspect import getargvalues | |
| except ImportError: | |
|     from inspect import getgeneratorlocals | |
|  | |
| import numpy as np | |
| import numpy.ma as ma | |
|  | |
| from .config import xarray_enabled | |
| from .projection import getproj, NullProjection | |
| from .constants import Constants, ALL_TIMES | |
| from .py3compat import (viewitems, viewkeys, viewvalues, isstr, py2round,  | |
|                         py3range, ucode) | |
| from .cache import cache_item, get_cached_item | |
|  | |
| if xarray_enabled(): | |
|     from xarray import DataArray | |
|     from pandas import NaT | |
|  | |
|  | |
| _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") | |
|  | |
| _LON_COORDS = ("XLONG", "XLONG_M", "XLONG_U","XLONG_V", "CLONG") | |
|  | |
| _TIME_COORD_VARS = ("XTIME",) | |
|  | |
|  | |
| def is_time_coord_var(varname): | |
|     return varname in _TIME_COORD_VARS | |
|  | |
|  | |
| def get_coord_pairs(varname): | |
|     return _COORD_PAIR_MAP[varname] | |
|  | |
|  | |
| def is_multi_time_req(timeidx): | |
|     return timeidx is None | |
|  | |
|  | |
| def is_multi_file(wrfnc): | |
|     return (isinstance(wrfnc, Iterable) and not isstr(wrfnc)) | |
|  | |
| def has_time_coord(wrfnc): | |
|     return "XTIME" in wrfnc.variables | |
|  | |
| def is_mapping(wrfnc): | |
|     return isinstance(wrfnc, Mapping) | |
|  | |
| def _generator_copy(gen): | |
|     funcname = gen.__name__ | |
|     try: | |
|         argvals = getargvalues(gen.gi_frame) | |
|     except NameError: | |
|         argvals = getgeneratorlocals(gen) | |
|     module = getmodule(gen.gi_frame) | |
|      | |
|     if module is not None: | |
|         res = module.get(funcname)(**argvals.locals) | |
|     else: | |
|         # Created in jupyter or the python interpreter | |
|         import __main__ | |
|         res = getattr(__main__, funcname)(**argvals.locals) | |
|          | |
|     return res | |
|  | |
| def test(): | |
|     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 | |
|         else: | |
|             val = self._i | |
|             self._i += 1 | |
|             return val | |
|      | |
|     # Python 3 | |
|     def __next__(self): | |
|         return self.next() | |
|  | |
| def latlon_coordvars(d): | |
|     lat_coord = None | |
|     lon_coord = None | |
|      | |
|     for name in _LAT_COORDS: | |
|         if name in viewkeys(d): | |
|             lat_coord = name | |
|             break | |
|          | |
|     for name in _LON_COORDS: | |
|         if name in viewkeys(d): | |
|             lon_coord = name | |
|             break | |
|          | |
|     return lat_coord, lon_coord | |
|  | |
| def is_coordvar(varname): | |
|     return varname in _COORD_VARS | |
|  | |
| class IterWrapper(Iterable): | |
|     """A wrapper class for generators and custom iterable classes which returns | |
|     a new iterator from the start of the sequence when __iter__ is called""" | |
|     def __init__(self, wrapped): | |
|         self._wrapped = wrapped | |
|          | |
|     def __iter__(self): | |
|         if isinstance(self._wrapped, GeneratorType): | |
|             return _generator_copy(self._wrapped) | |
|         else: | |
|             obj_copy = copy(self._wrapped) | |
|             return obj_copy.__iter__() | |
|              | |
|              | |
| def get_iterable(wrfseq): | |
|     """Returns a resetable iterable 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 | |
|              | |
|         else: | |
|             if isinstance(wrfseq, dict): | |
|                 return wrfseq | |
|             else: | |
|                 return dict(wrfseq) # generator/custom iterable class | |
|      | |
| # Helper to extract masked arrays from DataArrays that convert to NaN | |
| def npvalues(array_type): | |
|     if not isinstance(array_type, DataArray): | |
|         result = array_type | |
|     else: | |
|         try: | |
|             fill_value = array_type.attrs["_FillValue"] | |
|         except KeyError: | |
|             result = array_type.values | |
|         else: | |
|             result = ma.masked_invalid(array_type.values, copy=False) | |
|             result.set_fill_value(fill_value) | |
|      | |
|     return result | |
|  | |
| # Helper utilities for metadata | |
| class either(object): | |
|     def __init__(self, *varnames): | |
|         self.varnames = varnames | |
|      | |
|     def __call__(self, wrfnc): | |
|         if is_multi_file(wrfnc): | |
|             if not is_mapping(wrfnc): | |
|                 wrfnc = next(iter(wrfnc)) | |
|             else: | |
|                 entry = wrfnc[next(iter(viewkeys(wrfnc)))] | |
|                 return self(entry) | |
|              | |
|         for varname in self.varnames: | |
|             if varname in wrfnc.variables: | |
|                 return varname | |
|          | |
|         raise ValueError("{} are not valid variable names".format( | |
|                                                             self.varnames)) | |
|  | |
| class combine_with(object): | |
|     # Remove remove_idx first, then insert_idx is applied to removed set | |
|     def __init__(self, varname, remove_dims=None, insert_before=None,  | |
|                  new_dimnames=None, new_coords=None): | |
|         self.varname = varname | |
|         self.remove_dims = remove_dims | |
|         self.insert_before = insert_before | |
|         self.new_dimnames = new_dimnames if new_dimnames is not None else [] | |
|         self.new_coords = (new_coords if new_coords is not None  | |
|                            else OrderedDict()) | |
|      | |
|     def __call__(self, var): | |
|         new_dims = list(var.dims) | |
|         new_coords = OrderedDict(var.coords) | |
|          | |
|         if self.remove_dims is not None: | |
|             for dim in self.remove_dims: | |
|                 new_dims.remove(dim) | |
|                 del new_coords[dim] | |
|          | |
|         if self.insert_before is not None:      | |
|             insert_idx = new_dims.index(self.insert_before) | |
|             new_dims = (new_dims[0:insert_idx] + self.new_dimnames +  | |
|                     new_dims[insert_idx:]) | |
|         elif self.new_dimnames is not None: | |
|             new_dims = self.new_dimnames | |
|          | |
|         if self.new_coords is not None: | |
|             new_coords.update(self.new_coords) | |
|          | |
|         return new_dims, new_coords | |
|      | |
| # This should look like: | |
| # [(0, (-3,-2)), # variable 1 | |
| # (1, -1)] # variable 2 | |
| class combine_dims(object): | |
|      | |
|     def __init__(self, pairs): | |
|         self.pairs = pairs | |
|      | |
|     def __call__(self, *args): | |
|         result = [] | |
|         for pair in self.pairs: | |
|             var = args[pair[0]] | |
|             dimidxs = pair[1] | |
|             if isinstance(dimidxs, Iterable): | |
|                 for dimidx in dimidxs: | |
|                     result.append(var.shape[dimidx]) | |
|             else: | |
|                 result.append(var.shape[dimidxs]) | |
|          | |
|         return tuple(result) | |
|      | |
|    | |
| class from_var(object): | |
|     def __init__(self, varname, attribute): | |
|         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] | |
|              | |
|         if not isinstance(var, DataArray): | |
|             return None | |
|          | |
|         return var.attrs.get(self.attribute, None) | |
|          | |
|  | |
| def _corners_moved(wrfnc, first_ll_corner, first_ur_corner, latvar, lonvar): | |
|     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[-3] = i | |
|         start_idxs = tuple(start_idxs) | |
|          | |
|         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]): | |
|             return True | |
|      | |
|     return False | |
|  | |
|  | |
| def is_moving_domain(wrfseq, varname=None, latvar=either("XLAT", "XLAT_M"),  | |
|                       lonvar=either("XLONG", "XLONG_M"), _key=None): | |
|      | |
|     if isinstance(latvar, either): | |
|         latvar = latvar(wrfseq) | |
|          | |
|     if isinstance(lonvar, either): | |
|         lonvar = lonvar(wrfseq) | |
|          | |
|     # In case it's just a single file | |
|     if not is_multi_file(wrfseq): | |
|         wrfseq = [wrfseq] | |
|      | |
|     # Slow, but safe. Compare the corner points to the first item and see | |
|     # any move.  User iterator protocol in case wrfseq is not a list/tuple. | |
|     if not is_mapping(wrfseq): | |
|         wrf_iter = iter(wrfseq) | |
|         first_wrfnc = next(wrf_iter) | |
|     else: | |
|         # Currently only checking the first dict entry. | |
|         dict_key = next(iter(viewkeys(wrfseq))) | |
|         entry = wrfseq[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: | |
|         try: | |
|             coord_names = getattr(first_wrfnc.variables[varname],  | |
|                               "coordinates").split() | |
|         except AttributeError: | |
|             # Variable doesn't have a coordinates attribute, use the  | |
|             # arguments | |
|             lon_coord = lonvar | |
|             lat_coord = latvar | |
|         else:    | |
|             lon_coord = coord_names[0] | |
|             lat_coord = coord_names[1] | |
|     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) | |
|      | |
|     while True: | |
|         try: | |
|             wrfnc = next(wrf_iter) | |
|         except StopIteration: | |
|             break | |
|         else: | |
|             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 | |
|  | |
| def _get_global_attr(wrfnc, attr): | |
|     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 | |
|          | |
| def extract_global_attrs(wrfnc, attrs): | |
|     if isstr(attrs): | |
|         attrlist = [attrs] | |
|     else: | |
|         attrlist = attrs | |
|          | |
|     multifile = is_multi_file(wrfnc) | |
|      | |
|     if multifile: | |
|         if not is_mapping(wrfnc): | |
|             wrfnc = next(iter(wrfnc)) | |
|         else: | |
|             entry = wrfnc[next(iter(viewkeys(wrfnc)))] | |
|             return extract_global_attrs(entry, attrs) | |
|          | |
|     return {attr:_get_global_attr(wrfnc, attr) for attr in attrlist} | |
|  | |
| def extract_dim(wrfnc, dim): | |
|     if is_multi_file(wrfnc): | |
|         if not is_mapping(wrfnc): | |
|             wrfnc = next(iter(wrfnc)) | |
|         else: | |
|             entry = wrfnc[next(iter(viewkeys(wrfnc)))] | |
|             return extract_dim(entry, dim) | |
|      | |
|     d = wrfnc.dimensions[dim] | |
|     if not isinstance(d, int): | |
|         return len(d) #netCDF4 | |
|     return d # PyNIO | |
|          | |
| def _combine_dict(wrfdict, varname, timeidx, method, meta, _key): | |
|     """Dictionary combination creates a new left index for each key, then  | |
|     does a cat or join for the list of files for that key""" | |
|     keynames = [] | |
|     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) | |
|      | |
|     # Not quite sure how to handle coord caching with dictionaries, so  | |
|     # disabling it for now by setting _key to None. | |
|     first_array = _extract_var(wrfdict[first_key], varname,  | |
|                               timeidx, is_moving=is_moving, method=method,  | |
|                               squeeze=False, cache=None, meta=meta, | |
|                               _key=_key[first_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[:] | |
|      | |
|     idx = 1 | |
|     while True: | |
|         try: | |
|             key = next(key_iter) | |
|         except StopIteration: | |
|             break | |
|         else: | |
|             keynames.append(key) | |
|             vardata = _extract_var(wrfdict[key], varname, timeidx,  | |
|                                    is_moving=is_moving, method=method,  | |
|                                    squeeze=False, cache=None, meta=meta, | |
|                                    _key=_key[key]) | |
|              | |
|             if outdata.shape[1:] != vardata.shape: | |
|                 raise ValueError("data sequences must have the " | |
|                                    "same size for all dictionary keys") | |
|             outdata[idx,:] = npvalues(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 = [] | |
|         coord_vals = [] | |
|         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(npvalues(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  | |
|         # 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  | |
|         # 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,  | |
|                            dims=outdims, attrs=outattrs) | |
|     else: | |
|         outarr = outdata | |
|          | |
|     return outarr | |
|  | |
|  | |
| def _find_coord_names(coords): | |
|     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): | |
|     wrf_iter = iter(wrfseq) | |
|      | |
|     max_times = 0 | |
|     while True: | |
|         try: | |
|             wrfnc = next(wrf_iter) | |
|         except StopIteration: | |
|             break | |
|         else: | |
|             t = extract_dim(wrfnc, "Time") | |
|             max_times = t if t >= max_times else max_times | |
|      | |
|     return max_times | |
|  | |
|  | |
| def _build_data_array(wrfnc, varname, timeidx, is_moving_domain, is_multifile,  | |
|                       _key): | |
|      | |
|     # Note:  wrfnc is always a single netcdf file object | |
|     # 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  | |
|     # caching. | |
|     multitime = is_multi_time_req(timeidx) | |
|     time_idx_or_slice = timeidx if not multitime else slice(None) | |
|     var = wrfnc.variables[varname] | |
|     data = var[time_idx_or_slice, :] | |
|     time_coord = None | |
|      | |
|     # Want to preserve the time dimension | |
|     if not multitime: | |
|         data = data[np.newaxis, :] | |
|      | |
|     attrs = OrderedDict(var.__dict__) | |
|     dimnames = var.dimensions[-data.ndim:] | |
|      | |
|     # 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 | |
|             time_coord = None | |
|         else: | |
|             try: | |
|                 # met_em files | |
|                 stag_attr = getattr(var, "stagger") | |
|             except AttributeError: | |
|                 lon_coord = None | |
|                 lat_coord = None | |
|             else: | |
|                 # 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) | |
|     else: | |
|         coord_names = coord_attr.split() | |
|         lon_coord = coord_names[0] | |
|         lat_coord = coord_names[1] | |
|          | |
|         try: | |
|             time_coord = coord_names[2] | |
|         except IndexError: | |
|             time_coord = None | |
|      | |
|     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 | |
|         # once. | |
|         lon_coord_dimkey = lon_coord + "_dim" | |
|         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 | |
|                  | |
|                 # Returned lats/lons arrays will have a time dimension, so proj | |
|                 # will need to be a list due to moving corner points | |
|                 lats, lons, proj_params = get_proj_params(wrfnc,  | |
|                                                           timeidx,  | |
|                                                           varname) | |
|                 proj = [getproj(lats=lats[i,:],  | |
|                             lons=lons[i,:], | |
|                             **proj_params) for i in py3range(lats.shape[0])] | |
|                  | |
|             else: | |
|                 coords[lon_coord] = (lon_coord_dims[1:],  | |
|                                      lon_coord_vals[0,:]) | |
|                 coords[lat_coord] = (lat_coord_dims[1:],  | |
|                                      lat_coord_vals[0,:]) | |
|                  | |
|                 # Domain not moving, so just get the first time | |
|                 lats, lons, proj_params = get_proj_params(wrfnc, 0, varname) | |
|                 proj = getproj(lats=lats, lons=lons, **proj_params) | |
|                  | |
|             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,:]) | |
|              | |
|             if time_coord is not None: | |
|                 coords[time_coord] = (lon_coord_dims[0],  | |
|                                       [time_coord_vals[timeidx]]) | |
|              | |
|              | |
|             lats, lons, proj_params = get_proj_params(wrfnc, 0, varname) | |
|             proj = getproj(lats=lats, lons=lons, **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): | |
|  | |
|     wrf_iter = iter(wrfseq) | |
|     comboidx = 0 | |
|      | |
|     while True: | |
|         try: | |
|             wrfnc = next(wrf_iter) | |
|         except StopIteration: | |
|             break | |
|         else: | |
|             numtimes = extract_dim(wrfnc, "Time") | |
|              | |
|             if timeidx < comboidx + numtimes: | |
|                 filetimeidx = timeidx - comboidx | |
|                  | |
|                 if meta: | |
|                     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 | |
|                  | |
|             else: | |
|                 comboidx += numtimes | |
|              | |
|     raise IndexError("timeidx {} is out of bounds".format(timeidx)) | |
|  | |
|  | |
| def _find_reverse(wrfseq, varname, timeidx, is_moving, meta, _key): | |
|     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) | |
|         except StopIteration: | |
|             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,  | |
|                 # since the ncfile  needs to be iterated backwards as well. | |
|                 filetimeidx = numtimes - (revtimeidx - comboidx) - 1 | |
|                  | |
|                 if meta: | |
|                     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 | |
|             else: | |
|                 comboidx += numtimes | |
|              | |
|     raise IndexError("timeidx {} is out of bounds".format(timeidx)) | |
|  | |
|  | |
| def _find_arr_for_time(wrfseq, varname, timeidx, is_moving, meta, _key): | |
|     if timeidx >= 0: | |
|         return _find_forward(wrfseq, varname, timeidx, is_moving, meta, _key) | |
|     else: | |
|         return _find_reverse(wrfseq, varname, timeidx, is_moving, meta, _key) | |
|      | |
| # TODO:  implement in C | |
| def _cat_files(wrfseq, varname, timeidx, is_moving, squeeze, meta, _key): | |
|     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  | |
|     # 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,  | |
|                                       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 | |
|      | |
|     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 | |
|         projcached = False | |
|          | |
|         outxtimes = None | |
|         outlats = None | |
|         outlons = None | |
|         outprojs = 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 | |
|         projkey = "projection_cat" if is_moving 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] = npvalues(first_var.coords[timename][:]) | |
|             else: | |
|                 timecached = True | |
|      | |
|         if is_moving: | |
|             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, :] = npvalues(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, :] = npvalues(first_var.coords[lonname][:]) | |
|                 else: | |
|                     loncached = True | |
|                  | |
|             # Projections also need to be aggregated | |
|              | |
|             outprojs = get_cached_item(_key, projkey) | |
|             if outprojs is None: | |
|                 outprojs = np.empty(outdims[0], np.object) | |
|                 outprojs[startidx:endidx] = np.asarray( | |
|                     first_var.attrs["projection"], np.object)[:] | |
|             else: | |
|                 projcached = True | |
|              | |
|      | |
|     startidx = endidx | |
|     while True: | |
|         try: | |
|             wrfnc = next(wrf_iter) | |
|         except StopIteration: | |
|             break | |
|         else: | |
|             vardata = wrfnc.variables[varname][:] | |
|              | |
|             numtimes = vardata.shape[0] | |
|                  | |
|             endidx = startidx + numtimes | |
|              | |
|             outdata[startidx:endidx, :] = vardata[:] | |
|              | |
|             if xarray_enabled() and meta: | |
|                 # XTIME new in 3.7 | |
|                 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[:] | |
|                      | |
|                     if not projcached:   | |
|                         lats, lons, proj_params = get_proj_params(wrfnc,  | |
|                                                               ALL_TIMES,  | |
|                                                               varname) | |
|                         projs = [getproj(lats=lats[i,:],  | |
|                             lons=lons[i,:], | |
|                             **proj_params) for i in py3range(lats.shape[0])] | |
|                      | |
|                         outprojs[startidx:endidx] = np.asarray(projs,  | |
|                                                                np.object)[:]  | |
|              | |
|             startidx = endidx | |
|      | |
|     if xarray_enabled() and meta: | |
|          | |
|         # Cache the coords if applicable | |
|         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 projcached and outprojs is not None: | |
|             cache_item(_key, projkey, outprojs) | |
|         if not timecached and outxtimes is not None: | |
|             cache_item(_key, timekey, outxtimes) | |
|              | |
|         outname = ucode(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 | |
|              | |
|             outattrs["projection"] = outprojs[:] | |
|          | |
|         outdata = outdata[:] | |
|              | |
|         outarr = DataArray(outdata, name=outname, coords=outcoords,  | |
|                            dims=outdimnames, attrs=outattrs) | |
|          | |
|     else: | |
|         outdata = outdata[:] | |
|         outarr = outdata | |
|          | |
|     return outarr | |
|  | |
|  | |
| def _get_numfiles(wrfseq): | |
|     try: | |
|         return len(wrfseq) | |
|     except TypeError: | |
|         wrf_iter = iter(wrfseq) | |
|         return sum(1 for _ in wrf_iter) | |
|  | |
|  | |
| # TODO:  implement in C | |
| def _join_files(wrfseq, varname, timeidx, is_moving, meta, _key): | |
|     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 | |
|  | |
|     # wrfseq might be a generator | |
|     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,  | |
|                                       True, _key) | |
|         time_coord = np.full((numfiles, maxtimes), int(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  | |
|     # 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, Constants.DEFAULT_FILL, first_var.dtype) | |
|     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 | |
|         projcached = False | |
|          | |
|         outxtimes = None | |
|         outlats = None | |
|         outlons = None | |
|         outprojs = 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 | |
|         projkey = "projection_join" if is_moving else None | |
|              | |
|         if timename is not None: | |
|             outxtimes = get_cached_item(_key, timekey) | |
|             if outxtimes is None: | |
|                 outxtimes = np.full(outdims[0:2], Constants.DEFAULT_FILL,  | |
|                                 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, Constants.DEFAULT_FILL,  | |
|                                   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, Constants.DEFAULT_FILL,  | |
|                                   first_var.dtype) | |
|                     outlons[file_idx, 0:numtimes, :] = ( | |
|                         first_var.coords[lonname][:]) | |
|                 else: | |
|                     loncached = True | |
|                  | |
|             # Projections also need two dimensions | |
|              | |
|             outprojs = get_cached_item(_key, projkey) | |
|             if outprojs is None: | |
|                 outprojs = np.full(outdims[0:2], NullProjection(), np.object) | |
|              | |
|                 outprojs[file_idx, 0:numtimes] = np.asarray( | |
|                                                 first_var.attrs["projection"], | |
|                                                 np.object)[:] | |
|             else: | |
|                 projcached = True | |
|              | |
|      | |
|     file_idx=1 | |
|     while True: | |
|         try: | |
|             wrfnc = next(wrf_iter) | |
|         except StopIteration: | |
|             break | |
|         else: | |
|             numtimes = extract_dim(wrfnc, "Time") | |
|             if numtimes < maxtimes: | |
|                 file_times_less_than_max = True | |
|             outvar = wrfnc.variables[varname][:] | |
|              | |
|             if not multitime: | |
|                 outvar = outvar[np.newaxis, :] | |
|                  | |
|             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,  | |
|                                            do_xtime=False) | |
|                 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[:] | |
|                          | |
|                     if not projcached: | |
|                         lats, lons, proj_params = get_proj_params(wrfnc,  | |
|                                                                   ALL_TIMES,  | |
|                                                                   varname) | |
|                         projs = [getproj(lats=lats[i,:],  | |
|                             lons=lons[i,:], | |
|                             **proj_params) for i in py3range(lats.shape[0])] | |
|                      | |
|                         outprojs[file_idx, 0:numtimes] = ( | |
|                             np.asarray(projs, np.object)[:]) | |
|              | |
|             # 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  | |
|     # missing values | |
|     if file_times_less_than_max: | |
|         outdata = np.ma.masked_values(outdata, Constants.DEFAULT_FILL) | |
|          | |
|     if xarray_enabled() and meta: | |
|         # Cache the coords if applicable | |
|         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 projcached and outprojs is not None: | |
|             cache_item(_key, projkey, outprojs) | |
|         if not timecached and outxtimes is not None: | |
|             cache_item(_key, timekey, outxtimes) | |
|          | |
|         outname = ucode(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"] = Constants.DEFAULT_FILL | |
|             outattrs["missing_value"] = Constants.DEFAULT_FILL | |
|              | |
|         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: | |
|                     outlats = outlats[:, np.newaxis, :] | |
|                 outcoords[latname] = outlatdims, outlats | |
|             if lonname is not None: | |
|                 outlons = outlons[:, time_idx_or_slice, :] | |
|                 if not multitime: | |
|                     outlons = outlons[:, np.newaxis, :] | |
|                 outcoords[lonname] = outlatdims, outlons | |
|              | |
|             if not multitime: | |
|                 outattrs["projection"] = outprojs[:, timeidx] | |
|             else: | |
|                 outattrs["projection"] = outprojs | |
|              | |
|         if not multitime: | |
|             outdata = outdata[:, timeidx, :] | |
|             outdata = outdata[:, np.newaxis, :] | |
|      | |
|         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(wrfseq, varname, timeidx, is_moving=None, | |
|                   method="cat", squeeze=True, meta=True,  | |
|                   _key=None): | |
|      | |
|     # Handles generators, single files, lists, tuples, custom classes | |
|     wrfseq = get_iterable(wrfseq) | |
|      | |
|     # 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,  | |
|                             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 | |
|  | |
|  | |
| # Cache is a dictionary of already extracted variables | |
| def _extract_var(wrfnc, varname, timeidx, is_moving,  | |
|                  method, squeeze, cache, meta, _key): | |
|     # Mainly used internally so variables don't get extracted multiple times, | |
|     # particularly to copy metadata.  This can be slow. | |
|     if cache is not None: | |
|         try: | |
|             cache_var = cache[varname] | |
|         except KeyError: | |
|             pass | |
|         else: | |
|             if not meta: | |
|                 return npvalues(cache_var) | |
|              | |
|             return cache_var | |
|      | |
|     multitime = is_multi_time_req(timeidx) | |
|     multifile = is_multi_file(wrfnc) | |
|      | |
|     if is_time_coord_var(varname): | |
|         return extract_times(wrfnc, timeidx, method, squeeze, cache,  | |
|                              meta, do_xtime=True) | |
|      | |
|     if not multifile: | |
|         if xarray_enabled() and meta: | |
|             if is_moving is None: | |
|                 is_moving = is_moving_domain(wrfnc, varname, _key=_key) | |
|             result = _build_data_array(wrfnc, varname, timeidx, is_moving, | |
|                                        multifile, _key) | |
|         else: | |
|             if not multitime: | |
|                 result = wrfnc.variables[varname][timeidx,:] | |
|                 result = result[np.newaxis, :] # So that no squeeze works | |
|             else: | |
|                 result = wrfnc.variables[varname][:] | |
|     else: | |
|         # Squeeze handled in this routine, so just return it | |
|         return combine_files(wrfnc, varname, timeidx, is_moving,  | |
|                              method, squeeze, meta, _key) | |
|      | |
|     return result.squeeze() if squeeze else result | |
|  | |
|  | |
| def extract_vars(wrfnc, timeidx, varnames, method="cat", squeeze=True,  | |
|                  cache=None, meta=True, _key=None): | |
|     if isstr(varnames): | |
|         varlist = [varnames] | |
|     else: | |
|         varlist = varnames | |
|      | |
|     return {var:_extract_var(wrfnc, var, timeidx, None, | |
|                              method, squeeze, cache, meta, _key) | |
|             for var in varlist} | |
|  | |
| # Python 3 compatability | |
| def npbytes_to_str(var): | |
|     return (bytes(c).decode("utf-8") for c in var[:]) | |
|  | |
|  | |
| def _make_time(timearr): | |
|     return dt.datetime.strptime("".join(npbytes_to_str(timearr)),  | |
|                                 "%Y-%m-%d_%H:%M:%S") | |
|  | |
|  | |
| def _file_times(wrfnc, do_xtime): | |
|     if not do_xtime: | |
|         times = wrfnc.variables["Times"][:,:] | |
|         for i in py3range(times.shape[0]): | |
|             yield _make_time(times[i,:]) | |
|     else: | |
|         xtimes = wrfnc.variables["XTIME"][:] | |
|         for i in py3range(xtimes.shape[0]): | |
|             yield xtimes[i] | |
|      | |
|  | |
| def _extract_time_map(wrfnc, timeidx, do_xtime, meta=False): | |
|     return {key : extract_times(wrfseq, timeidx, do_xtime, meta)  | |
|             for key, wrfseq in viewitems(wrfnc)} | |
|          | |
|  | |
| def extract_times(wrfnc, timeidx, method="cat", squeeze=True, cache=None,  | |
|                   meta=False, do_xtime=False): | |
|     if is_mapping(wrfnc): | |
|         return _extract_time_map(wrfnc, timeidx, do_xtime) | |
|      | |
|     multitime = is_multi_time_req(timeidx) | |
|     multi_file = is_multi_file(wrfnc) | |
|     if not multi_file: | |
|         wrf_list = [wrfnc] | |
|     else: | |
|         wrf_list = wrfnc | |
|      | |
|     try: | |
|         if method.lower() == "cat": | |
|             time_list = [file_time  | |
|                          for wrf_file in wrf_list  | |
|                          for file_time in _file_times(wrf_file, do_xtime)] | |
|         elif method.lower() == "join": | |
|             time_list = [[file_time  | |
|                           for file_time in _file_times(wrf_file, do_xtime)] | |
|                          for wrf_file in wrf_list]  | |
|         else: | |
|             raise ValueError("invalid method argument '{}'".format(method)) | |
|     except KeyError: | |
|         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"] | |
|          | |
|         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_list, name=outname, coords=outcoords,  | |
|                            dims=outdimnames, attrs=outattrs) | |
|          | |
|     else: | |
|         outarr = np.asarray(time_list)  | |
|      | |
|     if not multitime: | |
|         return outarr[timeidx] | |
|      | |
|     return outarr | |
|      | |
|      | |
| def is_standard_wrf_var(wrfnc, var): | |
|     multifile = is_multi_file(wrfnc) | |
|     if multifile: | |
|         if not is_mapping(wrfnc): | |
|             wrfnc = next(iter(wrfnc)) | |
|         else: | |
|             entry = wrfnc[next(iter(viewkeys(wrfnc)))] | |
|             return is_standard_wrf_var(entry, var) | |
|                  | |
|     return var in wrfnc.variables | |
|  | |
|  | |
| def is_staggered(var, wrfnc): | |
|     we = extract_dim(wrfnc, "west_east") | |
|     sn = extract_dim(wrfnc, "south_north") | |
|     bt = extract_dim(wrfnc, "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(ref_var, expected_dims): | |
|     """Returns the extra left side dimensions for a variable with an expected | |
|     shape. | |
|      | |
|     For example, if a 2D variable contains an additional left side dimension | |
|     for time, this will return the time dimension size. | |
|      | |
|     """ | |
|     extra_dim_num = ref_var.ndim - expected_dims | |
|      | |
|     if (extra_dim_num == 0): | |
|         return [] | |
|      | |
|     return tuple([ref_var.shape[x] for x in py3range(extra_dim_num)])  | |
|  | |
| def iter_left_indexes(dims): | |
|     """A generator which yields the iteration tuples for a sequence of  | |
|     dimensions sizes. | |
|      | |
|     For example, if an array shape is (3,3), then this will yield: | |
|      | |
|     (0,0), (0,1), (1,0), (1,1) | |
|      | |
|     Arguments: | |
|      | |
|         - dims - a sequence of dimensions sizes (e.g. ndarry.shape) | |
|      | |
|     """ | |
|     arg = [py3range(dim) for dim in dims] | |
|     for idxs in product(*arg): | |
|         yield idxs | |
|          | |
| def get_right_slices(var, right_ndims, fixed_val=0): | |
|     extra_dim_num = var.ndim - right_ndims | |
|     if extra_dim_num == 0: | |
|         return [slice(None)] * right_ndims | |
|      | |
|     return tuple([fixed_val]*extra_dim_num +  | |
|                  [slice(None)]*right_ndims) | |
|  | |
| def get_proj_params(wrfnc, timeidx=0, varname=None): | |
|     proj_params = extract_global_attrs(wrfnc, attrs=("MAP_PROJ",  | |
|                                                 "CEN_LAT", "CEN_LON", | |
|                                                 "TRUELAT1", "TRUELAT2", | |
|                                                 "MOAD_CEN_LAT", "STAND_LON",  | |
|                                                 "POLE_LAT", "POLE_LON")) | |
|     multitime = is_multi_time_req(timeidx) | |
|     if not multitime: | |
|         time_idx_or_slice = timeidx | |
|     else: | |
|         time_idx_or_slice = slice(None) | |
|      | |
|     if varname is not None: | |
|         if not is_coordvar(varname): | |
|             coord_names = getattr(wrfnc.variables[varname],  | |
|                                   "coordinates").split() | |
|             lon_coord = coord_names[0] | |
|             lat_coord = coord_names[1] | |
|         else: | |
|             lat_coord, lon_coord = get_coord_pairs(varname) | |
|     else: | |
|         lat_coord, lon_coord = latlon_coordvars(wrfnc.variables) | |
|      | |
|     return (wrfnc.variables[lat_coord][time_idx_or_slice,:], | |
|             wrfnc.variables[lon_coord][time_idx_or_slice,:], | |
|             proj_params) | |
|      | |
|  | |
| class CoordPair(object): | |
|     def __init__(self, x=None, y=None, i=None, j=None, lat=None, lon=None): | |
|         self.x = x | |
|         self.y = y | |
|         self.i = i | |
|         self.j = j | |
|         self.lat = lat | |
|         self.lon = lon | |
|          | |
|     def __repr__(self): | |
|         args = [] | |
|         if self.x is not None: | |
|             args.append("x={}".format(self.x)) | |
|             args.append("y={}".format(self.y)) | |
|              | |
|         if self.i is not None: | |
|             args.append("i={}".format(self.i)) | |
|             args.append("j={}".format(self.j)) | |
|          | |
|         if self.lat is not None: | |
|             args.append("lat={}".format(self.lat)) | |
|             args.append("lon={}".format(self.lon)) | |
|              | |
|         argstr = ", ".join(args) | |
|          | |
|         return "{}({})".format(self.__class__.__name__, argstr) | |
|      | |
|     def __str__(self): | |
|         return self.__repr__() | |
|      | |
|     def xy_str(self, fmt="{:.4f}, {:.4f}"): | |
|         if self.x is None or self.y is None: | |
|             return None | |
|          | |
|         return fmt.format(self.x, self.y) | |
|      | |
|     def latlon_str(self, fmt="{:.4f}, {:.4f}"): | |
|         if self.lat is None or self.lon is None: | |
|             return None | |
|          | |
|         return fmt.format(self.lat, self.lon) | |
|      | |
|     def ij_str(self, fmt="{:.4f}, {:.4f}"): | |
|         if self.i is None or self.j is None: | |
|             return None | |
|          | |
|         return fmt.format(self.i, self.j) | |
|      | |
|  | |
| def from_args(func, argnames, *args, **kwargs): | |
|     """Parses the function args and kargs looking for the desired argument  | |
|     value. Otherwise, the value is taken from the default keyword argument  | |
|     using the arg spec. | |
|      | |
|     """ | |
|     if isstr(argnames): | |
|         arglist = [argnames] | |
|     else: | |
|         arglist = argnames | |
|      | |
|     result = {} | |
|     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]]  | |
|         else: | |
|             result[argname] = None | |
|      | |
|     return result | |
|  | |
| def _args_to_list2(func, args, kwargs): | |
|     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): | |
|             outargs[-i] = default | |
|      | |
|     # Add the supplied args | |
|     for i,arg in enumerate(args): | |
|         outargs[i] = arg | |
|      | |
|     # Fill in the supplied kargs  | |
|     for argname,val in viewitems(kwargs): | |
|         argidx = argspec.args.index(argname) | |
|         outargs[argidx] = val | |
|          | |
|     return outargs | |
|  | |
| def _args_to_list3(func, args, kwargs): | |
|     sig = signature(func) | |
|     bound = sig.bind(*args, **kwargs) | |
|     bound.apply_defaults() | |
|      | |
|     return [x for x in bound.arguments.values()] | |
|      | |
|  | |
| def args_to_list(func, args, kwargs): | |
|     """Converts the mixed args/kwargs to a single list of args""" | |
|     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): | |
|     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): | |
|     sig = signature(func) | |
|     params = list(sig.parameters.keys()) | |
|      | |
|     list_args = _args_to_list3(func, args, kwargs) | |
|      | |
|     try: | |
|         result_idx = params.index(argname)  | |
|     except ValueError: | |
|         return None | |
|          | |
|     return list_args, result_idx | |
|      | |
|      | |
| def arg_location(func, argname, args, kwargs): | |
|     """Parses the function args, kargs and signature looking for the desired  | |
|     argument location (either in args, kargs, or argspec.defaults),  | |
|     and returns a list containing representing all arguments in the  | |
|     correct order with defaults filled in. | |
|      | |
|     """ | |
|     if version_info > (3,): | |
|         _arg_location = _arg_location3 | |
|     else: | |
|         _arg_location = _arg_location2 | |
|          | |
|     return _arg_location(func, argname, args, kwargs) | |
|  | |
|  | |
| def psafilepath(): | |
|     return os.path.join(os.path.dirname(__file__), "data", "psadilookup.dat") | |
|  | |
|  | |
| def get_id(seq): | |
|     if not is_mapping(seq): | |
|         return id(seq) | |
|      | |
|     # For each key in the mapping, recurisvely call get_id until | |
|     # until a non-mapping is found | |
|     return {key : get_id(val) for key,val in viewitems(seq)} | |
|      | |
|      | |
|      | |
|      | |
|      | |
|      | |
|          | |
|  | |
|  | |
|          | |
|      | |
|      | |
|      | |
|      | |
|      | |
|      | |
|      | |
|  | |
|  | |
|  | |
|  | |
|          | |
|      | |
|      | |
|      |