Users Guide to RSL.

What is RSL good for?

The best feature of RSL is the ability to ingest many different RADAR data file formats with a single library call. It can, also, read compressed files -- compressed with GZIP or the older COMPRESS. The routine is called RSL_anyformat_to_radar. You give it a filename and it will return a pointer to a C structure called Radar. The structure Radar contains all the information found in the input file. The structure is intended to represent a superset of all RADAR data formats.

Below, is a table listing the input/output routines supplied in RSL. You will notice that there are only two output routines. RSL, by design, is not a format converter, but, a library to facilitate reading and manipulation of RADAR data. Output for UF and HDF are supplied because of the popularity of those two formats.
 

Data format

Input routine

Output routine

HDF 1B-51 and 1C-51 RSL_hdf_to_radar RSL_radar_to_hdf
Lassen (Darwin) RSL_lassen_to_radar None
WSR-88d (Nexrad) RSL_wsr88d_to_radar None
UF (Universal Format from NCAR) RSL_uf_to_radar RSL_radar_to_uf
SIGMET (Version 1) RSL_nsig_to_radar None
SIGMET (Version 2) RSL_nsig2_to_radar None
McGill  RSL_mcgill_to_radar None
TOGA  RSL_toga_to_radar None
RAPIC (Berrimah)  RSL_rapic_to_radar None
RADTEC (SPANDAR) RSL_radtec_to_radar None
EDGE RSL_edge_to_radar None
RSL is designed to provide you with a uniform data structure so that you can design RADAR independent science applications. You no longer need to wrestle over the input data format and have a different version of your algorithm for each different RADAR format you may need to analyze.

This paper presents RSL from a science application developer's point of view. It will present some of the more useful routines and which fields in the Radar structure that will be important to you and it will attempt to cover some of the programming pitfalls associated with RSL usage. One of the most difficult hurdles to overcome is that RSL makes extensive use of pointer syntax. You will find yourself becoming expert with C pointers. However, the design of RSL makes it possible to use C pointers painlessly.

Ok, I have some data, how do I look at it?

Let's first make some images. To do that you need only 3 RSL functions:

RSL_anyformat_to_radar
RSL_load_refl_color_table
RSL_volume_to_gif

The C program you need is incredibly short. It illustrates how to ingest radar data and create a GIF image of the DZ (reflectivity) field:

#include "rsl.h"
void main(int argc, char **argv)
{
  Radar *radar;
  radar = RSL_anyformat_to_radar("radar.dat", NULL);
  RSL_load_refl_color_table();
  RSL_volume_to_gif(radar->v[DZ_INDEX], "dz_sweep", 400, 400, 200.0);
}
The line:

#include "rsl.h"

is required when using the RSL. It defines important constants and declares all the RSL functions that your application may need.

The line:

Radar *radar;
declares the radar pointer. Only a pointer to a radar should be declared, because, the ingest routines allocate all the space to hold all the appropriate substructures: Volume, Sweep, Ray, and Range.

The line:

radar = RSL_anyformat_to_radar("radar.dat", NULL);
performs the actual ingest of data. The input file is called radar.dat. RSL_anyformat_to_radar automatically determines the type of radar data being read. It can handle *.gz or *.Z files transparently. Reading gzip or compress files is faster, especially over NFS.   Generally, reading compressed radar files is faster because of how UNIX pipes are implemented and that the compression is nearly 90%.   The second argument, NULL, is optional. A second argument is needed only when reading WSR-88D data. The WSR-88D site information is provided in the first physical file on the 8mm tape, but, it is used to fill lat/lon and other radar-site specific information when reading the 2nd through last physical files on the tape.

Note: RSL_anyformat_to_radar can't handle every radar format for which there is an RSL ingest routine. But, it does a good job at recognizing most formats. Currently, TOGA and MCGILL files cannot be automatically detected. In those cases, use: RSL_toga_to_radar and RSL_mcgill_to_radar.

While basic image generation is provided in RSL, it is never intended to be anything more than a diagnostic tool. Several assumptions are made, but, the image generation functions provided are useful. This is what the last two lines illustrate. First you must define a color table. That is done with:

