Source code for epygram.fields.V2DField

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

import numpy

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



[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)
[docs] def gettransect(self): """ Returns the section transect as a list of (lon, lat) tuples. """ return [(g.hlocation['lon'], g.hlocation['lat']) for g in self.geometry.grid] ################### # 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 **matplotlib**. """ 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] 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[:] elif p0.coordinate == 'hybrid_height': 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. x = numpy.zeros((self.geometry.dimensions['Z'], self.geometry.dimensions['X'])) plast = p0 distance = 0 for i in range(self.geometry.dimensions['X']): p = self.geometry.grid[i] distance += self.geometry.distance((plast.hlocation['lon'], plast.hlocation['lat']), \ (p.hlocation['lon'], p.hlocation['lat'])) x[:, i] = distance plast = p 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 == 'hybrid_height': Ycoordinate = 'Level \nHybrid-Height \ncoordinate' 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 ############# # 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