Exemple #1
0
 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)
Exemple #2
0
 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
Exemple #3
0
 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)
Exemple #4
0
 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
Exemple #5
0
 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
Exemple #6
0
 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
Exemple #7
0
    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
Exemple #8
0
    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
Exemple #9
0
 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
Exemple #10
0
    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
Exemple #11
0
    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]