Source code for epygram.V2DField

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from epygram.base import Field, FieldValidity
from epygram import config, util, V2DGeometry, epygramError
import numpy



[docs]class V2DField(Field): """ Vertical 2-Dimension (section) field class. A field is defined by its identifier 'fid', its data, its geometry, and its validity. At least for now, it is designed somehow like a collection of V1DFields. And so is V2DGeometry. """ _collector = ('field',) _footprint = dict( attr = dict( geometry = dict(type = V2DGeometry), validity = dict( type = FieldValidity, optional = True, default = FieldValidity()), processtype = dict( optional = True, info = "Generating process.") ) )
[docs] def setdata(self, data): """ Sets data, checking it to be 2D. """ if len(numpy.shape(data)) != 2: raise epygramError("data must be 2D array.") super(V2DField, self).setdata(data) ################### # PRE-APPLICATIVE # ################### # (but useful and rather standard) ! # [so that, subject to continuation through updated versions, # including suggestions/developments by users...]
[docs] def plotfield(self, colorbar='vertical', graphicmode='colorshades', minmax=None, levelsnumber=21, center_cmap_on_0=False, colormap='jet', zoom=None, title=None, logscale=False, minmax_in_title=True, contourcolor='k', contourwidth=1, contourlabel=True): """ Makes a simple (profile) plot of the field. Args: \n - *title* = title for the plot. - *logscale* = to set Y logarithmic scale - *minmax*: defines the min and max values for the plot colorbar. \n Syntax: [min, max]. [0.0, max] also works. Default is min/max of the field. - *graphicmode*: among ('colorshades', 'contourlines'). - *levelsnumber*: number of levels for contours and colorbar. - *colormap*: name of the **matplotlib** colormap to use. - *center_cmap_on_0*: aligns the colormap center on the value 0. - *colorbar*: if *False*, hide colorbar the plot; else, befines the colorbar orientation, among ('horizontal', 'vertical'). Defaults to 'vertical'. - *zoom*: a dict containing optional limits to zoom on the plot. \n Syntax: e.g. {'ymax':500, ...}. - *minmax_in_title*: if True and minmax != None, adds min and max values in title - *contourcolor*: color or colormap to be used for 'contourlines' graphicmode. It can be either a legal html color name, or a colormap name. - *contourwidth*: width of contours for 'contourlines' graphicmode. - *contourlabel*: displays labels on contours. Warning: requires **pyproj** and **matplotlib**. """ from pyproj import Geod import matplotlib.pyplot as plt plt.rc('font', family='serif') plt.rc('figure', figsize=config.plotsizes) # User colormaps if colormap not in plt.colormaps(): util.add_cmap(colormap) f = plt.figure() # coords p0 = self.geometry.grid[0] plast = self.geometry.grid[-1] if p0.coordinate == 'hybrid_pressure': z = numpy.zeros((self.geometry.dimensions['Z'], self.geometry.dimensions['X'])) levels = numpy.arange(1, self.geometry.dimensions['Z']+1) for i in range(self.geometry.dimensions['X']): z[:,i] = levels[:] else: z = numpy.array([g.grid['levels'] for g in self.geometry.grid]).transpose() if p0.coordinate == 'pressure': z = z /100. g = Geod(ellps='sphere') arc = g.inv(p0.hlocation['lon'], p0.hlocation['lat'], plast.hlocation['lon'], plast.hlocation['lat']) distance = arc[2] x = numpy.zeros((self.geometry.dimensions['Z'], self.geometry.dimensions['X'])) dists = numpy.linspace(0, distance, self.geometry.dimensions['X']) for i in range(self.geometry.dimensions['Z']): x[i,:] = dists[:] data = self.data if self.geometry.coordinate in ('hybrid_pressure', 'pressure'): reverseY = True else: reverseY = False # min/max m = data.min() M = data.max() if minmax != None: if minmax_in_title: minmax_in_title = '(min: ' + \ '{: .{precision}{type}}'.format(m, type='E', precision=3) + \ ' // max: ' + \ '{: .{precision}{type}}'.format(M, type='E', precision=3) + ')' try: m = float(minmax[0]) except Exception: m = data.min() try: M = float(minmax[1]) except Exception: M = data.max() else: minmax_in_title = '' if abs(m-M) > config.epsilon: levels = numpy.linspace(m, M, levelsnumber) vmin = vmax = None if center_cmap_on_0: vmax = max(abs(m), M) vmin = -vmax else: raise epygramError("cannot plot uniform field.") L = int((levelsnumber-1)//15) +1 hlevels = [levels[l] for l in range(len(levels)-L/3) if l%L == 0] + [levels[-1]] # plot if reverseY: plt.gca().invert_yaxis() if logscale: f.axes[0].set_yscale('log') plt.grid() if graphicmode == 'colorshades': pf = plt.contourf(x, z, data, levels, cmap=colormap, vmin=vmin, vmax=vmax) if colorbar: cb = plt.colorbar(pf, orientation=colorbar, ticks=hlevels) if minmax_in_title != '': cb.set_label(minmax_in_title) elif graphicmode == 'contourlines': pf = plt.contour(x, z, data, levels=levels, colors=contourcolor, linewidths=contourwidth) if contourlabel: f.axes[0].clabel(pf, colors=contourcolor) # decoration surf = z[-1,:] bottom = max(surf) if reverseY else min(surf) plt.fill_between(x[-1,:], surf, numpy.ones(len(surf))*bottom, color='k') if self.geometry.coordinate == 'hybrid_pressure': Ycoordinate = 'Level \nHybrid-Pressure \ncoordinate' elif self.geometry.coordinate == 'pressure': Ycoordinate = 'Pressure (hPa)' elif self.geometry.coordinate == 'altitude': Ycoordinate = 'Altitude (m)' elif self.geometry.coordinate == 'height': Ycoordinate = 'Height (m)' elif self.geometry.coordinate == 'potential_vortex': Ycoordinate = 'Potential \nvortex \n(PVU)' else: Ycoordinate = 'unknown \ncoordinate' f.axes[0].set_xlabel('Distance from left-end point (m).') f.axes[0].set_ylabel(Ycoordinate) if zoom != None: ykw = {} xkw = {} for pair in (('bottom', 'ymin'), ('top', 'ymax')): try: ykw[pair[0]] = zoom[pair[1]] except Exception: pass for pair in (('left', 'xmin'), ('right', 'xmax')): try: xkw[pair[0]] = zoom[pair[1]] except Exception: pass f.axes[0].set_ylim(**ykw) f.axes[0].set_xlim(**xkw) if title == None: title = 'Section of ' + str(self.fid['section']) + ' between \n' + \ '<- ('+str(p0.hlocation['lon'])+', '+str(p0.hlocation['lat'])+')' + ' and ' + \ '('+str(plast.hlocation['lon'])+', '+str(plast.hlocation['lat'])+') -> \n' + \ str(self.validity.get()) f.axes[0].set_title(title) return f
[docs] def stats(self): """ Computes some basic statistics on the field, as a dict containing: {'min', 'max', 'mean', 'std', 'quadmean', 'nonzero'}. See each of these methods for details. """ return {'min':self.min(), 'max':self.max(), 'mean':self.mean(), 'std':self.std(), 'quadmean':self.quadmean(), 'nonzero':self.nonzero()}
[docs] def min(self): """ Returns the minimum value of data. """ data = self.data return numpy.ma.masked_outside(data, -config.mask_outside, config.mask_outside).min()
[docs] def max(self): """ Returns the maximum value of data. """ data = self.data return numpy.ma.masked_outside(data, -config.mask_outside, config.mask_outside).max()
[docs] def mean(self): """ Returns the mean value of data. """ data = self.data return numpy.ma.masked_outside(data, -config.mask_outside, config.mask_outside).mean()
[docs] def std(self): """ Returns the standard deviation of data. """ data = self.data return numpy.ma.masked_outside(data, -config.mask_outside, config.mask_outside).std()
[docs] def quadmean(self): """ Returns the quadratic mean of data. """ data = self.data return numpy.sqrt((numpy.ma.masked_outside(data, -config.mask_outside, config.mask_outside)**2).mean())
[docs] def nonzero(self): """ Returns the number of non-zero values (whose absolute value > config.epsilon). """ data = self.data return numpy.count_nonzero(abs(numpy.ma.masked_outside(data, -config.mask_outside, config.mask_outside)) > config.epsilon) ############# # OPERATORS # #############
def __add__(self, other): """ Definition of addition, 'other' being: - a scalar (integer/float) - another Field of the same subclass. Returns a new Field whose data is the resulting operation, with 'fid' = {'op':'+'} and null validity. """ newfield = super(V2DField, self)._add(other, geometry=self.geometry) return newfield def __mul__(self, other): """ Definition of multiplication, 'other' being: - a scalar (integer/float) - another Field of the same subclass. Returns a new Field whose data is the resulting operation, with 'fid' = {'op':'*'} and null validity. """ newfield = super(V2DField, self)._mul(other, geometry=self.geometry) return newfield def __sub__(self, other): """ Definition of substraction, 'other' being: - a scalar (integer/float) - another Field of the same subclass. Returns a new Field whose data is the resulting operation, with 'fid' = {'op':'-'} and null validity. """ newfield = super(V2DField, self)._sub(other, geometry=self.geometry) return newfield def __div__(self, other): """ Definition of division, 'other' being: - a scalar (integer/float) - another Field of the same subclass. Returns a new Field whose data is the resulting operation, with 'fid' = {'op':'/'} and null validity. """ newfield = super(V2DField, self)._div(other, geometry=self.geometry) return newfield