RSL_load_refl_color_table();
then, to generate disk files, gif images, you must call one of the image generation functions, as in:
RSL_volume_to_gif(radar->v[DZ_INDEX], "dz_sweep", 400, 400, 200.0);
This routine will generate several images, one for each sweep, mapping the image to a 400 x 400 km grid, using a 1 x 1 km spacing, by collecting data out to 200 km.

Making images of velocity data, VR_INDEX, involves two more steps that are not very obvious. Because of the limited range of the values presented in velocity data, you must re-bin the data. Do that with any one of the following:

RSL_rebin_velocity_sweep,
RSL_rebin_velocity_volume
The second step is that you must call:
RSL_load_vel_color_table();
The nyquist velocity is used to determine the limits of the re-binning. These functions modify the data in a sweep, or volume. So, it is wise to make copies of the sweep, or volume, if you plan on using the data later in your application. Normally, though, making velocity images is the last step of a program, therefore, you don't need to copy the velocity volume as your program will be exiting shortly. RSL provides a number of color table manipulation functions. You are not limited by the default settings for DZ, VR, and SW color tables. You can specify any color table mapping you wish.

Whoopty doo, I really wanted to examine the values.

In order to get to values in the Radar structure, you have to trickle down all the substructues. The structures, in order of nesting are: Radar, Volume, Sweep, Ray, Range. Each of these structures is presented in that order. You will notice a common organization across all of the structures -- each structure contains a header and contains an array of pointers to the next substructure.

The Radar structure

Ok, make the call to RSL_anyformat_to_radar as above, so that you get a pointer to a radar. The structure Radar is the most general structure in RSL. Radar is composed of two parts: The radar header, will be presented and described fully later, but, it contains general information about the entire structure. To access the radar header use the syntax:
Radar *radar;
radar->h.member;
The array of pointers to Volumes contains either pointers to Volumes of data or NULL. The number of possible Volumes in the radar is specified by radar->h.nvolumes. This number represents the length of the array of pointers to Volumes and not the number of actual (non-NULL) volumes in the radar. The index of this array of pointers to Volumes is the field type index. There are MAX_RADAR_VOLUMES (currently set to 19) field types defined in RSL. Each field type index has a specific value. That value is illustrated in the table below. RSL ingest routines guarentee that the length of the array of pointers to Volumes, radar->v, is exactly the maximum number of field types, MAX_RADAR_VOLUMES. This is done so that you can check for the existance of a field type with the syntax:
if (radar->v[XZ_INDEX]) /* XZ exists */
Normally, radar->h.nvolumes is set to the length of the array of pointers to Volumes, radar->v. Because C array indexes start at 0, you should use a test similiar to: ivol < radar->h.nvolumes. The maximum value for radar->h.nvolumes is MAX_RADAR_VOLUMES which is a constant in RSL. The value for radar->h.nvolumes could be less though. But, you can rest assured that you can test for the existance of a field type simply by using the hard coded index name as specified in the table below.

There are basically two methods for indexing the array of pointers to Volumes:

Here are two coding examples that demonstrate how to access the array of pointers to volumes.

Example 1:

Radar *radar;
Volume *volume;

volume = radar->v[CZ_INDEX];
if (volume != NULL) {
   /* Do something with volume. */
}
Example 2:
Radar *radar;
Volume *volume;
int i;

for (i=0; i<radar->h.nvolumes) {
   volume = radar->v[i];
   if (volume == NULL) continue; /* skip this NULL volume */
   /* Do something with volume. */
}
It is very important that you check for the volume pointer being NULL. It is very common that radar->h.nvolumes is larger than the number of non-NULL volumes present in radar. By default, radar->h.nvolumes is the length of array of pointers to Volumes. The volumes are also known as field types. There are several field types and a Volume can be only one field type. The entire list of field types is presented in the table below. To reference a particular field, you use a simple syntax:

radar->v[DZ_INDEX]
radar->v[VR_INDEX]

Each field type encountered has a specific index within the radar->v array of pointers to Volumes. The field type indexes are hard-coded and are defined to be specific numbers starting at 0. Hard-coded field type indexes simplifies the syntax for accessing volumes. When there is no volume for a particular field type, the volume pointer is NULL. This is ok, as NULL is a perfectly acceptable, albeit useless, volume. Here is a table of all the field type indexes used in RSL.
 

