/* read_grads.c */ /* * Functions for reading GRADS files */ /* * Vis5D system for visualizing five dimensional gridded data sets. * Copyright (C) 1990 - 2000 Bill Hibbard, Johan Kellum, Brian Paul, * Dave Santek, and Andre Battaiola. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * As a special exception to the terms of the GNU General Public * License, you are permitted to link Vis5D with (and distribute the * resulting source and executables) the LUI library (copyright by * Stellar Computer Inc. and licensed for distribution with Vis5D), * the McIDAS library, and/or the NetCDF library, where those * libraries are governed by the terms of their own licenses. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "../config.h" #include #include #include #include #include #include "binio.h" #include "file_i.h" #include "grid_i.h" #include "misc_i.h" #include "proj_i.h" #include "projlist_i.h" #include "tokenize_i.h" #include "v5d.h" extern int Debug_i; /* -debug in read_grid_i.c*/ #define MAX(A,B) ( (A) > (B) ? (A) : (B) ) /* Max chars on input line in CTRL file: */ #define MAXLINE 100 /* * Given a time/date string in GrADS format, return it as a date (YYDDD) * and time (HHMMSS). */ static int parse_time( char *str, int *date, int *time ) { int hh, mm, year, day, leap; int k; /* Extract hours and minutes */ if (str[2]==':') { /* extract hh:mmZ */ if (!isdigit(str[0]) || !isdigit(str[1])) { return 0; } hh = (str[0] - '0') * 10 + str[1] - '0'; if (!isdigit(str[3]) || !isdigit(str[4])) { return 0; } mm = (str[3] - '0') * 10 + str[4] - '0'; if (str[5]!='Z' && str[5]!='z') { return 1; } k = 6; } else if (str[2]=='Z' || str[2]=='z') { /* extract hhZ */ if (!isdigit(str[0]) || !isdigit(str[1])) { return 0; } hh = (str[0] - '0') * 10 + str[1] - '0'; mm = 0; k = 3; } else if (str[1]==':') { /* extract h:mmZ */ if (!isdigit(str[0])) { return 0; } hh = str[0] - '0'; if (!isdigit(str[2]) || !isdigit(str[3])) { return 0; } mm = (str[2] - '0') * 10 + str[3] - '0'; if (str[4]!='Z' && str[4]!='z') { return 0; } k = 5; } else if (str[1]=='Z') { /* extract hZ */ if (!isdigit(str[0])) { return 0; } hh = str[0] - '0'; mm = 0; k = 2; } else { hh = mm = 0; k = 0; } *time = 100 * (100 * hh + mm); /* Extract day */ if (isdigit(str[k])) { /* day */ if (isdigit(str[k+1])) { day = (str[k] - '0') * 10 + str[k+1] - '0'; k += 2; } else { day = str[k] - '0'; k += 1; } } else { day = 0; } /* Extract year from str[k+3] .. str[k+(4 or 6)] */ if (isdigit(str[k+3]) && isdigit(str[k+4])) { if (isdigit(str[k+5]) && isdigit(str[k+6])) { year = (str[k+5] - '0') * 10 + str[k+6] - '0'; } else { year = (str[k+3] - '0') * 10 + str[k+4] - '0'; } } else { return 0; } /* check if leap year */ leap = ( (year % 4) == 0); /* Extract month to increment days */ if (strncmp(str+k,"jan",3)==0 || strncmp(str+k,"JAN",3)==0) { day += 0; } else if (strncmp(str+k,"feb",3)==0 || strncmp(str+k,"FEB",3)==0) { day += 31; } else if (strncmp(str+k,"mar",3)==0 || strncmp(str+k,"MAR",3)==0) { day += 59 + leap; } else if (strncmp(str+k,"apr",3)==0 || strncmp(str+k,"APR",3)==0) { day += 90 + leap; } else if (strncmp(str+k,"may",3)==0 || strncmp(str+k,"MAY",3)==0) { day += 120 + leap; } else if (strncmp(str+k,"jun",3)==0 || strncmp(str+k,"JUN",3)==0) { day += 151 + leap; } else if (strncmp(str+k,"jul",3)==0 || strncmp(str+k,"JUL",3)==0) { day += 181 + leap; } else if (strncmp(str+k,"aug",3)==0 || strncmp(str+k,"AUG",3)==0) { day += 211 + leap; } else if (strncmp(str+k,"sep",3)==0 || strncmp(str+k,"SEP",3)==0) { day += 242 + leap; } else if (strncmp(str+k,"oct",3)==0 || strncmp(str+k,"OCT",3)==0) { day += 272 + leap; } else if (strncmp(str+k,"nov",3)==0 || strncmp(str+k,"NOV",3)==0) { day += 303 + leap; } else if (strncmp(str+k,"dec",3)==0 || strncmp(str+k,"DEC",3)==0) { day += 333 + leap; } else { return 0; } *date = 1000 * year + day; return 1; } /* * Given a time increment string in GrADS format, return the increment * in days and seconds. */ static int parse_time_inc( char *inc, int *days, int *seconds ) { int i, k; i = inc[0] - '0'; if (isdigit(inc[1])) { i = i * 10 + inc[1] - '0'; k = 2; } else { k = 1; } if ((inc[k]=='M' && inc[k+1]=='N') || (inc[k]=='m' && inc[k+1]=='n')) { *days = 0; *seconds = i * 60; } else if ((inc[k]=='H' && inc[k+1]=='R') || (inc[k]=='h' && inc[k+1]=='r')) { *days = 0; *seconds = i * 60 * 60; } else if ((inc[k]=='D' && inc[k+1]=='Y') || (inc[k]=='d' && inc[k+1]=='y')) { *days = i; *seconds = 0; } else if ((inc[k]=='M' && inc[k+1]=='O') || (inc[k]=='m' && inc[k+1]=='o')) { *days = 30 * i; *seconds = 60 * 60 * 10; } else if ((inc[k]=='Y' && inc[k+1]=='R') || (inc[k]=='y' && inc[k+1]=='r')) { *days = 365 * i; *seconds = 0; } else { *days = 0; *seconds = 0; return 0; } return 1; } static void flip_layer( float *data, int rows, int cols, float missing_value ) { int i, j; float temp[MAXROWS*MAXCOLUMNS]; #define DATA(R,C) data[R*cols+C] #define TEMP(R,C) temp[C*rows+rows-R-1] for (i=0;i days_per_month[*outMM - 1] ) { /* Subtract off number of days in this month and move on to the next. */ *outDD -= days_per_month[*outMM - 1]; (*outMM)++; } } /* Convert a 2-digit year to a 4-digit year. This should be placed in a general utility file so it can be used in more places, so the hack will be in only one place. */ int yy2yyyy( int inYearYY ) { /* Eventually check the current century to make a better decision. */ if ( inYearYY > 50 ) { return inYearYY+1900; } else { return inYearYY+2000; } } /* * Scan the named file, createing a grid_info struct for each grid. Store * the grid_info structs in the grid data base. * Input: name - name of grads file. * db - the grid data base * Return: number of grids found. */ int get_grads_info( char *name, struct grid_db *db ) { FILE *f; char **token; int ntokens; int time, var; int grids = 0; struct grid_info *info; int pos = 0; int fileheader_size = 0; float args[1000]; struct projection *proj; struct vcs *vcs[MAXVARS]; /* GrADS file info: */ char dset_name[1000]; float missing_value; int byteswapped = 0; int nr, nc, nl[MAXLEVELS], maxnl; int vertical; float westbound, northbound, bottombound; float rowinc, colinc, levinc; float height[MAXLEVELS]; char varname[MAXVARS][100]; int timestamp[MAXTIMES], datestamp[MAXTIMES]; int numtimes, numvars; int projection_type = PROJ_LINEAR; int use_file_template = 0; char specific_file_name[TEMPLATE_OUTPUT_LEN]; /* Initialize args */ args[0] = args[1] = args[2] = args[3] = args[4] = 0.0f; f = fopen( name, "r" ); if (!f) { printf("Error: couldn't open %s\n", name ); return 0; } while (1) { char line[MAXLINE]; /* read a line of the control file */ if (!fgets( line, MAXLINE, f )) break; /* break line into tokens */ token = tokenize( line, &ntokens ); if (ntokens==0) continue; if (strcasecmp(token[0],"DSET")==0) { if (ntokens<2) { printf("Error: missing filename argument on DSET line\n"); } else { if (token[1][0]=='^') { /* skip the ^ (current directory) character */ /* This is not the current directory, rather it is the same directory in which the control file is located */ int i; for(i=strlen(name)-1;i>=0;i--){ if(name[i] == '/'){ strncpy(dset_name,name,i+1); i++; break; } } if(i<0) i=0; /* No path in ctl file name */ strcpy( dset_name+i, token[1]+1 ); } else { strcpy( dset_name, token[1] ); } } if(Debug_i) printf("dset name >%s<\n",dset_name); } else if (strcasecmp(token[0],"TITLE")==0) { /* ignore the title line */ } else if (strcasecmp(token[0],"UNDEF")==0) { missing_value = atof(token[1]); } else if (strcasecmp(token[0],"BYTESWAPPED")==0) { /* Is this valid??? Shouldn't it be "OPTIONS BYTESWAPPED" ? */ byteswapped = 1; } else if (strcasecmp(token[0],"FORMAT")==0) { if (strcasecmp(token[1],"SEQUENTIAL")==0) { /* this is the only format currently supported; */ /* also note: FORMAT keyword has been replaced by OPTIONS */ } else { printf("Warning: FORMAT not fully supported\n"); printf(" only SEQUENTIAL format is allowed.\n"); } } else if (strcasecmp(token[0],"OPTIONS")==0) { if (strcasecmp(token[1],"SEQUENTIAL")==0) { /* Don't need to do anything, supported by default??? */ } else if (strcasecmp(token[1],"BYTESWAPPED")==0) { byteswapped=1; } else if (strcasecmp(token[1],"TEMPLATE")==0) { use_file_template=1; } #ifdef WORDS_BIGENDIAN else if (strcasecmp(token[1],"LITTLE_ENDIAN")==0 ) { byteswapped=1; } #else else if (strcasecmp(token[1],"BIG_ENDIAN")==0 ) { byteswapped=1; } #endif else { printf("Warning: OPTIONS %s not supported\n", token[1]); } } else if (strcasecmp(token[0],"FILEHEADER")==0) { if (ntokens<2) { printf("Error: missing position argument on FILEHEADER line\n"); } fileheader_size = atoi( token[1] ); pos = fileheader_size; } else if (strcasecmp(token[0],"XDEF")==0) { if (ntokens<4) { printf("Error: missing arguments to XDEF line\n"); } else { nc = atoi( token[1] ); if (strcasecmp(token[2],"LINEAR")==0) { westbound = -atof( token[3] ); colinc = atof( token[4] ); } else if (strcasecmp(token[2],"LEVELS")==0) { printf("Warning: XDEF LEVELS not fully supported\n"); westbound = -atof( token[3] ); colinc = fabs( atof(token[3]) - atof(token[4]) ); } else { printf("Warning: XDEF %s not fully supported\n", token[2]); } } } else if (strcasecmp(token[0],"YDEF")==0) { if (ntokens<4) { printf("Error: missing arguments to YDEF line\n"); } else { nr = atoi( token[1] ); if (strcasecmp(token[2],"LINEAR")==0) { float southbound = atof( token[3] ); rowinc = atof( token[4] ); northbound = southbound + rowinc * (nr-1); } else if (strncmp(token[2],"GAUSR",5)==0 || strncmp(token[2],"gausr",5)==0) { printf("Warning: YDEF GAUSRnn not supported\n"); } else { printf("Warning: YDEF %s not fully supported\n", token[2]); } } } else if (strcasecmp(token[0],"PDEF")==0) { if (ntokens<4) { printf("Error: missing arguments to PDEF line\n"); } else { nc = atoi( token[1] ); nr = atoi( token[2] ); if (strcasecmp(token[3],"PSE")==0) { if (ntokens<11) { printf("Error: missing arguments to PDEF line\n"); printf("'PDEF ... pse ...' must have 10 arguments\n"); } else { double pseLat = atof( token[4] ); /* degrees */ double pseLon = atof( token[5] ); /* degrees */ double pseCenterCol = atof( token[6] ); /* grid units */ double pseCenterRow = atof( token[7] ); /* grid units */ double pseDeltaX = atof( token[8] ); /* in km */ /*double pseDeltaY = atof( token[9] ); */ /* ignored */ int pseNorthOrSouth = atoi( token[10] );/* +1 or -1 */ /* grads documentation describes this format as "high accuracy polar stereo (eccentric)" while vis5d calls it "azimuthal stereographic". The conversions performed here are somewhat cryptic, and were obtained with some combination of reading the documentation and trial and error. */ /* center latitude. NOT TESTED FOR SOUTH POLAR PROJECTION! */ args[0] = pseNorthOrSouth * 90.0; /* center longitude. Why minus 270? Seems to work though. */ args[1] = pseLon - 270.0; /* put longitude in the range -180 to +180 */ args[1] = fmod( args[1], 360.0 ); if ( args[1] > 180.0 ) args[1] -= 360.0; if ( args[1] <-180.0 ) args[1] += 360.0; /* center grid row. Why the minus sign and nr-1? */ args[2] = nr - ( pseCenterRow + 1 ); /* center grid column */ args[3] = pseCenterCol; /* column inc. in km, GrADS gives column increment at this lattitude, we need column increment at the pole. */ args[4] = pseDeltaX * ( 1.0 + sin( 90/57.29578 ) ) / ( 1.0 + sin( pseLat/57.29578 ) ); /* what to do with token[9] - row inc. in km??? */ /* what to do with token[10] - 1 is N pole, -1 is S pole???*/ projection_type = PROJ_STEREO; } } else { printf("Warning: \"PDEF ... %s ...\" not supported\n", token[3] ); } } } else if (strcasecmp(token[0],"ZDEF")==0) { if (ntokens<3) { printf("Error: missing arguments to ZDEF line\n"); } else { float pressure[MAXLEVELS]; int i; maxnl = atoi( token[1] ); if (strcasecmp(token[2],"LINEAR")==0) { vertical = VERT_EQUAL_KM; bottombound = atof( token[3] ); levinc = atof( token[4] ); for (i=0;i MAXTIMES ) { printf("Warning: %d is too many time steps, %d is limit.\n", numtimes, MAXTIMES ); } for (time=0;timeFileName = strdup( specific_file_name ); info->Format = FILE_GRADS; info->TimeStep = time; info->VarNum = var; info->Position = pos; /* pos. of data in binary grid file */ pos += nr * nc * nl[var] * 4; info->Nr = nr; info->Nc = nc; info->Nl = nl[var]; info->DateStamp = datestamp[time]; info->TimeStamp = timestamp[time]; info->VarName = strdup( varname[var] ); info->Proj = proj; info->Vcs = vcs[var]; info->MissingValue = missing_value; info->byteswapped = byteswapped; append_grid( info, db ); grids++; } } return grids; } /* * Get the grid data described by g. * Input: g - pointer to a grid_info structure. It tells us which grid * in which file is needed. * Return: pointer to grid data (can be free()'d when finished) * OR return NULL if there's an error. */ float *get_grads_data( struct grid_info *g ) { int f, n, nread; float *data; f = open( g->FileName, O_RDONLY ); if (f<0) { printf("Error: couldn't open %s\n", g->FileName ); return NULL; } if (lseek( f, g->Position, SEEK_SET )!=g->Position) { printf("Error: couldn't get GrADS data for time %d, var %s\n", g->TimeStep, g->VarName ); close(f); return NULL; } n = g->Nr * g->Nc * g->Nl; data = (float *) malloc( n * sizeof(float) ); nread = read_float4_array( f, data, n ); if (nreadTimeStep, g->VarName ); free( data ); close(f); return NULL; } else { int i; int should_swap_bytes_here; /* This can be confusing. read_float4_array() automatically performs byte-swapping on non-BIGENDIAN machines. So if we're on a non-BIGENDIAN machine and we asked for byte-swapping, do nothing; but if we didn't ask for byte swapping, then swap them back! On BIGENDIAN machines, behave normally. */ #ifdef WORDS_BIGENDIAN should_swap_bytes_here = g->byteswapped; #else should_swap_bytes_here = !g->byteswapped; #endif if (should_swap_bytes_here) { flip4((const unsigned int *) data, (unsigned int*) data, nread); } /* flip data */ for (i=0;iNl;i++) { flip_layer( data + i * g->Nr * g->Nc, g->Nr, g->Nc, g->MissingValue ); } } close(f); return data; }