Source code for pyart.aux_io.metranet_reader

"""
pyart.aux_io.metranet_reader
============================

Routines for putting METRANET data files into radar object.
(Used by ELDES www.eldesradar.it)

.. autosummary::
    :toctree: generated/

    read_metranet
    read_metranet_c
    read_metranet_python

"""

import os
import datetime
import platform
from warnings import warn

import numpy as np
from scipy.stats import circmean

from ..config import FileMetadata, get_fillvalue
from ..io.common import make_time_unit_str, _test_arguments
from ..core.radar import Radar
from ..exceptions import MissingOptionalDependency

from .metranet_c import Selex_Angle, get_library
from .metranet_python import read_polar as read_polar_python
from .metranet_c import read_polar as read_polar_c

from .dn_to_float import nyquist_vel
# check existence of METRANET library
try:
    METRANET_LIB = get_library(momentms=False)
    if platform.system() == 'Linux':
        METRANET_LIB = get_library(momentms=True)
    _METRANETLIB_AVAILABLE = True
except SystemExit:
    _METRANETLIB_AVAILABLE = False

METRANET_FIELD_NAMES = {
    'WID': 'spectrum_width',
    'VEL': 'velocity',
    'ZH': 'reflectivity',
    'ZV': 'reflectivity_vv',  # non standard name
    'ZDR': 'differential_reflectivity',
    'RHO': 'uncorrected_cross_correlation_ratio',
    'PHI': 'uncorrected_differential_phase',
    'ST1': 'stat_test_lag1',  # statistical test on lag 1 (non standard name)
    'ST2': 'stat_test_lag2',  # statistical test on lag 2 (non standard name)
    'WBN': 'wide_band_noise',  # (non standard name)
    'MPH': 'mean_phase',  # (non standard name)
    'CLT': 'clutter_exit_code',  # (non standard name)
    'ZHC': 'reflectivity_hh_clut',  # cluttered horizontal reflectivity
    'ZVC': 'reflectivity_hh_clut',  # cluttered vertical reflectivity
}

PM_MOM = ["ZH", "ZV", "ZDR", "RHO", "PHI", "VEL", "WID", "ST1", "ST2", "WBN",
          "MPH"]
PH_MOM = ["ZH", "ZV", "ZDR", "RHO", "PHI", "VEL", "WID", "ST1", "ST2", "WBN",
          "MPH", "CLT"]
PL_MOM = ["ZH", "ZV", "ZDR", "RHO", "PHI", "VEL", "WID", "ZHC", "ZVC"]

NPM_MOM = 11
NPH_MOM = 12
NPL_MOM = 9


