Source code for pyart.core.radar_spectra

"""
A general central radial scanning (or dwelling) instrument class.

"""

import copy
import sys
import warnings

import numpy as np

try:
    import xarray as xr
    _XARRAY_AVAILABLE = True
except ImportError:
    _XARRAY_AVAILABLE = False

from ..config import get_metadata
from ..exceptions import MissingOptionalDependency
from .transforms import antenna_vectors_to_cartesian, cartesian_to_geographic


[docs]class RadarSpectra(object): """ A class for storing antenna coordinate radar spectra data. The structure of the Radar class is based on the CF/Radial Data file format. Global attributes and variables (section 4.1 and 4.3) are represented as a dictionary in the metadata attribute. Other required and optional variables are represented as dictionaries in a attribute with the same name as the variable in the CF/Radial standard. When a optional attribute not present the attribute has a value of None. The data for a given variable is stored in the dictionary under the 'data' key. Moment field data is stored as a dictionary of dictionaries in the fields attribute. Sub-convention variables are stored as a dictionary of dictionaries under the meta_group attribute. Refer to the attribute section for information on the parameters. Attributes ---------- time : dict Time at the center of each ray. range : dict Range to the center of each gate (bin). fields : dict of dicts Spectra fields. metadata : dict Metadata describing the instrument and data. scan_type : str Type of scan, one of 'ppi', 'rhi', 'sector' or 'other'. If the scan volume contains multiple sweep modes this should be 'other'. latitude : dict Latitude of the instrument. longitude : dict Longitude of the instrument. altitude : dict Altitude of the instrument, above sea level. altitude_agl : dict or None Altitude of the instrument above ground level. If not provided this attribute is set to None, indicating this parameter not available. sweep_number : dict The number of the sweep in the volume scan, 0-based. sweep_mode : dict Sweep mode for each mode in the volume scan. fixed_angle : dict Target angle for thr sweep. Azimuth angle in RHI modes, elevation angle in all other modes. sweep_start_ray_index : dict Index of the first ray in each sweep relative to the start of the volume, 0-based. sweep_end_ray_index : dict Index of the last ray in each sweep relative to the start of the volume, 0-based. rays_per_sweep : LazyLoadDict Number of rays in each sweep. The data key of this attribute is create upon first access from the data in the sweep_start_ray_index and sweep_end_ray_index attributes. If the sweep locations needs to be modified, do this prior to accessing this attribute or use :py:func:`init_rays_per_sweep` to reset the attribute. target_scan_rate : dict or None Intended scan rate for each sweep. If not provided this attribute is set to None, indicating this parameter is not available. rays_are_indexed : dict or None Indication of whether ray angles are indexed to a regular grid in each sweep. If not provided this attribute is set to None, indicating ray angle spacing is not determined. ray_angle_res : dict or None If rays_are_indexed is not None, this provides the angular resolution of the grid. If not provided or available this attribute is set to None. azimuth : dict Azimuth of antenna, relative to true North. Azimuth angles are recommended to be expressed in the range of [0, 360], but other representations are not forbidden. elevation : dict Elevation of antenna, relative to the horizontal plane. Elevation angles are recommended to be expressed in the range of [-180, 180], but other representations are not forbidden. gate_x, gate_y, gate_z : LazyLoadDict Location of each gate in a Cartesian coordinate system assuming a standard atmosphere with a 4/3 Earth's radius model. The data keys of these attributes are create upon first access from the data in the range, azimuth and elevation attributes. If these attributes are changed use :py:func:`init_gate_x_y_z` to reset. gate_longitude, gate_latitude : LazyLoadDict Geographic location of each gate. The projection parameter(s) defined in the `projection` attribute are used to perform an inverse map projection from the Cartesian gate locations relative to the radar location to longitudes and latitudes. If these attributes are changed use :py:func:`init_gate_longitude_latitude` to reset the attributes. projection : dic or str Projection parameters defining the map projection used to transform from Cartesian to geographic coordinates. The default dictionary sets the 'proj' key to 'pyart_aeqd' indicating that the native Py-ART azimuthal equidistant projection is used. This can be modified to specify a valid pyproj.Proj projparams dictionary or string. The special key '_include_lon_0_lat_0' is removed when interpreting this dictionary. If this key is present and set to True, which is required when proj='pyart_aeqd', then the radar longitude and latitude will be added to the dictionary as 'lon_0' and 'lat_0'. gate_altitude : LazyLoadDict The altitude of each radar gate as calculated from the altitude of the radar and the Cartesian z location of each gate. If this attribute is changed use :py:func:`init_gate_altitude` to reset the attribute. scan_rate : dict or None Actual antenna scan rate. If not provided this attribute is set to None, indicating this parameter is not available. antenna_transition : dict or None Flag indicating if the antenna is in transition, 1 = yes, 0 = no. If not provided this attribute is set to None, indicating this parameter is not available. rotation : dict or None The rotation angle of the antenna. The angle about the aircraft longitudinal axis for a vertically scanning radar. tilt : dict or None The tilt angle with respect to the plane orthogonal (Z-axis) to aircraft longitudinal axis. roll : dict or None The roll angle of platform, for aircraft right wing down is positive. drift : dict or None Drift angle of antenna, the angle between heading and track. heading : dict or None Heading (compass) angle, clockwise from north. pitch : dict or None Pitch angle of antenna, for aircraft nose up is positive. georefs_applied : dict or None Indicates whether the variables have had georeference calculation applied. Leading to Earth-centric azimuth and elevation angles. instrument_parameters : dict of dicts or None Instrument parameters, if not provided this attribute is set to None, indicating these parameters are not avaiable. This dictionary also includes variables in the radar_parameters CF/Radial subconvention. radar_calibration : dict of dicts or None Instrument calibration parameters. If not provided this attribute is set to None, indicating these parameters are not available ngates : int Number of gates (bins) in a ray. nrays : int Number of rays in the volume. nsweeps : int Number of sweep in the volume. """ def __init__(self, time, _range, fields, metadata, scan_type, latitude, longitude, altitude, sweep_number, sweep_mode, fixed_angle, sweep_start_ray_index, sweep_end_ray_index, azimuth, elevation, npulses_max, velocity_bins, altitude_agl=None, target_scan_rate=None, rays_are_indexed=None, ray_angle_res=None, scan_rate=None, antenna_transition=None, instrument_parameters=None, radar_calibration=None, georefs_applied=None ): warnings.warn("Radar Spectra object is in early development, " "errors may arise, use at your own risk! ") if not _XARRAY_AVAILABLE: raise MissingOptionalDependency( "Xarray is required to use RadarSpectra but is " "not installed!") self.field_names = ['spectra'] self.ds = xr.Dataset( data_vars={ 'spectra': (('time', 'range', 'npulses_max'), fields), 'velocity_bins': velocity_bins, 'scan_type': scan_type, 'latitude': latitude, 'longitude': longitude, 'altitude': altitude, 'sweep_number': sweep_number, 'sweep_mode': sweep_mode, 'fixed_angle': fixed_angle, 'sweep_start_ray_index': sweep_start_ray_index, 'sweep_end_ray_index': sweep_end_ray_index, 'azimuth': azimuth, 'elevation': elevation}, coords={'time': time, 'range': _range, 'npulses_max': npulses_max}, attrs=metadata) self.ds['ngates'] = len(_range.values) self.ds['nrays'] = len(time.values) self.ds['nsweeps'] = len(sweep_number.values) self.ds.attrs['projection'] = {'proj': 'pyart_aeqd', '_include_lon_0_lat_0': True} # initalize attributes with lazy load dictionaries self.init_rays_per_sweep() self.init_gate_x_y_z() self.init_gate_longitude_latitude() self.init_gate_altitude() @property def fields(self): field_dict = {} for key in self.field_names: if key in self.ds.variables.keys(): field_dict[key] = self.ds[key] return xr.Dataset(field_dict) @property def time(self): return self.ds.time @property def range(self): return self.ds.range @property def npulses_max(self): return self.ds.npulses_max @property def velocity_bins(self): return self.ds.velocity_bins @property def latitude(self): return self.ds.latitude @property def longitude(self): return self.ds.longitude @property def altitude(self): return self.ds.altitude @property def fixed_angle(self): return self.ds.fixed_angle @property def sweep_mode(self): return self.ds.sweep_mode @property def sweep_number(self): return self.ds.sweep_number @property def scan_type(self): return self.ds.scan_type @property def elevation(self): return self.ds.elevation @property def azimuth(self): return self.ds.azimuth @property def sweep_start_ray_index(self): return self.ds.sweep_start_ray_index @property def sweep_end_ray_index(self): return self.ds.sweep_end_ray_index @property def rays_per_sweep(self): return self.ds.rays_per_sweep @property def gate_x(self): return self.ds.gate_x @property def gate_y(self): return self.ds.gate_y @property def gate_z(self): return self.ds.gate_z @property def gate_latitude(self): return self.ds.gate_latitude @property def gate_longitude(self): return self.ds.gate_longitude @property def gate_altitude(self): return self.ds.gate_altitude @property def ngates(self): return self.ds.ngates @property def nrays(self): return self.ds.nrays @property def nsweeps(self): return self.ds.nsweeps @property def projection(self): return self.ds.attrs['projection']
[docs] def init_rays_per_sweep(self): """ Initialize or reset the rays_per_sweep attribute. """ _rays_per_sweep_data_factory(self.ds)
[docs] def init_gate_x_y_z(self): """ Initialize or reset the gate_{x, y, z} attributes. """ _gate_data_factory(self.ds)
[docs] def init_gate_longitude_latitude(self): """ Initialize or reset the gate_longitude and gate_latitude attributes. """ _gate_lon_lat_data_factory(self.ds)
[docs] def init_gate_altitude(self): """ Initialize the gate_altitude attribute. """ _gate_altitude_data_factory(self.ds)
def _check_sweep_in_range(self, sweep): """ Check that a sweep number is in range. """ if sweep < 0 or sweep >= self.nsweeps: raise IndexError('Sweep out of range: ', sweep) # get methods
[docs] def get_start(self, sweep): """ Return the starting ray index for a given sweep. """ self._check_sweep_in_range(sweep) return self.sweep_start_ray_index.values[sweep]
[docs] def get_end(self, sweep): """ Return the ending ray for a given sweep. """ self._check_sweep_in_range(sweep) return self.sweep_end_ray_index.values[sweep]
[docs] def get_start_end(self, sweep): """ Return the starting and ending ray for a given sweep. """ return self.get_start(sweep), self.get_end(sweep)
[docs] def get_slice(self, sweep): """ Return a slice for selecting rays for a given sweep. """ start, end = self.get_start_end(sweep) return slice(start, end+1)
[docs] def check_field_exists(self, field_name): """ Check that a field exists in the fields dictionary. If the field does not exist raise a KeyError. Parameters ---------- field_name : str Name of field to check. """ if field_name not in self.fields.keys(): raise KeyError('Field not available: ' + field_name)
# Iterators
[docs] def iter_start(self): """ Return an iterator over the sweep start indices. """ return (s for s in self.sweep_start_ray_index.values)
[docs] def iter_end(self): """ Return an iterator over the sweep end indices. """ return (s for s in self.sweep_end_ray_index.values)
[docs] def iter_start_end(self): """ Return an iterator over the sweep start and end indices. """ return ((s, e) for s, e in zip(self.iter_start(), self.iter_end()))
[docs] def iter_slice(self): """ Return an iterator which returns sweep slice objects. """ return (slice(s, e+1) for s, e in self.iter_start_end())
[docs] def iter_field(self, field_name): """ Return an iterator which returns sweep field data. """ self.check_field_exists(field_name) return (self.fields[field_name].values[s] for s in self.iter_slice())
[docs] def iter_azimuth(self): """ Return an iterator which returns sweep azimuth data. """ return (self.azimuth.values[s] for s in self.iter_slice())
[docs] def iter_elevation(self): """ Return an iterator which returns sweep elevation data. """ return (self.elevation.values[s] for s in self.iter_slice())
[docs] def to_vpt(self): """ Returns a simple Radar object in VPT scan type with spectra moments such as reflectivity and mean velocity. """ from ..testing import make_empty_ppi_radar from ..retrieve import spectra_moments from ..util import to_vpt rng_len = len(self.range.values) time_len = len(self.time.values) vpt_radar = make_empty_ppi_radar( ngates=rng_len, rays_per_sweep=time_len, nsweeps=1) fields = spectra_moments(self) rng_dict = get_metadata('range') rng_dict['data'] = self.range.values time_dict = get_metadata('time') time_dict['data'] = self.time.values vpt_radar.range = rng_dict vpt_radar.time = time_dict vpt_radar.fields = fields vpt_radar.metadata['instrument_name'] = 'KAZR' to_vpt(vpt_radar) return vpt_radar
def _rays_per_sweep_data_factory(radar): """ Return a function which returns the number of rays per sweep. """ rays_per_sweep_dict = get_metadata('rays_per_sweep') rays_per_sweep = ( radar.sweep_end_ray_index.values - radar.sweep_start_ray_index.values + 1) radar['rays_per_sweep'] = xr.DataArray(np.array(rays_per_sweep), attrs=rays_per_sweep_dict) def _gate_data_factory(radar): """ Return a function which returns the Cartesian locations of gates. """ ranges = radar.range.values azimuths = radar.azimuth.values elevations = radar.elevation.values cartesian_coords = antenna_vectors_to_cartesian( ranges, azimuths, elevations, edges=False) # load x, y, and z data except for the coordinate in question gate_x_dict = get_metadata('gate_x') radar['gate_x'] = xr.DataArray(cartesian_coords[0], dims=('time', 'range'), attrs=gate_x_dict) gate_y_dict = get_metadata('gate_y') radar['gate_y'] = xr.DataArray(cartesian_coords[1], dims=('time', 'range'), attrs=gate_y_dict) gate_z_dict = get_metadata('gate_z') radar['gate_z'] = xr.DataArray(cartesian_coords[2], dims=('time', 'range'), attrs=gate_z_dict) def _gate_lon_lat_data_factory(radar): """ Return a function which returns the geographic locations of gates. """ x = radar.gate_x.values y = radar.gate_y.values projparams = radar.projection.copy() if projparams.pop('_include_lon_0_lat_0', False): projparams['lon_0'] = radar.longitude.values projparams['lat_0'] = radar.latitude.values geographic_coords = cartesian_to_geographic(x, y, projparams) # set the other geographic coordinate gate_latitude_dict = get_metadata('gate_latitude') radar['gate_latitude'] = xr.DataArray(geographic_coords[1], dims=('time', 'range'), attrs=gate_latitude_dict) gate_longitude_dict = get_metadata('gate_longitude') radar['gate_longitude'] = xr.DataArray(geographic_coords[0], dims=('time', 'range'), attrs=gate_longitude_dict) def _gate_altitude_data_factory(radar): """ Return a function which returns the gate altitudes. """ try: alt = radar.altitude.values + radar.gate_z.values except ValueError: alt = np.mean(radar.altitude.values) + radar.gate_z.values gate_altitude_dict = get_metadata('gate_altitude') radar['gate_altitude'] = xr.DataArray(alt, dims=('time', 'range'), attrs=gate_altitude_dict)