# 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

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:

- Radar header.
- Array of pointers to Volumes.

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:

- Use the index name, eg. CZ_INDEX or its value.
- Use a variable that ranges from 0 to `radar->h.nvolumes`-1.

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:

- Volume header.
- Array of pointers to Sweeps.

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:

- Use a loop index that ranges from 0 to `volume->h.nsweeps-1`.
- Use RSL_get_sweep or other similiar RSL sweep retieval functions.

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:

- Sweep header.
- Array of pointers to Rays.

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:

- Use a loop index that ranges from 0 to `sweep->h.nrays-1`.
- Use RSL_get_ray or other similiar RSL ray retieval functions.

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:

- Ray header.
- Array of field type measurements. These are not pointers. It is an array of values.

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:

- Use a loop index that ranges from 0 to `ray->h.nbins-1` calling the `ray->h.f` function.
- Use RSL_get_value or other similiar RSL get value functions.

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:

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

6. 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:

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

7. 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.
8. Not rebinning the velocity data before making velocity images. The default color table is setup to cover the range of -nyquist to +nyquist.