[docs]def read_metranet(filename, field_names=None, rmax=0., additional_metadata=None, file_field_names=False, exclude_fields=None, reader='C', nbytes=4, **kwargs): """ Read a METRANET file. Parameters ---------- filename : str Name of the METRANET file to read. field_names : dict, optional Dictionary mapping METRANET field names to radar field names. If a data type found in the file does not appear in this dictionary or has a value of None it will not be placed in the radar.fields dictionary. A value of None, the default, will use the mapping defined in the Py-ART configuration file. rmax : float, optional Maximum radar range to store in the radar object [m]. If 0 all data will be stored additional_metadata : dict of dicts, optional Dictionary of dictionaries to retrieve metadata during this read. This metadata is not used during any successive file reads unless explicitly included. A value of None, the default, will not introduct any addition metadata and the file specific or default metadata as specified by the Py-ART configuration file will be used. file_field_names : bool, optional True to use the MDV data type names for the field names. If this case the field_names parameter is ignored. The field dictionary will likely only have a 'data' key, unless the fields are defined in `additional_metadata`. exclude_fields : list or None, optional List of fields to exclude from the radar object. This is applied after the `file_field_names` and `field_names` parameters. reader : str The reader library to use. Can be either 'C' or 'python' nbytes : int The number of bytes used to store the data in numpy arrays, e.g. if nbytes=4 then floats are going to be stored as np.float32 Returns ------- radar : Radar Radar object containing data from METRANET file. """ # check if it is the right file bfile = os.path.basename(filename) supported_file = (bfile.startswith('PM') or bfile.startswith('PH') or bfile.startswith('PL') or bfile.startswith('MS') or bfile.startswith('MH') or bfile.startswith('ML')) if not supported_file: raise ValueError( 'Only polar data files starting by ' + 'PM, PH, PL, MS, MH or ML are supported') if reader == 'C' and _METRANETLIB_AVAILABLE: return read_metranet_c( filename, field_names, rmax, additional_metadata, file_field_names, exclude_fields, nbytes=nbytes, **kwargs) if reader == 'python': return read_metranet_python( filename, field_names, rmax, additional_metadata, file_field_names, exclude_fields, nbytes=nbytes, **kwargs) warn('Invalid reader name or C library not available,' + ' using python (default) instead') return read_metranet_python( filename, field_names, rmax, additional_metadata, file_field_names, exclude_fields, nbytes=nbytes, **kwargs)
def read_metranet_c(filename, field_names=None, rmax=0., additional_metadata=None, file_field_names=False, exclude_fields=None, nbytes=4, **kwargs): """ Read a METRANET file. Parameters ---------- filename : str Name of the METRANET file to read. field_names : dict, optional Dictionary mapping METRANET field names to radar field names. If a data type found in the file does not appear in this dictionary or has a value of None it will not be placed in the radar.fields dictionary. A value of None, the default, will use the mapping defined in the Py-ART configuration file. rmax : float, optional Maximum radar range to store in the radar object [m]. If 0 all data will be stored additional_metadata : dict of dicts, optional Dictionary of dictionaries to retrieve metadata during this read. This metadata is not used during any successive file reads unless explicitly included. A value of None, the default, will not introduct any addition metadata and the file specific or default metadata as specified by the Py-ART configuration file will be used. file_field_names : bool, optional True to use the MDV data type names for the field names. If this case the field_names parameter is ignored. The field dictionary will likely only have a 'data' key, unless the fields are defined in `additional_metadata`. exclude_fields : list or None, optional List of fields to exclude from the radar object. This is applied after the `file_field_names` and `field_names` parameters. nbytes : int The number of bytes used to store the data in numpy arrays, e.g. if nbytes=4 then floats are going to be stored as np.float32 Returns ------- radar : Radar Radar object containing data from METRANET file. """ # check that METRANET library is available if not _METRANETLIB_AVAILABLE: raise MissingOptionalDependency( "Metranet library is required to use read_metranet " + "but is not installed") if nbytes == 4: dtype = np.float32 elif nbytes == 8: dtype = np.float64 else: warn('Number of bytes to store the data ('+str(nbytes) + ') not supported. 4 bytes will be used') dtype = np.float32 # test for non empty kwargs _test_arguments(kwargs) bfile = os.path.basename(filename) # create metadata retrieval object if field_names is None: field_names = METRANET_FIELD_NAMES filemetadata = FileMetadata( 'METRANET', field_names, additional_metadata, file_field_names, exclude_fields) # get definitions from filemetadata class latitude = filemetadata('latitude') longitude = filemetadata('longitude') altitude = filemetadata('altitude') metadata = filemetadata('metadata') sweep_start_ray_index = filemetadata('sweep_start_ray_index') sweep_end_ray_index = filemetadata('sweep_end_ray_index') sweep_number = filemetadata('sweep_number') sweep_mode = filemetadata('sweep_mode') fixed_angle = filemetadata('fixed_angle') elevation = filemetadata('elevation') _range = filemetadata('range') azimuth = filemetadata('azimuth') _time = filemetadata('time') # other metadata frequency = filemetadata('frequency') beamwidth_h = filemetadata('radar_beam_width_h') beamwidth_v = filemetadata('radar_beam_width_v') pulse_width = filemetadata('pulse_width') rays_are_indexed = filemetadata('rays_are_indexed') ray_angle_res = filemetadata('ray_angle_res') nyquist_velocity = filemetadata('nyquist_velocity') npulses = filemetadata('number_of_pulses') ret = read_polar_c(filename, 'ZH', physic_value=True, masked_array=True) if ret is None: raise ValueError('Unable to read file '+filename) # total number of rays composing the sweep total_record = ret.header['row'] if total_record == 0: raise ValueError('Number of rays in file=0.') # M files returning 0 pulse width. Hardcode it for the moment # pulse_width['data'] = np.array( # [ret.header['PulseWidth']*1e-6], dtype='float64') pulse_width['data'] = 0.5e-6*np.ones(total_record, dtype=dtype) rays_are_indexed['data'] = np.array(['true']) ray_angle_res['data'] = np.array([1.], dtype=dtype) # number of gates in a ray num_gates = ret.header['column'] # sweep_number (is the sweep index) # current sweep number (from 0 to 19) sweep_number['data'] = np.array([ret.header['CurrentSweep']-1]) az_data = np.empty(total_record, dtype=dtype) el_data = np.empty(total_record, dtype=dtype) time_data = np.empty(total_record, dtype=dtype) ray_index_data = np.empty(total_record, dtype=dtype) angres = ray_angle_res['data'][0] valid_rays = np.ones((total_record)).astype(bool) ant_mode = ret.header['AntMode'] # scanning mode code if ant_mode == 0: scan_type = 'ppi' sweep_mode['data'] = np.array(['azimuth_surveillance']) # ray starting elevation angle information fixed_angle['data'] = np.array( [Selex_Angle(ret.pol_header[0].start_angle).el], dtype=dtype) # azimuth for i in range(total_record): # ray starting azimuth angle information if ret.pol_header[i].start_angle != 0: # ray starting azimuth angle information start_angle = Selex_Angle(ret.pol_header[i].start_angle).az # ray ending azimuth angle information end_angle = Selex_Angle(ret.pol_header[i].end_angle).az if end_angle > start_angle: az_data[i] = start_angle + (end_angle-start_angle)/2. else: az_data[i] = start_angle + (end_angle+360.-start_angle)/2. if az_data[i] > 360: # Can happen in spurious cases az_data[i] -= 360 else: valid_rays[i] = 0 if not np.all(valid_rays): # incomplete scan az_full_scan = np.arange(0+angres/2, 360+angres/2, angres) az_closest = angres*np.floor(az_data[valid_rays]/angres) idx_az = np.zeros((sum(valid_rays))).astype(int) for i, _ in enumerate(idx_az): idx_az[i] = np.searchsorted(az_full_scan, az_closest[i]) corr_az = az_full_scan corr_az[idx_az] = az_data[valid_rays] az_data = corr_az azimuth['data'] = az_data # elevation elevation['data'] = np.repeat(fixed_angle['data'], total_record) elif ant_mode == 1: scan_type = 'rhi' sweep_mode['data'] = np.array(['elevation_surveillance']) # ray starting azimuth angle information fixed_angle['data'] = np.array( [Selex_Angle(ret.pol_header[0].start_angle).az], dtype=dtype) # azimuth azimuth['data'] = np.repeat(fixed_angle['data'], total_record) # elevation for i in range(total_record): # ray starting elevation angle information start_angle = Selex_Angle(ret.pol_header[i].start_angle).el # ray ending elevation angle information end_angle = Selex_Angle(ret.pol_header[i].end_angle).el el_data[i] = start_angle + (end_angle-start_angle)/2. elevation['data'] = el_data elif ant_mode == 2: scan_type = 'sector' # sector scan sweep_mode['data'] = np.array(['sector']) # ray starting elevation angle information fixed_angle['data'] = np.array( [Selex_Angle(ret.pol_header[0].start_angle).el], dtype=dtype) # azimuth for i in range(total_record): # ray starting azimuth angle information start_angle = Selex_Angle(ret.pol_header[i].start_angle).az # ray ending azimuth angle information end_angle = Selex_Angle(ret.pol_header[i].end_angle).az if end_angle > start_angle: az_data[i] = start_angle + (end_angle-start_angle)/2. else: az_data[i] = start_angle + (end_angle+360.-start_angle)/2. azimuth['data'] = az_data # elevation elevation['data'] = np.repeat(fixed_angle['data'], total_record) elif ant_mode == 3: scan_type = 'other' # point of interest scan sweep_mode['data'] = np.array(['pointing']) # ray starting elevation angle information fixed_angle['data'] = np.array( [Selex_Angle(ret.pol_header[0].start_angle).el], dtype=dtype) # azimuth # ray starting elevation angle information azimuth['data'] = Selex_Angle(ret.pol_header[0].start_angle).az # elevation elevation['data'] = fixed_angle['data'] elif ant_mode == 4: scan_type = 'other' # off sweep_mode['data'] = np.array(['idle']) else: raise ValueError('Unknown scan type') if np.all(valid_rays): idx_az = np.arange(0, 360/angres).astype(int) # range (to center of beam [m]) # distance to start of first range gate [usually 0 m] start_range = float(ret.header['StartRange']) # range resolution [m] gate_width = float(ret.header['GateWidth'])*1000. _range['data'] = np.linspace( start_range+gate_width/2., float(num_gates-1.) * gate_width+gate_width/2., num_gates, dtype=dtype) if rmax > 0.: _range['data'] = _range['data'][_range['data'] < rmax] nrange = len(_range['data']) # time (according to default_config this is the Time at the center of # each ray, in fractional seconds since the volume started) # here we find the time of end of ray since the first ray in the sweep for i in range(total_record): # time when the ray was created [s from 1.1.1970]. # (last received pulse+processing time) data_time = float(ret.pol_header[i].data_time) # the hundreths of seconds to add to the data_time data_time_residue = float(ret.pol_header[i].data_time_residue) time_data[i] = data_time+data_time_residue/100. ray_index_data[i] = ret.pol_header[i].sequence sweep_start = min(time_data) start_time = datetime.datetime.utcfromtimestamp(sweep_start) _time['data'] = time_data-sweep_start _time['units'] = make_time_unit_str(start_time) # sweep_start_ray_index, sweep_end_ray_index # should be specified since start of volume but we do not have this # information so we specify it since start of sweep instead. if np.all(valid_rays): sweep_start_ray_index['data'] = np.array( [min(ray_index_data)], dtype='int32') # ray index of first ray sweep_end_ray_index['data'] = np.array( [max(ray_index_data)], dtype='int32') # ray index of last ray else: sweep_start_ray_index['data'] = np.array( [0], dtype='int32') # ray index of first ray sweep_end_ray_index['data'] = np.array( [len(ray_index_data)], dtype='int32') # ray index of last ray # ---- other information that can be obtained from metadata in file # sweep information: # total number of sweeps compositing the volume (i.e. 20): # total_sweep=ret.pol_header[0].total_sweep # total number of data bytes in the ray # (num_gates*number_of_moments*(number_of_bytes in each moment)): # data_bytes=ret.pol_header[0].data_bytes # # time period of repetition of the volume scan: # repeat_time=ret.pol_header[0].repeat_time # # Nyquist velocity [m/s]: # ny_quest=ret.pol_header[0].ny_quest # Maximum Doppler spectrum width [m/s]: # w_ny_quest=ret.pol_header[0].w_ny_quest # # # ray specific information # 0 no end of sweep, 1 end of sweep, 2 end of volume scan: # end_of_sweep=ret.pol_header[0].end_of_sweep # ------------------------------------------------------------------ # metadata # get radar id if isinstance(ret.header["RadarName"], str): radar_id = ret.header["RadarName"] else: radar_id = ret.header["RadarName"].decode('utf-8') metadata['instrument_name'] = radar_id # hardcoded radar dependent metadata latitude['data'] = np.array([ret.header['RadarLat']], dtype=dtype) longitude['data'] = np.array([ret.header['RadarLon']], dtype=dtype) altitude['data'] = np.array([ret.header['RadarHeight']], dtype=dtype) frequency['data'] = np.array([ret.header['Frequency']], dtype=dtype) beamwidth_h['data'] = np.array([1.0], dtype=dtype) beamwidth_v['data'] = np.array([1.0], dtype=dtype) # Nyquist velocity (+-nv_value) nv_value = nyquist_vel(sweep_number['data'][0]) nyquist_velocity['data'] = nv_value*np.ones(total_record, dtype=dtype) # number of pulses per ray npulses_list = [] for i in range(total_record): npulses_list.append(ret.pol_header[i].pulses) npulses['data'] = np.array(npulses_list, dtype=np.int16) # fields fields = {} # ZH field field_name = filemetadata.get_field_name('ZH') if field_name is not None: # create field dictionary field_dic = filemetadata(field_name) # incomplete scan if not np.all(valid_rays): data = np.ma.masked_all(ret.data.shape) data[idx_az, :] = ret.data[idx_az, :] else: data = ret.data field_dic['data'] = data if rmax > 0: field_dic['data'] = field_dic['data'][:, :nrange] field_dic['_FillValue'] = get_fillvalue() fields[field_name] = field_dic # rest of fields if bfile.startswith('PM') or bfile.startswith('MS'): for i in range(1, NPM_MOM): field_name = filemetadata.get_field_name(PM_MOM[i]) if field_name is not None: ret = read_polar_c( filename, PM_MOM[i], physic_value=True, masked_array=True) if ret is None: warn('Unable to read moment '+PM_MOM[i]+' in file '+filename) continue # create field dictionary field_dic = filemetadata(field_name) if not np.all(valid_rays): # incomplete scan data = np.ma.masked_all(ret.data.shape) data[idx_az, :] = ret.data[idx_az, :] else: data = ret.data field_dic['data'] = data if rmax > 0: field_dic['data'] = field_dic['data'][:, :nrange] field_dic['_FillValue'] = get_fillvalue() fields[field_name] = field_dic elif bfile.startswith('PH') or bfile.startswith('MH'): for i in range(1, NPH_MOM): field_name = filemetadata.get_field_name(PH_MOM[i]) if field_name is not None: ret = read_polar_c( filename, PH_MOM[i], physic_value=True, masked_array=True) if ret is None: warn('Unable to read moment '+PH_MOM[i]+' in file '+filename) continue # create field dictionary field_dic = filemetadata(field_name) if not np.all(valid_rays): data = np.ma.masked_all(ret.data.shape) data[idx_az, :] = ret.data[idx_az, :] else: data = ret.data field_dic['data'] = data if rmax > 0: field_dic['data'] = field_dic['data'][:, :nrange] field_dic['_FillValue'] = get_fillvalue() fields[field_name] = field_dic else: for i in range(1, NPL_MOM): field_name = filemetadata.get_field_name(PL_MOM[i]) if field_name is not None: ret = read_polar_c( filename, PL_MOM[i], physic_value=True, masked_array=True) if ret is None: warn('Unable to read moment '+PL_MOM[i]+' in file '+filename) continue # create field dictionary field_dic = filemetadata(field_name) if not np.all(valid_rays): data = np.ma.masked_all(ret.data.shape) data[idx_az, :] = ret.data[idx_az, :] else: data = ret.data field_dic['data'] = data if rmax > 0: field_dic['data'] = field_dic['data'][:, :nrange] field_dic['_FillValue'] = get_fillvalue() fields[field_name] = field_dic if not fields: raise ValueError('No valid moments found in '+filename) # instrument_parameters instrument_parameters = dict() instrument_parameters.update({'frequency': frequency}) instrument_parameters.update({'radar_beam_width_h': beamwidth_h}) instrument_parameters.update({'radar_beam_width_v': beamwidth_v}) instrument_parameters.update({'pulse_width': pulse_width}) instrument_parameters.update({'nyquist_velocity': nyquist_velocity}) instrument_parameters.update({'number_of_pulses': npulses}) return Radar(_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, rays_are_indexed=rays_are_indexed, ray_angle_res=ray_angle_res, instrument_parameters=instrument_parameters) def read_metranet_python(filename, field_names=None, rmax=0., additional_metadata=None, file_field_names=False, exclude_fields=None, nbytes=4, **kwargs): """ Read a METRANET file. Parameters ---------- filename : str Name of the METRANET file to read. field_names : dict, optional Dictionary mapping METRANET field names to radar field names. If a data type found in the file does not appear in this dictionary or has a value of None it will not be placed in the radar.fields dictionary. A value of None, the default, will use the mapping defined in the Py-ART configuration file. rmax : float, optional Maximum radar range to store in the radar object [m]. If 0 all data will be stored additional_metadata : dict of dicts, optional Dictionary of dictionaries to retrieve metadata during this read. This metadata is not used during any successive file reads unless explicitly included. A value of None, the default, will not introduct any addition metadata and the file specific or default metadata as specified by the Py-ART configuration file will be used. file_field_names : bool, optional True to use the MDV data type names for the field names. If this case the field_names parameter is ignored. The field dictionary will likely only have a 'data' key, unless the fields are defined in `additional_metadata`. exclude_fields : list or None, optional List of fields to exclude from the radar object. This is applied after the `file_field_names` and `field_names` parameters. nbytes : int The number of bytes used to store the data in numpy arrays, e.g. if nbytes=4 then floats are going to be stored as np.float32 Returns ------- radar : Radar Radar object containing data from METRANET file. """ if nbytes == 4: dtype = np.float32 elif nbytes == 8: dtype = np.float64 else: warn('Number of bytes to store the data ('+str(nbytes) + ') not supported. 4 bytes will be used') dtype = np.float32 # test for non empty kwargs _test_arguments(kwargs) bfile = os.path.basename(filename) # create metadata retrieval object if field_names is None: field_names = METRANET_FIELD_NAMES filemetadata = FileMetadata( 'METRANET', field_names, additional_metadata, file_field_names, exclude_fields) # get definitions from filemetadata class latitude = filemetadata('latitude') longitude = filemetadata('longitude') altitude = filemetadata('altitude') metadata = filemetadata('metadata') sweep_start_ray_index = filemetadata('sweep_start_ray_index') sweep_end_ray_index = filemetadata('sweep_end_ray_index') sweep_number = filemetadata('sweep_number') sweep_mode = filemetadata('sweep_mode') fixed_angle = filemetadata('fixed_angle') elevation = filemetadata('elevation') _range = filemetadata('range') azimuth = filemetadata('azimuth') _time = filemetadata('time') # other metadata frequency = filemetadata('frequency') beamwidth_h = filemetadata('radar_beam_width_h') beamwidth_v = filemetadata('radar_beam_width_v') pulse_width = filemetadata('pulse_width') rays_are_indexed = filemetadata('rays_are_indexed') ray_angle_res = filemetadata('ray_angle_res') nyquist_velocity = filemetadata('nyquist_velocity') npulses = filemetadata('number_of_pulses') ret = read_polar_python(filename, physic_value=True, masked_array=True, reorder_angles=True) if ret is None: raise ValueError('Unable to read file '+filename) # total number of rays composing the sweep total_record = ret.data['ZH'].shape[0] if total_record == 0: raise ValueError('Number of rays in file=0.') # M files returning 0 pulse width. Hardcode it for the moment # pulse_width['data'] = np.array( # [ret.header['PulseWidth']*1e-6], dtype='float64') pulse_width['data'] = 0.5e-6*np.ones(total_record, dtype=dtype) rays_are_indexed['data'] = np.array(['true']) ray_angle_res['data'] = np.array([1.], dtype=dtype) angres = ray_angle_res['data'][0] # number of gates in a ray num_gates = ret.data['ZH'].shape[1] # sweep_number (is the sweep index) # current sweep number (from 0 to 19) sweep_number['data'] = np.array([ret.header['currentsweep']-1]) time_data = np.empty(total_record, dtype=dtype) ray_index_data = np.empty(total_record, dtype=dtype) ant_mode = ret.header['antmode'] # scanning mode code no_missing_az = True # will be checked later on if ant_mode == 0: scan_type = 'ppi' sweep_mode['data'] = np.array(['azimuth_surveillance']) # ray starting elevation angle information fixed_angle['data'] = np.array([ret.pol_header[0]['startangle_el']], dtype=dtype) start_az = np.array([ray['startangle_az'] for ray in ret.pol_header]) end_az = np.array([ray['endangle_az'] for ray in ret.pol_header]) start_az = np.deg2rad(start_az) end_az = np.deg2rad(end_az) az_data = np.rad2deg( circmean(np.column_stack((start_az, end_az)), axis=1)) if len(az_data) != 360: # incomplete scan az_full_scan = np.arange(0+angres/2, 360+angres/2, angres) az_closest = angres*np.floor(az_data/angres) idx_az = np.zeros((len(az_data))).astype(int) for i, _ in enumerate(idx_az): idx_az[i] = np.searchsorted(az_full_scan, az_closest[i]) corr_az = az_full_scan corr_az[idx_az] = az_data az_data = corr_az no_missing_az = False azimuth['data'] = az_data # elevation elevation['data'] = np.repeat(fixed_angle['data'], total_record) elif ant_mode == 1: scan_type = 'rhi' sweep_mode['data'] = np.array(['elevation_surveillance']) # ray starting azimuth angle information fixed_angle['data'] = np.array([ret.pol_header[0]['startangle_az']], dtype=dtype) # azimuth azimuth['data'] = np.repeat(fixed_angle['data'], total_record) elevation['data'] = 0.5 * np.array([ ray['startangle_el']+ray['endangle_el'] for ray in ret.pol_header]) elif ant_mode == 2: scan_type = 'sector' # sector scan sweep_mode['data'] = np.array(['sector']) # ray starting elevation angle information fixed_angle['data'] = np.array([ret.pol_header[0]['startangle_el']], dtype=dtype) # azimuth start_az = np.array([ray['startangle_az'] for ray in ret.pol_header]) end_az = np.array([ray['endangle_az'] for ray in ret.pol_header]) start_az = np.deg2rad(start_az) end_az = np.deg2rad(end_az) az_data = np.rad2deg( circmean(np.column_stack((start_az, end_az)), axis=1)) if len(az_data) != 360: # incomplete scan az_full_scan = np.arange(0+angres/2, 360+angres/2, angres) az_closest = angres*np.floor(az_data/angres) idx_az = np.zeros((len(az_data))).astype(int) for i, _ in enumerate(idx_az): idx_az[i] = np.searchsorted(az_full_scan, az_closest[i]) corr_az = az_full_scan corr_az[idx_az] = az_data az_data = corr_az no_missing_az = False azimuth['data'] = az_data # elevation elevation['data'] = np.repeat(fixed_angle['data'], total_record) elif ant_mode == 3: scan_type = 'other' # point of interest scan sweep_mode['data'] = np.array(['pointing']) # ray starting elevation angle information fixed_angle['data'] = np.array([ret.pol_header[0]['startangle_el']], dtype=dtype) # azimuth # ray starting elevation angle information azimuth['data'] = np.array( [ret.pol_header[0]['startangle_az']], dtype=dtype) # elevation elevation['data'] = fixed_angle['data'] elif ant_mode == 4: scan_type = 'other' # off sweep_mode['data'] = np.array(['idle']) else: raise ValueError('Unknown scan type') # range (to center of beam [m]) # distance to start of first range gate [usually 0 m] start_range = float(ret.header['startrange']) # range resolution [m] gate_width = float(ret.header['gatewidth'])*1000. _range['data'] = np.linspace( start_range+gate_width/2., float(num_gates-1.) * gate_width+gate_width/2., num_gates, dtype=dtype) if rmax > 0.: _range['data'] = _range['data'][_range['data'] < rmax] nrange = len(_range['data']) # time (according to default_config this is the Time at the center of # each ray, in fractional seconds since the volume started) # here we find the time of end of ray since the first ray in the sweep for i in range(total_record): # time when the ray was created [s from 1.1.1970]. # (last received pulse+processing time) data_time = float(ret.pol_header[i]['datatime']) # the hundreths of seconds to add to the data_time data_time_residue = float(ret.pol_header[i]['datatime_residue']) time_data[i] = data_time+data_time_residue/100. ray_index_data = range(total_record) sweep_start = min(time_data) start_time = datetime.datetime.utcfromtimestamp(sweep_start) _time['data'] = time_data-sweep_start _time['units'] = make_time_unit_str(start_time) # sweep_start_ray_index, sweep_end_ray_index # should be specified since start of volume but we do not have this # information so we specify it since start of sweep instead. if no_missing_az: sweep_start_ray_index['data'] = np.array( [min(ray_index_data)], dtype='int32') # ray index of first ray sweep_end_ray_index['data'] = np.array( [max(ray_index_data)], dtype='int32') # ray index of last ray else: sweep_start_ray_index['data'] = np.array( [0], dtype='int32') # ray index of first ray sweep_end_ray_index['data'] = np.array( [360], dtype='int32') # ray index of last ray # ---- other information that can be obtained from metadata in file # sweep information: # total number of sweeps compositing the volume (i.e. 20): # total_sweep=ret.pol_header[0].total_sweep # total number of data bytes in the ray # (num_gates*number_of_moments*(number_of_bytes in each moment)): # data_bytes=ret.pol_header[0].data_bytes # # time period of repetition of the volume scan: # repeat_time=ret.pol_header[0].repeat_time # # Nyquist velocity [m/s]: # ny_quest=ret.pol_header[0].ny_quest # Maximum Doppler spectrum width [m/s]: # w_ny_quest=ret.pol_header[0].w_ny_quest # # # ray specific information # 0 no end of sweep, 1 end of sweep, 2 end of volume scan: # end_of_sweep=ret.pol_header[0].end_of_sweep # number of pulses used in data integration: # pulses=ret.pol_header[0].pulses # ------------------------------------------------------------------ # metadata # get radar id if isinstance(ret.header["radarname"], str): radar_id = ret.header["radarname"] else: radar_id = ret.header["radarname"].decode('utf-8') metadata['instrument_name'] = radar_id # hardcoded radar dependent metadata latitude['data'] = np.array([ret.header['radarlat']], dtype=dtype) longitude['data'] = np.array([ret.header['radarlon']], dtype=dtype) altitude['data'] = np.array([ret.header['radarheight']], dtype=dtype) frequency['data'] = np.array([ret.header['frequency']], dtype=dtype) beamwidth_h['data'] = np.array([1.0], dtype=dtype) beamwidth_v['data'] = np.array([1.0], dtype=dtype) # Nyquist velocity (+-nv_value) nv_value = nyquist_vel(sweep_number['data'][0]) nyquist_velocity['data'] = nv_value*np.ones(total_record, dtype=dtype) # number of pulses per ray if 'pulses' in ret.pol_header[0]: pulse_name = 'pulses' else: pulse_name = 'numpulses' npulses_list = [] for i in range(total_record): npulses_list.append(ret.pol_header[i][pulse_name]) npulses['data'] = np.array(npulses_list, dtype=np.int16) # fields fields = {} # rest of fields if bfile.startswith('MS'): nmoments = NPM_MOM momnames = PM_MOM elif bfile.startswith('MH'): nmoments = NPH_MOM momnames = PH_MOM else: nmoments = NPL_MOM momnames = PL_MOM for i in range(0, nmoments): field_name = filemetadata.get_field_name(momnames[i]) if field_name is not None: # create field dictionary field_dic = filemetadata(field_name) if momnames[i] not in ret.data: warn('Moment '+momnames[i]+' not in file') continue data = ret.data[momnames[i]] # Check if scan is complete if len(data) != len(azimuth['data']): tmp = np.ma.masked_all((len(azimuth['data']), data.shape[1])) tmp[idx_az, :] = data data = tmp field_dic['data'] = data if rmax > 0: field_dic['data'] = field_dic['data'][:, :nrange] field_dic['_FillValue'] = get_fillvalue() fields[field_name] = field_dic if not fields: raise ValueError('No valid moments found in '+filename) # instrument_parameters instrument_parameters = dict() instrument_parameters.update({'frequency': frequency}) instrument_parameters.update({'radar_beam_width_h': beamwidth_h}) instrument_parameters.update({'radar_beam_width_v': beamwidth_v}) instrument_parameters.update({'pulse_width': pulse_width}) instrument_parameters.update({'nyquist_velocity': nyquist_velocity}) instrument_parameters.update({'number_of_pulses': npulses}) return Radar(_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, rays_are_indexed=rays_are_indexed, ray_angle_res=ray_angle_res, instrument_parameters=instrument_parameters)