def normalize(self, fcst): if 'time' in fcst.dims: if not fcst.dims['time'] == 1: raise ValueError("Expected a single time for VelocityField") fcst = fcst.isel(time=0) fcst = self.variable.normalize(fcst) units.convert_units(fcst[self.variable.speed_name], self.speed_units) units.convert_units(fcst[self.variable.direction_name], 'radians') return fcst
def check_beaufort(obj): if conv.UWND in obj: units.convert_units(obj[conv.UWND], _units[conv.WIND_SPEED]) # we need both UWND and VWND to do anything with wind assert conv.VWND in obj units.convert_units(obj[conv.VWND], _units[conv.WIND_SPEED]) # double check assert obj[conv.UWND].attrs[conv.UNITS] == _units[conv.WIND_SPEED] assert obj[conv.VWND].attrs[conv.UNITS] == _units[conv.WIND_SPEED] if conv.ENS_SPREAD_WS in obj: units.convert_units(obj[conv.ENS_SPREAD_WS], _units[conv.ENS_SPREAD_WS]) # double check assert (obj[conv.ENS_SPREAD_WS].attrs[conv.UNITS] == _units[conv.ENS_SPREAD_WS]) # make sure latitudes are in degrees and are on the correct scale assert 'degrees' in obj[conv.LAT].attrs[conv.UNITS] assert np.min(np.asarray(obj[conv.LAT].values)) >= -90 assert np.max(np.asarray(obj[conv.LAT].values)) <= 90 # make sure longitudes are in degrees and are on the correct scale assert 'degrees' in obj[conv.LON].attrs[conv.UNITS] obj[conv.LON].values[:] = np.mod(obj[conv.LON].values + 180., 360) - 180. assert obj[conv.UWND].shape == obj[conv.VWND].shape if conv.PRECIP in obj: units.convert_units(obj[conv.PRECIP], _units[conv.PRECIP])
def normalize(self, fcsts): fcsts = ensure_single_location(fcsts) fcsts = self.variable.normalize(fcsts) # add a realization dimension if it doesn't exist. This # allows us to support both ensemble and non-ensemble # forecasts with the same code. if not 'realization' in fcsts: def add_ensemble(vn): fcsts[vn] = (xray.concat([fcsts[vn]], 'realization') .transpose('time', 'realization')) add_ensemble(self.variable.speed_name) add_ensemble(self.variable.direction_name) units.convert_units(fcsts[self.variable.speed_name], self.speed_units) units.convert_units(fcsts[self.variable.direction_name], 'radians') return fcsts
def __init__(self, fcst, velocity_variable, speed_units='knots', ax=None, fig=None, **kwdargs): self.variable = velocity_variable self.speed_units = speed_units # set the default colormap if needed kwdargs['cmap'] = kwdargs.get('cmap', velocity_cmap) # convert the bins to knots bins = xray.Variable('bins', velocity_variable.speed_bins.copy(), {'units': velocity_variable.units}) _, self.bins, _ = units.convert_units(bins, speed_units) default_norm = plt.cm.colors.BoundaryNorm(self.bins, self.bins.size) kwdargs['norm'] = kwdargs.get('norm', default_norm) self.variable = velocity_variable self.ax, self.fig = utils.axis_figure(ax, fig) # add the color bar self.cax = self.fig.add_axes([0.92, 0.05, 0.03, 0.9]) cbar = mpl.colorbar.ColorbarBase(self.cax, cmap=kwdargs['cmap'], norm=kwdargs['norm']) cbar.set_label("Knots") fcst = self.normalize(fcst) # use the longitude grid to define the radius of the circles sorted_lats = np.sort(fcst['latitude'].values) resol = np.median(angles.angle_diff(sorted_lats[1:], sorted_lats[:-1])) # create the map self.m = utils.get_basemap(fcst, ax=self.ax, lon_pad=0.75 * resol, lat_pad = 0.75 * resol) def create_circle(one_loc): # determine the circle center x, y = self.m(one_loc['longitude'].values, one_loc['latitude'].values) speeds = one_lonlat[self.variable.speed_name].values dirs = one_lonlat[self.variable.direction_name].values orientation = self.variable.direction_orientation return DirectionCircle(x, y, speeds=np.atleast_1d(speeds), directions=np.atleast_1d(dirs), orientation=orientation, radius=0.4 * resol, ax=self.ax, **kwdargs) self.circles = [[create_circle(one_lonlat) for lo, one_lonlat in one_lat.groupby('longitude')] for la, one_lat in fcst.groupby('latitude')] self.set_title(fcst)
def __init__(self, fcsts, velocity_variable, speed_units='knot', max_speed=None, ax=None, fig=None): self.variable = velocity_variable self.speed_units = speed_units self.ax, self.fig = utils.axis_figure(ax, fig) self.ax.set_ylabel("Speed (%s)" % speed_units) # convert the bins to knots bins = xray.Variable('bins', velocity_variable.speed_bins.copy(), {'units': velocity_variable.units}) _, self.bins, _ = units.convert_units(bins, speed_units) # convert the data to knots and radians fcsts = self.normalize(fcsts) # determine the upper bound on speed so we can place # the direction circles. all_speeds =fcsts[self.variable.speed_name].values max_speed = max_speed or np.max(all_speeds) max_bin = np.sum(self.bins <= max_speed) + 2 self.max_bin = np.minimum(max_bin, self.bins.size) self.plot(fcsts)
def plot_gridded_ensemble(gfsx, contour_units=None, max_level=None, barb_units='knot', cols=2, save_path=None, save_fmt='svg'): """ Plots the average windspeed deviation of ensemble forecast members over the published GSF forecast for each grid point and forecast time. The result is (for each forecast time) a color filled contour plot indicating the ensemble deviation superimposed over the usual wind barbs with the published GFS forecast vectors. Parameters: ----------- fcst_gfsx: xray.Dataset Dataset with the GFS forecast for windspeed (uwnd and vwnd) and ensemble forecast deviation (other forecast variables will be ignored if present). contour_units: str The units in which to plot the ensemble spread indicator (heatmap). If specified, the data sets current unit must be convertible into the desired unit by slocum.lib.units. If not specified the exsiting units in the data set will be used. max_level: float Heatmap/contour plot maximum level. Minimum level is assumed to be 0. Values greater tha max_level will be mapped onto the max_level color. If not specified, the maximum value found in the data set will be used as the upper end of the color bar. barb_units: str Units in which to plot the GFS wind speed/direction forecast data (wind bars). If specified, the data sets current unit must be convertible into the desired unit by slocum.lib.units. cols: int Number of columns for subplot layout. save_path: str If specified the plot will be saved into this directory with file name ``ens_<bounding box>_<t0>.svg`` where *bounding box* is specified as *ll_lat, ll_lon - ur_lat, ur_lon*. If not specified or ``None`` the plot will be displayed interactively. save_fmt: str Format under which to save the image (only relevant if *save_path* is specified). Can be any image file extension that plt.savefig() will recognize as a valid format. """ # adjust units as requested: for v in (conv.UWND, conv.VWND): units.convert_units(gfsx[v], barb_units) if contour_units: units.convert_units(gfsx[conv.ENS_SPREAD_WS], contour_units) if not max_level: max_level = gfsx[conv.ENS_SPREAD_WS].max() if isinstance(gfsx, np.datetime64): # True is gfsx has not been packed f_times = gfsx[conv.TIME].values else: # time variable has int offsets f_times = xray.conventions.decode_cf_datetime( gfsx['time'], gfsx['time'].attrs['units']) lats = gfsx[conv.LAT].values lons = gfsx[conv.LON].values # Basemap.transform_vector requires lats and lons each to be in ascending # order: lat_inds = range(len(lats)) lon_inds = range(len(lons)) if NautAngle(lats[0]).is_north_of(NautAngle(lats[-1])): lat_inds.reverse() lats = lats[lat_inds] if NautAngle(lons[0]).is_east_of(NautAngle(lons[-1])): lon_inds.reverse() lons = lons[lon_inds] # determine layout: fig_width_inches = 10 plot_aspect_ratio = (abs(lons[-1] - lons[0]) / float(abs(lats[-1] - lats[0]))) rows = int(np.ceil(gfsx.dimensions[conv.TIME] / float(cols))) fig_height_inches = (fig_width_inches / (float(cols) * plot_aspect_ratio) * rows + 2) fig = plt.figure(figsize=(fig_width_inches, fig_height_inches)) fig.suptitle("GFS wind forecast in %s and " "ensemble wind speed deviation" % barb_units, fontsize=12) grid = AxesGrid(fig, [0.01, 0.01, 0.95, 0.93], nrows_ncols=(rows, cols), axes_pad=0.8, cbar_mode='single', cbar_pad=0.0, cbar_size=0.2, cbar_location='bottom', share_all=True,) # size for lat/lon labels, timestamp: label_fontsize = 'medium' if cols <= 2 else 'small' # format string for colorbar labels: decimals = max(0, int(2 - np.floor(np.log10(max_level)))) cb_label_fmt = '%.' + '%d' % decimals + 'f' # heatmap color scaling and levels spread_levels = np.linspace(0., max_level, 50) m = Basemap(projection='merc', llcrnrlon=lons[0], llcrnrlat=lats[0], urcrnrlon=lons[-1], urcrnrlat=lats[-1], resolution='l') for t_step, t in enumerate(f_times): ax = grid[t_step] m.drawcoastlines(ax=ax) m.drawparallels(lats,labels=[1,0,0,0], ax=ax, fontsize=label_fontsize) m.drawmeridians(lons,labels=[0,0,0,1], ax=ax, fontsize=label_fontsize, rotation='vertical') # ensemble spread heatmap: x, y = m(*np.meshgrid(lons, lats)) data = gfsx[conv.ENS_SPREAD_WS] data = data.indexed(**{conv.TIME: t_step}) data = data.indexed(**{conv.LAT: lat_inds}) data = data.indexed(**{conv.LON: lon_inds}).values cs = m.contourf(x, y, data, spread_levels, ax=ax, extend='max', cmap=cm.jet) # wind barbs: u = gfsx[conv.UWND].indexed(**{conv.TIME: t_step}) u = u.indexed(**{conv.LAT: lat_inds}) u = u.indexed(**{conv.LON: lon_inds}).values v = gfsx[conv.VWND].indexed(**{conv.TIME: t_step}) v = v.indexed(**{conv.LAT: lat_inds}) v = v.indexed(**{conv.LON: lon_inds}).values # transform from spherical to map projection coordinates (rotation # and interpolation). nxv = len(lons) nyv = len(lats) barb_length = 8 - cols barb_width = 1.2 - (cols / 10.) udat, vdat, xv, yv = m.transform_vector( u, v, lons, lats, nxv, nyv, returnxy=True) # plot barbs. m.barbs(xv, yv, udat, vdat, ax=ax, length=barb_length, barbcolor='w', flagcolor='r', linewidth=barb_width) ax.set_title(t.astype('M8[h]').item().strftime('%Y-%m-%dT%H:%MZ'), fontsize=label_fontsize) cbar = fig.colorbar(cs, cax=grid.cbar_axes[0], orientation='horizontal', format=cb_label_fmt) attr = gfsx[conv.ENS_SPREAD_WS].attrs cb_label = attr.get('long_name', 'Average (normalized) wind speed delta (ens - gfs)') s = ["%s = %s" % (k, attr[k]) for k in attr if k != 'long_name'] if s: cb_label = "%s (%s)" % (cb_label, ', '.join(s)) cbar.set_label(cb_label) if save_path: t0_str = f_times[0].astype('M8[h]').item().strftime('%Y%m%dT%H%MZ') file_name = "ens_%s%s-%s%s_%s.%s" % ( NautAngle(lats[0]).named_str(conv.LAT), NautAngle(lons[0]).named_str(conv.LON), NautAngle(lats[-1]).named_str(conv.LAT), NautAngle(lons[-1]).named_str(conv.LON), t0_str, save_fmt) plt.savefig(os.path.join(save_path, file_name), bbox_inches='tight') else: plt.show() plt.close()