def gen_xyll(self): from collections import OrderedDict from rpnpy.librmn.all import gdll from fstd2nc.mixins import _var_type, _axis_type self._xaxisatts['long_name'] = 'longitude in rotated pole grid' self._xaxisatts['standard_name'] = 'grid_longitude' self._xaxisatts['units'] = 'degrees' self._xaxisatts['axis'] = 'X' self._yaxisatts['long_name'] = 'latitude in rotated pole grid' self._yaxisatts['standard_name'] = 'grid_latitude' self._yaxisatts['units'] = 'degrees' self._yaxisatts['axis'] = 'Y' self.xaxis = _axis_type('rlon', self._xaxisatts, self._ax) self.yaxis = _axis_type('rlat', self._yaxisatts, self._ay) self.gridaxes = [self.yaxis, self.xaxis] ll = gdll(self._grd['id']) self._lonarray = ll['lon'].transpose( ) # Switch from Fortran to C order. self._lonatts = OrderedDict() self._lonatts['long_name'] = 'longitude' self._lonatts['standard_name'] = 'longitude' self._lonatts['units'] = 'degrees_east' self._latarray = ll['lat'].transpose( ) # Switch from Fortran to C order. self._latatts = OrderedDict() self._latatts['long_name'] = 'latitude' self._latatts['standard_name'] = 'latitude' self._latatts['units'] = 'degrees_north' self.lon = _var_type('lon', self._lonatts, self.gridaxes, self._lonarray) self.lat = _var_type('lat', self._latatts, self.gridaxes, self._latarray) return (self.xaxis, self.yaxis, self.gridaxes, self.lon, self.lat)
def _makevars(self): from fstd2nc.mixins import _var_type, _dim_type import numpy as np super(Ensembles, self)._makevars() for etikets, varlist in self._iter_axes(name='etiket', varlist=True): # Python3: convert bytes to str. array = [str(arr.decode()) for arr in etikets.array] array = np.array(array, dtype=np.char.string_) # Strip out trailing whitespace. array[:] = [arr.rstrip() for arr in array] # Encode it as 2D character array for netCDF file output. n = len(array) array = array.view('|S1').reshape(n, -1) n, strlen = array.shape ensemble_id = _dim_type('ensemble_id', n) ensemble_strlen = _dim_type('ensemble_strlen', strlen) etiket_var = _var_type(name='ensemble', atts={}, axes=[ensemble_id, ensemble_strlen], array=array) for var in varlist: # Replace 'etiket' dimension of variable with ensemble_id dimension. var.axes[var.dims.index('etiket')] = ensemble_id # Add the etiket strings as a coordinate variable. coordinates = var.atts.get('coordinates', []) coordinates.append(etiket_var) var.atts['coordinates'] = coordinates
def _gen_xyll(self): from collections import OrderedDict import numpy as np from rpnpy.librmn.all import gdll, gdxyfll from fstd2nc.mixins import _var_type, _axis_type ll = gdll(self._grd['id']) self._lonarray = ll['lon'].transpose( ) # Switch from Fortran to C order. self._lonatts = OrderedDict() self._lonatts['long_name'] = 'longitude' self._lonatts['standard_name'] = 'longitude' self._lonatts['units'] = 'degrees_east' self._latarray = ll['lat'].transpose( ) # Switch from Fortran to C order. self._latatts = OrderedDict() self._latatts['long_name'] = 'latitude' self._latatts['standard_name'] = 'latitude' self._latatts['units'] = 'degrees_north' xy = gdxyfll(self._grd['id'], ll['lat'], ll['lon']) # Scale grid coordinates back to actual coordinates in projection plane self._ax = (np.rint(xy['x'][:, 0]) - 1) * self._res # metres self._ay = (np.rint(xy['y'][0, :]) - 1) * self._res # Determine the false easting and northing from # the coordinates of the pole and of grid point (1,1) _pole_pi = self._grd['pi'] _pole_pj = self._grd['pj'] self._false_easting = self._ax[int(round(_pole_pi)) - 1] - self._ax[0] self._false_northing = self._ay[int(round(_pole_pj)) - 1] - self._ay[0] self._xaxisatts[ 'long_name'] = 'x-coordinate of polar-stereographic projection' self._xaxisatts['standard_name'] = 'projection_x_coordinate' self._xaxisatts['units'] = 'm' self._xaxisatts['axis'] = 'X' self._yaxisatts[ 'long_name'] = 'y-coordinate of polar-stereographic projection' self._yaxisatts['standard_name'] = 'projection_y_coordinate' self._yaxisatts['units'] = 'm' self._yaxisatts['axis'] = 'Y' self.xaxis = _axis_type('xc', self._xaxisatts, self._ax) self.yaxis = _axis_type('yc', self._yaxisatts, self._ay) self.gridaxes = [self.yaxis, self.xaxis] self.lon = _var_type('lon', self._lonatts, self.gridaxes, self._lonarray) self.lat = _var_type('lat', self._latatts, self.gridaxes, self._latarray) return (self._false_easting, self._false_northing, self.xaxis, self.yaxis, \ self.gridaxes, self.lon, self.lat)
def gen_gmapvar(self): from fstd2nc.mixins import _var_type import numpy as np self._atts['grid_mapping_name'] = 'latitude_longitude' self._atts['earth_radius'] = self._earth_radius # Grid mapping variable self.gmap = _var_type(self._name, self._atts, [], np.array(b"")) return self.gmap
def _iter_dask (self): """ Iterate over all the variables, and convert to dask arrays. """ from fstd2nc.mixins import _iter_type, _var_type from dask import array as da from dask.base import tokenize import numpy as np from itertools import product unique_token = tokenize(self._files,id(self)) # Make a local copy of two columns from the header table. # This speeds up access to their elements in the inner loop. all_file_ids = np.array(self._headers['file_id'],copy=True) all_keys = np.array(self._headers['key'],copy=True) self._makevars() for var in self._iter_objects(): if isinstance(var,_iter_type): name = var.name+"-"+unique_token ndim = len(var.axes) shape = var.shape ndim_outer = var.record_id.ndim ndim_inner = ndim - ndim_outer inner_zeros = (0,)*ndim_inner dsk = dict() chunk_shape = (1,)*ndim_outer+shape[ndim_outer:] for ind in product(*map(range,var.record_id.shape)): # Pad index with all dimensions (including inner ones). key = (name,) + ind + inner_zeros rec_id = int(var.record_id[ind]) # Add this record as a chunk in the dask Array. # Also, specify the preferred order of reading the chunks within the # file. if rec_id >= 0: file_id = all_file_ids[rec_id] filename = self._files[file_id] rec_key = all_keys[rec_id] dsk[key] = (_preferred_chunk_order,filename,rec_key,(self._read_chunk, rec_id, chunk_shape, var.dtype)) else: # Fill missing chunks with fill value or NaN. if hasattr(self,'_fill_value'): var.atts['_FillValue'] = self._fill_value dsk[key] = (np.full, chunk_shape, self._fill_value, var.dtype) else: dsk[key] = (np.full, chunk_shape, float('nan'), var.dtype) chunks = [] for i in range(ndim_outer): chunks.append((1,)*shape[i]) for i in range(ndim_outer,ndim): chunks.append((shape[i],)) array = da.Array(dsk, name, chunks, var.dtype) var = _var_type(var.name,var.atts,var.axes,array) yield var
def gen_gmapvar(self): from fstd2nc.mixins import _var_type import numpy as np self._atts['grid_mapping_name'] = 'rotated_latitude_longitude' self._atts['earth_radius'] = self._earth_radius self._atts['grid_north_pole_latitude'] = self._grid_north_pole_latitude self._atts[ 'grid_north_pole_longitude'] = self._grid_north_pole_longitude # self._atts['north_pole_grid_longitude'] = self._north_pole_grid_longitude # Set the optional grid mapping parameter 'north_pole_grid_longitude' to 0 to avoid # some problems, such as the conversion from netcdf to grib performed by some tools # self._atts['north_pole_grid_longitude'] = 0. # self._atts['longitude_of_prime_meridian'] = 0. # Grid mapping variable self.gmap = _var_type(self._name, self._atts, [], np.array(b"")) return self.gmap
def _makevars(self): from fstd2nc.mixins import _var_type, _axis_type, _dim_type from collections import OrderedDict import numpy as np handled_agg_codes = dict() super(Sfc_Codes, self)._makevars() for var in self._varlist: levels = var.getaxis('level') # Look for variables with surface codes. if var.atts.get('kind', None) != 3 or levels is None: continue codes = tuple(levels.array) coordinates = var.atts.get('coordinates', []) if var.name in sfc_agg_nomvars: # Generate the list of surface types. if codes not in handled_agg_codes: codenames = tuple( sfc_agg_codes.get(code, "unknown") for code in codes) array = np.array( codenames, dtype=np.char.string_).view('|S1').reshape( len(codes), -1) atts = OrderedDict([('standard_name', 'area_type')]) sfctype = _dim_type('sfctype', array.shape[0]) sfctype_strlen = _dim_type('sfctype_strlen', array.shape[1]) surface_type = _var_type("surface_type", atts, [sfctype, sfctype_strlen], array) handled_agg_codes[codes] = surface_type surface_type = handled_agg_codes[codes] # "Levels" are actually surface type ids. var.axes[var.dims.index('level')] = surface_type.getaxis( 'sfctype') # Add the area type to the list of auxiliary coordinates. coordinates.append(surface_type) if len(coordinates) > 0: var.atts['coordinates'] = coordinates
def __iter__(self): """ Processes the records into multidimensional variables. Iterates over (name, atts, axes, array) tuples. Note that array may not be a true numpy array (values are not yet loaded in memory). To load the array, pass it to numpy.asarray(). Deprecated - use .to_xarray() to get a multidimensional structure. """ from warnings import warn warn( "Iterating over a Buffer is deprecated. Use .to_xarray() to access the multidimensional data.", stacklevel=2) from fstd2nc.mixins import _iter_type, _var_type self._makevars() for var in self._iter_objects(): if isinstance(var, _iter_type): array = _Array(self, var) var = _var_type(var.name, var.atts, var.axes, array) yield var
def gen_gmapvar(self): import numpy as np from fstd2nc.mixins import _var_type self._atts['grid_mapping_name'] = 'polar_stereographic' self._atts['earth_radius'] = self._earth_radius if self._grd['north']: self._atts['latitude_of_projection_origin'] = 90. else: self._atts['latitude_of_projection_origin'] = -90. # Set central meridian so that easting and northing directions match those returned by RPN routines self._atts['straight_vertical_longitude_from_pole'] = -( self._grd['dgrw'] + 90.) # Set either 'standard_parallel' or 'scale_factor_at_projection_origin', not both! # self._atts['standard_parallel'] = 60. self._atts[ 'scale_factor_at_projection_origin'] = self.map_scale_factor( self._std_parallel) self._atts['resolution_at_standard_parallel'] = self._res self._gen_xyll() self._atts['false_easting'] = self._false_easting self._atts['false_northing'] = self._false_northing # Grid mapping variable self.gmap = _var_type(self._name, self._atts, [], np.array(b"")) return self.gmap
def _makevars(self): from fstd2nc.mixins import _var_type, _axis_type, _dim_type from fstd2nc.mixins.dates import stamp2datetime from collections import OrderedDict import numpy as np from rpnpy.librmn.fstd98 import fstlir forecast_axis = None # To attach the forecast axis. station = None # To attach the station names as coordinates. momentum = thermo = None # To attach the vertical axes. super(Series, self)._makevars() # Get station and forecast info. # Need to read from original records, because this into isn't in the # data stream. station_header = fstlir(self._meta_funit, nomvar='STNS') if station_header is not None: array = station_header['d'].transpose() # Re-cast array as string. # I don't know why I have to subtract 128 - maybe something to do with # how the characters are encoded in the file? # This isn't always needed. Have test files for both cases. # Need help making this more robust! if array.flatten()[0] >= 128: array -= 128 array = array.view('|S1') nstations, strlen = array.shape array = array.flatten().view('|S%d' % strlen) # Strip out trailing whitespace. # Python3: convert bytes to str array[:] = [str(arr.decode()).rstrip() for arr in array] array = array.view('|S1').reshape(nstations, strlen) station_id = _dim_type('station_id', nstations) station_strlen = _dim_type('station_strlen', strlen) # Encode it as 2D character array for netCDF file output. station = _var_type('station', {}, [station_id, station_strlen], array) # Create forecast axis. forecast_header = fstlir(self._meta_funit, nomvar='HH') if forecast_header is not None: atts = OrderedDict(units='hours') # Note: the information in 'HH' is actually the hour of validity. # Need to subtract the hour from the date of origin in order to get # the leadtime. starting_hour = stamp2datetime(forecast_header['dateo']).hour array = forecast_header['d'].flatten() - starting_hour forecast_timedelta = np.array(array * 3600, 'timedelta64[s]') forecast_axis = _axis_type('forecast', atts, array) # Extract vertical coordinates. for vertvar in ('SH', 'SV'): header = fstlir(self._meta_funit, nomvar=vertvar) if header is None: continue array = header['d'].squeeze() # Drop the top or bottom levels to match the profile data? if self._missing_bottom_profile_level: array = array[:-1] if array.ndim != 1: continue atts = OrderedDict(self._get_header_atts(header)) if vertvar == 'SH': thermo = _axis_type('level', atts, array) if vertvar == 'SV': momentum = _axis_type('level', atts, array) # 'Y' data should be handled fine by _XYCoords - just give a more # specific name to the ni axis for clarity. for var in self._varlist: if var.atts.get('typvar') == 'T' and var.atts.get('grtyp') == 'Y': dims = var.dims iaxis = var.getaxis('i') if iaxis is not None and station is not None and len( iaxis) == station.shape[0]: var.axes[dims.index('i')] = station.axes[0] # Remove degenerate vertical axis for '+' data. # (The one coming from IP1, which is not used.) for var in self._varlist: if var.atts.get('typvar') == 'T' and var.atts.get('grtyp') == '+': dims = var.dims if 'level' in dims: var.record_id = var.record_id.squeeze( axis=dims.index('level')) var.axes.pop(dims.index('level')) # For '+' data, ni is the vertical level, and nj is the forecast. known_levels = dict() for var in self._varlist: if var.atts.get('typvar') != 'T': continue if var.atts.get('grtyp') != '+': continue dims = var.dims # The j dimension is actually the forecast time. jaxis = var.getaxis('j') if jaxis is not None and forecast_axis is not None and len( jaxis) == len(forecast_axis): var.axes[dims.index('j')] = forecast_axis # The i dimension is actually the vertical coordinate for this type of # data. iaxis = var.getaxis('i') if iaxis is not None: # If there's only 1 level (degenerate), then remove that dimension. if len(iaxis) == 1: var.axes.pop(dims.index('i')) continue # Try to map to thermodynamic or momentum levels. level = iaxis level.name = 'level' if var.name in self._momentum_vars and momentum is not None: if len(level) == len(momentum): level = momentum else: warn( _("Wrong number of momentum levels found in the data." )) if var.name in self._thermo_vars and thermo is not None: if len(level) == len(thermo): level = thermo else: warn( _("Wrong number of thermodynamic levels found in the data." )) if level is iaxis: warn( _("Unable to find the vertical coordinates for %s." % var.name)) # Attach a generic level dimension. nlev = len(level) if nlev not in known_levels: known_levels[nlev] = _dim_type('level', nlev) level = known_levels[nlev] else: # Found vertical levels, now define the level kind so VCoords # mixin can add more metadata. var.atts['kind'] = 5 var.axes[dims.index('i')] = level # Some support for squashing forecasts. if getattr(self, '_squash_forecasts', False) is True: known_squashed_forecasts = dict() known_leadtimes = dict() known_reftimes = dict() for var in self._varlist: # Can only do this for a single date of origin, because the time # axis and forecast axis are not adjacent for this type of data. time = var.getaxis('time') forecast = var.getaxis('forecast') if time is None or forecast is None: continue if len(time) != 1: warn( _("Can't use datev for timeseries data with multiple dates of origin. Try re-running with the --dateo option." )) continue var.record_id = var.record_id.squeeze( axis=var.dims.index('time')) var.axes.pop(var.dims.index('time')) key = (id(time), id(forecast)) if key not in known_squashed_forecasts: time0 = time.array[0] # Convert pandas times (if using pandas for processing the headers) time0 = np.datetime64(time0, 's') # Calculate the date of validity forecast_timedelta = np.array(forecast.array * 3600, 'timedelta64[s]') squashed_times_array = time0 + forecast_timedelta time = _axis_type( 'time', OrderedDict([('standard_name', 'time'), ('long_name', 'Validity time'), ('axis', 'T')]), squashed_times_array) known_squashed_forecasts[key] = time # Include forecast and reftime auxiliary coordinates (emulate # what's done in the dates mixin) leadtime = _var_type( 'leadtime', OrderedDict([ ('standard_name', 'forecast_period'), ('long_name', 'Lead time (since forecast_reference_time)'), ('units', 'hours') ]), [time], forecast.array) reftime = _var_type( 'reftime', OrderedDict([('standard_name', 'forecast_reference_time')]), {}, np.array(time0)) known_leadtimes[key] = leadtime known_reftimes[key] = reftime var.axes[var.dims.index( 'forecast')] = known_squashed_forecasts[key] # Add leadtime and reftime as auxiliary coordinates. coords = var.atts.get('coordinates', []) coords.extend([known_leadtimes[key], known_reftimes[key]]) var.atts['coordinates'] = coords # Hook in the station names as coordinate information. if station is not None: for station_id, varlist in self._iter_axes('station_id', varlist=True): # Try to use the provided station coordinate, if it has a consistent # length. if len(station_id) == station.shape[0]: for var in varlist: var.axes[var.dims.index( 'station_id')] = station.axes[0] station_id = station.axes[0] station_coord = station # Otherwise, need to construct a new coordinate with the subset of # stations used. # Assume station_ids start at 1 (not 0). else: indices = station_id.array - 1 array = station.array[indices, :] # Use _axis_type instead of _dim_type to retain the station_id values. station_id = _axis_type('station_id', {}, station_id.array) axes = [station_id, station.axes[1]] station_coord = _var_type('station', {}, axes, array) # Attach the station as a coordinate. for var in varlist: coords = var.atts.get('coordinates', []) coords.append(station_coord) var.atts['coordinates'] = coords
def _makevars(self): from fstd2nc.mixins import _iter_type, _var_type, _axis_type, _dim_type from collections import OrderedDict from rpnpy.librmn.interp import ezqkdef, EzscintError, ezget_nsubgrids from rpnpy.librmn.all import readGrid, RMNError import numpy as np # Scan through the data, and look for any use of horizontal coordinates. grids = OrderedDict() gridmaps = OrderedDict() lats = OrderedDict() lons = OrderedDict() # Only output 1 copy of 1D coords (e.g. could have repetitions with # horizontal staggering. coords = set() super(XYCoords, self)._makevars() # Make sure any LA/LO records get processed first, so we can apply them as # coordinates to other variables. varlist = self._varlist varlist = [v for v in varlist if v.name in ('LA', 'LO') ] + [v for v in varlist if v.name not in ('LA', 'LO')] for var in varlist: # Don't touch variables with no horizontal grid. if all(a not in var.dims for a in ('i', 'j', 'station_id')): continue # Get grid parameters. ni = int(var.atts['ni']) nj = int(var.atts['nj']) grtyp = var.atts['grtyp'] ig1 = int(var.atts['ig1']) ig2 = int(var.atts['ig2']) ig3 = int(var.atts['ig3']) ig4 = int(var.atts['ig4']) # Uniquely identify the grid for this variable. # # Use a looser identifier for timeseries data (ni/nj have different # meanings here (not grid-related), and could have multiple grtyp # values ('+','Y') that should share the same lat/lon info. if var.atts.get('typvar', '').strip() == 'T': key = ('T', ig1, ig2) else: key = (grtyp, ni, nj, ig1, ig2, ig3, ig4) if grtyp in ('Y', '+'): key = key[1:] # Check if we already defined this grid. if key not in grids: lat = lon = xaxis = yaxis = None # Check if GridMap recognizes this grid. if grtyp not in self._direct_grids: try: grd = readGrid(self._meta_funit, var.atts.copy()) gmap = GridMap.gen_gmap(grd) gmapvar = gmap.gen_gmapvar() gridmaps[key] = gmapvar (xaxis, yaxis, gridaxes, lon, lat) = gmap.gen_xyll() except (TypeError, EzscintError, KeyError, RMNError, ValueError): pass # Wasn't supported. # Otherwise, need to decode the information here. if lat is None or lon is None: latatts = OrderedDict() latatts['long_name'] = 'latitude' latatts['standard_name'] = 'latitude' latatts['units'] = 'degrees_north' lonatts = OrderedDict() lonatts['long_name'] = 'longitude' lonatts['standard_name'] = 'longitude' lonatts['units'] = 'degrees_east' latarray = lonarray = None try: # First, handle non-ezqkdef grids. if grtyp in self._direct_grids: latarray = self._find_coord( var, '^^')['d'].squeeze(axis=2) lonarray = self._find_coord( var, '>>')['d'].squeeze(axis=2) # Handle ezqkdef grids. else: gdid = ezqkdef(ni, nj, grtyp, ig1, ig2, ig3, ig4, self._meta_funit) ll = gdll(gdid) latarray = ll['lat'] lonarray = ll['lon'] xycoords = gdgaxes(gdid) ax = xycoords['ax'].transpose() ay = xycoords['ay'].transpose() # Convert from degenerate 2D arrays to 1D arrays. ax = ax[0, :] ay = ay[:, 0] xaxis = _axis_type('x', {'axis': 'X'}, ax) yaxis = _axis_type('y', {'axis': 'Y'}, ay) except (TypeError, EzscintError, KeyError, RMNError, ValueError): pass # Check for LA/LO variables, and use these as the coordinates if # nothing else available. if latarray is None and var.name == 'LA': var.name = 'lat' var.atts.update(latatts) #grids[key] = list(var.axes) lats[key] = var continue if lonarray is None and var.name == 'LO': var.name = 'lon' var.atts.update(lonatts) grids[key] = list(var.axes) lons[key] = var continue if latarray is None or lonarray is None: warn( _("Unable to get lat/lon coordinates for '%s'") % var.name) continue # Construct lat/lon variables from latarray and lonarray. latarray = latarray.transpose( ) # Switch from Fortran to C order. lonarray = lonarray.transpose( ) # Switch from Fortran to C order. # Case 1: lat/lon can be resolved into 1D Cartesian coordinates. # Calculate the mean lat/lon arrays in double precision. meanlat = np.mean(np.array(latarray, dtype=float), axis=1, keepdims=True) meanlon = np.mean(np.array(lonarray, dtype=float), axis=0, keepdims=True) if latarray.shape[ 1] > 1 and lonarray.shape[1] > 1 and np.allclose( latarray, meanlat) and np.allclose( lonarray, meanlon): # Reduce back to single precision for writing out. meanlat = np.array(meanlat, dtype=latarray.dtype).squeeze() meanlon = np.array(meanlon, dtype=lonarray.dtype).squeeze() # Ensure monotonicity of longitude field. # (gdll may sometimes wrap last longitude to zero). # Taken from old fstd_core.c code. if meanlon[-2] > meanlon[-3] and meanlon[-1] < meanlon[ -2]: meanlon[-1] += 360. latarray = meanlat lonarray = meanlon lat = _axis_type('lat', latatts, latarray) lon = _axis_type('lon', lonatts, lonarray) gridaxes = [lat, lon] # Case 2: lat/lon are series of points. elif latarray.shape[0] == 1 and lonarray.shape[ 0] == 1 and ('i' in var.dims or 'station_id' in var.dims): latarray = latarray[0, :] lonarray = lonarray[0, :] # Special case for station data station_id = var.getaxis('station_id') if station_id is not None: gridaxes = [station_id] # Subset the lat/lon to the stations that are actually found. # Assuming the station id (ip3) starts at 1. if isinstance(station_id, _axis_type): indices = np.array(station_id.array, dtype=int) - 1 latarray = latarray[indices] lonarray = lonarray[indices] else: gridaxes = [var.getaxis('i')] lat = _var_type('lat', latatts, gridaxes, latarray) lon = _var_type('lon', lonatts, gridaxes, lonarray) # Case 3: General 2D lat/lon fields on X/Y coordinate system. elif xaxis is not None and yaxis is not None: gridaxes = [yaxis, xaxis] # Special case: have supergrid data, and the user wants to split it? if grtyp == 'U' and self._subgrid_axis: ngrids = ezget_nsubgrids(gdid) ny = len(yaxis.array) // ngrids yaxis.array = yaxis.array[:ny] subgrid = _dim_type('subgrid', ngrids) gridaxes = [subgrid, yaxis, xaxis] latarray = latarray.reshape(ngrids, ny, -1) lonarray = lonarray.reshape(ngrids, ny, -1) lat = _var_type('lat', latatts, gridaxes, latarray) lon = _var_type('lon', lonatts, gridaxes, lonarray) # Case 4: General 2D lat/lon fields with no coordinate system. elif 'i' in var.dims and 'j' in var.dims: gridaxes = [var.getaxis('j'), var.getaxis('i')] lat = _var_type('lat', latatts, gridaxes, latarray) lon = _var_type('lon', lonatts, gridaxes, lonarray) # --- End of lat/lon/xaxis/yaxis decoding. if lat is None or lon is None: warn( _("Unable to get lat/lon coordinates for '%s'") % var.name) continue # Sanity check on lat/lon - make sure we have something of the right size. if lat.array.shape == lat.shape and lon.array.shape == lon.shape: grids[key] = gridaxes lats[key] = lat lons[key] = lon else: warn(_("Wrong shape of lat/lon for '%s'") % var.name) continue # --- End of grid decoding. gridaxes = grids[key] lat = lats[key] lon = lons[key] # Update the var's horizontal coordinates. newaxes = [] if len(gridaxes) == 1: newaxes = [('i', gridaxes[0])] elif len(gridaxes) == 2: newaxes = [('j', gridaxes[0]), ('i', gridaxes[1])] elif len(gridaxes) == 3: newaxes = [('k', gridaxes[0]), ('j', gridaxes[1]), ('i', gridaxes[2])] else: warn(_("Unusual grid axes for '%s' - ignoring.") % var.name) dims = var.dims for oldname, newaxis in newaxes: if oldname in dims: var.axes[dims.index(oldname)] = newaxis # For 2D lat/lon, need to reference them as coordinates in order for # netCDF viewers to display the field properly. if 'lat' not in var.dims or 'lon' not in var.dims: coordinates = var.atts.get('coordinates', []) coordinates.extend([lon, lat]) var.atts['coordinates'] = coordinates if key in gridmaps: var.atts['grid_mapping'] = gridmaps[key] # Throw out superfluous LA/LO variables, if lat/lon was already decoded. if var.name == 'LA' and ('lat' in var.dims or lat in coordinates): var.name = None if var.name == 'LO' and ('lon' in var.dims or lon in coordinates): var.name = None self._varlist = [v for v in varlist if v.name is not None]