# -*- coding: utf8 -*-
"""Utilities derived from mpl_toolkits.basemap"""
# Copyright or © or Copr. Actimar/IFREMER (2010-2016)
#
# 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.
#
__all__ = ['gshhs_reslist', 'gshhs_autores', 'cached_map', 'cache_map', 'get_map',
'GSHHS_BM', 'merc', 'clean_cache', 'reset_cache', 'get_map_dir', 'get_proj',
'create_map', 'RSHPERE_WGS84', 'GSHHS_RESLIST']
__all__.sort()
import os
import numpy as N
from mpl_toolkits.basemap import Basemap, __version__ as basemap_version
from mpl_toolkits.basemap.proj import Proj
from matplotlib import get_configdir
import cPickle, stat
#FIXME:imports
from ...misc.io import Shapes
from misc import get_xy
from ...misc.phys.constants import R as rsphere_mean
from ...misc.phys.units import deg2m, m2deg
from ...misc.misc import kwfilter, dict_check_defaults, zoombox
from vacumm.config import get_config_value
from ...__init__ import vacumm_warn
#: Earth radius of wgs84 ellipsoid
RSHPERE_WGS84 = (6378137.0,6356752.3141)
rshpere_wgs84 = RSHPERE_WGS84
#: GSHHS shorelines letters
GSHHS_RESLIST = ['f', 'h', 'i', 'l', 'c']
gshhs_reslist = GSHHS_RESLIST
[docs]def gshhs_autores(lon_min, lon_max, lat_min, lat_max, asindex=False, shift=None):
"""Guess best resolution from lon/lat bounds"""
testresol=((lon_max-lon_min)+(lat_max-lat_min))/2.0
ires = N.array([-1.,1. ,5.,15.,50.]).searchsorted(testresol)-1
if isinstance(shift, int):
ires += shift
ires = N.clip(ires, 0, len(GSHHS_RESLIST)-1)
if asindex:
return ires
return GSHHS_RESLIST[ires]
# Cached maps
[docs]def cached_map(m=None, mapdir=None, verbose=False, **kwargs):
"""Check if we have a cached map
- *m*: A Basemap instance [Default: None]
- *mapdir*: Where are stored the cached maps. If ``None``,
:func:`matplotlib.get_configdir` is used as a parent directory,
which is the matplotlib configuration directory
(:file:`~/.matplotlib` undex linux), and
:file:`basemap/cached_maps` as the subdirectory.
:Example:
>>> m = cached_map(lon_min=-5, lon_max=6, lat_min=40, lat_max=50, projection='lcc', resolution='f')
>>> m = cached_map(m) # Does only caching of map
"""
# We already have one in live memory
if isinstance(m, Basemap):
# Save it
cache_map(m, mapdir=mapdir)
# Get it
return m
# Guess
file = _cached_map_file_(mapdir=mapdir, **kwargs)
if file is None: return None
if verbose: print 'Checking', file, os.path.exists(file)
if not os.path.exists(file): return None
if verbose: print 'Loadind cached map from '+os.path.basename(file)
try:
f = open(file)
m = cPickle.load(f)
f.close()
return m
except:
vacumm_warn('Error while loading cached basemap instance from dir: '+
os.path.dirname(file))
os.remove(file)
return None
[docs]def cache_map(m, mapdir=None):
"""Cache a map if still not cached"""
if m is None or m.resolution is None: return
file = _cached_map_file_(m, mapdir=mapdir)
if file is None: return
if not os.path.exists(file):
# Dump
try:
f = open(file, 'wb')
m.ax = None
cPickle.dump(m, f)
f.close()
except:
vacumm_warn('Error while trying to cache basemap instance into: '+
os.path.dirname(file))
return
# Access to all if not in user directory
if not file.startswith(os.path.expanduser("~")):
os.chmod(file, stat.S_IROTH+stat.S_IWOTH+stat.S_IWGRP+stat.S_IRGRP+stat.S_IWUSR+stat.S_IRUSR)
# Clean
clean_cache()
[docs]def clean_cache(mapdir=None, maxsize=None):
"""Clean cache directory by checking its size
:Params:
- **mapdir**, optional: Directory where maps are cached
- **maxsize**, optional: Maximal size of directory in bytes.
Default value from :confopt:`[vacumm.misc.grid.basemap]max_cache_size`
configuration value.
"""
from ...misc.misc import dirsize
mapdir = get_map_dir(mapdir)
if mapdir is None:
mapdir = os.path.join(get_configdir(), 'basemap', 'cached_maps')
cache_size = dirsize(mapdir)
if maxsize is None:
maxsize = eval(get_config_value('vacumm.misc.grid.basemap', 'max_cache_size'))
if cache_size>maxsize:
files = [os.path.join(mapdir, ff) for ff in os.listdir(mapdir)]
files.sort(cmp=lambda f1, f2: cmp(os.stat(f1)[8], os.stat(f2)[8]))
for ff in files:
cache_size -= os.path.getsize(ff)
try:
os.remove(ff)
except:
vacumm_warn('Error while trying to clean basemap cache in: '+
os.path.dirname(ff))
return
if cache_size<=maxsize: break
[docs]def reset_cache(mapdir=None):
"""Remove all cached maps"""
mapdir = get_map_dir(mapdir)
for file in [os.path.join(mapdir, ff) for ff in os.listdir(mapdir)]:
os.remove(file)
[docs]def get_map_dir(mapdir=None):
"""Get the directory where cqched maps are stored"""
if mapdir is None:
mapdir = os.path.join(get_configdir(), 'basemap', 'cached_maps')
return mapdir
def _cached_map_file_(m=None, mapdir=None, **kwargs):
mapdir = get_map_dir(mapdir)
if not os.path.exists(mapdir):
os.makedirs(mapdir)
if m is None:
if kwargs.has_key('resolution') and kwargs['resolution'] is None:
return None
res = kwargs['resolution']
kwargs['resolution'] = None
m = Basemap(**kwargs)
elif m.resolution is None:
return None
else:
res = m.resolution
srs = m.srs.replace(' ', '')+'+res='+res
szone = '+%.5f+%.5f+%.5f+%.5f' % (m.llcrnrlon, m.llcrnrlat, m.urcrnrlon, m.urcrnrlat)
# bversion = '.'.join(basemap_version.split('.')[:2])
return os.path.join(mapdir, 'basemap-%s.%s.%s.pyk' % (basemap_version, srs, szone))
[docs]def create_map(lon_min=-180., lon_max=180., lat_min=-90., lat_max=90.,
projection='cyl', resolution='auto', epsg=None,
lon_center=None, lat_center=None, lat_ts=None, zoom=None, ax=None,
overlay=False, fullscreen=False, nocache=False, cache_dir=None, **kwargs):
"""Generic creation of a :class:`Basemap` instance with caching
.. todo:: Merge :func:`get_map` with :func:`create_map`
"""
kwmap = kwfilter(kwargs, 'basemap', defaults={'area_thresh':0.})
kwmap.update(kwfilter(kwargs, 'map_'))
# Map arguments
kwargs.setdefault('area_thresh', 0.)
kwargs.setdefault('rsphere', RSHPERE_WGS84) # WGS-84
if kwargs['rsphere'] in [None, False, True]: del kwargs['rsphere']
projection = kwargs.pop('proj', projection)
if lon_center is None: lon_center = .5*(lon_min+lon_max)
if lat_center is None: lat_center = .5*(lat_min+lat_max)
if lat_ts is None: lat_ts = lat_center
if lon_max-lon_min<1.e-5:
lon_min = lon_center-1
lon_max = lon_center+1
if lat_max-lat_min<1.e-5:
lat_min = N.clip(lat_center-1, 0, 90)
lat_max = N.clip(lat_center+1, 0, 90)
if isinstance(zoom, (int, float)):
lon_min, lat_min, lon_max, lat_max = zoombox(
[lon_min, lat_min, lon_max, lat_max], zoom)
# Special overlay case
if overlay:
projection = 'merc'
resolution = None
lat_center = 0
lon_center = 0
elif projection == None:
projection = 'cyl'
# Guess resolution
res = kwargs.pop('res', resolution)
if res is True:
res = 'auto'
elif res is False or res=='None':
res = None
elif isinstance(res, int):
if res < 0:
res= 'auto'
else:
res = GSHHS_RESLIST[4-res]
if res == 'auto':
res = gshhs_autores(lon_min, lon_max, lat_min, lat_max)
if res in GSHHS_RESLIST:
kwargs.setdefault('resolution', res)
else:
kwargs['resolution'] = None
# Basemap args
if isinstance(projection, str) and projection.lower() == 'rgf93' :
# RGF93
kwargs.update(lon_0=3, lat_0=46.5, lat_1=44, lat_2=49,
rsphere=RSHPERE_WGS84, projection='lcc')
else:
# standard
kwargs.setdefault('lon_0', lon_center)
kwargs.setdefault('lat_0', N.clip(lat_center, -90, 90))
kwargs.setdefault('lat_1', kwargs['lat_0'])
kwargs.setdefault('lat_2', kwargs['lat_1'])
kwargs.setdefault('lat_ts', N.clip(lat_center, -90, 90))
kwargs.setdefault('projection', projection)
kwargs.setdefault('llcrnrlon', lon_min)
kwargs.setdefault('urcrnrlon', lon_max)
kwargs.setdefault('llcrnrlat', N.clip(lat_min, -90, 90))
kwargs.setdefault('urcrnrlat', N.clip(lat_max, -90, 90))
kwargs['epsg'] = epsg
# Check the cache
kwcache = kwargs.copy()
if cache_dir is not None:
kwcache['mapdir'] = cache_dir
if not nocache:
mymap = cached_map(**kwcache)
else:
mymap = None
# Create the map object
if mymap is None:
mymap = Basemap(ax=ax, **kwargs)
# Cache it?
if int(nocache)<=1:
if cache_dir is not None:
kwcache = {'mapdir':cache_dir}
else:
kwcache = {}
cache_map(mymap, **kwcache)
elif ax is not None:
mymap.ax = ax
mymap.res = res
return mymap
[docs]def get_map(gg=None, proj=None, res=None, auto=False, **kwargs):
"""Quickly create :class:`Basemap` instance
:Params:
- **gg**, optional: cdms grid or variable, or (xx,yy).
- **res**, optional: Resolution.
- **proj**, optional: Projection [default: None->'merc']
- **auto**, optional: If True, get geo specs according to grid. If False, whole earth.
If None, auto = res is None.
.. todo:: Merge with :func:`create_map`
"""
from vacumm.misc.grid import misc
if proj is None:
proj = 'merc'
if auto is None:
auto = res is not None
if gg is None: auto = False
kwmap = dict(resolution=res, projection=proj)
if auto:
xx, yy = misc.get_xy(gg, proj=False)
lat_center = yy.mean()
lon_center = xx.mean()
kwmap.update(
llcrnrlon = xx.min(),
urcrnrlon = xx.max(),
llcrnrlat = yy.min(),
urcrnrlat = yy.max())
else:
lat_center = 0.
lon_center = 0.
return Basemap(lat_ts=lat_center, lat_0=lat_center, lon_0=lon_center, **kwmap)
[docs]class GSHHS_BM(Shapes):
"""Shoreline from USGS using Basemap
Initialized with a valid Basemap instance with resolution not equal to None,
or thanks to arguments passed to :func:`create_mapplot.map`
- *input*: Basemap or Shapes instance [default: None]
"""
def __init__(self, input=None, clip=None, sort=True, reverse=True, proj=False, **kwargs):
# From another Shapes instance
if isinstance(input, Shapes):
if clip is None: # m is not None and
clip = [input.xmin, input.ymin, input.xmax, input.ymax]
input = input._m
# Get the map without projection
if not isinstance(input, Basemap):
# Base to create the map
kwmap = kwargs.copy()
if isinstance(input, str):
kwmap['res'] = input
elif isinstance(input, dict):
kwmap.update(input)
# Clipping zone
if clip is not None:
# Vertices
clip = create_polygon(clip, mode='verts')
# Map extension from clip bounds
kwmap.setdefault('lon_min', clip[:, 0].min())
kwmap.setdefault('lon_max', clip[:, 0].max())
kwmap.setdefault('lat_min', clip[:, 1].min())
kwmap.setdefault('lat_max', clip[:, 1].max())
# Default resolution is 'i' if nothing to estimate it
if not 'res' in kwmap and not 'resolution' in kwmap and \
( (not 'lon' in kwmap and
(not 'lon_min' in kwmap or not 'lon_max' in kwmap)) or
(not 'lat' in kwmap and
(not 'lat_min' in kwmap or not 'lat_max' in kwmap))):
kwmap['res'] = 'i'
# Check lats
if 'lat_min' in kwmap: kwmap['lat_min'] = max(kwmap['lat_min'], -89.99)
if 'lat_max' in kwmap: kwmap['lat_max'] = min(kwmap['lat_max'], 89.99)
# Build the map
m = create_map(**kwmap)
self.res = m.resolution
else:
clip = False
m = input
# Get unprojected polygons vertices
self.res = m.resolution
assert m.resolution is not None, 'Your map needs its resolution to be set'
all_verts = []
for i, verts in enumerate(m.coastpolygons):
if m.coastpolygontypes[i] in [2,4]: continue # Skip lakes
if m.projection!='cyl': # Project back
verts = m.projtran(verts[0], verts[1], inverse=True)
all_verts.append(N.asarray(verts).T)
# Final initialization
Shapes.__init__(self, all_verts, m=m, clip=clip, sort=sort, proj=proj,
shapetype=Shapes.POLYGON, **kwargs)
# def __init__(self, input=None, clip=None, sort=True, reverse=True, proj=False, **kwargs):
# # Clipping argument
# if clip is not None:
# clip = create_polygon(clip)
#
# from_map = not isinstance(input, Shapes)
# if not from_map:
# # Already a Shapes instance
# self._m = input._m
# self._proj = input.get_proj(proj)
# polys = input.get_shapes(proj=proj)
#
# else:
# # Get the map
# if isinstance(input, Basemap):
# assert input.resolution is not None, 'Your map needs its resolution to be set'
# m = input
# else:
# if isinstance(input, str):
# kwargs['res'] = input
# elif isinstance(input, dict):
# kwargs.update(input)
#
# # Map extension from clip bounds
# if clip is not None:
# bb = clip.boundary
# kwargs.setdefault('lon_min', bb[:, 0].min())
# kwargs.setdefault('lon_max', bb[:, 0].max())
# kwargs.setdefault('lat_min', bb[:, 1].min())
# kwargs.setdefault('lat_max', bb[:, 1].max())
#
# # Default resolution is 'i' if nothing to estimate it
# if not kwargs.has_key('res') and not kwargs.has_key('resolution') and \
# ( (not kwargs.has_key('lon') and
# (not kwargs.has_key('lon_min') or not kwargs.has_key('lon_max'))) or
# (not kwargs.has_key('lat') and
# (not kwargs.has_key('lat_min') or not kwargs.has_key('lat_max')))):
# kwargs['res'] = 'i'
#
# # Check lats
# if kwargs.has_key('lat_min'): kwargs['lat_min'] = max(kwargs['lat_min'], -90)
# if kwargs.has_key('lat_max'): kwargs['lat_max'] = min(kwargs['lat_max'], 90)
#
# # Build the map
# m = create_map(**kwargs)
#
#
# polys = m.coastpolygons
# self._m = m
# self._proj = proj
#
# # Convert to GEOS polygons and clip
# self._shapes = []
# for i, pp in enumerate(polys):
#
# # Get the polygon with good projection
# if not from_map:
# poly = pp
# else:
# if m.coastpolygontypes[i] in [2,4]: continue # Skip lakes
# if callable(proj) and m.projection!='cyl': # Project back for reprojection
# pp = m.projtran(pp[0], pp[1], inverse=True)
# poly = create_polygon(pp, proj=proj)
#
# # Clip it
# self._shapes.extend(clip_shape(poly, clip))
#
# # Save some info
# self._info = []
# self._type = 2
# self._shaper = Polygon
# if self._shapes:
# xy = N.concatenate([s.boundary for s in self._shapes])
# self.xmin = xy[:, 0].min()
# self.xmax = xy[:, 0].max()
# self.ymin = xy[:, 1].min()
# self.ymax = xy[:, 1].max()
# del xy
# else:
# xmin = N.inf
# xmax = -N.inf
# ymin = N.inf
# ymax = -N.inf
#
# # Sort polygons?
# if sort:
# self.sort(reverse=reverse)
[docs]def merc(lon=None, lat=None, **kwargs):
"""Mercator map
- Extra keywords are passed to :class:`mpl_toolkits.basemap.Basemap`
- *lon*: Longitudes to define ``llcrnrlon`` and ``urcrnrlon``
- *lat*: Latitudes to define ``lat_ts``, ``llcrnrlat`` and ``urcrnrlat``
"""
kwargs.setdefault('resolution', None)
if lon is not None:
lon = N.asarray(lon)
kwargs.setdefault('llcrnrlon', lon.min())
kwargs.setdefault('urcrnrlon', lon.max())
if lat is not None:
lat = N.asarray(lat)
kwargs.setdefault('llcrnrlat', lat.min())
kwargs.setdefault('urcrnrlat', lat.max())
lat_ts = N.median(lat)
else:
lat_ts = 0.
kwargs.setdefault('lat_ts',lat_ts)
return Basemap(projection='merc', **kwargs)
#proj = merc()
def basic_proj(xx, yy, inverse=False):
"""A basic projection using :func:`vacumm.misc.phys.units.deg2m`
and :func:`vacumm.misc.phys.units.deg2m`
"""
if inverse:
yy = m2deg(yy)
return m2deg(xx, yy), yy
return deg2m(xx, yy), deg2m(yy)
[docs]def get_proj(gg=None, proj=None, **kwargs):
"""Setup a default projection using x,y coordinates and
:class:`~mpl_toolkits.basemap.proj.Proj` or :func:`basic_proj`
Projection is set by default to "basic".
:Params:
- **gg**, optional: Grid or coordinates (see :func:`~vacumm.misc.grid.misc.get_xy`).
If not provided, lon bounds are set to (-180,180) and lat bounds to (-89.99,89.99).
- Other keywords are passed to :class:`~mpl_toolkits.basemap.proj.Proj`. One of
them is the projection type, which defaults to configuration option
:confopt:`[vacumm.misc.grid.basemap] proj`.
:Return:
A :class:`mpl_toolkits.basemap.proj.Proj` instance
or :func:`basic_proj`
:Examples:
>>> proj = get_proj(sst.getGrid(), proj='laea')
>>> x, y = proj(lon, lat)
>>> proj = get_proj((lon, lat))
>>> xx, yy = N.meshgrid(lon, lat)
>>> xx, yy = proj(xx, yy)
>>> print proj(xx, yy, inverse=True)
>>> proj = get_proj(R=6000000.)
"""
if proj is False:
return False
if proj is None or proj is True:
proj = 'basic'
if proj == 'basic':
return basic_proj
if callable(proj): return proj
if gg is not None:
x,y = get_xy(gg, num=True)
xmin, ymin, xmax, ymax = x.min(), y.min(), x.max(), y.max()
else:
xmin, ymin, xmax, ymax = -180, -90, 180, 90
y = [0]
projparams = kwargs.copy()
ymax = min(ymax, 89.99)
ymin = max(ymin, -89.99)
if not isinstance(proj, str):
proj = get_config_value('vacumm.misc.grid.basemap', 'proj')
dict_check_defaults(projparams, R=rsphere_mean, units='m',
proj=proj,
lat_ts = N.median(y) if len(y)>10 else N.mean(y),
lon_0 = N.median(x) if len(x)>10 else N.mean(x))
dict_check_defaults(projparams, lat_0=projparams['lat_ts'])
return Proj(projparams, xmin, ymin, xmax, ymax)
from masking import polygons, create_polygon, proj_shape, clip_shape