#
# Changes
# 
# 2022-09, Arjo Segers, Met-Norway
#   Added class to collect co-located observations.
#


########################################################################
###
### help
###
########################################################################

"""
.. _cso-colocate:

***********************
``cso_colocate`` module
***********************

The :py:mod:`cso_colocate` module provides classes to collect 
observations and simulations from pixels that are co-located with surface locations.


Class hierchy
=============

The classes and are defined according to the following hierchy:

* :py:class:`.UtopyaRc`

  * :py:class:`.CSO_CoLocate`
  * :py:class:`.CSO_CoLocatePlotTimeSeries`



Classes
=======


"""


########################################################################
###
### modules
###
########################################################################

# modules:
import logging

# tools:
import utopya
import cso_catalogue


########################################################################
###
### timeseries of average over co-located pixels
###
########################################################################

class CSO_CoLocate( utopya.UtopyaRc ) :

    """
    Collect time series of observations and/or simulations co-located with
    surface locations. The value assigned to location and time is an average over all pixels
    within a specified distance and valid for the time.
    
    Location should be specified in a csv file with columns at least longitude and latiude::
    
      name                   ;longitude;latitude
      Peyrusse Vieille       ;     0.18;   43.62
      San Pablo de los Montes;    -4.35;   39.55
      Lahemaa                ;    25.9 ;   59.5
      
    The column names are not pre-defined, and additional columns are allowed too.
    Lines that start with ``'#'`` are ignored.
    By default the seperation character is ``';'``, with surrounding white-space ignored.
    In the settings, specifiy the name of the csv file and eventually other characters
    for the seperation and comment::

        ! table with station locations:
        <rcbase>.locations.file           :  /data/stations.csv
        ! special characters:
        <rcbase>.locations.sep            :  ;
        <rcbase>.locations.comment        :  #
    
    Also specify the column names that used for the longitude and latitude::

        ! column names:
        <rcbase>.locations.longitude      :  longitude
        <rcbase>.locations.latiutde       :  latiutde

    Time series are created within a time range::
    
      ! time range:
      <rcbase>.timerange.start        :  2018-06-01 00:00
      <rcbase>.timerange.end          :  2018-06-03 23:59

    Output is collected in files per time interval (daily, hourly).
    Specify the output freqency and a template for the name of the target file::

      ! time step: monthly, daily, ..
      <rcbase>.output.freq            :  monthly

      ! target file:
      <rcbase>.output.file            :   /scratch/colocated/timeseries_%Y%m.nc
    
    Specify a target time resolution; pixels are collected in intervals ``[t1,t2)`` ::

        ! time resolution: hourly
        <rcbase>.resolution             :  hourly

    Pixels are selected within a distance of a location::

        ! collect pixels within radion of km:
        <rcbase>.distance               :  100.0

    Input data could originate from different sources, for example a *data* file with footprints
    and retrieval, and a *state* file with simulations by a model.
    Specify a list with keywords describing the sources; the first source should be a file
    that contains the footprints::
    
        ! keywords for source files:
        <rcbase>.sources                :  data

    For each source, specify the input file(s) as a filename pattern::
    
        ! input file or filename pattern for each source type:
        <rcbase>.source.data         :  /scratch/output/CSO_output_%Y%m%d_%H%M_S5p-no2_data.nc
    
    The input could be filtered by applying selections, for example on quality value;
    see :py:meth:`CSO_File.SelectPixels` method for supported filter types::

        ! keywords for filters:
        <rcbase>.filters                :  quality

        ! minimum quality value required:
        <rcbase>.filter.quality.var       :  qa_value
        <rcbase>.filter.quality.type      :  min
        <rcbase>.filter.quality.min       :  0.8
        <rcbase>.filter.quality.units     :  1

    Specify a list of the variables to be created, and for each of them the source type and source variable::

        ! data variables to be created:
        <rcbase>.output.vars            :  xvmr

        ! input variables:
        !   data:vcd    : from data file
        !   state:hx    : from state file
        <rcbase>.output.xvmr.source     :  data:xvmr

    Existing files are replaced if the following flag is set::
    
        ! renew existing files?
        <rcbase>.renew                  :  True

    To reduce file size, by default all variables are written to file as short-integers (2 bytes) 
    accompanied by ``add_offset`` and ``scale_factor`` attributes. 
    A flag is available to disable packing.
    In addition, ``zlib`` compression is enabled.
    The default compression level is 1 (out of 9); set the following flag to a higher level
    to have stronger compression (on expense of computation time), or set to 0 to disable compression::
    
        ! pack floats as shorts:
        <rcbase>.output.packed              :  True
        ! zlib compression level (default 1, 0 for no compression):
        <rcbase>.output.complevel           :  1

    """
    
    def __init__( self, rcfile, rcbase='', env={}, indent='' ) :
    
        """
        Extract timeseries.
        """
                        
        # modules:
        import os
        import numpy
        import datetime
        import glob
        import pandas
        import xarray
        
        # tools:
        import cso_file
        import cso_mapping

        # init base object:
        utopya.UtopyaRc.__init__( self, rcfile, rcbase=rcbase, env=env )
        
        # info ...
        logging.info( indent+'' )
        logging.info( indent+'** Co-located CSO data' )
        logging.info( indent+'' )

        # time range:
        t1 = self.GetSetting( 'timerange.start', totype='datetime' )
        t2 = self.GetSetting( 'timerange.end'  , totype='datetime' )

        # info ...
        tfmt = '%Y-%m-%d %H:%M'
        logging.info( indent+'timerange: [%s,%s]' % (t1.strftime(tfmt),t2.strftime(tfmt)) )
        
        # output time step:
        output_freq = self.GetSetting( 'output.freq' )
        # info ...
        logging.info( indent+'output freqency: %s' % output_freq )
        # output time intervals:
        if output_freq in ['D','day','daily'] :
            outtimes1 = pandas.date_range( t1, t2, freq='D' )
            outtimes2 = outtimes1 + pandas.Timedelta(days=1)
        elif output_freq in ['M','month','monthly'] :
            outtimes1 = pandas.date_range( t1, t2, freq='MS' )
            outtimes2 = outtimes1 + pandas.DateOffset(months=1,seconds=-1)
        else :
            logging.error( 'unsupported output time step "%s"' % step )
            raise Exception
        #endif
        
        # target file:
        outfile_template = self.GetSetting( 'output.file' )
        # info ...
        logging.info( indent+'target files: %s' % outfile_template )

        # renew?
        renew = self.GetSetting( 'renew', totype='bool' )
        # info ...
        logging.info( indent+'renew output: %s' % renew )
        
        # pack floats as shorts?
        packed = self.GetSetting( 'output.packed', 'bool' )
        # compression level:
        complevel = self.GetSetting( 'output.complevel', 'int' )

        # global attributes
        gattrs = {}
        attrnames = self.GetSetting( 'output.attrs' ).split()
        for key in attrnames :
            gattrs[key] = self.GetSetting( 'output.attr.%s' % (key) )
        #endfor

        # station file:
        locations_file = self.GetSetting( 'locations.file' )
        # info ...
        logging.info( indent+'location file: %s' % locations_file )
        # check ..
        if not os.path.isfile(locations_file) :
            logging.error( 'file not found: %s' % locations_file )
            raise Exception
        #endif
        # special characters:
        sep     = self.GetSetting( 'locations.sep', default=';' )
        comment = self.GetSetting( 'locations.sep', comment='#' )
        # column names:
        loc_lon  = self.GetSetting( 'locations.longitude' )
        loc_lat  = self.GetSetting( 'locations.latitude' )
        # read:
        locations = pandas.read_csv( locations_file, 
                                        sep='\s*'+sep+'\s*', engine='python', comment=comment )
        # count:
        nloc = len(locations)
        
        # time resolution
        resolution = self.GetSetting( 'resolution' )
        
        # selection distance:
        distance_km = self.GetSetting( 'distance', 'float' )
        
        # source types:
        stypes = self.GetSetting( 'sources' ).split()
        # storage per source:
        sources = {}
        # loop:
        for stype in stypes :
            # tempate for input file:
            sources[stype] = {}
            sources[stype]['input'] = self.GetSetting( 'source.%s' % stype )
        #endfor
        
        # first source type, this should have the footprints:
        stype0 = stypes[0]
        
        # variables:
        vkeys = self.GetSetting( 'output.vars' ).split()
        # storage for input description:
        varsources = {}
        for vkey in vkeys :
            # source description:
            varsources[vkey] = self.GetSetting( 'output.%s.source' % (vkey) )
        #endfor
        
        # loop over times for output files:
        for iout in range(len(outtimes1)) :
            # current:
            outtime1 = outtimes1[iout]
            outtime2 = outtimes2[iout]
            # target file:
            outfile = outtime1.strftime( outfile_template )
            # create?
            if (not os.path.isfile(outfile)) or renew :
                # info ...
                logging.info( indent+'create %s ..' % outfile )
                
                # averaging time intervals:
                if resolution == 'hourly' :
                    # start and end times:
                    tt1 = pandas.date_range( outtime1, outtime2, freq='H' )
                    tt2 = t1 + pandas.Timedelta(hours=1)
                #
                elif resolution == 'daily' :
                    # start and end times:
                    tt1 = pandas.date_range( outtime1, outtime2, freq='D' )
                    tt2 = tt1 + pandas.Timedelta(days=1)
                #
                else :
                    logging.error( 'unsupported resolution "%s"' % resolution )
                    raise Exception
                #endif
                # mid:
                ttm = tt1 + (tt2-tt1)/2
                # count:
                nt = len(ttm)
                
                # info ...
                logging.info( indent+'init output file ...' )
                # init output file:
                outf = cso_file.CSO_File()
                # locations:
                outf.AddCoord( 'location', xarray.DataArray( numpy.arange(1,nloc+1), dims=('location',),
                                                      attrs={ 'long_name' : 'location', 'units' : '1' } ) )
                # times:
                outf.AddTimeCoord( ttm, (tt1,tt2) )
                # store location info:
                for key in locations.keys() :
                    # variable name:
                    vname = 'location_%s' % key
                    # add data:
                    outf.AddDataVariable( vname, ('location',), values=locations[key],
                                                attrs={ 'long_name' : 'location %s' % key, 'units' : '1' } )
                #endfor
                # storage for number of selected pixels:
                outf.AddDataVariable( 'npixel', ('time','location'), values=numpy.zeros((nt,nloc),dtype='i2'),
                                            attrs={ 'long_name' : 'number of pixels within %i km' % distance_km, 'units' : '1' } )
                # variables not defined yet, require attributes from input:
                outf_setup = False

                # init history: 
                line = 'averages over pixels with %i km from locations specified in %s;' % (distance_km,locations_file)
                line = line+'data files:'
                for stype in stypes :
                    line = line+' '+sources[stype]['input']
                #endfor
                # store:
                history = [line]

                # assume input has hourly timestamps ..
                times = pandas.date_range( outtime1, outtime2, freq='H' )
                # info ...
                logging.info( indent+'search for input files in [%s,%s] ..' % (outtime1,outtime2) )
                logging.info( indent+'  search times: %s, .., %s' % (times[0],times[-1]) )
                # list of processed files:
                donefiles = []
                # loop over input times:
                for time in times :

                    # data file with locations:
                    infile0 = time.strftime( sources[stype0]['input'] )
                    # already done? then next time:
                    if infile0 in donefiles : continue
                    # not all hours have simulations..
                    if not os.path.isfile(infile0) : continue

                    # info ...
                    logging.info( indent+'  read input file(s) ...' )
                    # loop:
                    for stype in stypes :
                        # input file:
                        infile = time.strftime( sources[stype]['input'] )
                        # info ..
                        logging.info( indent+'    %s' % infile )
                        # read:
                        sources[stype]['file'] = cso_file.CSO_File( filename=infile )
                        ## update history:
                        #history.insert( 0, 'pixel data from %s' % os.path.basename(infile) )
                    #endfor

                    # filter:
                    selected,history = sources[stype0]['file'].SelectPixels( self.rcf, rcbase, indent=indent+'    ' )
                    # count:
                    npix = selected.size
                    nsel = selected.sum()
                    # none?
                    if nsel == 0 :
                        loggin.info( indent+'      no pixels selected ..' )
                        continue
                    #endif
                    # info ..
                    logging.info( indent+'      selected %i / %i pixels ..' % (nsel,npix) )
                    
                    # target record:
                    ii, = numpy.where( (time >= tt1) & (time < tt2) )
                    # check ..
                    if len(ii) != 1 :
                        logging.error( 'time %s found %i times within bounds:' % (time,len(ii)) )
                        for it in range(len(tt1)) :
                            logging.error( '[%s,%s]' % (t1[it],t2[it]) )
                        #endfor
                        raise Exception
                    #endif
                    # extract:
                    itime = ii[0]
                    # info ..
                    logging.info( indent+'      target time interval: [%s,%s)' % (tt1[itime],tt2[itime]) )
                    
                    # storage:
                    das_in = {}
                    # loop over target variables:
                    for vkey in vkeys :
                        # source format:  state:y
                        source = varsources[vkey]
                        stype,sname = source.split(':')
                        # link input data array:
                        das_in[vkey] = sources[stype]['file'].ds[sname]
                        # check ..
                        shp = das_in[vkey].shape[1:]
                        if numpy.product(shp) != 1 :
                            logging.error( 'source variable "%s" has shape %s per pixel, but currently only scalars supported ...' % (vkey,str(shp)) )
                            raise Exception
                        #endif
                    #endfor

                    # neet to define output? uses attributes from input, so could only be done here
                    if not outf_setup :
                        # loop over target variables:
                        for vkey in vkeys :
                            # empty array:
                            outf.AddDataVariable( vkey, ('time','location'), dtype='f4', attrs=das_in[vkey].attrs )
                        #endfor
                        # reset:
                        outf_setup = True
                    #endif # init output?

                    # info ..
                    logging.info( indent+'      loop over locations ...' )
                    # loop over locations:
                    for iloc in range(nloc) :
                        # index:
                        indx = locations.index[iloc]
                        # extract:
                        lon  = locations.at[indx,loc_lon]
                        lat  = locations.at[indx,loc_lat]
                        # info ...
                        logging.info( indent+'        location % (%f,%f)' % (iloc+1,lon,lat) )
                        # distance in m from pixels to location:
                        dist_m = cso_mapping.LonLatDistance( lon, lat, 
                                                            sources[stype0]['file'].ds['longitude'].values,
                                                            sources[stype0]['file'].ds['latitude'].values   )
                        # over-all selection:
                        iipix, = numpy.where( (dist_m/1000.0 <= distance_km) & selected )
                        # any?
                        if len(iipix) == 0 :
                            # info ...
                            logging.info( indent+'          no pixels within %i km' % distance_km )
                        else :
                            # count:
                            nval = len(iipix)
                            # info ...
                            logging.info( indent+'          found %i selected pixels within %i km' % (nval,distance_km) )
                            # add contribution:
                            outf.ds['npixel'].values[itime,iloc] += nval
                            # loop over target variables:
                            for vkey in vkeys :
                                # sum over selected pixels:
                                vsum = das_in[vkey].values[iipix,:].sum()
                                # add contribution:
                                if numpy.isnan(outf.ds[vkey].values[itime,iloc]) :
                                    outf.ds[vkey].values[itime,iloc]  = vsum
                                else :                                    
                                    outf.ds[vkey].values[itime,iloc] += vsum
                                #endif
                            #endfor
                        #endif
                    #endfor # locations
                    
                #endfor # input times
                
                # info ...
                logging.info( indent+'  averages ...' )
                # times/locations with pixels found:
                kk,ii = numpy.where( outf.ds['npixel'].values > 0 )
                # any?
                if len(ii) > 0 :
                    # loop over target variables:
                    for vkey in vkeys :
                        # convert sum to average:
                        outf.ds[vkey].values[kk,ii] /= outf.ds['npixel'].values[kk,ii]
                    #endfor # vkey
                #endif # any pixels co-located
                
                # info ...
                logging.info( indent+'  write ...' )
                # write:
                outf.Write( outfile, history=history, attrs=gattrs, unlimited_dims=['time'], 
                                packed=packed, complevel=complevel )

            else :
                # info ...
                logging.info( indent+'keep  %s ..' % outfile )
            #endif # renew

        #endfor # outtimes
             
        #endfor # time loop
        
        # info ...
        logging.info( indent+'' )
        logging.info( indent+'** end' )
        logging.info( indent+'' )
    
    #enddef __init__
    