INDEX NAME

Value

Description

DZ_INDEX 0 Reflectivity (dBZ)
VR_INDEX 1 Radial Velocity (m/s)
SW_INDEX 2 Spectral Width (m2/s2)
CZ_INDEX 3 QC Reflectivity (dBZ)
ZT_INDEX 4 Total Reflectivity (dBZ)
DR_INDEX 5 Differential reflectivity
LR_INDEX 6 Another differential refl.
ZD_INDEX 7 Reflectivity Depolarization Ratio 

ZDR = 10log(ZH/ZV) (dB)

DM_INDEX 8 Received power (dBm)
RH_INDEX 9 Rho: Correlation coefficient
PH_INDEX 10 Phi (MCTEX parameter)
XZ_INDEX 11 X-band reflectivity
CR_INDEX 12 Corrected DR reflectivity (differential).
MZ_INDEX 13 DZ mask volume for HDF 1C-51 product.
MR_INDEX 14 DR mask volume for HDF 1C-51 product.
ZE_INDEX 15 Edited reflectivity.
VE_INDEX 16 Edited velocity.
KD_INDEX 17 KDP (unknown) for MCTEX data.
TI_INDEX 18 TIME (unknown) for MCTEX data.

The Volume structure

The Volume structure represents the RADAR data for one, and only one, field type. Upon ingest, the data for each field type is separated and placed into separate volumes. This makes it convenient to manipulate volumes based on their field type.

The organization of the Volume structure closely resembles the organization of the Radar structure. It, too, is compose of two parts:

To access elements in the Volume header, you use the syntax:
Volume *volume;
volume->h.member;
You can find a description of each volume header member later. The array of pointers to Sweeps contains either pointers to Sweeps of data or NULL. The number of possible Sweeps in the Volume is specified by volume->h.nsweeps. This number represents the length of the array of pointers to Sweeps and not the number of actual (non-NULL) sweeps in the volume.

There are two methods to accessing sweeps:

Here are two coding examples that demonstrate how to access the array of pointers to sweeps.

Example 1:

Volume *volume;
Sweep *sweep;
int i;

/* Assume a non-NULL volume at this point. */
for (i=0; i<volume->h.nsweeps; i++) {
   sweep = volume->sweep[i];
   if (sweep == NULL) continue; /* Skip NULL sweeps. */
   /* Do something with this sweep. */
   printf("Sweep %d elevation is %f\n", i, sweep->h.elev);
}
Example 2:
Volume *volume;
Sweep *sweep;
float elev;

/* No assumption about volume, it *can* be NULL! */
/* That's because RSL_get_sweep checks it. */
elev = 2.0;
sweep = RSL_get_sweep(volume, elev);
if (sweep != NULL)
   printf("Sweep %d elevation is %f\n", i, sweep->h.elev);
Again, it is very important to check for NULL sweeps. By default volume->h.nsweeps is the length of the array of pointers to Sweeps.

The Sweep structure

The Sweep represents the data collected for one field type during one 360o revolution of the RADAR. Like the Radar and Volume structures, the Sweep organization is composed of two parts: To access elements in the Sweep header, you use the syntax:
Sweep *sweep;
sweep->h.member;
A description of each member of the Sweep header is presented later. The array of pointers to Rays contains either pointers to Rays of data or NULL. The number of possible Rays in the Sweep is specified by sweep->h.nrays. This number represents the length of the array of pointers to Rays and not the number of actual (non-NULL) rays in the Sweep.

There are two methods to accessing rays:

Here are two coding examples illustrating how to access the array of pointers to Rays.

Example 1:

Sweep *sweep;
Ray *ray;
int i;

/* Assume a non-NULL sweep at this point. */
for (i=0; i<sweep->h.nrays; i++) {
   ray = sweep->ray[i];
   if (ray == NULL) continue; /* Skip NULL rays. */
   /* Do something with this ray. */
   printf("Ray %d azimuth is %f\n", i, ray->h.azimuth);
}
Example 2:
Volume *volume;
Ray *ray;
float elev, azimuth;

