# -*- coding: utf8 -*-
"""
Information about seaports and stations
StationInfo : A class to retrieve alot of information about seaports and stations.
MeanSeaLevel : A class dedicated to mean sea level along french coast (more complete than StationInfo).
"""
# 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 os
#dirname = os.path.dirname(__file__)
#cfgfiles = dict(
# default = os.path.join(dirname, 'config.cfg'),
# site = os.path.join(dirname, 'site.cfg'),
#)
#def _get_option_(key, **kwargs):
# f = ConfigParser()
# f.read([cfgfiles['default'], cfgfiles['site']])
# if f.has_option(__name__, key):
# return f.get(__name__, key)
from vacumm.config import get_config_value
from _geoslib import Point
import numpy as N
from vacumm.bathy.bathy import XYZBathy
from ConfigParser import ConfigParser
[docs]class StationInfoError(Exception):
pass
class _StationPlot_(object):
def plot(self,lon=None,lat=None,loc='upper left',
fontsize=11.,alone=False,color='blue', show=True):
"""Show the current station on a map
- *lon/lat*: Longitude and latitude plot ranges
- *fontsize*: Font size of the station name [default:15.]
- *color*: Color of name and symbol [default: 'blue']
- *alone*: Do not plot other stations around [default: False]
- Other keywords are passed to :func:`~vacumm.misc.plot.map`.
"""
if self.nom is None:
print "Aucune station n'est actuellement definie"
return None
import pylab as P
from vacumm.misc.color import bistre
from vacumm.misc import auto_scale
from vacumm.plot import map
A = N.array
dlon = 2.8
dlat = 2.
if lon is None:
lon = A([-dlon/2.,dlon/2.])+self.longitude
if lat is None:
lat = A([-dlat/2.,dlat/2.])+self.latitude
mlon, mlat = m([self.longitude,],[self.latitude,])
# Map
kwargs.setdefault('proj', 'lcc')
m = m(lon=lon, lat=lat, title='Station information', show=False, **kwargs)
# Plot other stations
if isinstance(self, StationInfo) and not alone:
for station in self._stations:
if station['nom'] != self.nom and \
station['longitude'] > min(lon) and \
station['longitude'] < max(lon) and \
station['latitude'] > min(lat) and \
station['latitude'] < max(lat):
long, lati = m([station['longitude'],],[station['latitude']])
m.plot(long,lati,'o',color='k', label = '_nolegend_')
P.text(long,lati,station['nom']+'\n',va='bottom',ha='center',fontsize=fontsize*0.6)
# Plot our station and add info
long, lati = m([self.longitude,],[self.latitude,])
ll = m.plot(mlon,mlat,'o',color=color,markersize=10.)
P.text(mlon, mlat,self.nom+'\n',
va='bottom',ha='center',fontsize=fontsize,color=color)
# atts = ['longitude','latitude']
# atts.extend(self._ids)
# atts.extend(self._numerics)
mylabel = 'Details of the station:\n'
for att in self.attributes:
if att not in ['zero_hydro']:
val = getattr(self,att)
if att in ['longitude','latitude']:
val = '%5.2f' % val
elif att in self._ids:
att = 'ID '+att.upper()
else:
att = att.upper()
val = '%g m' % val
if val is not None:
mylabel += ' %s: %s\n' % (att,str(val))
P.legend((ll,),(mylabel[:-1],),loc=loc,shadow=True,numpoints=1)
if savefig:
P.savefig(savefig)
elif savefigs:
savefigs(savefigs)
if show:
P.show()
def show(self, *args, **kwargs):
"""Shortcut to :meth:`plot`"""
return self.plot(*args, **kwargs)
[docs]class StationInfo(_StationPlot_):
"""Finding information about tidal stations
This class helps finding information on
ports and tide jauge measurement stations.
It attempts to search for a station using several
possible criteria, and returns attributes such as
ids, positions, mean sea level, etc (listed using
method :meth:`attributes`).
See help on :meth:`search` method.
:Params:
- **nom**: name or regexp to search for.
- **regexp**:
- **file**: File in wich to search for info
- **verbose**: Verbose mode
- All other keywords are passed to :func:`search` method after initialization.
"""
def __init__(self,nom=None, regexp=True, verbose=True,
file=None, *args, **kwargs):
self.nom = None
# File name
if file is None:
# file = _get_option_('ports_file')
file = get_config_value(__name__, 'ports_file', ispath=True)
if file is not None:
# if not os.path.isabs(file):
# file = os.path.abspath(os.path.join(dirname, '../../../../data', file))
if not os.path.exists(file):
raise StationInfoError('File of ports not found: '+file)
else:
raise StationInfoError('No valid file of ports found')
# Chargement du fichier
self.set_file(file)
# Send optional argument to search()
if nom is not None or len(kwargs) or len(args):
self.find(nom=nom,regexp=regexp,verbose=verbose,*args,**kwargs)
[docs] def set_file(self,file,**kwargs):
""" Set the current file and load it """
import os
self._loaded = False
if not os.path.exists(file):
print 'Fichier de station introuvable : '+file
self._file = file
self._load_file(**kwargs)
def _load_file(self,**kwargs):
# Open information file
if kwargs.has_key('verbose'):
if verbose:
print 'Lecture de ',self._file
f = open(self._file)
# Parse general attributes
self._headers = {}
for line in f:
sline = line[:-1].split(':')
attribute = sline[0].strip().lower()
definition = ':'.join(sline[1:]).strip()
self._headers[attribute] = definition
if attribute == 'zone':
break
# Parse ids and numerics
self._ids = []
self._numerics = {}
for line in f:
sline = line[:-1].split(':')
attribute = sline[0].strip().lower()
definition = ':'.join(sline[1:]).strip()
if attribute == 'url_zone' :
break
else:
setattr(self,attribute,None)
if not definition.find('Identif'):
self._ids.append(attribute)
else:
self._numerics[attribute] = definition
# Parse zones
self._zones = {}
for line in f:
sline = line[:-1].split(':')
zone = sline[0].strip()[4:]
description = ':'.join(sline[1:]).strip()
if description != 'Gironde':
self._zones[zone] = definition % description
else:
self._zones[zone] = description
break
# Header line
line = f.next()
attributes = line[27:-1].lower().split()
# Loop on stations
self._stations = []
for line in f:
# Name
dic = {}
dic['nom'] = dic['name'] = line[0:27].strip()
# Create a list of values following each attribute
sline = line[27:-1].split()
ilon = len(self._ids)
for i in 0,1:
sline[ilon+i] = ' '.join(sline[ilon+i:ilon+i+2])
del sline[ilon+i+1]
if len(attributes) != len(sline):
sline[len(attributes)-1] = ' '.join(sline[len(attributes)-1:])
del sline[len(attributes):]
# Ids
for iatt in xrange(len(attributes)):
value = sline[iatt]
# Coordinates
if attributes[iatt] in ['longitude','latitude']:
value = self._str2deg(value)
# Undefined values
elif value == 'None':
value = None
# Url of zone
elif attributes[iatt] == 'zone':
value = self._zones[value.lower()]
# Zero hydro (!= None)
elif attributes[iatt] == 'zero_hydro':
value = float(value.replace(',','.').split()[0])
# Centimeters to meters
elif len(value) < 5 and value.isdigit():
value = float(value) * 0.01
# Store in a dictionary
dic[attributes[iatt]] = value
# Append dictionary to the station list
self._stations.append(dic)
f.close()
self._loaded = True
self._atts = attributes
[docs] def attributes(self):
"""Return a list of attributes"""
return self._atts
[docs] def file(self):
""" Return the file path where information are stored """
return self._file
[docs] def is_set(self):
""" Return if a station is set """
return self._loaded and self.nom is not None
def _search(self,nom=None,regexp=True,nmax=5,**kwargs):
""" Generic search within stations """
stations = []
if nom is not None:
# Search within names using regular expression of strict comparisons
nom = nom.strip()
if regexp:
# Regular expression
import re
this_re = re.compile(nom, re.I)
for station in self._stations:
if this_re.search(station['nom']) is not None:
stations.append(station)
if len(stations) == nmax:
break
else:
# Strict equality
for station in self._stations:
if station['nom'].lower() == nom.lower():
stations.append(station)
if len(stations) == nmax:
break
else:
# Search using other specific arguments
for key,val in kwargs.items():
if key in self._ids:
# Use ids
for station in self._stations:
if station in stations:
continue
if station[key] is not None and \
station[key].lower() == val.lower():
stations.append(station)
break
elif key == 'position' and len(val) == 2:
# Find the closest station
import vacumm.misc as M,math
distances = []
x0 = deg2m(*val)
y0 = deg2m(val[1])
for station in self._stations:
if station in stations:
continue
x = deg2m(station['longitude'], station['latitude'])
y = deg2m(station['latitude'])
distances.append(math.sqrt((x-x0)**2+(y-y0)**2))
stations.append(self._stations[N.argmin(distances)])
if len(stations) == nmax:
break
if len(stations):
if nmax == 1:
return stations[0]
else:
return stations
else:
return None
[docs] def search(self,nom=None,regexp=True,nmax=5,**kwargs):
"""Search for stations using several possible criteria and display results.
- *nom*: Search within station names using this pattern
- *regexp*: If True, use regular expression for search within names [default: True]
- *position*: A two-element iterable ([lon,lat]) for searching for the closest station to this positions
- *nmax*: Maximal number of stations displayed [default: 5]
"""
stations = self._search(nom=nom,regexp=regexp,nmax=nmax,**kwargs)
if stations is None:
print 'Aucune station trouvee verifiant vos criteres'
else:
print 'Liste des stations trouvees (max %i) :' % nmax
for station in stations:
self._print_one(station)
print 'Definition des termes accessible avec definitions()'
[docs] def find(self,nom=None,regexp=True,verbose=False,*args,**kwargs):
"""Search for a station using several possible criteria and load it.
- *nom*: Search within station names using this pattern
- *regexp*: If True, use regular expression for search within names [default: True]
- *position*: A two-element iterable ([lon,lat]) for searching for the closest station to this positions
- *verbose*: Verbose mode [default: True]
"""
station = self._search(nom=nom,regexp=regexp,nmax=1,*args,**kwargs)
if station is None:
if verbose:
print 'Aucune station trouvee verifiant vos criteres'
else:
if verbose:
print 'Chargement de la station suivante :'
self._print_one(station)
print 'Definition des termes accessible avec definitions()'
self._set(station)
return Station(station)
def _str2deg(self,str):
sstr = str.split()
deg = float(sstr[0])
deg += float(sstr[1][0:2])/60.
deg += float(sstr[1][3:5])/3600.
if sstr[1][-1].lower() in ['s','w']:
deg = -deg
return deg
def _print_one(self,station):
""" Print info for one station"""
import types,vacumm.misc as M
# Name and position
self._print_one_arg('Nom',station['nom'])
self._print_one_arg('Position','%s / %s' % \
(M.lonlab(station['longitude']),M.latlab(station['latitude'])))
self._print_one_arg('Zone',station['zone'])
# Ids
for id in self._ids:
self._print_one_arg(id.upper(),station[id])
# Properties
keys = self._numerics.keys()
keys.sort()
for att in keys:
if station[att] is not None:
if type(station[att]) is types.StringType:
fmt = '%s'
else:
fmt = '%g'
self._print_one_arg(att.upper(),fmt % station[att])
def _print_one_arg(self,att,val):
if val is not None:
print ' '+att.ljust(10)+' : '+val.encode('utf8')
def _set(self,station):
""" Set a station """
# Internal data
for att,val in station.items():
setattr(self,att,val)
[docs] def info(self):
""" Show all available information about the current station """
if not self.is_set():
print "Aucune station n'est actuellement definie"
else:
print 'Station actuelle :'
self._print_one(self.get_dict())
[docs] def get_dict(self):
""" Get the current station as a dictionnary """
if self.nom is None:
print "Aucune station n'est actuellement definie"
return None
station = {}
for att in self._headers.keys():
station[att] = getattr(self,att)
for id in self._ids:
station[id] = getattr(self,id)
for att in self._numerics.keys():
station[att] = getattr(self,att)
return station
[docs] def definitions(self):
""" Print out the definition of all terms """
print 'Definition des termes :'
headers = ['nom','longitude','latitude','zone']
for att in headers:
self._print_one_arg(att,self._headers[att])
for id in self._ids:
self._print_one_arg(id.upper(),id.upper())
for att in self._numerics.keys():
self._print_one_arg(att.upper(),self._numerics[att])
[docs]class Station(dict, _StationPlot_):
"""A station with its info as a result from :class:`StationInfo`
:Example:
>>> from vacumm import StationInfo
>>> station = StationInfo().find('Brest', nmax=1)
>>> print station.longitude
"""
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self._atts = self.keys()
for key, value in self.items():
setattr(self, key, value)
#class _MeanSeaLevelData(object):
# """Class for convenient sealevel data use"""
# def __init__(self, data):
# self._data = data
# def __getattr__(self, att):
# if att == 'data': return self._data
# if att == 'names': return [d[0] for d in self._data]
# if att in ['x', 'lon']:
# return N.asarray([nxyz[1] for nxyz in self._data])
# if att in ['y', 'lat']:
# return N.asarray([nxyz[2] for nxyz in self._data])
# if att in ['z', 'sealevel']:
# return N.asarray([nxyz[3] for nxyz in self._data])
# if att in ['xy', 'coords']:
# return N.asarray([nxyz[1:3] for nxyz in self._data]).transpose()
# if att in ['xyz', 'block']:
# return N.asarray([nxyz[1:] for nxyz in self._data]).transpose()
# return self.__dict__[att]
# def __len__(self):
# return len(self._data)
#
#
#class _MeanSeaLevel(_MeanSeaLevelData):
# """Retrieve mean sea level at stations
#
# Information can be retrieved thanks to the following properties:
# .data: List of internal data, each element being [name, x, y, z]
# .names: List of seaport and station names
# .x or .lon: longitudes array (n,)
# .y or .lat: latitudes array (n,)
# .xy or coords: array of coordinates (2,n)
# .z or .sealevel: mean sea level array (n,)
# .xyz or .block: array combining coordinates and sea levels (3,n)
#
# :Example:
#
# >>> from vacumm.tide.station_info import MeanSeaLevel
# >>> msl = MeanSeaLevel()
# >>> for i in xrange(len(msl)):
# >>> print msl.names[i], msl.x[i], msl.y[i]
# >>> print len(msl.clip((-4,44.,0,48)))
# """
# def __init__(self, file=None):
#
# data = []
# f = open(meansealevel_file)
# for line in f:
# name = unicode(line[:29].strip(), 'utf8')
# y, x, z = [float(v) for v in line[29:].split()]
# data.append([name, x, y, z])
# f.close()
# _MeanSeaLevelData.__init__(self, data)
# def clip(self, zone=None):
# """Get data within specific bounds
#
# - *zone*: A tuple of (xmin,ymin,xmax,ymax), a Polygon instance or an array to build a polygon (see vacumm.misc.grid.masking.polygons)
# """
# if zone is None: return self
# zone = polygons([zone])[0]
# mydata = [d for d in self._data if Point((d[1], d[2])).within(zone)]
# return _MeanSeaLevelData(mydata)
#
[docs]class MeanSeaLevelError(Exception):
pass
[docs]class MeanSeaLevel(XYZBathy):
"""Retrieve mean sea level at stations
Information can be retrieved thanks to the following properties:
.data: List of internal data, each element being [name, x, y, z]
.names: List of seaport and station names
.x or .lon: longitudes array (n,)
.y or .lat: latitudes array (n,)
.xy or coords: array of coordinates (2,n)
.z or .sealevel: mean sea level array (n,)
.xyz or .block: array combining coordinates and sea levels (3,n)
:Example:
>>> from vacumm.tide.station_info import MeanSeaLevel
>>> msl = MeanSeaLevel()
>>> for i in xrange(len(msl)):
>>> print msl.names[i], msl.x[i], msl.y[i]
>>> print len(msl.clip((-4,44.,0,48)))
.. seealso::
:class:`~vacumm.misc.io.XYZ`
"""
def __init__(self, msl=None, names=None, **kwargs):
if msl is None:
msl = get_config_value(__name__, 'meansealevel_file')
# msl = _get_option_('meansealevel_file')
if msl is None:
raise MeanSeaLevel("Can't find a default file of mean sea levels")
if isinstance(msl, str): # file
if not os.path.isabs(msl):
msl = os.path.abspath(os.path.join(dirname, '../../../../data', msl))
if not os.path.exists(msl):
raise MeanSeaLevel("File of mean sea levels not found: %s"%msl)
data = []
names = []
xx = [] ; yy = [] ; zz = []
f = open(msl)
for line in f:
name = unicode(line[:29].strip(), 'utf8')
y, x, z = [float(v) for v in line[29:].split()]
names.append(name)
xx.append(x)
yy.append(y)
zz.append(z)
f.close()
msl = (xx, yy, zz)
elif isinstance(msl, MeanSeaLevel): # instance
names = list(msl.names())
kwargs.setdefault('units', 'm')
kwargs.setdefault('long_name', 'Mean sea level')
XYZBathy.__init__(self, msl, **kwargs)
self._names = names
[docs] def get_names(self, mask=True):
"""Get :attr:`names`"""
if self._names is None: return
return self._filter_(self._names, mask)
names = property(get_names, doc='Get name of valid stations')
[docs] def clip(self, zone=None, inverse=False, **kwargs):
"""Geographical selection of part of the data
- **zone**: (xmin,ymin,xmax,ymax) or a complex polygon (see :func:`~vacumm.misc.grid.masking.polygons`).
- *inverse*: Inverse the selection.
"""
if zone is None:
return self
mask = self._clip_mask_(zone, inverse)
names= self.names
names = [names[i] for i, m in enumerate(mask) if m]
return MeanSeaLevel(XYZ.clip(self, zone=mask, mask=True).xyz, names=names)
from vacumm.misc.grid.masking import polygons
from vacumm.misc.phys.units import deg2m