Source code for vacumm.tide.marigraph

# -*- coding: utf8 -*-
"""
Provides a class to perform basic operations on marigraphic sea level data
"""
# Copyright or © or Copr. Actimar/IFREMER (2011-2015)
#
# This software is a computer program whose purpose is to provide
# utilities for handling oceanographic and atmospheric data,
# with the ultimate goal of validating the MARS model from IFREMER.
#
# This software is governed by the CeCILL license under French law and
# abiding by the rules of distribution of free software.  You can  use,
# modify and/ or redistribute the software under the terms of the CeCILL
# license as circulated by CEA, CNRS and INRIA at the following URL
# "http://www.cecill.info".
#
# As a counterpart to the access to the source code and  rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty  and the software's author,  the holder of the
# economic rights,  and the successive licensors  have only  limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading,  using,  modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean  that it is complicated to manipulate,  and  that  also
# therefore means  that it is reserved for developers  and  experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and,  more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.
import numpy as N,numpy.ma as MA,cdms2 as cdms,MV2 as MV,cdtime
from genutil import minmax
from genutil.statistics import std as STD

import pylab as P,types
import scipy.interpolate as S
import types
from operator import isNumberType
from warnings import warn

__all__ = ['Marigraph']

_docs = dict(
    time_range= "time range for data selection (like ('1999','2000-01-01','co')). If None, time range is reset [default: None]",
    tide_filter=    "Tide ilter name within [False,'demerliac','godin']. Default: False"
)


for key, val in _docs.items():
    ss = '*'*(1+val.startswith('+'))
    _docs[key] = '- %(ss)s%(key)s%(ss)s: %(val)s' % dict(ss=ss, key=key, val=val)
_docs['base'] = '\n\t\t\t'.join([_docs[key] for key in 'time_range', 'tide_filter'])

def _fmtdoc_(func):
#   try:
#       func.__doc__ = func.__doc__ % _docs
#   except:
#       pass
    return func