/* No assumption about volume, it *can* be NULL! */
/* That's because RSL_get_ray checks it. */
elev = 2.0;
azimuth = 30.2;
ray = RSL_get_ray(volume, elev, azimuth);
if (ray != NULL)
   printf("Ray %d elevation is %f, azimuth is %f\n", i, ray->h.elev, ray->h.azimuth);
You never know when you'll encounter NULL rays, so, make sure you test for it. By default, sweep->h.nrays is the length of the array of pointers to Rays which may or may not be the number of non-NULL Rays present.

The Ray structure

A ray of RADAR measurements represents data collected from close to the RADAR to some maximum physical range. The Ray, too, is composed of two parts: We're getting close to the data, now. The ray header contains the largest collection of members and describe all characteristics of the ray. To access elements in the Ray header, you use the syntax:
Ray *ray;
ray->h.member;
A description of each member of the Ray header is described later. The array of field type measurements contains the data, finally. The data type for the data is Range. The Range data type must be converted to float by using the function that is in ray header: ray->h.f(r), where r is of type Range. These conversion functions are in the headers for the volume and sweep. They are there only as a convenience to the application developer. The number of data values in the Rays is specified by ray->h.nbins. This number represents the length of the array of Range values. There is no abiguity here, the number of data values (Range values) exactly matches ray->h.nbins.

There are two methods to accessing the data:

Here are two coding examples illustrating how to access the array of Range values..

Example 1:

Ray *ray;
int i;
float x;

/* Assume a non-NULL ray at this point. */
for (i=0; i<ray->h.nbins; i++) {
   x = ray->h.f(ray->range[i]);
   /* Do something with this floating point value 'x'. */
   printf("BIN %d value is %f\n", i, x);
}
Example 2:
Volume *volume;
float x;
float elev, azimuth, range;

/* No assumption about volume, it *can* be NULL! */
/* That's because RSL_get_value checks it. */
elev = 2.0;
azimuth = 30.2;
range = 87.3; /* KM */
x = RSL_get_value(volume, elev, azimuth, range);

No assumptions as to the validity of the data.

The RSL does not modify the data in any way. It merely, loads the data into the Radar structure. For instance, during the MCTEX experiment, the azimuth values were incorrect for the first four tapes. They remain incorrect. It is up to you to write a conversion procedure that corrects the problem.

Pitfalls when using RSL in an application.

Here are some common mistakes made and things you should observe.
  1. Not checking for NULL. It is very important to check for NULL pointers. In the RSL context, NULL is a perfectly valid ray, sweep or volume. Blindly assuming that a volume, sweep, or ray exists is asking for trouble. When using an RSL interface routine, a routine that is prefixed with RSL_, you don't have to worry too much about passing null pointers. RSL routines check their arguments.
  2. Not checking for NULL, when passing a volume, sweep, or ray pointer into a routine. Check for NULL immediately.
  3. Not using the value for radar->h.nvolumes, volume->h.nsweeps, sweep->h.nray. They represent the maximum index possible and not the actual number of non-NULL structures. Remember a NULL sweep, in RSL, is a valid sweep; you just can't do anything with it. If you want to know how many non-NULL volumes you have, you'll have to count them yourself. Do that by looping from 0 to radar->h.nvolumes - 1.
  4. Not using the value for radar->h.nvolumes, volume->h.nsweeps, sweep->h.nrays, and ray->h.nbins for the current object. Never assume that the values are constant throughout the radar structure. They constantly change. For instance, the number of bins may decrease as the sweep elevation increases.
  5. Not converting the data in the Radar, Volume, Sweep, Ray, (really the Ray) to floating point before comparing with anything, including comparing it with BADVAL, RFVAL, APFLAG, NOECHO. Do this conversion with the h.f(c), where c is ray->range[i] and is of type Range. For example:

  6.  

     

    x = ray->h.f(ray->range[ibin]);

  7. Not converting a floating point number to internal storage with h.invf(x), where x is of type float, before filling the range array. For example:

  8.  

     

    ray->range[ibin] = ray->h.invf(x);

  9. Forgetting to load a color table before calling an image generation function. If you don't load a color table, your images will be black.
  10. Not rebinning the velocity data before making velocity images. The default color table is setup to cover the range of -nyquist to +nyquist.