/* @(#) File name: fsdf_header.c Release: 1.1 Date: 7/28/94, 14:00:08 */ /**************************************************************************** FILE: fsdf_header FIRE Standard Data Format (SDF) header file scanning routines. AUTHOR: Meng-chun Lin Mail stop 157B, EOSDIS LANGLEY DAAC, Hampton, VA (804)864-8657. e-mail: M.LIN@LaRC.NASA.GOV PURPOSE/DESCRIPTION: This file contains a primary function (i.e., SDF_HEADER) used to parse the FIRE SDF defined header file, and two supporting functions (i.e., SDF_DDR, DDR_VAR). Upon successful invocation, the SDF_HEADER funciton will return an array of all data variable's field definitions in the observation DDR and ancillary DDR, if applicable. These field definitions can be used to read the binary data out of the observation file(s) and ancillary file(s). NOTE: FIRE CI1 SRB data has changed the SDF format to accomodate their needs (See header file). In order for this program to work on both FIRE CI1 SRB and other SDF data sets, a preprocessor option is defined. FUNCTIONS: sdf_header - is the primary function which returns an array of variables in the observation file and if applicable, an array of variables in the ancillary file. sdf_ddr - scans through the DDR section. It extracts the record name, logical and physical record sizes, and loops through the data variable descriptors within the DDR. DDR_var - extracts short name, type and length, display format, scaling constants, and min. max. values for a data variable. *****************************************************************************/ #ifdef sccs static char sccsid[] = "File: fsdf_header.c Release: 1.1 Date: 7/28/94, 14:00:08" #endif #include #include #include #include #include #include /* Constant MAXPATHLEN is defined here. */ #include #include "fire_sdf.h" extern int str_trim(); /**************************************************************************** FUNCTION: sdf_header () PURPOSE/DESCRIPTION: Function sdf_header scans the SDF defined header file. It reads the ancillary data descriptor record (DDR), if any, and observation data description record (DDR) information from the header file. It returns the two arrays of variables field definitions, one for observation the other for ancillary variables (See Notes below for the schematic drawings on both record arrays). It also returns the number of data variables in each of the two segments. NOTE: Since the storage for the variables information is dynamically allocated, the calling routine should 'free' the pointer to insure no memory leak condition can occur. The following paragraph pertains to FIRE CI1 SRB: Since the prefix area has the same variables throughout the observation and the ancillary files in the SRB data set. Also, it is necessary to know mnemonics in the prefix area for the purpose of checksum, the variables in the prefix area are not stored in the returned arrays. Only the variables in the data area for both ancillary and observation are stored in the arrays. INVOCATION: return_value = sdf_header (file_name, &obs_dat, &aux_dat, swap_flg, nobs); WHERE - I is the file name of the observation data without file extension. - I, O is the address of the pointer points to the base of a list of data descriptor records. It returns the information of the number of data variables in DDR (Data Descriptor Record), the logical record size, block size, and the observation DDR record name. - I, O is the address of the pointer points to the base of a list of data descriptor records. It returns the information of the number of data variables in DDR (Data Descriptor Record), the logical record size, block size, optionally prefix size, and the ancillary DDR record name. The calling routine should send in a NULL pointer if the data set does not have ancillary files. - I, O returns the value indicating whether or not the bytes need to be swapped in the data files. A 0 means no byte-swapping is needed, A 1 means byte-swapping is needed. - I, O returns the number of records defined in the observation segment. This value can be used to determine if there is any observation variables. - O returns the number of records defined in the ancillary segment. It returns -1 when there is an error. FILE/RECORD REFERENCES: None. EXTERNAL ROUTINES: None. NOTES: The format of the tape header file is described as follows: 1.) General Segment. The first 23 logical records (80 bytes per logical record). This segment ends with the phrase "END OF GENERAL SEGMENT" in the 22nd logical record, then followed by a blank 80-byte record. 2.) Ancillary Segment. It ends with the phrase "END OF ANCILLARY SEGMENT", then followed by a blank 80-byte record. If there is an ancillary file, it starts with the phrase "RECORD NAME: ". FIRE data can have no ancillary file or multiple ancillary files. 3.) Observation Segment. It starts with the phrase "RECORD NAME: ", and ends with the phrase "END OF OBSERVATION SEGMENT", and one 80-byte logical record of blanks. 4.) The array returned to the "obs_dat" and "aux_dat" are as follows: ============================== header_rec1 |nvars| .. |rec_name|vd_ptr ---|--->---------------------------- ============================== | ddr_var1 | ddr_var2 | ... | header_rec2 | | .. | ... | ... | ---------------------------- ============================== ... | ... | .. | ... | ... | ============================== | ... | .. | ... | ... ---|--->---------------------------- ============================== | ddr_var1 | ddr_var2 | ... | ---------------------------- *****************************************************************************/ int sdf_header (file_name, obs_dat, aux_dat, swap_flg, nobs) char *file_name; header **obs_dat, **aux_dat; int *swap_flg, *nobs; { char buf[241], str[81], *s; FILE *fptr; int j, nauxs=0; void sdf_ddr(); if ((fptr = fopen (file_name, "r")) == NULL) { fprintf (stderr,"ERROR:Can't open FIRE header file:\t%s\n", file_name); return (-1); } *swap_flg = 0; /**** FIRE header file is in ASCII format. It consists of three **** segments, starting with general segment, followed by ancillary **** segment, and end with observation segment. ****/ while ((fgets (buf, RECSIZE+1, fptr) != NULL)) { if ((strstr (buf, "VAX") != (char *)NULL) || (strstr (buf, "vax") != (char *)NULL) || (strstr (buf, "Vax") != (char *)NULL)) *swap_flg = 1; if (strstr (buf, END_GENERAL) != (char *)NULL) break; } /*** Skip the blank lines. ***/ while ((fgets (buf, RECSIZE+1, fptr) != NULL)) { if (str_trim (buf, 0) != 0) break; } j = 0; /**** Ancillary segment is followed after general segment. */ while (strstr (buf, END_ANCILLARY) == (char *)NULL) { if ((s=strstr (buf, KEY_DDR)) != (char *)NULL) { if (aux_dat != NULL) { if (j++ == 0) *aux_dat = (header *) malloc (sizeof (struct header)); (void) sdf_ddr (fptr, aux_dat, buf, &nauxs); } } fgets (buf, RECSIZE+1, fptr); } *nobs = 0; *obs_dat = (header *) malloc (sizeof (struct header)); while ((fgets (buf, RECSIZE+1, fptr) != NULL)) /* Skip blank line(s) */ if (str_trim (buf, 0) != 0) break; /* Start checking the Observation segment. In case the END OF * OBSERVATION SEGMENT keyword is missing, read to the end of file, * then return to the calling routine. */ while (strstr (buf, END_OBSERVE) == (char *)NULL) { if ((s=strstr (buf, KEY_DDR)) != (char *)NULL) (void) sdf_ddr (fptr, obs_dat, buf, nobs); if (fgets (buf, RECSIZE+1, fptr) == NULL) break; } fclose (fptr); return (nauxs); } /**************************************************************************** FUNCTION: sdf_ddr () PURPOSE/DESCRIPTION: Function sdf_ddr scans through the DDR section. It extracts the record name, logical and physical record sizes, and loops through the data variable descriptors within the DDR. NOTE for the FIRE CI1 SRB data set, only variable descriptors in the record-data-subsection are obtained. The record-prefix-subsection specifies the fixed variables whose values will be extracted from the data files in the data read routine. INVOCATION: (void) sdf_ddr (fptr, ddr_dat, buf, nrecs) WHERE - I is the file pointer of the header file specified from the command line argument. - I, O is the address of the pointer points to the base of a list of data descriptor records. It returns the record name, the number of data variables in DDR (Data Descriptor Record), the logical record size, block size, and either ancillary or observation data variables list. - I is the buffer containing the first 80 bytes of DDR. - I, O returns the number of records defined in the observation segment. There is NO returned value. FILE/RECORD REFERENCES: None. EXTERNAL ROUTINES: None. NOTES: Because some data sets do not follow the standard data format by padding the blanks in the unused bytes (they are padded with one or multiple "CR" or newline characters), that's why this function can not just count the number of lines read to decide if the data variables section starts. *****************************************************************************/ void sdf_ddr (fptr, ddr_dat, buf, nrecs) FILE *fptr; header **ddr_dat; char buf[]; int *nrecs; { char str[81], end_keyword[80], *s; header *hd; int i, j, tot; *ddr_dat = (header *) realloc (*ddr_dat, (++(*nrecs))*sizeof (header)); hd = (*ddr_dat)+(*nrecs-1); hd->vd_ptr = (ddr_var *) malloc (sizeof (struct ddr_var)); /*s = strstr (buf, KEY_DDR)+sizeof(KEY_DDR); sscanf (s, "%s", hd->rec_name); */ /** Skip 14-character "RECORD NAME: " key word to get the name. Because ** some data sets has blanks in the record name itself, that's why I ** use the strncpy instead of sscanf to extract the name. **/ strncpy (hd->rec_name, &(buf[14]), 8); hd->rec_name[8] = '\0'; #ifndef SRB sscanf (&buf[HD_SIZ_FLDS], "%d %d", &(hd->lrsiz), &(hd->blksiz)); strcpy (end_keyword, END_VR); #else sscanf (&buf[SIZ_FLDS],"%d %d %d",&(hd->npfx),&(hd->lrsiz),&(hd->blksiz)); strcpy (end_keyword, END_VAR); #endif do { j = str_trim (fgets (buf, RECSIZE+1, fptr), 0); if (j == 0) continue; } while (buf[10] != ':'); for (j = 0; (str_trim (fgets (buf, RECSIZE+1, fptr), 0) > 0) && (j < 2); j++) ; while (j == 0) /** Skip the blank lines **/ j = str_trim (fgets (buf, RECSIZE+1, fptr), 0); #ifdef SRB while ((strstr (buf, DATA_VAR) == (char *)NULL) && (fgets (buf, RECSIZE+1, fptr) != NULL)) ; while ((strstr(buf, VAR_BEGIN) == (char *)NULL) && (fgets (buf, RECSIZE+1, fptr) != NULL)) ; while ((j = str_trim (fgets (buf, RECSIZE+1, fptr), 0)) == 0) ; #endif /*** The first line of first data variable is passed in. ***/ for (hd->nvars = 0, i = 0, tot = 0; (i < hd->lrsiz) && (tot >= 0); fgets (buf, RECSIZE+1, fptr)) { if (strstr (buf, end_keyword) != (char *)NULL) break; if ((j = str_trim (buf, 0)) == 0) { j = str_trim (fgets (str, RECSIZE+1, fptr), 0); strcpy (buf, str); } strncpy (&(buf[j]), " ", (RECSIZE-j)); if ((j = str_trim (fgets (str, RECSIZE+1, fptr), 0)) == 0) { j = str_trim (fgets (str, RECSIZE+1, fptr), 0); } strncpy (&buf[RECSIZE], str, j); fgets (&(buf[RECSIZE+j]), RECSIZE+1, fptr); tot = DDR_var (buf, &(hd->vd_ptr), &(hd->nvars)); i += tot; } } /**************************************************************************** FUNCTION: DDR_var () PURPOSE/DESCRIPTION: Function DDR_var extracts short name, type and length, display format, scaling constants, and min. max. values for a data variable within a DDR. It converts the Fortran display format in the header file to C output format string. Also, the constant b value is decided and returned based on the defined field length. INVOCATION: return_value = DDR_var (string, var_lst, num_vars); WHERE - I is a 240-byte character string (three logical records) that contains one variable descriptor. - I, O is the address of the pointer points to the base of a list of data variables. It returns the updated data variables list. It returns the same list if it's an implicit variable. - I, O returns the updated number of data variables in the current data descriptor records. - O returns the data length of the current data variable. FILE/RECORD REFERENCES: None. EXTERNAL ROUTINES: None. NOTES: 1.) In FIRE SDF, if the data values are integer, no conversion is needed. Therefore, conversion constant b is set to 0 when the display format is defined as 'integer'. The read function can use b to decide whether or not the data values are integers. 2.) If the display format is not one of I, F, E, G, O, or Z, it is converted to floating point format. *****************************************************************************/ int DDR_var (string, var_lst, num_vars) char *string; ddr_var **var_lst; int *num_vars; { int i, j; char mnemonic[9], type_len[5], disp_form[9], len_str[8], fmt[12], *s; int min, max; ddr_var *cur_var; for (i = 0, s = &string[24]; i < 4; i++, s++) type_len[i] = (isspace (*s)) ? '\0' : *s; if ((type_len[0] == '0') && (type_len[1] == '0')) /* implicit variable */ return (0); *var_lst = (ddr_var *) realloc (*var_lst, (++*num_vars)*sizeof(ddr_var)); cur_var = (*var_lst)+(*num_vars - 1); for (i = 0, s = &string[16]; (i < 8) && (isspace (*s)); i++, s++) ; for (j = 0, s = &string[16+i]; i < 8; i++, j++, s++) { if (isspace (*s)) { cur_var->mnemonic[j] = '\0'; break; } cur_var->mnemonic[j] = *s; } for (i = 0, s = &string[40]; i < 8; i++, s++) { disp_form[i] = (isspace (*s)) ? '\0' : *s; } if (strlen (disp_form) > 1) strcpy (len_str, &(disp_form[1])); else len_str[0] = '\0'; /*** Convert the Fortran display format to C output format string. ***/ if ((toupper(disp_form[0]) == 'I') || (toupper(disp_form[0]) == 'F')) sprintf (cur_var->disp_form, "%%%s%c", len_str, tolower(disp_form[0])); /** Can't do complex number, convert to double. **/ else if ((toupper(disp_form[0]) == 'E') || (toupper(disp_form[0]) == 'G')) sprintf (cur_var->disp_form, "%%%sl%c", len_str, disp_form[0]); else if (toupper(disp_form[0]) == 'O') /** Octal **/ sprintf (cur_var->disp_form, "%%%s%c", len_str, disp_form[0]); else if (toupper(disp_form[0]) == 'Z') /** Hexadecimal **/ sprintf (cur_var->disp_form, "%%%sX", len_str); else /** All other formats are converted to double. **/ sprintf (cur_var->disp_form, "%%%sG", len_str); /*** Extract the length (in bytes) of the data variable. ***/ if ((toupper(type_len[0]) == 'I') && (type_len[1] == '*')) cur_var->size = atoi (&type_len[2]); /*** Constant b value is decided by the field length. But if the display *** format is 'integer', b is set to 0 for data read routine to identify. ***/ cur_var->b = 8 * cur_var->size - 1; if ((toupper(disp_form[0]) == 'I') || (toupper(disp_form[0]) == 'O') || (toupper(disp_form[0]) == 'Z')) cur_var->b = 0; sscanf (&(string[48]), "%4u", &(cur_var->N)); sscanf (&(string[52]), "%12d", &(cur_var->A)); /** Except for Octal and Hexadecimal, other formats are read in as ** floating point value. Octal & Hex formats are casted to float. **/ if (toupper(disp_form[0]) == 'Z') { sscanf (&(string[88]), "%12X", &min); sscanf (&(string[100]), "%12X", &max); cur_var->min = (float) min; cur_var->max = (float) max; } else if (toupper(disp_form[0]) == 'O') { sscanf (&(string[88]), "%12O", &min); sscanf (&(string[100]), "%12O", &max); cur_var->min = (float) min; cur_var->max = (float) max; } else if (toupper(disp_form[0]) == 'I') { sscanf (&(string[88]), "%12d", &min); sscanf (&(string[100]), "%12d", &max); cur_var->min = (float) min; cur_var->max = (float) max; } else { sscanf (&(string[88]), "%12f", &(cur_var->min)); sscanf (&(string[100]), "%12f", &(cur_var->max)); } return (cur_var->size); } /**************************************************************************** FUNCTION: get_fire_id () PURPOSE/DESCRIPTION: Function get_fire_id extracts the information from the first two lines in the Fire SDF header file. These two lines contain the data source and producer's (contact's) name, which uniquely identify the Fire SDF data sets. INVOCATION: return_value = get_fire_id (file_name); WHERE - I is the Fire SDF header file name to be processed. - O returns the index Fire SDF file currently at Langley DAAC. The list of available SDF files are defined in the header file (.h). FILE/RECORD REFERENCES: None. EXTERNAL ROUTINES: None. NOTES: 1.) This function is added because some Fire SDF data sets are not following the Fire "Standard Data Format". *****************************************************************************/ int get_fire_id (filename) char *filename; { int i; FILE *fptr; char buf1[160], buf2[160], *s; if ((fptr = fopen (filename, "r")) == NULL) { fprintf (stderr,"ERROR:Can't open FIRE header file:\t%s\n", filename); return (-1); } if ((fgets (buf1, RECSIZE+1, fptr) != NULL) && (fgets (buf2, RECSIZE+1, fptr) != NULL)) { for (i = 0; i < (sizeof (sdf_list) / sizeof (sdf_list[0])); i++) { if ((strstr (buf1, sdf_list[i].fire_id) != NULL) && (strstr (buf2, sdf_list[i].producer) != NULL)) return (i+1); } } }