Source code for pyart.graph.radardisplay

"""
Class for creating plots from Radar objects.

"""

import warnings

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.dates import DateFormatter
from scipy.interpolate import griddata

from ..core.transforms import antenna_vectors_to_cartesian, geographic_to_cartesian_aeqd
from ..util.datetime_utils import datetimes_from_radar
from . import common


[docs]class RadarDisplay: """ A display object for creating plots from data in a radar object. Parameters ---------- radar : Radar Radar object to use for creating plots. shift : (float, float) Shifts in km to offset the calculated x and y locations. Attributes ---------- plots : list List of plots created. plot_vars : list List of fields plotted, order matches plot list. cbs : list List of colorbars created. origin : str 'Origin' or 'Radar'. shift : (float, float) Shift in meters. loc : (float, float) Latitude and Longitude of radar in degrees. fields : dict Radar fields. scan_type : str Scan type. ranges : array Gate ranges in meters. azimuths : array Azimuth angle in degrees. elevations : array Elevations in degrees. fixed_angle : array Scan angle in degrees. antenna_transition : array or None Antenna transition flag (1 in transition, 0 in transition) or None if no antenna transition. """ def __init__(self, radar, shift=(0.0, 0.0)): """Initialize the object.""" # save radar object self._radar = radar # populate attributes from radar object self.fields = radar.fields self.scan_type = radar.scan_type self.ranges = radar.range["data"] self.azimuths = radar.azimuth["data"] self.elevations = radar.elevation["data"] self.fixed_angle = radar.fixed_angle["data"] if radar.antenna_transition is None: self.antenna_transition = None else: self.antenna_transition = radar.antenna_transition["data"] # origin if shift != (0.0, 0.0): self.origin = "origin" else: self.origin = "radar" self.shift = shift # radar location in latitude and longitude if radar.latitude["data"].size == 1: lat = float(radar.latitude["data"][0]) lon = float(radar.longitude["data"][0]) else: # for moving platforms stores use the median location. # The RadarDisplay object does not give a proper # visualization for moving platform data as the origin # of each ray changes and needs to be calculated individually or # georeferences. When that is not available the following # gives acceptable results. lat = np.median(radar.latitude["data"]) lon = np.median(radar.longitude["data"]) warnings.warn("RadarDisplay does not correct for moving platforms") self.loc = (lat, lon) # list to hold plots, plotted fields and plotted colorbars self.plots = [] self.plot_vars = [] self.cbs = [] #################### # Plotting methods # ####################
[docs] def plot(self, field, sweep=0, **kwargs): """ Create a plot appropiate for the radar. This function calls the plotting function corresponding to the scan_type of the radar. Additional keywords can be passed to customize the plot, see the appropiate plot function for the allowed keywords. Parameters ---------- field : str Field to plot. sweep : int Sweep number to plot, not used for VPT scans. See Also -------- plot_ppi : Plot a PPI scan plot_rhi : Plot a RHI scan plot_vpt : Plot a VPT scan """ if self.scan_type == "ppi" or self.scan_type == "sector": self.plot_ppi(field, sweep, **kwargs) elif self.scan_type == "rhi": self.plot_rhi(field, sweep, **kwargs) elif self.scan_type == "vpt": self.plot_vpt(field, **kwargs) else: raise ValueError(f"unknown scan_type {self.scan_type: }") return
[docs] def plot_ray( self, field, ray, format_str="k-", mask_tuple=None, ray_min=None, ray_max=None, mask_outside=False, title=None, title_flag=True, axislabels=(None, None), gatefilter=None, axislabels_flag=True, ax=None, fig=None, ): """ Plot a single ray. Parameters ---------- field : str Field to plot. ray : int Ray number to plot. Other Parameters ---------------- format_str : str Format string defining the line style and marker. mask_tuple : (str, float) Tuple containing the field name and value below which to mask field prior to plotting, for example to mask all data where NCP < 0.5 set mask_tuple to ['NCP', 0.5]. None performs no masking. ray_min : float Minimum ray value, None for default value, ignored if mask_outside is False. ray_max : float Maximum ray value, None for default value, ignored if mask_outside is False. mask_outside : bool True to mask data outside of vmin, vmax. False performs no masking. title : str Title to label plot with, None to use default title generated from the field and ray parameters. Parameter is ignored if title_flag is False. title_flag : bool True to add a title to the plot, False does not add a title. gatefilter : GateFilter GateFilter instance. None will result in no gatefilter mask being applied to data. axislabels : (str, str) 2-tuple of x-axis, y-axis labels. None for either label will use the default axis label. Parameter is ignored if axislabels_flag is False. axislabels_flag : bool True to add label the axes, False does not label the axes. ax : Axis Axis to plot on. None will use the current axis. fig : Figure Figure to add the colorbar to. None will use the current figure. """ # parse parameters ax, fig = common.parse_ax_fig(ax, fig) # get the data and mask data = self._get_ray_data(field, ray, mask_tuple, gatefilter) # mask the data where outside the limits data = _mask_outside(mask_outside, data, ray_min, ray_max) # plot the data (line,) = ax.plot(self.ranges / 1000.0, data, format_str) if title_flag: self._set_ray_title(field, ray, title, ax) if axislabels_flag: self._label_axes_ray(axislabels, field, ax) # add plot and field to attribute lists self.plots.append(line) self.plot_vars.append(field)
[docs] def plot_ppi( self, field, sweep=0, mask_tuple=None, vmin=None, vmax=None, norm=None, cmap=None, mask_outside=False, title=None, title_flag=True, axislabels=(None, None), axislabels_flag=True, colorbar_flag=True, colorbar_label=None, colorbar_orient="vertical", edges=True, gatefilter=None, filter_transitions=True, ax=None, fig=None, ticks=None, ticklabs=None, raster=False, title_datetime_format=None, title_use_sweep_time=True, **kwargs, ): """ Plot a PPI. Additional arguments are passed to Matplotlib's pcolormesh function. Parameters ---------- field : str Field to plot. sweep : int, optional Sweep number to plot. Other Parameters ---------------- mask_tuple : (str, float) Tuple containing the field name and value below which to mask field prior to plotting, for example to mask all data where NCP < 0.5 set mask_tuple to ['NCP', 0.5]. None performs no masking. vmin : float Luminance minimum value, None for default value. Parameter is ignored is norm is not None. vmax : float Luminance maximum value, None for default value. Parameter is ignored is norm is not None. norm : Normalize or None, optional matplotlib Normalize instance used to scale luminance data. If not None the vmax and vmin parameters are ignored. If None, vmin and vmax are used for luminance scaling. cmap : str or None Matplotlib colormap name. None will use the default colormap for the field being plotted as specified by the Py-ART configuration. mask_outside : bool True to mask data outside of vmin, vmax. False performs no masking. title : str Title to label plot with, None to use default title generated from the field and sweep parameters. Parameter is ignored if title_flag is False. title_datetime_format : str Format of datetime in the title (using strftime format). title_use_sweep_time : bool True for the current sweep's beginning time to be used for the title. False for the radar's beginning time. title_flag : bool True to add a title to the plot, False does not add a title. axislabels : (str, str) 2-tuple of x-axis, y-axis labels. None for either label will use the default axis label. Parameter is ignored if axislabels_flag is False. axislabels_flag : bool True to add label the axes, False does not label the axes. colorbar_flag : bool True to add a colorbar with label to the axis. False leaves off the colorbar. colorbar_label : str Colorbar label, None will use a default label generated from the field information. colorbar_orient : 'vertical' or 'horizontal' Colorbar orientation. ticks : array Colorbar custom tick label locations. ticklabs : array Colorbar custom tick labels. edges : bool True will interpolate and extrapolate the gate edges from the range, azimuth and elevations in the radar, treating these as specifying the center of each gate. False treats these coordinates themselves as the gate edges, resulting in a plot in which the last gate in each ray and the entire last ray are not plotted. gatefilter : GateFilter GateFilter instance. None will result in no gatefilter mask being applied to data. filter_transitions : bool True to remove rays where the antenna was in transition between sweeps from the plot. False will include these rays in the plot. No rays are filtered when the antenna_transition attribute of the underlying radar is not present. ax : Axis Axis to plot on. None will use the current axis. fig : Figure Figure to add the colorbar to. None will use the current figure. raster : bool False by default. Set to true to render the display as a raster rather than a vector in call to pcolormesh. Saves time in plotting high resolution data over large areas. Be sure to set the dpi of the plot for your application if you save it as a vector format (i.e., pdf, eps, svg). """ # parse parameters ax, fig = common.parse_ax_fig(ax, fig) vmin, vmax = common.parse_vmin_vmax(self._radar, field, vmin, vmax) cmap = common.parse_cmap(cmap, field) # get data for the plot data = self._get_data(field, sweep, mask_tuple, filter_transitions, gatefilter) x, y = self._get_x_y(sweep, edges, filter_transitions) # mask the data where outside the limits data = _mask_outside(mask_outside, data, vmin, vmax) # plot the data if norm is not None: # if norm is set do not override with vmin/vmax vmin = vmax = None pm = ax.pcolormesh( x, y, data, vmin=vmin, vmax=vmax, cmap=cmap, norm=norm, **kwargs ) if raster: pm.set_rasterized(True) if title_flag: self._set_title( field, sweep, title, ax, datetime_format=title_datetime_format, use_sweep_time=title_use_sweep_time, ) if axislabels_flag: self._label_axes_ppi(axislabels, ax) # add plot and field to lists self.plots.append(pm) self.plot_vars.append(field) if colorbar_flag: self.plot_colorbar( mappable=pm, label=colorbar_label, orient=colorbar_orient, field=field, ax=ax, fig=fig, ticks=ticks, ticklabs=ticklabs, )
[docs] def plot_rhi( self, field, sweep=0, mask_tuple=None, vmin=None, vmax=None, norm=None, cmap=None, mask_outside=False, title=None, title_flag=True, axislabels=(None, None), axislabels_flag=True, reverse_xaxis=None, colorbar_flag=True, colorbar_label=None, colorbar_orient="vertical", edges=True, gatefilter=None, filter_transitions=True, ax=None, fig=None, ticks=None, ticklabs=None, raster=False, title_datetime_format=None, title_use_sweep_time=True, **kwargs, ): """ Plot a RHI. Additional arguments are passed to Matplotlib's pcolormesh function. Parameters ---------- field : str Field to plot. sweep : int, Sweep number to plot. Other Parameters ---------------- mask_tuple : (str, float) 2-Tuple containing the field name and value below which to mask field prior to plotting, for example to mask all data where NCP < 0.5 set mask to ['NCP', 0.5]. None performs no masking. vmin : float Luminance minimum value, None for default value. Parameter is ignored is norm is not None. vmax : float Luminance maximum value, None for default value. Parameter is ignored is norm is not None. norm : Normalize or None, optional matplotlib Normalize instance used to scale luminance data. If not None the vmax and vmin parameters are ignored. If None, vmin and vmax are used for luminance scaling. cmap : str or None Matplotlib colormap name. None will use the default colormap for the field being plotted as specified by the Py-ART configuration. title : str Title to label plot with, None to use default title generated from the field and sweep parameters. Parameter is ignored if title_flag is False. title_datetime_format : str Format of datetime in the title (using strftime format). title_use_sweep_time : bool True for the current sweep's beginning time to be used for the title. False for the radar's beginning time. title_flag : bool True to add a title to the plot, False does not add a title. axislabels : (str, str) 2-tuple of x-axis, y-axis labels. None for either label will use the default axis label. Parameter is ignored if axislabels_flag is False. axislabels_flag : bool True to add label the axes, False does not label the axes. reverse_xaxis : bool or None True to reverse the x-axis so the plot reads east to west, False to have east to west. None (the default) will reverse the axis only when all the distances are negative. colorbar_flag : bool True to add a colorbar with label to the axis. False leaves off the colorbar. colorbar_label : str Colorbar label, None will use a default label generated from the field information. colorbar_orient : 'vertical' or 'horizontal' Colorbar orientation. ticks : array Colorbar custom tick label locations. ticklabs : array Colorbar custom tick labels. edges : bool True will interpolate and extrapolate the gate edges from the range, azimuth and elevations in the radar, treating these as specifying the center of each gate. False treats these coordinates themselves as the gate edges, resulting in a plot in which the last gate in each ray and the entire last ray are not not plotted. gatefilter : GateFilter GateFilter instance. None will result in no gatefilter mask being applied to data. filter_transitions : bool True to remove rays where the antenna was in transition between sweeps from the plot. False will include these rays in the plot. No rays are filtered when the antenna_transition attribute of the underlying radar is not present. ax : Axis Axis to plot on. None will use the current axis. fig : Figure Figure to add the colorbar to. None will use the current figure. raster : bool False by default. Set to true to render the display as a raster rather than a vector in call to pcolormesh. Saves time in plotting high resolution data over large areas. Be sure to set the dpi of the plot for your application if you save it as a vector format (i.e., pdf, eps, svg). """ # parse parameters ax, fig = common.parse_ax_fig(ax, fig) vmin, vmax = common.parse_vmin_vmax(self._radar, field, vmin, vmax) cmap = common.parse_cmap(cmap, field) # get data for the plot data = self._get_data(field, sweep, mask_tuple, filter_transitions, gatefilter) x, y, z = self._get_x_y_z(sweep, edges, filter_transitions) # mask the data where outside the limits data = _mask_outside(mask_outside, data, vmin, vmax) # plot the data # check for negative values sweep_slice = self._radar.get_slice(sweep) az_median = np.abs(np.median(self._radar.azimuth["data"][sweep_slice])) if (89.5 <= az_median <= 90.0) or (269.0 <= az_median <= 271.0): R = np.sqrt(x**2 + y**2) * np.sign(x) else: R = np.sqrt(x**2 + y**2) * np.sign(y) if reverse_xaxis is None: # reverse if all distances are nearly negative (allow up to 1 m) reverse_xaxis = np.all(R < 1.0) if reverse_xaxis: R = -R if norm is not None: # if norm is set do not override with vmin/vmax vmin = vmax = None pm = ax.pcolormesh( R, z, data, vmin=vmin, vmax=vmax, cmap=cmap, norm=norm, **kwargs ) if raster: pm.set_rasterized(True) if title_flag: self._set_title( field, sweep, title, ax, datetime_format=title_datetime_format, use_sweep_time=title_use_sweep_time, ) if axislabels_flag: self._label_axes_rhi(axislabels, ax) # add plot and field to lists self.plots.append(pm) self.plot_vars.append(field) if colorbar_flag: self.plot_colorbar( mappable=pm, label=colorbar_label, orient=colorbar_orient, field=field, ax=ax, fig=fig, ticks=ticks, ticklabs=ticklabs, )
[docs] def plot_vpt( self, field, mask_tuple=None, vmin=None, vmax=None, norm=None, cmap=None, mask_outside=False, title=None, title_flag=True, axislabels=(None, None), axislabels_flag=True, colorbar_flag=True, colorbar_label=None, colorbar_orient="vertical", edges=True, gatefilter=None, filter_transitions=True, time_axis_flag=False, date_time_form=None, tz=None, ax=None, fig=None, ticks=None, ticklabs=None, raster=False, **kwargs, ): """ Plot a VPT scan. Additional arguments are passed to Matplotlib's pcolormesh function. Parameters ---------- field : str Field to plot. Other Parameters ---------------- mask_tuple : (str, float) Tuple containing the field name and value below which to mask field prior to plotting, for example to mask all data where NCP < 0.5 set mask_tuple to ['NCP', 0.5]. None performs no masking. vmin : float Luminance minimum value, None for default value. Parameter is ignored is norm is not None. vmax : float Luminance maximum value, None for default value. Parameter is ignored is norm is not None. norm : Normalize or None, optional matplotlib Normalize instance used to scale luminance data. If not None the vmax and vmin parameters are ignored. If None, vmin and vmax are used for luminance scaling. cmap : str or None Matplotlib colormap name. None will use the default colormap for the field being plotted as specified by the Py-ART configuration. mask_outside : bool True to mask data outside of vmin, vmax. False performs no masking. title : str Title to label plot with, None to use default title generated from the field and sweep parameters. Parameter is ignored if title_flag is False. title_flag : bool True to add a title to the plot, False does not add a title. axislabels : (str, str) 2-tuple of x-axis, y-axis labels. None for either label will use the default axis label. Parameter is ignored if axislabels_flag is False. axislabels_flag : bool True to add label the axes, False does not label the axes. colorbar_flag : bool True to add a colorbar with label to the axis. False leaves off the colorbar. colorbar_label : str Colorbar label, None will use a default label generated from the field information. ticks : array Colorbar custom tick label locations. ticklabs : array Colorbar custom tick labels. colorbar_orient : 'vertical' or 'horizontal' Colorbar orientation. edges : bool True will interpolate and extrapolate the gate edges from the range, azimuth and elevations in the radar, treating these as specifying the center of each gate. False treats these coordinates themselves as the gate edges, resulting in a plot in which the last gate in each ray and the entire last ray are not not plotted. gatefilter : GateFilter GateFilter instance. None will result in no gatefilter mask being applied to data. filter_transitions : bool True to remove rays where the antenna was in transition between sweeps from the plot. False will include these rays in the plot. No rays are filtered when the antenna_transition attribute of the underlying radar is not present. time_axis_flag : bool True to plot the x-axis as time. False uses the index number. Default is False - index-based. date_time_form : str, optional Format of the time string for x-axis labels. Parameter is ignored if time_axis_flag is set to False. tz : str, optional Time zone info to use when creating axis labels (see datetime). Parameter is ignored if time_axis_flag is set to False. ax : Axis Axis to plot on. None will use the current axis. fig : Figure Figure to add the colorbar to. None will use the current figure. raster : bool False by default. Set to true to render the display as a raster rather than a vector in call to pcolormesh. Saves time in plotting high resolution data over large areas. Be sure to set the dpi of the plot for your application if you save it as a vector format (i.e., pdf, eps, svg). """ # parse parameters ax, fig = common.parse_ax_fig(ax, fig) vmin, vmax = common.parse_vmin_vmax(self._radar, field, vmin, vmax) cmap = common.parse_cmap(cmap, field) # get data for the plot data = self._get_vpt_data(field, mask_tuple, filter_transitions, gatefilter) if edges: y = np.empty((self.ranges.shape[0] + 1,), dtype=self.ranges.dtype) y[1:-1] = (self.ranges[:-1] + self.ranges[1:]) / 2.0 y[0] = self.ranges[0] - (self.ranges[1] - self.ranges[0]) / 2.0 y[-1] = self.ranges[-1] - (self.ranges[-2] - self.ranges[-1]) / 2.0 y[y < 0] = 0 # do not allow range to become negative y = y / 1000.0 x = np.arange(data.shape[1] + 1) else: x = np.arange(data.shape[1]) y = self.ranges / 1000.0 # set up the time axis if time_axis_flag: self._set_vpt_time_axis(ax, date_time_form=date_time_form, tz=tz) times = datetimes_from_radar(self._radar) if edges: times = _edge_time(times) x = times.astype("datetime64[ns]") # mask the data where outside the limits data = _mask_outside(mask_outside, data, vmin, vmax) # plot the data if norm is not None: # if norm is set do not override with vmin/vmax vmin = vmax = None pm = ax.pcolormesh( x, y, data, vmin=vmin, vmax=vmax, cmap=cmap, norm=norm, **kwargs ) if raster: pm.set_rasterized(True) if title_flag: self._set_vpt_title(field, title, ax) if axislabels_flag: self._label_axes_vpt(axislabels, time_axis_flag, ax) # add plot and field to lists self.plots.append(pm) self.plot_vars.append(field) if colorbar_flag: self.plot_colorbar( mappable=pm, label=colorbar_label, orient=colorbar_orient, field=field, ax=ax, fig=fig, ticks=ticks, ticklabs=ticklabs, )
[docs] def plot_azimuth_to_rhi( self, field, target_azimuth, mask_tuple=None, vmin=None, vmax=None, norm=None, cmap=None, mask_outside=False, title=None, title_flag=True, axislabels=(None, None), axislabels_flag=True, colorbar_flag=True, colorbar_label=None, colorbar_orient="vertical", edges=True, gatefilter=None, reverse_xaxis=None, filter_transitions=True, ax=None, fig=None, ticks=None, ticklabs=None, raster=False, **kwargs, ): """ Plot pseudo-RHI scan by extracting the vertical field associated with the given azimuth. Additional arguments are passed to Matplotlib's pcolormesh function. Parameters ---------- field : str Field to plot. target_azimuth : integer Azimuthal angle in degrees where cross section will be taken. Other Parameters ---------------- mask_tuple : (str, float) 2-Tuple containing the field name and value below which to mask field prior to plotting, for example to mask all data where NCP < 0.5 set mask to ['NCP', 0.5]. None performs no masking. vmin : float Luminance minimum value, None for default value. Parameter is ignored is norm is not None. vmax : float Luminance maximum value, None for default value. Parameter is ignored is norm is not None. norm : Normalize or None, optional matplotlib Normalize instance used to scale luminance data. If not None the vmax and vmin parameters are ignored. If None, vmin and vmax are used for luminance scaling. cmap : str or None Matplotlib colormap name. None will use the default colormap for the field being plotted as specified by the Py-ART configuration. title : str Title to label plot with, None to use default title generated from the field and sweep parameters. Parameter is ignored if title_flag is False. title_flag : bool True to add a title to the plot, False does not add a title. axislabels : (str, str) 2-tuple of x-axis, y-axis labels. None for either label will use the default axis label. Parameter is ignored if axislabels_flag is False. axislabels_flag : bool True to add label the axes, False does not label the axes. reverse_xaxis : bool or None True to reverse the x-axis so the plot reads east to west, False to have east to west. None (the default) will reverse the axis only when all the distances are negative. colorbar_flag : bool True to add a colorbar with label to the axis. False leaves off the colorbar. colorbar_label : str Colorbar label, None will use a default label generated from the field information. ticks : array Colorbar custom tick label locations. ticklabs : array Colorbar custom tick labels. colorbar_orient : 'vertical' or 'horizontal' Colorbar orientation. edges : bool True will interpolate and extrapolate the gate edges from the range, azimuth and elevations in the radar, treating these as specifying the center of each gate. False treats these coordinates themselves as the gate edges, resulting in a plot in which the last gate in each ray and the entire last ray are not not plotted. gatefilter : GateFilter GateFilter instance. None will result in no gatefilter mask being applied to data. filter_transitions : bool True to remove rays where the antenna was in transition between sweeps from the plot. False will include these rays in the plot. No rays are filtered when the antenna_transition attribute of the underlying radar is not present. ax : Axis Axis to plot on. None will use the current axis. fig : Figure Figure to add the colorbar to. None will use the current figure. raster : bool False by default. Set to True to render the display as a raster rather than a vector in call to pcolormesh. Saves time in plotting high resolution data over large areas. Be sure to set the dpi of the plot for your application if you save it as a vector format (i.e., pdf, eps, svg). """ # parse parameters ax, fig = common.parse_ax_fig(ax, fig) vmin, vmax = common.parse_vmin_vmax(self._radar, field, vmin, vmax) cmap = common.parse_cmap(cmap, field) data, x, y, z = self._get_azimuth_rhi_data_x_y_z( field, target_azimuth, edges, mask_tuple, filter_transitions, gatefilter ) # mask the data where outside the limits data = _mask_outside(mask_outside, data, vmin, vmax) # plot the data R = np.sqrt(x**2 + y**2) * np.sign(y) if reverse_xaxis is None: # reverse if all distances (nearly, up to 1 m) negative. reverse_xaxis = np.all(R < 1.0) if reverse_xaxis: R = -R if norm is not None: # if norm is set do not override with vmin/vmax vmin = vmax = None pm = ax.pcolormesh( R, z, data, vmin=vmin, vmax=vmax, cmap=cmap, norm=norm, **kwargs ) if raster: pm.set_rasterized(True) if title_flag: self._set_az_rhi_title(field, target_azimuth, title, ax) if axislabels_flag: self._label_axes_rhi(axislabels, ax) # add plot and field to lists self.plots.append(pm) self.plot_vars.append(field) if colorbar_flag: self.plot_colorbar( mappable=pm, label=colorbar_label, orient=colorbar_orient, field=field, ax=ax, fig=fig, ticks=ticks, ticklabs=ticklabs, )
[docs] def plot_cr_raster( self, field="reflectivity", target_range=None, ax=None, fig=None, delta_x=None, delta_y=None, az_limits=None, el_limits=None, vmin=None, vmax=None, cmap=None, title=None, title_flag=True, axislabels=[None, None], axislabels_flag=True, colorbar_flag=True, colorbar_label=None, colorbar_orient="vertical", ticks=None, ticklabs=None, raster=False, ): """ Plot a corner reflector raster scan Parameters ---------- field : String Field to plot if other than reflectivity target_range : Float Estimated range of the corner reflector Other Parameters ---------------- ax : Axis Axis to plot on. None will use the current axis. fig : Figure Figure to add the colorbar to. None will use the current figure. delta_x : Float Azimuth grid spacing for griddata delta_y : Float Elevation grid spacing for griddata az_limits : list Azimuth limits in form [min, max] el_limits : list Elevation limits in form [min, max] vmin : float Luminance minimum value, None for default value. Parameter is ignored is norm is not None. vmax : float Luminance maximum value, None for default value. Parameter is ignored is norm is not None. cmap : str or None Matplotlib colormap name. None will use the default colormap for the field being plotted as specified by the Py-ART configuration. title : str Title to label plot with, None to use default title generated from the field and sweep parameters. Parameter is ignored if title_flag is False. title_flag : bool True to add a title to the plot, False does not add a title. axislabels : (str, str) 2-tuple of x-axis, y-axis labels. None for either label will use the default axis label. Parameter is ignored if axislabels_flag is False. axislabels_flag : bool True to add label the axes, False does not label the axes. colorbar_flag : bool True to add a colorbar with label to the axis. False leaves off the colorbar. colorbar_label : str Colorbar label, None will use a default label generated from the field information. ticks : array Colorbar custom tick label locations. ticklabs : array Colorbar custom tick labels. colorbar_orient : 'vertical' or 'horizontal' Colorbar orientation. raster : bool False by default. Set to True to render the display as a raster rather than a vector in call to pcolormesh. Saves time in plotting high resolution data over large areas. Be sure to set the dpi of the plot for your application if you save it as a vector format (i.e., pdf, eps, svg). """ ax, fig = common.parse_ax_fig(ax, fig) # Get data and coordinate information az = self._radar.azimuth["data"] el = self._radar.elevation["data"] if el[0] is None: raise ValueError( "Elevation is set to None. CR raster plotting is unavailable " "for this dataset. Elevation can be set to None due to not " "being present per Halfword 30 Table V of the ICD for NEXRAD " "level 3 data." ) rng = self._radar.range["data"] data = self._radar.fields[field]["data"] # Calculate delta for x and y if az_limits is None: min_az = np.nanmin(az) max_az = np.nanmax(az) else: min_az = az_limits[0] max_az = az_limits[1] if el_limits is None: min_el = np.nanmin(el) max_el = np.nanmax(el) else: min_el = el_limits[0] max_el = el_limits[1] if delta_x is None: delta_x = max_az - min_az if delta_y is None: delta_y = max_el - min_el # Get range closest to target_range if target_range is None: target_index = 0 else: target_index = np.argmin(np.abs(np.array(rng) - target_range)) data = data[:, target_index] # Geet azimuth and elevation onto a meshgrid xi, yi = np.meshgrid( np.linspace(min_az, max_az, int(delta_x / 0.01)), np.linspace(min_el, max_el, int(delta_y / 0.01)), ) # Grid up the data for plotting grid = griddata((az, el), data, (xi, yi), method="linear") # Plot data using pcolormesh pm = ax.pcolormesh(xi[0, :], yi[:, 0], grid, vmin=vmin, vmax=vmax, cmap=cmap) if title_flag is True: if title is None: time_str = common.generate_radar_time_begin(self._radar) title = " ".join( [ "Corner Reflector", field.title(), time_str.strftime("%m/%d/%Y %H:%M:%S"), ] ) ax.set_title(title) if axislabels_flag is True: if axislabels[0] is None: axislabels[0] = "Azimuth (deg)" if axislabels[1] is None: axislabels[1] = "Elevation (deg)" ax.set_xlabel(axislabels[0]) ax.set_ylabel(axislabels[1]) if raster: pm.set_rasterized(True) # add plot and field to lists self.plots.append(pm) self.plot_vars.append(field) if colorbar_flag: self.plot_colorbar( mappable=pm, label=colorbar_label, orient=colorbar_orient, field=field, ax=ax, fig=fig, ticks=ticks, ticklabs=ticklabs, )
[docs] def plot_range_rings(self, range_rings, ax=None, col="k", ls="-", lw=2): """ Plot a series of range rings. Parameters ---------- range_rings : list List of locations in km to draw range rings. ax : Axis Axis to plot on. None will use the current axis. col : str or value Color to use for range rings. ls : str Linestyle to use for range rings. """ for range_ring_location_km in range_rings: self.plot_range_ring(range_ring_location_km, ax=ax, col=col, ls=ls, lw=lw)
[docs] @staticmethod def plot_range_ring( range_ring_location_km, npts=100, ax=None, col="k", ls="-", lw=2 ): """ Plot a single range ring. Parameters ---------- range_ring_location_km : float Location of range ring in km. npts: int Number of points in the ring, higher for better resolution. ax : Axis Axis to plot on. None will use the current axis. col : str or value Color to use for range rings. ls : str Linestyle to use for range rings. """ ax = common.parse_ax(ax) theta = np.linspace(0, 2 * np.pi, npts) r = np.ones([npts], dtype=np.float32) * range_ring_location_km x = r * np.sin(theta) y = r * np.cos(theta) ax.plot(x, y, c=col, ls=ls, lw=lw)
[docs] @staticmethod def plot_grid_lines(ax=None, col="k", ls=":"): """ Plot grid lines. Parameters ---------- ax : Axis Axis to plot on. None will use the current axis. col : str or value Color to use for grid lines. ls : str Linestyle to use for grid lines. """ ax = common.parse_ax(ax) ax.grid(c=col, ls=ls)
[docs] def plot_labels(self, labels, locations, symbols="r+", text_color="k", ax=None): """ Plot symbols and labels at given locations. Parameters ---------- labels : list of str List of labels to place just above symbols. locations : list of 2-tuples List of latitude, longitude (in degrees) tuples at which symbols will be place. Labels are placed just above the symbols. symbols : list of str or str List of matplotlib color+marker strings defining symbols to place at given locations. If a single string is provided, that symbol will be placed at all locations. text_color : str Matplotlib color defining the color of the label text. ax : Axis Axis to plot on. None will use the current axis. """ ax = common.parse_ax(ax) if isinstance(symbols, str): symbols = [symbols] * len(labels) if len(labels) != len(locations): raise ValueError("length of labels and locations must match") if len(labels) != len(symbols): raise ValueError("length of labels and symbols must match") for loc, label, sym in zip(locations, labels, symbols): self.plot_label(label, loc, sym, text_color, ax)
[docs] def plot_label(self, label, location, symbol="r+", text_color="k", ax=None): """ Plot a single symbol and label at a given location. Transforms of the symbol location in latitude and longitude units to x and y plot units is performed using an azimuthal equidistance map projection centered at the radar. Parameters ---------- label : str Label text to place just above symbol. location : 2-tuples Tuple of latitude, longitude (in degrees) at which the symbol will be place. The label is placed just above the symbol. symbol : str Matplotlib color+marker strings defining the symbol to place at the given location. text_color : str Matplotlib color defining the color of the label text. ax : Axis Axis to plot on. None will use the current axis. """ ax = common.parse_ax(ax) location_lat, location_lon = location radar_lat, radar_lon = self.loc location_x, location_y = geographic_to_cartesian_aeqd( location_lon, location_lat, radar_lon, radar_lat ) location_x /= 1000.0 location_y /= 1000.0 ax.plot([location_x], [location_y], symbol) ax.text(location_x - 5.0, location_y, label, color=text_color)
[docs] @staticmethod def plot_cross_hair(size, npts=100, ax=None): """ Plot a cross-hair on a ppi plot. Parameters ---------- size : float Size of cross-hair in km. npts: int Number of points in the cross-hair, higher for better resolution. ax : Axis Axis to plot on. None will use the current axis. """ ax = common.parse_ax(ax) x = np.zeros(npts, dtype=np.float32) y = np.linspace(-size, size, npts) ax.plot(x, y, "k-") # verticle ax.plot(y, x, "k-") # horizontal
[docs] def plot_colorbar( self, mappable=None, field=None, label=None, orient="vertical", cax=None, ax=None, fig=None, ticks=None, ticklabs=None, **kwargs, ): """ Plot a colorbar. Parameters ---------- mappable : Image, ContourSet, etc. Image, ContourSet, etc to which the colorbar applied. If None the last mappable object will be used. field : str Field to label colorbar with. label : str Colorbar label. None will use a default value from the last field plotted. orient : str Colorbar orientation, either 'vertical' [default] or 'horizontal'. cax : Axis Axis onto which the colorbar will be drawn. None is also valid. ax : Axes Axis onto which the colorbar will be drawn. None is also valid. fig : Figure Figure to place colorbar on. None will use the current figure. ticks : array Colorbar custom tick label locations. ticklabs : array Colorbar custom tick labels. """ if fig is None: fig = plt.gcf() if mappable is None: mappable = self.plots[-1] if label is None: if field is None: field = self.plot_vars[-1] label = self._get_colorbar_label(field) cb = fig.colorbar(mappable, orientation=orient, ax=ax, cax=cax, **kwargs) if ticks is not None: cb.set_ticks(ticks) if ticklabs: cb.set_ticklabels(ticklabs) cb.set_label(label) self.cbs.append(cb)
########################## # Plot adjusting methods # ##########################
[docs] @staticmethod def set_limits(xlim=None, ylim=None, ax=None): """ Set the display limits. Parameters ---------- xlim : tuple, optional 2-Tuple containing y-axis limits in km. None uses default limits. ylim : tuple, optional 2-Tuple containing x-axis limits in km. None uses default limits. ax : Axis Axis to adjust. None will adjust the current axis. """ common.set_limits(xlim, ylim, ax)
[docs] def label_xaxis_x(self, ax=None): """Label the xaxis with the default label for x units.""" ax = common.parse_ax(ax) ax.set_xlabel("East West distance from " + self.origin + " (km)")
[docs] def label_yaxis_y(self, ax=None): """Label the yaxis with the default label for y units.""" ax = common.parse_ax(ax) ax.set_ylabel("North South distance from " + self.origin + " (km)")
[docs] def label_xaxis_r(self, ax=None): """Label the xaxis with the default label for r units.""" ax = common.parse_ax(ax) ax.set_xlabel("Distance from " + self.origin + " (km)")
[docs] def label_yaxis_z(self, ax=None): """Label the yaxis with the default label for z units.""" ax = common.parse_ax(ax) ax.set_ylabel("Distance Above " + self.origin + " (km)")
[docs] @staticmethod def label_xaxis_rays(ax=None): """Label the yaxis with the default label for rays.""" ax = common.parse_ax(ax) ax.set_xlabel("Ray number (unitless)")
[docs] @staticmethod def label_xaxis_time(ax=None): """Label the yaxis with the default label for rays.""" ax = common.parse_ax(ax) ax.set_xlabel("Time (HH:MM)")
[docs] def label_yaxis_field(self, field, ax=None): """Label the yaxis with the default label for a field units.""" ax = common.parse_ax(ax) ax.set_ylabel(self._get_colorbar_label(field))
[docs] @staticmethod def set_aspect_ratio(aspect_ratio=0.75, ax=None): """Set the aspect ratio for plot area.""" ax = common.parse_ax(ax) ax.set_aspect(aspect_ratio)
def _set_title( self, field, sweep, title, ax, datetime_format=None, use_sweep_time=True ): """Set the figure title using a default title.""" if title is None: ax.set_title( self.generate_title(field, sweep, datetime_format, use_sweep_time) ) else: ax.set_title(title) def _set_vpt_title(self, field, title, ax): """Set the figure title using a default title.""" if title is None: ax.set_title(self.generate_vpt_title(field)) else: ax.set_title(title) def _set_ray_title(self, field, ray, title, ax): """Set the figure title for a ray plot using a default title.""" if title is None: ax.set_title(self.generate_ray_title(field, ray)) else: ax.set_title(title) def _set_az_rhi_title(self, field, azimuth, title, ax): """Set the figure title for a ray plot using a default title.""" if title is None: ax.set_title(self.generate_az_rhi_title(field, azimuth)) else: ax.set_title(title) def _label_axes_ppi(self, axis_labels, ax): """Set the x and y axis labels for a PPI plot.""" x_label, y_label = axis_labels if x_label is None: self.label_xaxis_x(ax) else: ax.set_xlabel(x_label) if y_label is None: self.label_yaxis_y(ax) else: ax.set_ylabel(y_label) def _label_axes_rhi(self, axis_labels, ax): """Set the x and y axis labels for a RHI plot.""" x_label, y_label = axis_labels if x_label is None: self.label_xaxis_r(ax) else: ax.set_xlabel(x_label) if y_label is None: self.label_yaxis_z(ax) else: ax.set_ylabel(y_label) def _label_axes_ray(self, axis_labels, field, ax): """Set the x and y axis labels for a ray plot.""" x_label, y_label = axis_labels if x_label is None: self.label_xaxis_r(ax) else: ax.set_xlabel(x_label) if y_label is None: self.label_yaxis_field(field, ax) else: ax.set_ylabel(y_label) def _label_axes_vpt(self, axis_labels, time_axis_flag, ax): """Set the x and y axis labels for a PPI plot.""" x_label, y_label = axis_labels if x_label is None: if time_axis_flag: self.label_xaxis_time(ax) else: self.label_xaxis_rays(ax) else: ax.set_xlabel(x_label) if y_label is None: self.label_yaxis_z(ax) else: ax.set_ylabel(y_label) @staticmethod def _set_vpt_time_axis(ax, date_time_form=None, tz=None): """ Set the x axis as a time formatted axis. Parameters ---------- ax : Matplotlib axis instance Axis to plot. None will use the current axis. date_time_form : str Format of the time string for x-axis labels. tz : str Time zone info to use when creating axis labels (see datetime). """ if date_time_form is None: date_time_form = "%H:%M" # Set the date format date_Fmt = DateFormatter(date_time_form, tz=tz) ax.xaxis.set_major_formatter(date_Fmt) # Turn the tick marks outward ax.tick_params(which="both", direction="out") ########################## # name generator methods # ##########################
[docs] def generate_filename( self, field, sweep, ext="png", datetime_format="%Y%m%d%H%M%S", use_sweep_time=False, ): """ Generate a filename for a plot. Generated filename has form: radar_name_field_sweep_time.ext Parameters ---------- field : str Field plotted. sweep : int Sweep plotted. ext : str Filename extension. datetime_format : str Format of datetime (using strftime format). use_sweep_time : bool If true, the current sweep's beginning time is used. Returns ------- filename : str Filename suitable for saving a plot. """ return common.generate_filename( self._radar, field, sweep, ext, datetime_format, use_sweep_time )
[docs] def generate_title(self, field, sweep, datetime_format=None, use_sweep_time=True): """ Generate a title for a plot. Parameters ---------- field : str Field plotted. sweep : int Sweep plotted. datetime_format : str Format of datetime (using strftime format). use_sweep_time : bool If true, the current sweep's beginning time is used. Returns ------- title : str Plot title. """ return common.generate_title( self._radar, field, sweep, datetime_format, use_sweep_time )
[docs] def generate_vpt_title(self, field): """ Generate a title for a VPT plot. Parameters ---------- field : str Field plotted. Returns ------- title : str Plot title. """ return common.generate_vpt_title(self._radar, field)
[docs] def generate_ray_title(self, field, ray): """ Generate a title for a ray plot. Parameters ---------- field : str Field plotted. ray : int Ray plotted. Returns ------- title : str Plot title. """ return common.generate_ray_title(self._radar, field, ray)
[docs] def generate_az_rhi_title(self, field, azimuth): """ Generate a title for a ray plot. Parameters ---------- field : str Field plotted. azimuth : float Azimuth plotted. Returns ------- title : str Plot title. """ return common.generate_az_rhi_title(self._radar, field, azimuth)
############### # Get methods # ############### def _get_data(self, field, sweep, mask_tuple, filter_transitions, gatefilter): """Retrieve and return data from a plot function.""" sweep_slice = self._radar.get_slice(sweep) data = self.fields[field]["data"][sweep_slice] # mask data if mask_tuple provided if mask_tuple is not None: mask_field, mask_value = mask_tuple mdata = self.fields[mask_field]["data"][sweep_slice] data = np.ma.masked_where(mdata < mask_value, data) # mask data if gatefilter provided if gatefilter is not None: mask_filter = gatefilter.gate_excluded[sweep_slice] data = np.ma.masked_array(data, mask_filter) # filter out antenna transitions if filter_transitions and self.antenna_transition is not None: in_trans = self.antenna_transition[sweep_slice] data = data[in_trans != 1] return data def _get_vpt_data(self, field, mask_tuple, filter_transitions, gatefilter): """Retrieve and return vpt data from a plot function.""" data = self.fields[field]["data"] # mask data if mask_tuple provided if mask_tuple is not None: mask_field, mask_value = mask_tuple mdata = self.fields[mask_field]["data"] data = np.ma.masked_where(mdata < mask_value, data) # mask data if gatefilter provided if gatefilter is not None: mask_filter = gatefilter.gate_excluded data = np.ma.masked_array(data, mask_filter) # filter out antenna transitions if filter_transitions and self.antenna_transition is not None: in_trans = self.antenna_transition data = data[in_trans != 1] return data.T def _get_ray_data(self, field, ray, mask_tuple, gatefilter): """Retrieve and return ray data from a plot function.""" data = self.fields[field]["data"][ray] if mask_tuple is not None: mask_field, mask_value = mask_tuple mdata = self.fields[mask_field]["data"][ray] data = np.ma.masked_where(mdata < mask_value, data) # mask data if gatefilter provided if gatefilter is not None: mask_filter = gatefilter.gate_excluded[ray] data = np.ma.masked_array(data, mask_filter) return data def _get_azimuth_rhi_data_x_y_z( self, field, target_azimuth, edges, mask_tuple, filter_transitions, gatefilter ): """Retrieve and return pseudo-RHI data from a plot function.""" # determine which rays from the ppi radar make up the pseudo RHI data = self.fields[field]["data"] if mask_tuple is not None: mask_field, mask_value = mask_tuple mdata = self.fields[mask_field]["data"] data = np.ma.masked_where(mdata < mask_value, data) # mask data if gatefilter provided if gatefilter is not None: mask_filter = gatefilter.gate_excluded data = np.ma.masked_array(data, mask_filter) # filter out antenna transitions if filter_transitions and self.antenna_transition is not None: in_trans = self.antenna_transition data = data[in_trans == 0] prhi_rays = [] for sweep_slice in self._radar.iter_slice(): sweep_azimuths = self.azimuths[sweep_slice] ray_number = np.argmin(np.abs(sweep_azimuths - target_azimuth)) prhi_rays.append(ray_number + sweep_slice.start) azimuth = self.azimuths[prhi_rays] if self.elevations[0] is None: raise ValueError( "Elevation is set to None. RHI plotting is unavailable for this " "dataset. Elevation can be set to None due to not being " "present per Halfword 30 Table V of the ICD for NEXRAD level " "3 data." ) elevation = self.elevations[prhi_rays] data = data[prhi_rays] rng = self.ranges if edges and len(prhi_rays) == 1: rng = self.ranges[:-1] x, y, z = antenna_vectors_to_cartesian(rng, azimuth, elevation, edges=edges) x = (x + self.shift[0]) / 1000.0 y = (y + self.shift[1]) / 1000.0 z = z / 1000.0 return data, x, y, z def _get_x_z(self, sweep, edges, filter_transitions): """Retrieve and return x and z coordinate in km.""" x, _, z = self._get_x_y_z(sweep, edges, filter_transitions) return x, z def _get_x_y(self, sweep, edges, filter_transitions): """Retrieve and return x and y coordinate in km.""" x, y, _ = self._get_x_y_z(sweep, edges, filter_transitions) return x, y def _get_x_y_z(self, sweep, edges, filter_transitions): """Retrieve and return x, y, and z coordinate in km.""" x, y, z = self._radar.get_gate_x_y_z( sweep, edges=edges, filter_transitions=filter_transitions ) # add shift and convert to km x = (x + self.shift[0]) / 1000.0 y = (y + self.shift[1]) / 1000.0 z = z / 1000.0 return x, y, z def _get_colorbar_label(self, field): """Return a colorbar label for a given field.""" last_field_dict = self.fields[field] if "standard_name" in last_field_dict: standard_name = last_field_dict["standard_name"] elif "long_name" in last_field_dict: standard_name = last_field_dict["long_name"] else: standard_name = field if "units" in last_field_dict: units = last_field_dict["units"] else: units = "?" return common.generate_colorbar_label(standard_name, units)
def _mask_outside(flag, data, v1, v2): """Return the data masked outside of v1 and v2 when flag is True.""" if flag: data = np.ma.masked_invalid(data) data = np.ma.masked_outside(data, v1, v2) return data def _edge_time(times): """Appends the last time with the added subtraction of the last two times in the time array.""" timedelta = times[-1] - times[-2] edge_time = times[-1] + timedelta return np.append(times, edge_time)