[docs]class Marigraph(object): """A marigraph class that support statistics and plots. You give as input a 1D cdms sea level variable with a suitable time, or a tide.StationInfo() object, or simply a 5-letter shom ID. The class will design an object with high and low tides, and pure tidal and without tidal signal. You can optionnally specify a time axis on wich the input signal is to be interpolated before any processing. You can also restrict the time range and perfom a running average. :Parameters: All parameters are passed to :meth:`set` - **data** : It can be either - A ``cdms2`` 1D array with a time axis. - A :class:`~vacumm.tide.StationInfo` instance. - The name of a station that can be search for using :class:`~vacumm.tide.StationInfo`. - *select*: time range for data selection (like ('1999','2000-01-01','co')). If None, time range is reset [default: None], - *global_anomaly*: First remove the anomaly of the whole signal before any processing [default: False] - *anomaly*: Remove the anomaly of the restricted sample before any processing [default: False] - *shom_anomaly*: Same but removed mean is get from observation at correspondind shom station [default: False] %s :Example: >>> # Get data >>> from vacumm.tide import Marigraph >>> brest = Marigraph('BREST',range=('2006-03','2006-08'),hourly=True) >>> # Plot >>> brest.plot_sea_level() >>> brest.plot_low_high() >>> brest.plot_cotes() >>> # Change time range >>> brest.set_time_range(('2006-07','2006-08','co')) >>> # Change tide filter >>> brest.set_tide_filter('godin') >>> # Save >>> f = cdms.open('mytide.nc') >>> f.write(brest.cotes()) >>> f.write(brest.high()) >>> f.write(brest.low()) >>> f.write(brest.zeros()) >>> f.close() """ # Initialization # -------------- def __init__(self,data, tide_filter='demerliac', outside_std=3.5,bad_values=None,verbose=False, mean=None, **kwargs): # Data synchronisation self._data_funcs = ['_check_tide_filter_','_check_low_high_',] """Assign a data object to the marigraph :Parameters: - **data** : It can be either - A ``cdms2`` 1D array with a time axis. - A :class:`~vacumm.tide.StationInfo` instance. - The name of a station that can be search for using :class:`~vacumm.tide.StationInfo`. - *global_anomaly*: First remove the anomaly of the whole signal before any processing [default: False] - *anomaly*: Remove the anomaly of the restricted sample before any processing [default: False] - *shom_anomaly*: Same but removed mean is get from observation at correspondind shom station [default: False] %s """ self.clean() self._verbose = verbose if isinstance(data,StationInfo) or \ (isinstance(data, str) and len(data) == 5): # Data is not a variable, but a StationInfo object or a string = shom id if isinstance(data, str): # We start from a shom id if verbose: print 'Searching for SHOM station "'+data+'"...' print '-'*80 station = StationInfo(shom=data,**self._clean_kwargs(kwargs)) if station is None: print 'Not found' raise StandardError if verbose: print '-'*80 shom = data self.name = station.name self.longitude = station.longitude self.latitude = station.latitude else: # We already have a station info shom = data.shom if shom is None: raise StandardError, 'Not a valid SHOM station' if data.nom is not None: self.name = data.nom self.longitude = data.longitude self.latitude = data.latitude from vacumm.tide.sonel_mareg import get_slv_shom data = get_slv_shom(shom,**self._clean_kwargs(kwargs,bad=['tide_filter'])) if mean == 'shom': mean = station.nm else: # Here we have cdms variable, so we just check it assert cdms.isVariable(data), \ 'The marigraph data object is not a valid cdms variable' data = data.clone() # Check time axis taxis = data.getTime() if taxis is None: taxis = data.getAxis(0).designateTime() assert hasattr(taxis, 'units'), 'Time axis has no units' # Must be a vector if data.ndim > 1: # Reorder if not data.getOrder().endswith('t'): data = data.reorder('...t') # Reshape to average tmp = MV2.reshape(data, (N.multiply.reduce(data.shape[:-1]), data.shape[-1])) data = MV2.average(tmp, axis=0) del temp # Get some attributes for att in ['name','longitude','latitude']: latt = 'station_'+att if data.attributes.has_key(latt): setattr(self,att,getattr(data,latt)) # Remove extrem values if outside_std not in [False,None,0.]: std = data.std() if mean is not None: tmpmean = mean else: tmpmean = float(data.mean()) data[:] = MV.masked_outside(data,tmpmean-outside_std*std, tmpmean+outside_std*std) # Attributes self.data['base'] = data if not self.data['base'].attributes.has_key('units'): self.data['base'].units = 'm' if not self.data['base'].attributes.has_key('long_name'): self.data['base'].units = 'Sea level' # Real mean if mean is None: mean = data.mean() self._mean = float(mean) self.data['anom'] = data self.data['anom'][:] -= mean self.data['anom'].id += '_anom' self.data['anom'].long_name = 'Anomaly of '+self.data['anom'].long_name self.tide_filter = tide_filter # Apply tide filters # ------------------
[docs] def mean(self): """Mean sea level""" return self._mean
[docs] def anomaly(self): """Sea level anomaly""" return self._data['anom']
[docs] @_fmtdoc_ def set_tide_filter(self,tide_filter): """Set the tide filter :Parameters: %(tide_filter)s """ self._check_tide_filter_(tide_filter)
def _check_tide_filter_(self, tide_filter=None, **kwargs): # Do we have something? need = self.data['tide'] is None # A filter is specified if tide_filter is not None: if tide_filter is True: # Default filter tide_filter = 'demerliac' else: # Valid filter? valid_filters = ['demerliac','godin',False] assert tide_filter in valid_filters, "Wrong filter name. Must be within %s. Please use set_tide_filter()) method."%str(valid_filters) # Change? need = need or (self.tide_filter != tide_filter) # Something changed? if not need: return if self.tide_filter is False: # Reset self.data['tide'] = self.data['base'] self.data['cotes'] = self.data['base'].clone() self.data['cotes'][:] = 0. else: # Apply filter filter_func = getattr(filters, tide_filter) self.data['cotes'],self.data['tide'] = filter_func(self.data['anom'],get_tide=True) if self._verbose: print 'Computed tide signal using %s filter' % tide_filter self.tide_filter = tide_filter # Low and high tides # ------------------ def _check_low_high_(self, ref, **kwargs): """Computes high and low tides from a sea level anomaly. It defines following variables: high,low """ # Check changes if self.data['lows'] is not None: return # Reference self._lowhigh_ref = ref if ref in ['demerliac', 'godin']: if self.tide_filter != ref: ref, tide = getattr(filters, ref)(self.data['base']) else: ref = self.data['cotes'] # Call to extema self.data['lows'], self.data['highs'] = filters.extrema(self.data['anom'], ref=ref, **kwargs) self.data['zeros'] = filters.zeros(self.data['anom'], ref=ref, **kwargs) for ex in 'lows', 'highs', 'zeros': self.data[ex][:] += self._mean if self._verbose: print 'Computed low and high tides' # Check changes # ------------- #### Get data
[docs] def sea_level(self): """Sea level""" return self.data['base']
[docs] @_fmtdoc_ def tide(self, anom=True, **kwargs): """Return the tide signal: this signal is obtained by filtering the original sea level signal using a demerliac or a godin filer. %(ref)s .. seealso: :meth:`set_tide_filter`, :meth:`cotes`, :mod:`vacumm.tide.filters` """ self._check_tide_filter_(**kwargs) data = self.data['tide'] if not anom: data[:] += self._mean return data
[docs] @_fmtdoc_ def cotes(self, anom=True, **kwargs): """Return the sea level without the tide signal (surcotes and decotes). You must specify the filter before using this method. %(ref)s .. seealso: :meth:`set_tide_filter`, :meth:`tide`, :mod:`vacumm.tide.filters` """ self._check_tide_filter_(**kwargs) data = self.data['cotes'] if not anom: data[:] += self._mean return data
[docs] @_fmtdoc_ def lows(self, ref='mean', **kwargs): """Low tides %(ref)s .. seealso: :meth:`high`, :meth:`zeros`, :mod:`vacumm.tide.filters` """ self._check_low_high_(ref, **kwargs) return self.data['lows']
[docs] @_fmtdoc_ def highs(self, ref='mean',**kwargs): """High tides %(ref)s .. seealso: :meth:`low`, :meth:`zeros`, :mod:`vacumm.tide.filters` """ self._check_low_high_(ref, **kwargs) return self.data['highs']
[docs] @_fmtdoc_ def zeros(self, ref='mean',**kwargs): """Zeros of sea level anomaly %(ref)s .. seealso: :meth:`low`, :meth:`high`, :mod:`vacumm.tide.filters` """ self._check_low_high_(ref, **kwargs) return self.data['zeros']
#### Misc methods
[docs] def clean(self): """ Clean out the marigraph object """ # Data self.data = {} for d in 'base', 'tide', 'cotes', 'lows', 'highs', 'zeros': self.data[d] = None # Filters self.tide_filter = False # Misc self.name = None self.longitude = None self.latitude = None self.mean_sea_level = -999. self.global_mean_sea_level = -999.
def _clean_kwargs(self,kwargs,bad = ['tide_filter']): kwargs = kwargs.copy() for kw in bad: if kwargs.has_key(kw): del kwargs[kw] return kwargs
[docs] def name(self,name=None): """ Set or return the station name """ if name is None: return self.name else: self.name = name
[docs] def longitude(self,val=None): """ Set or return the station longitude """ if val is None: return self.longitude else: self.longitude = val
[docs] def latitude(self,val=None): """ Set or return the station latitude """ if val is None: return self.latitude else: self.latitude = val
#### Plots
[docs] def plot(self, var=None, orig=True, tide=True, cotes=True, highs=True, lows=True, zeros=True, legend=True, title=None, savefig=None, savefigs=None, show=True, marker='o', **kwargs): # Which variable? vtypes = 'orig', 'tide', 'highs', 'lows', 'zeros', 'cotes' if var is not None: assert var in vtypes for vt in vtypes: exec "%s = %s"%(vt, vt==var) # Plot params # - specific kwplot = {} for vt in vtypes: kwplot[vt] = kwfilter(kwargs, vt) kwplot[vt].update(show=False) kwplot[vt].update(title=False) # - global nt = self.data['base'].shape[0] times = T.mpl(self.data['base'][0:nt:nt-1].getTime()) kwargs.setdefault('xmin', times[0]) kwargs.setdefault('xmax', times[1]) # - complete for vt in vtypes: tmp = kwargs.copy() tmp.update(kwplot[vt]) kwplot[vt] = tmp anom = not (orig or highs or lows or zeros) # Original signal if orig: kwplot['orig'].setdefault('color', '#888888') kwplot['orig'].setdefault('zorder', '10') curve(self.sea_level(), **kwplot['orig']) # High tides if highs: kwplot['highs'].setdefault('color', 'r') kwplot['highs'].setdefault('linewidth', 0) kwplot['highs'].setdefault('markersize', 5) kwplot['highs'].setdefault('zorder', '12') curve(self.highs(), marker, **kwplot['highs']) # Low tides if lows: kwplot['lows'].setdefault('color', 'b') kwplot['lows'].setdefault('linewidth', 0) kwplot['lows'].setdefault('markersize', 5) kwplot['lows'].setdefault('zorder', '12') curve(self.lows(), marker, **kwplot['lows']) # Zeros if zeros: kwplot['zeros'].setdefault('color', 'k') kwplot['zeros'].setdefault('linewidth', 0) kwplot['zeros'].setdefault('markersize', 5) kwplot['zeros'].setdefault('zorder', '12') curve(self.zeros(), marker, **kwplot['zeros']) # Tidal signal if tide: kwplot['orig'].setdefault('color', 'k') kwplot['orig'].setdefault('zorder', '11') curve(self.tide(anom=anom), **kwplot['tide']) # Surcote/decotes if cotes: kwplot['cotes'].setdefault('color', 'g') kwplot['cotes'].setdefault('zorder', '12') curve(self.cotes(anom=anom), **kwplot['cotes']) # Misc if title is None: if var is not None: title = var.title() else: title = 'Marigraph' if title: P.title(title) if savefig: P.savefig(savefig, **kwfilter(kwargs, 'savefig')) if savefigs: Savefigs(savefigs, **kwfilter(kwargs, 'savefigs')) if show: P.show()
from station_info import StationInfo from vacumm.misc.grid import bounds1d import vacumm.misc.atime as T, filters from vacumm.misc import kwfilter from vacumm.misc.plot import curve2 as curve, savefigs as Savefigs