Each procedure in the RSL is a function that returns a pointer to an object that it is designed, or self documented, to return. In the case of scalar functions, scalar values are returned. There are exceptions to this rule wherein we have procedures that modify their arguments, but, these are few. Also, each function does one thing well. This makes the implementation of each function simple and easy to understand and, therefore, easy to maintain. The power of the library has come from building higher level functions from many lower level functions. Eventually, the highest level function takes a filename as an argument and returns a Radar pointer.
The Radar data structure is designed to contain values and header information
making it a superset of all the radar data formats currently encountered.
Currently RSL can ingest the following file formats: nexrad, lassen both
1.3 and 1.4 file format versions (MCTEX too), UF, toga, new sigment (nsig)
both version 1 and version 2 file formats, mcgill, kwajalein and rapic
(Berrimah data).
Similiarly, UF output can be saved using the gzip filter. The new routine added is RSL_radar_to_uf_gzip which utilizes the gzip compression filter.
extern int radar_verbose_flag; /* Define before usage. */ /* Somewhere in the code. */ if (radar_verbose_flag) printf("I'm here now. Whatever.\n");
Ray *add_rays(Ray *ray1, Ray *ray2) { int i; Ray *ray_new; if (ray1 == NULL) return NULL; if (ray2 == NULL) return NULL; ray_new = RSL_new_ray(ray1->h.nbins); /* Allocate the new ray. */ if (ray_new == NULL) return NULL; ray_new->h = ray_new->h; /* Copy the header information. */ for (i=0; i <= ray_new->h.nbins; i++) /* Add each range element. */ ray_new->range[i] = ray1->range[i] + ray2->range[i]; return ray_new; /* Return this new ray. */ }It is valid for the routine to accept NULL's. This should be checked and appropriate values returned. In the case above, we return NULL. We could easily return the ray that is not NULL, too. The choice is up to you. The routine is a function and returns a structure pointer similar to those it manipulates; or self documented to return. Space is allocated and header information is copied. You might ask, "Why don't we call RSL_copy_ray, then call RSL_clear_ray?". This is a viable approach and it will work well at the ray level, however, for the sweep, volume, and radar interfaces we don't want to recursively copy all substructures because it is more troublesome to copy, clear and set all the levels, than to simply allocate and set at each level. To construct the interfaces for Sweeps and Volumes is easy. For the Sweep interface we recognize that a Sweep is an array of Rays. You will notice that this routine is coded almost identically to the ray routine.
Sweep *add_sweeps(Sweep *s1, Sweep *s2) { int i; Sweep *s_new; if (s1 == NULL) return NULL; if (s2 == NULL) return NULL; s_new = RSL_new_sweep(s1->h.nrays); s_new->h = s1->h; for (i=0; i <= s1->h.nrays; i++) s_new->ray[i] = add_rays(s1->ray[i], s2->ray[i]); return s_new;
}The pattern continues for making the Volume interface.
Volume *add_volumes(Volume *v1, Volume *v2) { int i; Volume *v_new; if (v1 == NULL) return NULL; if (v2 == NULL) return NULL; v_new = RSL_new_volume(v1->h.nsweeps); v_new->h = v1->h; for (i=0; i <= v1->h.nsweeps; i++) v_new->sweep[i] = add_sweeps(v1->sweep[i], v2->sweep[i]); return v_new; }And finally, for Radar.
Radar *add_radars(Radar *r1, Radar *r2) { int i; Radar *r_new; if (r1 == NULL) return NULL; if (r2 == NULL) return NULL; r_new = RSL_new_radar(r1->h.nvolumes); r_new->h = r1->h; for (i=0; i <= r1->h.nvolumes; i++) r_new->v[i] = add_volumes(r1->v[i], r2->v[i]); return r_new; }These four functions allow us to add two radars, add two volumes, add two sweeps or add two rays. The development was simple. For the radar routine we loop on the number of volumes and call the volume routine. For the volume routine we loop on the number of sweeps and call the sweep routine. For the sweep routine we loop on the number of rays and call the ray routine. For the ray routine we loop on the number of bins and perform the work. Simple. This is how many of the RSL routines were written.
static Range invf(float x) { return (Range) (x/2 + 10); } static float f (Range x) { return (float) (x - 10)*2; /* Not quite a perfect inverse function. */ } Volume *volume_routine(Volume *v) { v->h.f = f; /* Assign the float function. */ v->h.invf = invf; /* Assign the Range function. */ return v; }So far in the construction of RSL, the only location where you assign h.f and h.invf is in the ingest routine for a particular data format. The routines RSL_uf_to_radar, RSL_lassen_to_radar, RSL_wsr88d_to_radar, RSL_nsig_to_radar, RSL_toga_to_radar, and RSL_mcgill_to_radar each define the invf and f functions; in some cases the default functions are used. The file volume.c contains the default specification for DZ_F, DZ_INVF, etc.
Radar *RSL_something_to_radar(char *infile);The file added to RSL will be called something.c. All the code needed to ingest the file and create the Radar structure will be in that file. It may be necessary to rely on another library that can ingest the data file utilizing a nicer interface. That is ok, go ahead and use it. This new interface definition will be placed in the prototype section of rsl.h so that other routines are aware of it. Also, code to automatically determine the type of input file must be placed in anyformat.c. If it is not possible to automatically determine the type of file, then this must be documented as such and an explanation that when anyformat_to_radar returns NULL, it may be necessary to call RSL_something_to_radar directly. Memory allocation routines you will need are: RSL_new_radar, RSL_new_volume, RSL_new_sweep and RSL_new_ray. You may not know ahead of time just how many substructures to allocate, so allocate some reasonable maximum. You can reset the value of nvolumes, nsweeps, or nrays after the ingest is complete.
radar = RSL_new_radar(20); ...load radar->v[i] ... ...keep track of the max index for v[i] ... radar->h.nvolumes = max_vol_index; return radar;You don't have to worry about deallocating the extra memory. Output routines -- to disk -- will ignore it. And, after re-reading it, the right amount of memory will be allocated.
New in v0.41 is the implementation of the radar structure member radar_type. Assign a descriptive string indicating the origin or the format type of the data. Currently used strings are: "wsr88d", "lassen", "nsig", "uf", "mcgill", "toga", "kwajalein". The string may not exceed 50 characters, including the null character.
Common problems that have occurred are:
On the other hand, RSL_new_{radar, volume, sweep, ray} allocates memory for the header structure and only the pointers to the substructure. You have to allocate the substructure yourself. For instance, if you call
RSL_new_radar(4); /* Four volumes. */you will have to call RSL_new_volume four times. Do that with,
for (i=0; i<=radar->h.nvolumes; i++) { radar->v[i] = RSL_new_volume(40); /* 40 sweep pointers. */ ... construct the volume ... }Here only 40 pointers for the sweeps are allocated and not the space for the entire sweep which would include all the rays. For each sweep you must use the code,
for (i=0; i<volume->h.nsweeps; i++) { volume->sweep[i] = RSL_new_sweep(400); /* 400 is a good max. */ ... construct the sweep ... }
my_new_volume = RSL_some_function_of_volume(volume);The natural hierarchical construction will be:
Radar *RSL_some_function_of_radar(radar); Volume *RSL_some_function_of_volume(volume); Sweep *RSL_some_function_of_sweep(sweep); Ray *RSL_some_function_of_ray(ray);As I've stated before, the radar routine will loop on the number of volumes and call the volume routine. The volume routine will loop on the number of sweeps and call the sweep routine. The sweep routine will loop on the number of rays and call the ray routine. The ray routine will perform the actual function. One approach might be to define the radar routine as:
Radar *RSL_some_function_of_radar(Radar *radar) { int i; Volume *volume; Radar *new_radar; if (radar == NULL) return NULL; new_radar = RSL_copy_radar(radar); /* This is bad, bad, bad. */ for (i=0; i<=radar->h.nvolumes; i++) new_radar->v[i] = RSL_some_function_of_volume(radar->v[i]); return new_radar; }Now, why did I place the comment /* bad, bad, bad */ in the code? At first, it seems, it may not be all that bad. But, remember that RSL_copy_radar will copy all the volumes and sweeps and rays and bins. The line that assigns new_radar->v[i] is allocating space for a volume too. Therefore, we are more than doubling the memory requirements, if we code the volume and sweep routines similarly. The amount of memory allocated will be nvolumes*space_allocated_to_a_volume + nsweeps*space_allocated_to_a_sweep + nrays*space_allocated_to_a_ray. That is a tremendous amount of memory, you're talking 3*15*15Mbytes (675Mbytes). In order to use RSL_copy_{radar,volume,sweep,ray} you will need to clear out all the volumes, sweeps and rays with a call to RSL_clear_{volume,sweep,ray} and you will have to pass the target structure to the routine which will make it an argument that is modified. This goes against the design of the typical RSL interface where a pointer to an object is returned by a function. A better solution is to allocate a new radar and copy the header information. Instead of writing,
new_radar = RSL_copy_radar(radar);you write,
new_radar = RSL_new_radar(radar->h.nvolumes); new_radar->h = radar->h;The volume, sweep and ray routine can be coded similarly. For example, here is the sweep routine:
Sweep *RSL_some_function_of_sweep(Sweep *sweep) { int i; Ray *ray; Sweep *new_sweep; if (sweep == NULL) return NULL; new_sweep = RSL_new_sweep(sweep->h.nrays); new_sweep->h = sweep->h; for (i=0; i<=sweep->h.nrays; i++) new_sweep->ray[i] = RSL_some_function_of_ray(sweep->ray[i]); return new_sweep; }