#endclass CSO_CoLocate


########################################################################
###
### CoLocatePlotTimeseries
###
########################################################################

class CSO_CoLocatePlotTimeSeries( cso_catalogue.CSO_CatalogueBase ) :

    """
    Plot timeseries of averages over co-located pixels,
    as collected by the :py:class:`CSO_CoLocate` class.
    
    The following is an example of a timeseries plot that shows averages
    over co-located pixels with 100 km of a site for at daily and hourly resolution:

    .. figure:: figs/HCHO/timeseries_001.png
       :scale: 70 %
       :align: center
       :alt: Timeseries of averages over pixels collected within 100 km of site.

    In the settings, specify the range of the time series::

        ! time range:
        <rcbase>.timerange.start        :  ${my.timerange.start}
        <rcbase>.timerange.end          :  ${my.timerange.end}

    Input files are created by the :py:class:`CSO_CoLocate` class; specify a file name pattern
    for the file(s)::
    
        ! filter for input files:
        <rcbase>.input.files            :  /scratch/colocated/timeseries_*_daily.nc

    For each location a single figure will be created, with time series at one or more resolutions
    (daily, monthly), and one or more variables (observations, simulations).
    The filename should include a pattern to substitute at least one location property,
    for example a station code; if no suitable properties are available, the location index (1-based)
    could be used::
    
        ! target figure; 
        ! see 'str.format' for substitution of station codes etc,
        ! use 'location' for 1-based index:
        !    timeseries_{code}.png
        !    timeseries_{location:0>3}.png
        <rcbase>.fig.file            :  /scratch/colocated/figs/timeseries_{location:0>3}.png

    By default existing files are created again::
    
        ! renew existing files?
        <rcbase>.renew                  :  True
    
    Define the figure size with::

        ! figure size:
        <rcbase>.fig.size            :  (8,4)
        
    The title could include substitutions for location properties::

        ! title, include location tempates:
        <rcbase>.fig.title           :  {name} ({longitude:7.2f},{latitude:6.2f})

    Also define the label for the y-axis, as well as the value range::

        ! label, use '{units}' template
        <rcbase>.fig.ylabel          :  Glyoxal tropospheric column [{units}]
        <rcbase>.fig.ylim            :  [None,None]

    The plot could include multiple temporal resolutions.
    The :py:meth:``xarray.DataArray.resample`` method is used to create for example
    monthly average from daily time series.
    Specify a list of resolutions to be resampled, or use ``N`` for the native resolution::

        ! time resamplings: 
        !   D=daily, M=monthly
        !   or "N" for native
        <rcbase>.resolutions         :  N M

    Specify a list of keywords for the variables to be plotted;
    for each variable, specify target units and line style (including label for legend) per temporal resolution::

        ! variables to be plotted:
        <rcbase>.vars                :  obs sim
        ! style for observations:
        <rcbase>.var.obs.units        :  umol/m2
        <rcbase>.var.obs.N.style      :  color='0.7', marker='.', linestyle='None'
        <rcbase>.var.obs.M.style      :  color='black', marker='o', linestyle='-', label='observations'
        ! style:
        <rcbase>.var.sim.units        :  umol/m2
        <rcbase>.var.sim.N.style      :  color='orange', alpha=0.5, marker='.', linestyle='None'
        <rcbase>.var.sim.M.style      :  color='orange', marker='o', linestyle='-', label='simulations'

    """
    
    def __init__( self, rcfile, rcbase='', env={}, indent='' ) :
    
        """
        Create catalogue of CSO figures.
        """
                        
        # modules:
        import os
        import glob
        import xarray
        import pandas
        import warnings
        import datetime
        import matplotlib.pyplot as plt
        import matplotlib.dates  as mdates
        
        # init base object:
        cso_catalogue.CSO_CatalogueBase.__init__( self, rcfile, rcbase=rcbase, env=env )
        
        # info ...
        logging.info( indent+'' )
        logging.info( indent+'** Plot Co-located time series' )
        logging.info( indent+'' )

        # time range:
        t1 = self.GetSetting( 'timerange.start', totype='datetime' )
        t2 = self.GetSetting( 'timerange.end'  , totype='datetime' )

        # info ...
        tfmt = '%Y-%m-%d %H:%M'
        logging.info( indent+'timerange: [%s,%s]' % (t1.strftime(tfmt),t2.strftime(tfmt)) )
        
        # renew?
        renew = self.GetSetting( 'renew', totype='bool' )
        # info ...
        logging.info( indent+'renew output: %s' % renew )
        
        # filter for input files:
        fpattern = self.GetSetting( 'input.files' )
        # list:
        fnames = glob.glob( fpattern )
        fnames.sort()
        # check ..
        if len(fnames) == 0 :
            logging.error( 'no files matching: %s' % fpattern )
            raise Exception
        #endif
        
        # read files, use nested mode to no have a "time" dimension added to "location_name('location')" variables:
        ds = xarray.open_mfdataset( fnames, combine='nested' )
        
        # count:
        nloc = ds.dims['location']
        # collect location info dicts:
        #    loci = [ dict(name='AAA',lon=12.3,..), .. ]
        loci = [ { 'location' : iloc+1 } for iloc in range(nloc) ]
        # loop over all variables:
        for key in ds.keys() :
            # location info?
            if key.startswith('location_') :
                # header name:
                name = key.replace('location_','')
                # store:
                for iloc in range(nloc) :
                    loci[iloc][name] = ds[key].values[iloc]
                #endfor
            #endif
        #endfor
        
        # resampling resolutions:
        resolutions = self.GetSetting( 'resolutions' ).split()
        
        # variabels:
        varnames = self.GetSetting( 'vars' ).split()
        
        # storage for variable info:
        vari = {}
        # loop over variables:
        for varname in varnames :
            # check ...
            if varname not in ds :
                logging.error( 'no variable "%s" in files: %s' % (varname,fpattern) )
            #endif
            # settings:
            vari[varname] = {}
            vari[varname]['units'] = self.GetSetting( 'var.%s.units' % varname )
        #endfor
        
        # template for output file:
        figfile_template = self.GetSetting( 'fig.file' )
        
        # style:
        figsize = eval( self.GetSetting( 'fig.size' ))
        ylabel_template = self.GetSetting( 'fig.ylabel' )
        ylim = eval(self.GetSetting( 'fig.ylim' ))
        title_template = self.GetSetting( 'fig.title' )
        styles = {}
        for varname in varnames :
            styles[varname] = {}
            for resolution in resolutions :
                styles[varname][resolution] = self.GetSetting( 'var.%s.%s.style' % (varname,resolution), totype='dict' )
            #endfor
        #endfor
        
        # info ...
        logging.info( indent+'resample ...' )
        # resample (all locations):
        #   das['N']['yr'   ] = ..
        #   das['N']['times'] = ..
        das = {}
        das['times'] = {}
        # first variable, used to extract time axis:
        varname0 = varnames[0]
        # loop over time resolutions:
        for resolution in resolutions :
            # info ..
            logging.info('    resolution "%s" ..' % resolution )
            # storage:
            das[resolution] = {}
            # native or resample?
            if resolution == 'N' :
                # loop over variables:
                for varname in varnames :
                    # native data:
                    das[resolution][varname] = ds[varname]
                #endfor # varname
                # extract time values:
                das[resolution]['times']  = das[resolution][varname0].coords['time'].values
            #
            elif resolution == 'M' :
                # loop over variables:
                for varname in varnames :
                    # use start of month as timestamp ...
                    das[resolution][varname] = ds[varname].resample(time='MS',skipna=True).mean(skipna=True,keep_attrs=True)
                #endfor # varname
                # mid of month:
                das[resolution]['times'] = das[resolution][varname0].coords['time'].values + pandas.Timedelta(days=15)
            #
            else :
                # loop over variables:
                for varname in varnames :
                    # resample variable:
                    das[resolution][varname] = ds[varname].resample(time=resolution,skipna=True).mean(skipna=True,keep_attrs=True)
                #endif
                # extract time values:
                das[resolution]['times']  = das[resolution][varname0].coords['time'].values
            #endif # native, monthly, ..
        #endfor # time resols

        # loop over locations:
        for iloc in range(ds.dims['location']) :
            # info ..
            logging.info( 'location %i / %i ..' % (iloc+1,ds.dims['location']) )
            
            # target file, replace templates:
            figfile = figfile_template.format( **loci[iloc] )
            # renew?
            if (not os.path.isfile(figfile)) or renew :
                # info ..
                logging.info( '  create %s ..' % figfile )
                
                # new:
                fig = plt.figure( figsize=figsize )
                ax = fig.add_axes([0.10,0.07,0.87,0.85])
                
                # loop over variables:
                for varname in varnames :
                    # loop over time resolutions:
                    for resolution in resolutions :
                        # trap warnings on division by zero in case no data is present at all:
                        with warnings.catch_warnings() :
                            # no warnings:
                            warnings.simplefilter("ignore")
                            # extract:
                            values_in = das[resolution][varname].values[:,iloc]
                        #endwith
                        # values, convert if necessary:
                        values,units = self._ConvertUnits( values_in, das[resolution][varname].attrs, 
                                                           vari[varname]['units'] )
                        # add:
                        ax.plot_date( das[resolution]['times'], values, **styles[varname][resolution] )
                    #endfor # time resols
                #endfor # variables

                # annote:
                ax.set_title( title_template.format(**loci[iloc]) )
                # lables:
                ax.legend()
                # time axis:
                ax.plot( [t1,t2], [0,0], color='black', linestyle='-', marker='None' )
                ax.set_xlim([t1,t2])
                ax.xaxis.set_minor_locator( mdates.MonthLocator() )
                ax.xaxis.set_major_locator( mdates.YearLocator() )
                ax.xaxis.set_major_formatter( mdates.DateFormatter('%Y'))
                # value axis:
                ax.set_ylim(ylim)
                ax.set_ylabel( ylabel_template.format(units=units) )
                
                # target dir:
                figdir = os.path.dirname(figfile)
                if len(figdir) > 0 :
                    if not os.path.isdir(figdir) : os.makedirs( figdir )
                #endif
                # save:
                fig.savefig( figfile )
                # clear:
                plt.close( fig )
                
            else :
                # info ...
                logging.info( '  keep   %s ..' % figfile )
            #endif
            
        #endfor # locations        
        
        # info ...
        logging.info( indent+'' )
        logging.info( indent+'** end' )
        logging.info( indent+'' )
    
    #enddef __init__
    
#endclass CSO_CoLocatePlotTimeSeries




########################################################################
###
### end
###
########################################################################

