Source code for pyart.retrieve.wind

"""
pyart.retrieve.wind
=========================================

Functions for wind estimation

.. autosummary::
    :toctree: generated/

    est_wind_vel
    est_vertical_windshear
    est_wind_profile
    _wind_coeff
    _vad
    _vel_variance

"""

from copy import deepcopy

import numpy as np

from ..config import get_metadata, get_field_name


[docs]def est_wind_vel(radar, vert_proj=False, vel_field=None, wind_field=None): """ Estimates wind velocity. Projects the radial wind component to the horizontal or vertical of the azimuth plane. It assumes that the orthogonal component is negligible. The horizontal wind component is given by: v = v_r*cos(el)-v_el*sin(el)+v_az where: v_r is the radial wind component (measured by the radar) v_el is the perpendicular wind component in the azimuth plane. v_az is the horizontal component perpendicular to the radial direction and the azimuth plane el is the elevation The horizontal wind component in the azimuth plane is given by: v_h = v_r*cos(el) - v_el*sin(el) which since we do not know v_el we assume: v_h ~ v_r*cos(el) This assumption holds for small elevation angles The vertical wind component in the azimuth plane is given by: v_h = v_r*sin(el) - v_el*cos(el) which since we do not know v_el we assume: v_h ~ v_r*sin(el) This assumption holds for angles close to 90 deg Parameters ---------- radar : Radar Radar object vert_proj : Boolean If true estimates the vertical projection, otherwise the horizontal vel_field : str name of the velocity field wind_field : str name of the velocity field Returns ------- wind : dict Field dictionary containing the estimated wind velocity """ # parse the field parameters if vel_field is None: vel_field = get_field_name('velocity') if wind_field is None: wind_field = get_field_name('azimuthal_horizontal_wind_component') if vert_proj: wind_field = get_field_name('vertical_wind_component') radar.check_field_exists(vel_field) vel = radar.fields[vel_field]['data'] ele = deepcopy(radar.elevation['data']) ele[ele > 90.] = 180.-ele[ele > 90.] ele = np.broadcast_to(ele.reshape(radar.nrays, 1), (radar.nrays, radar.ngates)) if vert_proj: wind_data = vel * np.sin(ele*np.pi/180.) else: wind_data = vel * np.cos(ele*np.pi/180.) wind = get_metadata(wind_field) wind['data'] = wind_data return wind
[docs]def est_vertical_windshear(radar, az_tol=0.5, wind_field=None, windshear_field=None): """ Estimates wind shear. Parameters ---------- radar : Radar Radar object az_tol : float azimuth tolerance to consider gate on top of selected one wind_field : str name of the horizontal wind velocity field windshear_field : str name of the vertical wind shear field Returns ------- windshear : dict Field dictionary containing the wind shear field """ # parse the field parameters if wind_field is None: wind_field = get_field_name('azimuthal_horizontal_wind_component') if windshear_field is None: windshear_field = get_field_name('vertical_wind_shear') radar.check_field_exists(wind_field) wind = radar.fields[wind_field]['data'] # compute ground range ele = deepcopy(radar.elevation['data']) ele[ele > 90.] = 180.-ele[ele > 90.] ele = np.broadcast_to(ele.reshape(radar.nrays, 1), (radar.nrays, radar.ngates)) rng_mat = np.broadcast_to(radar.range['data'].reshape(1, radar.ngates), (radar.nrays, radar.ngates)) rng_ground = rng_mat*np.cos(ele*np.pi/180.) # initialize output windshear_data = np.ma.empty((radar.nrays, radar.ngates), dtype=float) windshear_data[:] = np.ma.masked azi_vec = radar.azimuth['data'] ele_vec = radar.elevation['data'] alt_mat = radar.gate_altitude['data'] mask = np.ma.getmaskarray(wind) for ray in range(radar.nrays): # look for the elevation on top of the current ray ind_rays_top = np.where( np.logical_and( ele_vec > ele_vec[ray], np.abs(azi_vec-azi_vec[ray]) < az_tol))[0] if ind_rays_top.size == 0: continue ind_rays_top = np.where(ele_vec == np.min(ele_vec[ind_rays_top]))[0] ele_nearest = ele_vec[ind_rays_top[0]] azi_top = azi_vec[ind_rays_top] azi_nearest = azi_top[np.argmin(np.abs(azi_top-azi_vec[ray]))] ind_ray = np.where( np.logical_and( ele_vec == ele_nearest, azi_vec == azi_nearest))[0][0] for rng in range(radar.ngates): if mask[ray, rng]: continue # look for the nearest gate on top of selected gate ind_rng = np.argmin( np.abs(rng_ground[ind_ray, :]-rng_ground[ray, rng])) if mask[ind_ray, ind_rng]: continue # interpolate the two closest gates on top of the one # examined if rng_ground[ind_ray, ind_rng] < rng_ground[ray, rng]: if ind_rng+1 >= radar.ngates: continue if mask[ind_ray, ind_rng+1]: continue rng_ground_near = rng_ground[ind_ray, ind_rng:ind_rng+2] wind_near = wind[ind_ray, ind_rng:ind_rng+2] alt_near = alt_mat[ind_ray, ind_rng:ind_rng+2] else: if ind_rng-1 < 0: continue if mask[ind_ray, ind_rng-1]: continue rng_ground_near = rng_ground[ind_ray, ind_rng-1:ind_rng+1] wind_near = wind[ind_ray, ind_rng-1:ind_rng+1] alt_near = alt_mat[ind_ray, ind_rng-1:ind_rng+1] wind_top = np.interp( rng_ground[ray, rng], rng_ground_near, wind_near) gate_altitude_top = np.interp( rng_ground[ray, rng], rng_ground_near, alt_near) # compute wind shear windshear_data[ray, rng] = ( 1000.*(wind_top-wind[ray, rng]) / (gate_altitude_top-alt_mat[ray, rng])) windshear = get_metadata(windshear_field) windshear['data'] = windshear_data return windshear
[docs]def est_wind_profile(radar, npoints_min=6, azi_spacing_max=45., vel_diff_max=10., sign=1, rad_vel_field=None, u_vel_field=None, v_vel_field=None, w_vel_field=None, vel_est_field=None, vel_std_field=None, vel_diff_field=None): """ Estimates the vertical wind profile using VAD techniques Parameters ---------- radar : Radar Radar object npoints_min : int Minimum number of points in the VAD to retrieve wind components. 0 will retrieve them regardless azi_spacing_max : float Maximum spacing between valid gates in the VAD to retrieve wind components. 0 will retrieve them regardless. vel_diff_max : float Maximum velocity difference allowed between retrieved and measured radial velocity at each range gate. Gates exceeding this threshold will be removed and VAD will be recomputed. If -1 there will not be a second pass. sign : int, optional Sign convention which the radial velocities in the volume created from the sounding data will will. This should match the convention used in the radar data. A value of 1 represents when positive values velocities are towards the radar, -1 represents when negative velocities are towards the radar. rad_vel_field : str name of the measured radial velocity field u_vel_field, v_vel_field, w_vel_field : str names of the 3 wind components fields vel_est_field : str name of the retrieved radial Doppler velocity field vel_std_field : str name of the standard deviation of the velocity retrieval field vel_diff_field : str name of the diference between retrieved and measured radial velocity field Returns ------- wind : dict Field dictionary containing the estimated wind velocity """ # parse the field parameters if rad_vel_field is None: rad_vel_field = get_field_name('velocity') if u_vel_field is None: u_vel_field = get_field_name('eastward_wind_component') if v_vel_field is None: v_vel_field = get_field_name('northward_wind_component') if w_vel_field is None: w_vel_field = get_field_name('vertical_wind_component') if vel_est_field is None: vel_est_field = get_field_name('retrieved_velocity') if vel_std_field is None: vel_std_field = get_field_name('retrieved_velocity_std') if vel_diff_field is None: vel_diff_field = get_field_name('velocity_difference') radar.check_field_exists(rad_vel_field) vel = deepcopy(radar.fields[rad_vel_field]['data']) # Compute wind coefficients u_coeff, v_coeff, w_coeff = _wind_coeff(radar) # first guess VAD u_vel, v_vel, w_vel, vel_est = _vad( radar, u_coeff, v_coeff, w_coeff, vel, npoints_min=npoints_min, azi_spacing_max=azi_spacing_max, sign=sign) # Remove gates where velocity difference exceeds threshold and recompute # VAD if applicable if vel_diff_max > -1: vel = np.ma.masked_where(np.ma.abs(vel-vel_est) > vel_diff_max, vel) # Final VAD u_vel, v_vel, w_vel, vel_est = _vad( radar, u_coeff, v_coeff, w_coeff, vel, npoints_min=npoints_min, azi_spacing_max=azi_spacing_max) # Compute velocity estimation variance vel_std, vel_diff = _vel_std(radar, vel, vel_est) # prepare output u_vel_dict = get_metadata(u_vel_field) u_vel_dict['data'] = u_vel v_vel_dict = get_metadata(v_vel_field) v_vel_dict['data'] = v_vel w_vel_dict = get_metadata(w_vel_field) w_vel_dict['data'] = w_vel vel_est_dict = get_metadata(vel_est_field) vel_est_dict['data'] = vel_est vel_std_dict = get_metadata(vel_std_field) vel_std_dict['data'] = vel_std vel_diff_dict = get_metadata(vel_diff_field) vel_diff_dict['data'] = vel_diff return (u_vel_dict, v_vel_dict, w_vel_dict, vel_est_dict, vel_std_dict, vel_diff_dict)
def _wind_coeff(radar): """ Computes the coefficients to transform 3-D wind vectors into radial velocity at each range gate Parameters ---------- radar : Radar Radar object Returns ------- u_coeff, v_coeff, w_coeff : 2D float arrays The coefficients for each wind component """ cos_ele = np.cos(radar.elevation['data']*np.pi/180.) sin_ele = np.sin(radar.elevation['data']*np.pi/180.) cos_azi = np.cos(radar.azimuth['data']*np.pi/180.) sin_azi = np.sin(radar.azimuth['data']*np.pi/180.) u_coeff = sin_azi*cos_ele v_coeff = cos_azi*cos_ele w_coeff = sin_ele u_coeff = np.reshape( np.tile(u_coeff, radar.ngates), (radar.ngates, radar.nrays)).T v_coeff = np.reshape( np.tile(v_coeff, radar.ngates), (radar.ngates, radar.nrays)).T w_coeff = np.reshape( np.tile(w_coeff, radar.ngates), (radar.ngates, radar.nrays)).T return u_coeff, v_coeff, w_coeff def _vad(radar, u_coeff, v_coeff, w_coeff, vel, npoints_min=6, azi_spacing_max=45., sign=1): """ Estimates wind components using VAD techniques Parameters ---------- radar : Radar Radar object u_coeff, v_coeff, w_coeff : 2D float arrays the coefficients to transform 3D winds into radial velocity vel : 2D float array The measured radial velocity field npoints_min : int Minimum number of points in the VAD to retrieve wind components. 0 will retrieve them regardless azi_spacing_max : float Maximum spacing between valid gates in the VAD to retrieve wind components. 0 will retrieve them regardless. sign : int, optional Sign convention which the radial velocities in the volume created from the sounding data will will. This should match the convention used in the radar data. A value of 1 represents when positive values velocities are towards the radar, -1 represents when negative velocities are towards the radar. Returns ------- u_vel, v_vel, w_vel : 2D float arrays The 3 estimated wind components at each range gate vel_est : 2D float array The estimated radial velocity at each range gate """ vel_aux = deepcopy(vel) if sign == 1: vel_aux = -vel_aux # prepare wind matrices u_vel = np.ma.empty((radar.nrays, radar.ngates), dtype=float) u_vel[:] = np.ma.masked v_vel = np.ma.empty((radar.nrays, radar.ngates), dtype=float) v_vel[:] = np.ma.masked w_vel = np.ma.empty((radar.nrays, radar.ngates), dtype=float) w_vel[:] = np.ma.masked # first guess VAD for ind_sweep in range(radar.nsweeps): ind_start = radar.sweep_start_ray_index['data'][ind_sweep] ind_end = radar.sweep_end_ray_index['data'][ind_sweep] for ind_rng in range(radar.ngates): vel_azi = vel_aux[ind_start:ind_end+1, ind_rng] # check minimum number of valid points if npoints_min > 0: if vel_azi.compressed().size < npoints_min: continue # get position of valid gates is_valid_azi = np.logical_not(np.ma.getmaskarray(vel_azi)) # check maximum allowed gap between data if azi_spacing_max > 0.: valid_azi = np.sort( radar.azimuth['data'][ind_start:ind_end+1][is_valid_azi]) delta_azi_max = np.max(np.append( valid_azi[1:]-valid_azi[:-1], valid_azi[0]-(valid_azi[-1]-360))) if delta_azi_max > azi_spacing_max: continue # get wind coefficients for this azimuth u_coeff_aux = u_coeff[ind_start:ind_end+1, ind_rng][is_valid_azi] v_coeff_aux = v_coeff[ind_start:ind_end+1, ind_rng][is_valid_azi] w_coeff_aux = w_coeff[ind_start:ind_end+1, ind_rng][is_valid_azi] coeff_arr = np.array([u_coeff_aux, v_coeff_aux.T, w_coeff_aux]).T # retrieve velocity using least square method vel_ret, _, _, _ = np.linalg.lstsq( coeff_arr, vel_azi.compressed()) u_vel[ind_start:ind_end+1, ind_rng] = vel_ret[0] v_vel[ind_start:ind_end+1, ind_rng] = vel_ret[1] w_vel[ind_start:ind_end+1, ind_rng] = vel_ret[2] # Compute estimated radial velocity vel_est = u_vel*u_coeff+v_vel*v_coeff+w_vel*w_coeff if sign == 1: vel_est = -vel_est return u_vel, v_vel, w_vel, vel_est def _vel_std(radar, vel, vel_est): """ Computes the variance of the retrieved wind velocity Parameters ---------- radar : Radar Radar object vel : 2D float array The measured radial velocity field vel_est : 2D float array The estimated radial velocity field Returns ------- vel_std : 2D float arrays The estimated standard deviation at each range gate (one for VAD) vel_diff : 2D float array The actual velocity difference between estimated and measured radial velocities """ vel_diff = vel-vel_est vel_std = np.ma.empty((radar.nrays, radar.ngates), dtype=float) for ind_sweep in range(radar.nsweeps): ind_start = radar.sweep_start_ray_index['data'][ind_sweep] ind_end = radar.sweep_end_ray_index['data'][ind_sweep] for ind_rng in range(radar.ngates): vel_diff_aux = vel_diff[ind_start:ind_end+1, ind_rng] nvalid = vel_diff_aux.compressed().size vel_std[ind_start:ind_end+1, ind_rng] = np.ma.sqrt( np.ma.sum(np.ma.power(vel_diff_aux, 2.))/(nvalid-3)) return vel_std, vel_diff