/* * (C) Copyright 2005- ECMWF. * * This software is licensed under the terms of the Apache Licence Version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * * In applying this licence, ECMWF does not waive the privileges and immunities granted to it by * virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction. */ /* cmake config header */ #ifdef HAVE_ECCODES_CONFIG_H #include "eccodes_config.h" #endif /* autoconf config header */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #ifndef ECCODES_ON_WINDOWS #include #else #include #include #ifdef _MSC_VER # define strcasecmp _stricmp #endif #endif #include #include "tigge_tools.h" void validate(const char* path); #define CHECK(a) check(#a,a) #define WARN(a) warn(#a,a) #define NUMBER(a) (sizeof(a)/sizeof(a[0])) typedef struct pair { const char* key; int key_type; long value_long; char* value_string; } pair; typedef struct parameter parameter; typedef void (*check_proc)(grib_handle*,const parameter*,double min,double max); struct parameter { const char* name; double min1; double min2; double max1; double max2; pair pairs[15]; check_proc checks[5]; }; static void point_in_time(grib_handle*,const parameter*,double,double); static void statistical_process(grib_handle*,const parameter*,double,double); static void six_hourly(grib_handle*,const parameter*,double,double); static void since_prev_pp(grib_handle*,const parameter*,double,double); static void three_hourly(grib_handle* h,const parameter* p,double min,double max); static void from_start(grib_handle*,const parameter*,double,double); static void daily_average(grib_handle*,const parameter*,double,double); static void given_level(grib_handle*,const parameter*,double,double); static void predefined_level(grib_handle*,const parameter*,double,double); static void predefined_thickness(grib_handle*,const parameter*,double,double); static void given_thickness(grib_handle*,const parameter*,double,double); static void has_bitmap(grib_handle*,const parameter*,double,double); static void has_soil_level(grib_handle*,const parameter*,double,double); static void has_soil_layer(grib_handle*,const parameter*,double,double); static void resolution_s2s(grib_handle*,const parameter*,double,double); static void resolution_s2s_ocean(grib_handle*,const parameter*,double,double); static void height_level(grib_handle*,const parameter*,double,double); static void pressure_level(grib_handle*,const parameter*,double,double); static void potential_temperature_level(grib_handle*,const parameter*,double,double); static void potential_vorticity_level(grib_handle*,const parameter*,double,double); /* TODO: - Shape of the earth - Levels */ #include "tigge_check.h" int error = 0; const char* file = 0; int field = 0; int warning = 0; int valueflg = 0; const char* param = "unknown"; int warnflg = 0; int zeroflg = 0; int is_lam = 0; int is_s2s = 0; int is_s2s_refcst = 0; int is_uerra = 0; int is_crra = 0; const char* good = NULL; const char* bad = NULL; FILE* fgood = NULL; FILE* fbad = NULL; static void check(const char* name,int a) { if(!a) { printf("%s, field %d [%s]: %s failed\n",file,field,param,name); error++; } } /* static void warn(const char* name,int a) { if(!a) { printf("%s, field %d [%s]: %s failed\n",file,field,param,name); warning++; } } */ static void save(grib_handle* h, const char *name,FILE* f) { size_t size; const void *buffer; int e; if(!f) return; if((e = grib_get_message(h,&buffer,&size)) != GRIB_SUCCESS) { printf("%s, field %d [%s]: cannot get message: %s\n",file,field,param,grib_get_error_message(e)); exit(1); } if(fwrite(buffer,1,size,f) != size) { perror(name); exit(1); } } static long get(grib_handle *h,const char* what) { int e; long val; if((e = grib_get_long(h,what,&val)) != GRIB_SUCCESS) { printf("%s, field %d [%s]: cannot get %s: %s\n",file,field,param,what,grib_get_error_message(e)); error++; val = -1; } return val; } static double dget(grib_handle *h,const char* what) { int e; double val; if((e = grib_get_double(h,what,&val)) != GRIB_SUCCESS) { printf("%s, field %d [%s]: cannot get %s: %s\n",file,field,param,what,grib_get_error_message(e)); error++; val = -1; } return val; } static int missing(grib_handle *h,const char* what) { int err=0; return grib_is_missing(h,what,&err); } static int eq(grib_handle *h,const char* what,long value) { return get(h,what) == value; } static int ne(grib_handle *h,const char* what,long value) { return get(h,what) != value; } static int ge(grib_handle *h,const char* what,long value) { return get(h,what) >= value; } static int le(grib_handle *h,const char* what,long value) { return get(h,what) <= value; } static int DBL_EQUAL(double d1, double d2, double tolerance) { return fabs(d1-d2) <= tolerance; } static void gaussian_grid(grib_handle* h) { const double tolerance = 1.0/1000000.0; /* angular tolerance for grib2: micro degrees */ long n = get(h,"numberOfParallelsBetweenAPoleAndTheEquator"); /* This is the key N */ static double* values = NULL; static long last_n = 0; double north = dget(h,"latitudeOfFirstGridPointInDegrees"); double south = dget(h,"latitudeOfLastGridPointInDegrees"); double west = dget(h,"longitudeOfFirstGridPointInDegrees"); double east = dget(h,"longitudeOfLastGridPointInDegrees"); int e; if(n != last_n) { if(values) free(values); values = (double*)malloc(2*sizeof(double)*n); if(!values) { printf("%s, field %d [%s]: failed to allocate %ld bytes\n",file,field,param,2*(long)sizeof(double)*(n)); error++; return; } if((e = grib_get_gaussian_latitudes(n,values)) != GRIB_SUCCESS) { printf("%s, field %d [%s]: cannot get gaussian latitudes for N%ld: %s\n",file,field,param,n,grib_get_error_message(e)); error++; free(values); last_n = 0; return; } last_n = n; } if (!values) { assert(0); return; } if (values) { values[0] = rint(values[0]*1e6)/1e6; } if ( !DBL_EQUAL(north, values[0], tolerance) || !DBL_EQUAL(south, -values[0], tolerance) ) printf("N=%ld north=%f south=%f v(=gauss_lat[0])=%f north-v=%0.30f south-v=%0.30f\n", n,north,south,values[0],north-values[0],south+values[0]); CHECK(DBL_EQUAL(north, values[0], tolerance)); CHECK(DBL_EQUAL(south, -values[0], tolerance)); if(missing(h,"numberOfPointsAlongAParallel")) /* same as key Ni */ { /* If missing, this is a REDUCED gaussian grid */ const long MAXIMUM_RESOLUTION = 640; CHECK(get(h,"PLPresent")); CHECK(DBL_EQUAL(west, 0.0, tolerance)); if (n > MAXIMUM_RESOLUTION) { printf("Gaussian number N (=%ld) cannot exceed %ld\n", n, MAXIMUM_RESOLUTION); CHECK(n <= MAXIMUM_RESOLUTION); } } else { /* REGULAR gaussian grid */ long l_west = get(h,"longitudeOfFirstGridPoint"); long l_east = get(h,"longitudeOfLastGridPoint"); long parallel = get(h,"numberOfPointsAlongAParallel"); long we = get(h,"iDirectionIncrement"); double dwest = dget(h,"longitudeOfFirstGridPointInDegrees"); double deast = dget(h,"longitudeOfLastGridPointInDegrees"); double dwe = dget(h,"iDirectionIncrementInDegrees"); /*printf("parallel=%ld east=%ld west=%ld we=%ld\n",parallel,east,west,we);*/ CHECK(parallel == (l_east-l_west)/we + 1); CHECK(fabs((deast-dwest)/dwe + 1 - parallel) < 1e-10); CHECK(!get(h,"PLPresent")); } CHECK(ne(h,"Nj",0)); if(get(h,"PLPresent")) { size_t count, i, nPl; int err_code = grib_get_size(h,"pl",&count); long *pl = NULL; double expected_lon2 = 0; long total, max_pl = 0; long numberOfValues = get(h,"numberOfValues"); long numberOfDataPoints = get(h,"numberOfDataPoints"); if(err_code) { printf("%s, field %d [%s]: cannot get size of pl: %s\n",file,field,param,grib_get_error_message(err_code)); error++; return; } pl = (long*)malloc(sizeof(long)*count); CHECK(pl != NULL); if (!pl) return; nPl = count; if((err_code = grib_get_long_array(h, "pl", pl, &count))) { printf("%s, field %d [%s]: cannot get pl: %s\n",file,field,param,grib_get_error_message(err_code)); free(pl); error++; return; } if(nPl != count) printf("nPl=%ld count=%ld\n",(long)nPl,(long)count); CHECK(nPl == count); CHECK(nPl == (size_t)2*n); total = 0; max_pl = pl[0]; /* max elem of pl array = num points at equator */ for(i = 0 ; i < count; i++) { total += pl[i]; if (pl[i] > max_pl) max_pl = pl[i]; } free(pl); /* Do not assume maximum of pl array is 4N! not true for octahedral */ expected_lon2 = 360.0 - 360.0/max_pl; if (!DBL_EQUAL(expected_lon2, east, tolerance)) printf("east actual=%g expected=%g diff=%g\n",east, expected_lon2, expected_lon2-east); CHECK(DBL_EQUAL(expected_lon2, east, tolerance)); if(numberOfDataPoints != total) printf("GAUSS numberOfValues=%ld numberOfDataPoints=%ld sum(pl)=%ld\n", numberOfValues, numberOfDataPoints, total); CHECK(numberOfDataPoints == total); CHECK(missing(h,"iDirectionIncrement")); CHECK(missing(h,"iDirectionIncrementInDegrees")); CHECK(eq(h,"iDirectionIncrementGiven",0)); CHECK(eq(h,"jDirectionIncrementGiven",1)); } CHECK(eq(h,"resolutionAndComponentFlags1",0)); CHECK(eq(h,"resolutionAndComponentFlags2",0)); CHECK(eq(h,"resolutionAndComponentFlags6",0)); CHECK(eq(h,"resolutionAndComponentFlags7",0)); CHECK(eq(h,"resolutionAndComponentFlags8",0)); } static void check_validity_datetime(grib_handle* h) { /* If we just set the stepRange (for non-instantaneous fields) to its * current value, then this causes the validity date and validity time * keys to be correctly computed. * Then we can compare the previous (possibly wrongly coded) value with * the newly computed one */ char stepType[15]={0,}; int err = 0; size_t str_len = 100; err = grib_get_string(h, "stepType", stepType, &str_len); if (err) return; if (strcmp(stepType, "instant")!=0) { /* not instantaneous */ char stepRange[100]={0,}; long saved_validityDate, saved_validityTime; long validityDate, validityTime; /* Check only applies to accumulated, max etc. */ str_len = 100; err = grib_get_string(h, "stepRange", stepRange, &str_len); if (err) return; saved_validityDate = get(h, "validityDate"); saved_validityTime = get(h, "validityTime"); err = grib_set_string(h, "stepRange", stepRange, &str_len); if (err) return; validityDate = get(h, "validityDate"); validityTime = get(h, "validityTime"); if (validityDate!=saved_validityDate || validityTime!=saved_validityTime) { printf("warning: %s, field %d [%s]: invalid validity Date/Time (Should be %ld and %ld)\n", file,field,param, validityDate, validityTime); warning++; } } } static void check_range(grib_handle* h,const parameter* p,double min,double max) { double missing = 0; if(!valueflg) return; missing = dget(h,"missingValue"); /* See ECC-437 */ if (!(get(h,"bitMapIndicator") == 0 && min == missing && max == missing)) { if(min < p->min1 || min > p->min2) { printf("warning: %s, field %d [%s]: %s minimum value %g is not in [%g,%g]\n",file,field,param, p->name, min,p->min1,p->min2); printf(" => [%g,%g]\n",min < p->min1 ? min : p->min1, min > p->min2 ? min : p->min2); warning++; } if(max < p->max1 || max > p->max2 ) { printf("warning: %s, field %d [%s]: %s maximum value %g is not in [%g,%g]\n",file,field,param, p->name, max,p->max1,p->max2); printf(" => [%g,%g]\n",max < p->max1 ? max : p->max1, max > p->max2 ? max : p->max2); warning++; } } } static void point_in_time(grib_handle* h,const parameter* p,double min,double max) { switch(get(h,"typeOfProcessedData")) { case 0: /* Analysis */ if (is_uerra) CHECK(eq(h,"productDefinitionTemplateNumber",0)||eq(h,"productDefinitionTemplateNumber",1)); if (get(h,"productDefinitionTemplateNumber") == 1){ CHECK(ne(h,"numberOfForecastsInEnsemble",0)); CHECK(le(h,"perturbationNumber",get(h,"numberOfForecastsInEnsemble"))); } break; case 1: /* Forecast */ if (is_uerra) CHECK(eq(h,"productDefinitionTemplateNumber",0)||eq(h,"productDefinitionTemplateNumber",1)); if (get(h,"productDefinitionTemplateNumber") == 1){ CHECK(ne(h,"numberOfForecastsInEnsemble",0)); CHECK(le(h,"perturbationNumber",get(h,"numberOfForecastsInEnsemble"))); } break; case 2: /* Analysis and forecast products */ CHECK(eq(h,"productDefinitionTemplateNumber",0)); break; case 3: /* Control forecast products */ CHECK(eq(h,"perturbationNumber",0)); CHECK(ne(h,"numberOfForecastsInEnsemble",0)); if (is_s2s_refcst) CHECK(eq(h,"productDefinitionTemplateNumber",60)); else if (is_s2s) /*CHECK(eq(h,"productDefinitionTemplateNumber",60)||eq(h,"productDefinitionTemplateNumber",11)||eq(h,"productDefinitionTemplateNumber",1));*/ CHECK(eq(h,"productDefinitionTemplateNumber",1)); else CHECK(eq(h,"productDefinitionTemplateNumber",1)); break; case 4: /* Perturbed forecast products */ CHECK(ne(h,"perturbationNumber",0)); CHECK(ne(h,"numberOfForecastsInEnsemble",0)); if (is_s2s_refcst) CHECK(eq(h,"productDefinitionTemplateNumber",60)); else if (is_s2s) /*CHECK(eq(h,"productDefinitionTemplateNumber",60)||eq(h,"productDefinitionTemplateNumber",11)||eq(h,"productDefinitionTemplateNumber",1));*/ CHECK(eq(h,"productDefinitionTemplateNumber",1)); else CHECK(eq(h,"productDefinitionTemplateNumber",1)); if (is_lam) { CHECK(le(h,"perturbationNumber",get(h,"numberOfForecastsInEnsemble"))); } else { /* Is there always cf in tigge global datasets?? */ CHECK(le(h,"perturbationNumber",get(h,"numberOfForecastsInEnsemble")-1)); } break; default: printf("Unsupported typeOfProcessedData %ld\n",get(h,"typeOfProcessedData")); CHECK(0); break; } if (is_lam) { if(get(h,"indicatorOfUnitOfTimeRange") == 10 ) /* three hours */ { /* Three hourly is OK */ ; } else { CHECK(eq(h,"indicatorOfUnitOfTimeRange",1));/* Hours */ CHECK((get(h,"forecastTime") % 3) == 0); /* Every three hours */ } } else if (is_uerra) { if(get(h,"indicatorOfUnitOfTimeRange") == 1) /* hourly */ { CHECK((eq(h,"forecastTime",1)||eq(h,"forecastTime",2)||eq(h,"forecastTime",4)||eq(h,"forecastTime",5))||(get(h,"forecastTime") % 3) == 0); } } else { if(get(h,"indicatorOfUnitOfTimeRange") == 11) /* six hours */ { /* Six hourly is OK */ ; } else { CHECK(eq(h,"indicatorOfUnitOfTimeRange",1));/* Hours */ CHECK((get(h,"forecastTime") % 6) == 0); /* Every six hours */ } } check_range(h,p,min,max); } static void height_level(grib_handle* h,const parameter* p,double min,double max) { long level = get(h,"level"); if (is_uerra){ switch(level) { case 15: case 30: case 50: case 75: case 100: case 150: case 200: case 250: case 300: case 400: case 500: break; default: printf("%s, field %d [%s]: invalid height level %ld\n",file,field,param,level); error++; break; } } } static void pressure_level(grib_handle* h,const parameter* p,double min,double max) { long level = get(h,"level"); if (is_uerra && !is_crra){ switch(level) { case 1000: case 975: case 950: case 925: case 900: case 875: case 850: case 825: case 800: case 750: case 700: case 600: case 500: case 400: case 300: case 250: case 200: case 150: case 100: case 70: case 50: case 30: case 20: case 10: break; default: printf("%s, field %d [%s]: invalid pressure level %ld\n",file,field,param,level); error++; break; } } else if (is_uerra && is_crra){ switch(level) { case 1000: case 975: case 950: case 925: case 900: case 875: case 850: case 825: case 800: case 750: case 700: case 600: case 500: case 400: case 300: case 250: case 200: case 150: case 100: case 70: case 50: case 30: case 20: case 10: case 7: case 5: case 3: case 2: case 1: break; default: printf("%s, field %d [%s]: invalid pressure level %ld\n",file,field,param,level); error++; break; } } else if (is_s2s){ switch(level) { case 1000: case 925: case 850: case 700: case 500: case 300: case 200: case 100: case 50: case 10: break; default: printf("%s, field %d [%s]: invalid pressure level %ld\n",file,field,param,level); error++; break; } } else { switch(level) { case 1000: case 200: case 250: case 300: case 500: case 700: case 850: case 925: case 50: break; default: printf("%s, field %d [%s]: invalid pressure level %ld\n",file,field,param,level); error++; break; } } } static void potential_vorticity_level(grib_handle* h,const parameter* p,double min,double max) { long level = get(h,"level"); switch(level) { case 2: break; default: printf("%s, field %d [%s]: invalid potential vorticity level %ld\n",file,field,param,level); error++; break; } } static void potential_temperature_level(grib_handle* h,const parameter* p,double min,double max) { long level = get(h,"level"); switch(level) { case 320: break; default: printf("%s, field %d [%s]: invalid potential temperature level %ld\n",file,field,param,level); error++; break; } } static void statistical_process(grib_handle* h,const parameter* p,double min,double max) { switch(get(h,"typeOfProcessedData")) { case 0: /* Analysis */ if (is_uerra) CHECK(eq(h,"productDefinitionTemplateNumber",8)||eq(h,"productDefinitionTemplateNumber",11)); break; case 1: /* Forecast */ if (is_uerra) CHECK(eq(h,"productDefinitionTemplateNumber",8)||eq(h,"productDefinitionTemplateNumber",11)); break; case 2: /* Analysis and forecast products */ CHECK(eq(h,"productDefinitionTemplateNumber",8)); break; case 3: /* Control forecast products */ if (!is_s2s_refcst) CHECK(eq(h,"productDefinitionTemplateNumber",11)); else CHECK(eq(h,"productDefinitionTemplateNumber",61)); break; case 4: /* Perturbed forecast products */ if (!is_s2s_refcst) CHECK(eq(h,"productDefinitionTemplateNumber",11)); else CHECK(eq(h,"productDefinitionTemplateNumber",61)); break; default: printf("Unsupported typeOfProcessedData %ld\n",get(h,"typeOfProcessedData")); error++; return; break; } if (is_lam) { if(get(h,"indicatorOfUnitOfTimeRange") == 10 ) /* three hours */ { /* Three hourly is OK */ ; } else { CHECK(eq(h,"indicatorOfUnitOfTimeRange",1));/* Hours */ CHECK((get(h,"forecastTime") % 3) == 0); /* Every three hours */ } } else if (is_uerra) { /* forecastTime for uerra might be all steps decreased by 1 i.e 0,1,2,3,4,5,8,11...29 too many... */ if(get(h,"indicatorOfUnitOfTimeRange") == 1) { CHECK(le(h,"forecastTime",30)); } } else { if(get(h,"indicatorOfUnitOfTimeRange") == 11) /* six hours */ { /* Six hourly is OK */ ; } else { CHECK(eq(h,"indicatorOfUnitOfTimeRange",1));/* Hours */ CHECK((get(h,"forecastTime") % 6) == 0); /* Every six hours */ } } CHECK(eq(h,"numberOfTimeRange",1)); CHECK(eq(h,"numberOfMissingInStatisticalProcess",0)); CHECK(eq(h,"typeOfTimeIncrement",2)); /*CHECK(eq(h,"indicatorOfUnitOfTimeForTheIncrementBetweenTheSuccessiveFieldsUsed",255));*/ if (is_s2s) if(get(h,"typeOfStatisticalProcessing") == 0) CHECK(eq(h,"timeIncrementBetweenSuccessiveFields",1)||eq(h,"timeIncrementBetweenSuccessiveFields",4)); else CHECK(eq(h,"timeIncrementBetweenSuccessiveFields",0)); else CHECK(eq(h,"timeIncrementBetweenSuccessiveFields",0)); CHECK(eq(h,"minuteOfEndOfOverallTimeInterval",0)); CHECK(eq(h,"secondOfEndOfOverallTimeInterval",0)); if (is_uerra) { CHECK((eq(h,"endStep",1)||eq(h,"endStep",2)||eq(h,"endStep",4)||eq(h,"endStep",5))||(get(h,"endStep") % 3) == 0); } else if (is_lam) { CHECK((get(h,"endStep") % 3) == 0); /* Every three hours */ } else { CHECK((get(h,"endStep") % 6) == 0); /* Every six hours */ } if(get(h,"indicatorOfUnitForTimeRange") == 11) { /* Six hourly is OK */ CHECK(get(h,"lengthOfTimeRange")*6 + get(h,"startStep") == get(h,"endStep")); } else if(get(h,"indicatorOfUnitForTimeRange") == 10) { /* Three hourly is OK */ CHECK(get(h,"lengthOfTimeRange")*3 + get(h,"startStep") == get(h,"endStep")); } else { CHECK(eq(h,"indicatorOfUnitForTimeRange",1)); /* Hours */ CHECK(get(h,"lengthOfTimeRange") + get(h,"startStep") == get(h,"endStep")); } } static void has_bitmap(grib_handle* h,const parameter* p,double min,double max) { /* printf("bitMapIndicator %ld\n",get(h,"bitMapIndicator")); */ CHECK(eq(h,"bitMapIndicator",0)); } static void has_soil_level(grib_handle* h,const parameter* p,double min,double max) { CHECK(get(h,"topLevel") == get(h,"bottomLevel")); CHECK(le(h,"level",14)); /* max in UERRA */ } static void has_soil_layer(grib_handle* h,const parameter* p,double min,double max) { CHECK(get(h,"topLevel") == get(h,"bottomLevel") - 1); CHECK(le(h,"level",14)); /* max in UERRA */ } static void resolution_s2s(grib_handle* h,const parameter* p,double min,double max) { CHECK(eq(h,"iDirectionIncrement",1500000)); CHECK(eq(h,"jDirectionIncrement",1500000)); } static void resolution_s2s_ocean(grib_handle* h,const parameter* p,double min,double max) { CHECK(eq(h,"iDirectionIncrement",1000000)); CHECK(eq(h,"jDirectionIncrement",1000000)); } static void six_hourly(grib_handle* h,const parameter* p,double min,double max) { statistical_process(h,p,min,max); if(get(h,"indicatorOfUnitForTimeRange") == 11) CHECK(eq(h,"lengthOfTimeRange",1)); else CHECK(eq(h,"lengthOfTimeRange",6)); CHECK(get(h,"endStep") == get(h,"startStep") + 6); check_range(h,p,min,max); } static void since_prev_pp(grib_handle* h,const parameter* p,double min,double max) { statistical_process(h,p,min,max); CHECK(eq(h,"indicatorOfUnitForTimeRange",1)); CHECK(get(h,"endStep") == get(h,"startStep") + get(h,"lengthOfTimeRange")); check_range(h,p,min,max); } static void three_hourly(grib_handle* h,const parameter* p,double min,double max) { statistical_process(h,p,min,max); if(get(h,"indicatorOfUnitForTimeRange") == 11) CHECK(eq(h,"lengthOfTimeRange",1)); else CHECK(eq(h,"lengthOfTimeRange",3)); CHECK(get(h,"endStep") == get(h,"startStep") + 3); check_range(h,p,min,max); } static void from_start(grib_handle* h,const parameter* p,double min,double max) { long step = get(h,"endStep"); statistical_process(h,p,min,max); CHECK(eq(h,"startStep",0)); if(step == 0){ if(!is_uerra){ CHECK(min == 0 && max == 0); /* ??? xxx */ } } else { check_range(h,p,min/step,max/step); } } static void daily_average(grib_handle* h,const parameter* p,double min,double max) { long step = get(h,"endStep"); CHECK(get(h,"startStep") == get(h,"endStep") - 24); statistical_process(h,p,min,max); if(step == 0) CHECK(min == 0 && max == 0); else { check_range(h,p,min,max); } } static void given_level(grib_handle* h,const parameter* p,double min,double max) { CHECK(ne(h,"typeOfFirstFixedSurface",255)); CHECK(!missing(h,"scaleFactorOfFirstFixedSurface")); CHECK(!missing(h,"scaledValueOfFirstFixedSurface")); CHECK(eq(h,"typeOfSecondFixedSurface",255)); CHECK(missing(h,"scaleFactorOfSecondFixedSurface")); CHECK(missing(h,"scaledValueOfSecondFixedSurface")); } static void predefined_level(grib_handle* h,const parameter* p,double min,double max) { CHECK(ne(h,"typeOfFirstFixedSurface",255)); CHECK(missing(h,"scaleFactorOfFirstFixedSurface")); CHECK(missing(h,"scaledValueOfFirstFixedSurface")); CHECK(eq(h,"typeOfSecondFixedSurface",255)); CHECK(missing(h,"scaleFactorOfSecondFixedSurface")); CHECK(missing(h,"scaledValueOfSecondFixedSurface")); } static void predefined_thickness(grib_handle* h,const parameter* p,double min,double max) { CHECK(ne(h,"typeOfFirstFixedSurface",255)); CHECK(missing(h,"scaleFactorOfFirstFixedSurface")); CHECK(missing(h,"scaledValueOfFirstFixedSurface")); CHECK(ne(h,"typeOfSecondFixedSurface",255)); CHECK(missing(h,"scaleFactorOfSecondFixedSurface")); CHECK(missing(h,"scaledValueOfSecondFixedSurface")); } static void given_thickness(grib_handle* h,const parameter* p,double min,double max) { CHECK(ne(h,"typeOfFirstFixedSurface",255)); CHECK(!missing(h,"scaleFactorOfFirstFixedSurface")); CHECK(!missing(h,"scaledValueOfFirstFixedSurface")); CHECK(ne(h,"typeOfSecondFixedSurface",255)); CHECK(!missing(h,"scaleFactorOfSecondFixedSurface")); CHECK(!missing(h,"scaledValueOfSecondFixedSurface")); } static void latlon_grid(grib_handle* h) { const double tolerance = 1.0/1000000.0; /* angular tolerance for grib2: micro degrees */ long data_points = get(h,"numberOfDataPoints"); long meridian = get(h,"numberOfPointsAlongAMeridian"); long parallel = get(h,"numberOfPointsAlongAParallel"); long north = get(h,"latitudeOfFirstGridPoint"); long south = get(h,"latitudeOfLastGridPoint"); long west = get(h,"longitudeOfFirstGridPoint"); long east = get(h,"longitudeOfLastGridPoint"); long ns = get(h,"jDirectionIncrement"); long we = get(h,"iDirectionIncrement"); double dnorth = dget(h,"latitudeOfFirstGridPointInDegrees"); double dsouth = dget(h,"latitudeOfLastGridPointInDegrees"); double dwest = dget(h,"longitudeOfFirstGridPointInDegrees"); double deast = dget(h,"longitudeOfLastGridPointInDegrees"); double dns = dget(h,"jDirectionIncrementInDegrees"); double dwe = dget(h,"iDirectionIncrementInDegrees"); if(eq(h,"basicAngleOfTheInitialProductionDomain",0)) { CHECK(missing(h,"subdivisionsOfBasicAngle")); } else { /* long basic = get(h,"basicAngleOfTheInitialProductionDomain"); */ /* long division = get(h,"subdivisionsOfBasicAngle"); */ CHECK(!missing(h,"subdivisionsOfBasicAngle")); CHECK(!eq(h,"subdivisionsOfBasicAngle",0)); } if(missing(h,"subdivisionsOfBasicAngle")) { CHECK(eq(h,"basicAngleOfTheInitialProductionDomain",0)); } CHECK(meridian*parallel == data_points); CHECK(eq(h,"resolutionAndComponentFlags1",0)); CHECK(eq(h,"resolutionAndComponentFlags2",0)); CHECK(eq(h,"resolutionAndComponentFlags6",0)); CHECK(eq(h,"resolutionAndComponentFlags7",0)); CHECK(eq(h,"resolutionAndComponentFlags8",0)); CHECK(eq(h,"iDirectionIncrementGiven",1)); CHECK(eq(h,"jDirectionIncrementGiven",1)); CHECK(eq(h,"numberOfOctectsForNumberOfPoints",0)); CHECK(eq(h,"interpretationOfNumberOfPoints",0)); if(get(h,"iScansNegatively")) { long tmp = east; double dtmp = deast; east = west; west = tmp; deast = dwest; dwest = dtmp; } if(get(h,"jScansPositively")) { long tmp = north; double dtmp = dnorth; north = south; south = tmp; dnorth = dsouth; dsouth = dtmp; } if (!(is_lam || is_uerra)) { double area, globe; CHECK(north > south); CHECK(east > west); /* Check that the grid is symmetrical */ CHECK(north == -south); CHECK( DBL_EQUAL(dnorth, -dsouth, tolerance) ); CHECK(parallel == (east-west)/we + 1); CHECK(fabs((deast-dwest)/dwe + 1 - parallel) < 1e-10); CHECK(meridian == (north-south)/ns + 1); CHECK(fabs((dnorth-dsouth)/dns + 1 - meridian) < 1e-10 ); /* Check that the field is global */ area = (dnorth-dsouth) * (deast-dwest); globe = 360.0*180.0; CHECK(area <= globe); CHECK(area >= globe*0.95); } /* GRIB2 requires longitudes are always positive */ CHECK(east >= 0); CHECK(west >= 0); /* printf("meridian=%ld north=%ld south=%ld ns=%ld \n",meridian,north,south,ns); printf("meridian=%ld north=%f south=%f ns=%f \n",meridian,dnorth,dsouth,dns); printf("parallel=%ld east=%ld west=%ld we=%ld \n",parallel,east,west,we); printf("parallel=%ld east=%f west=%f we=%f \n",parallel,deast,dwest,dwe); */ } #define X(x) printf("%s=%ld ",#x,get(h,#x)) static void check_parameter(grib_handle* h,double min,double max) { int err; int best = -1; int match = -1; size_t i = 0; for(i = 0; i < NUMBER(parameters); i++) { int j = 0; int matches = 0; while(parameters[i].pairs[j].key != NULL) { long val = -1; const int ktype = parameters[i].pairs[j].key_type; if (ktype == GRIB_TYPE_LONG) { if(grib_get_long(h,parameters[i].pairs[j].key,&val) == GRIB_SUCCESS) { if(parameters[i].pairs[j].value_long == val) { matches++; } } } else if (ktype == GRIB_TYPE_STRING) { char strval[256]={0,}; size_t len = 256; if (is_uerra && strcasecmp(parameters[i].pairs[j].key,"model")==0) { /* printf("Skipping model keyword for UERRA class\n"); */ matches++; /*xxx hack to pretend that model key was matched.. */ } else { if (strcasecmp(parameters[i].pairs[j].value_string,"MISSING")==0) { int is_miss = grib_is_missing(h, parameters[i].pairs[j].key, &err); if (err == GRIB_SUCCESS && is_miss) { matches++; } } else if(grib_get_string(h,parameters[i].pairs[j].key,strval,&len) == GRIB_SUCCESS) { if(strcmp(parameters[i].pairs[j].value_string, strval) == 0) { matches++; } } } } else { assert(!"Unknown key type"); } j++; #if 0 printf("%s %s %ld val -> %d %d %d\n", parameters[i].pairs[j].key, parameters[i].pairs[j].value_string, val, matches, j, best); #endif } if(matches == j && matches > best) { best = matches; match = i; } } if(match >= 0) { /*int j = 0;*/ param = parameters[match].name; i = 0; while(parameters[match].checks[i]) (*parameters[match].checks[i++])(h,¶meters[match],min,max); /* printf("=========================\n"); printf("%s -> %d %d\n",param, match, best); while(parameters[match].pairs[j].key != NULL) { printf("%s val -> %ld %d\n",parameters[match].pairs[j].key,parameters[match].pairs[j].value,j); j++; } printf("matched parameter: %s\n", param); */ } else { printf("%s, field %d [%s]: cannot match parameter\n",file,field,param); X(origin); X(discipline); X(parameterCategory); X(parameterNumber); X(typeOfFirstFixedSurface); X(scaleFactorOfFirstFixedSurface); X(scaledValueOfFirstFixedSurface); X(typeOfSecondFixedSurface); X(scaleFactorOfSecondFixedSurface); X(scaledValueOfSecondFixedSurface); printf("\n"); error++; } } static void check_packing(grib_handle* h) { /* ECC-1009: Warn if not using simple packing */ int err = 0; char packingType[254] = {0,}; size_t len = sizeof(packingType); const char* expected_packingType = "grid_simple"; err = grib_get_string(h, "packingType", packingType, &len); if (err) return; if (strcmp(packingType, expected_packingType)!=0) { printf("warning: %s, field %d [%s]: invalid packingType %s (Should be %s)\n", file, field, param, packingType, expected_packingType); warning++; } } static void verify(grib_handle* h) { double min = 0,max = 0; CHECK(eq(h,"editionNumber",2)); CHECK(missing(h,"reserved") || eq(h,"reserved",0)); if (valueflg) { size_t count, n; int e = grib_get_size(h,"values",&count); double *values; size_t i; int bitmap = !eq(h,"bitMapIndicator",255); if(e) { printf("%s, field %d [%s]: cannot number of values: %s\n",file,field,param,grib_get_error_message(e)); error++; return; } values = (double*)malloc(sizeof(double)*(count)); if(!values) { printf("%s, field %d [%s]: failed to allocate %ld bytes\n",file,field,param,(long)(sizeof(double)*count)); error++; return; } CHECK(eq(h,"numberOfDataPoints",count)); n = count; if((e = grib_get_double_array(h,"values",values,&count))) { printf("%s, field %d [%s]: cannot get values: %s\n",file,field,param,grib_get_error_message(e)); free(values); error++; return; } if(n != count) { printf("%s, field %d [%s]: value count changed %ld -> %ld\n",file,field,param,(long)count,(long)n); free(values); error++; return; } if(bitmap) { double missing = dget(h,"missingValue"); min = max = missing; for(i = 0; i < count ; i++) { if((min == missing) || ((values[i] != missing) && (min>values[i]))) min = values[i]; if((max == missing) || ((values[i] != missing) && (maxvalues[i]) min = values[i]; if(max 20060101); */ CHECK( (date / 10000) == get(h,"year")); CHECK( ((date % 10000) / 100) == get(h,"month")); CHECK( ((date % 100)) == get(h,"day")); } } if (is_uerra){ CHECK(le(h,"hour",24)); } else if (is_lam){ CHECK(eq(h,"hour",0) || eq(h,"hour",3) || eq(h,"hour",6) || eq(h,"hour",9) || eq(h,"hour",12) || eq(h,"hour",15) || eq(h,"hour",18) || eq(h,"hour",21)); } else { /* Only 00, 06 12 and 18 Cycle OK */ CHECK(eq(h,"hour",0) || eq(h,"hour",6) || eq(h,"hour",12) || eq(h,"hour",18)); } CHECK(eq(h,"minute",0)); CHECK(eq(h,"second",0)); CHECK(ge(h,"startStep",0)); if (is_s2s){ CHECK(eq(h,"productionStatusOfProcessedData",6)||eq(h,"productionStatusOfProcessedData",7)); /* S2S prod||test */ CHECK(le(h,"endStep",100*24)); } else if (!is_uerra) { CHECK(eq(h,"productionStatusOfProcessedData",4)||eq(h,"productionStatusOfProcessedData",5)); /* TIGGE prod||test */ CHECK(le(h,"endStep",30*24)); } if (is_uerra){ CHECK((eq(h,"step",1)||eq(h,"step",2)||eq(h,"step",4)||eq(h,"step",5))||(get(h,"step") % 3) == 0); } else if (is_lam) { CHECK((get(h,"step") % 3) == 0); } else { CHECK((get(h,"step") % 6) == 0); } if (is_uerra){ if (is_crra){ CHECK(eq(h,"productionStatusOfProcessedData",10)||eq(h,"productionStatusOfProcessedData",11)); /* CRRA prod||test */ } else { CHECK(eq(h,"productionStatusOfProcessedData",8)||eq(h,"productionStatusOfProcessedData",9)); /* UERRA prod||test */ } CHECK(le(h,"endStep",30)); /* 0 = analysis , 1 = forecast */ CHECK(eq(h,"typeOfProcessedData",0)||eq(h,"typeOfProcessedData",1)); if (get(h,"typeOfProcessedData") == 0) CHECK(eq(h,"step",0)); else CHECK((eq(h,"step",1)||eq(h,"step",2)||eq(h,"step",4)||eq(h,"step",5))||(get(h,"step") % 3) == 0); } else { /* 2 = analysis or forecast , 3 = control forecast, 4 = perturbed forecast */ CHECK(eq(h,"typeOfProcessedData",2)||eq(h,"typeOfProcessedData",3)||eq(h,"typeOfProcessedData",4)); } /* TODO: validate local usage. Empty for now xxx*/ /* CHECK(eq(h,"section2.sectionLength",5)); */ /* Section 3 */ CHECK(eq(h,"sourceOfGridDefinition",0)); /* Specified in Code table 3.1 */ switch(get(h,"gridDefinitionTemplateNumber")) { case 0: case 1: /*rotated latlon*/ latlon_grid(h); break; case 30: /*Lambert conformal*/ /*lambert_grid(h); # TODO xxx printf("warning: Lambert grid - geometry checking not implemented yet!\n"); */ /*CHECK(eq(h,"scanningMode",64));*/ /* M-F data used to have it wrong.. but it might depends on other projection set up as well!*/ break; case 40: /* gaussian grid (regular or reduced) */ gaussian_grid(h); break; default: printf("%s, field %d [%s]: Unsupported gridDefinitionTemplateNumber %ld\n", file,field,param, get(h,"gridDefinitionTemplateNumber")); error++; return; break; } /* If there is no bitmap, this should be true */ /* CHECK(eq(h,"bitMapIndicator",255));*/ if(eq(h,"bitMapIndicator",255)) CHECK(get(h,"numberOfValues") == get(h,"numberOfDataPoints")); else CHECK(get(h,"numberOfValues") <= get(h,"numberOfDataPoints")); /* Check values */ CHECK(eq(h,"typeOfOriginalFieldValues",0)); /* Floating point */ check_validity_datetime(h); /* do not store empty values e.g. fluxes at step 0 todo ?? now it's allowed in the code here! if(!missing(h,"typeOfStatisticalProcessing")) CHECK(ne(h,"stepRange",0));*/ } void validate(const char* path) { FILE *f = fopen(path,"rb"); grib_handle *h = 0; int err; int count = 0; file = path; field = 0; if(!f) { printf("%s: %s\n",path,strerror(errno)); error++; return; } while( (h= grib_handle_new_from_file(0,f,&err)) != NULL) { int last_error = error; int last_warning = warning; ++field; verify(h); if((last_error != error) || (warnflg && (last_warning != warning))) save(h,bad,fbad); else save(h,good,fgood); grib_handle_delete(h); count++; param = "unknown"; } fclose(f); if(err) { printf("%s: grib_handle_new_from_file: %s\n",path,grib_get_error_message(err)); error++; return; } if(count == 0) { printf("%s does not contain any GRIBs\n",path); error++; return; } } static void usage() { printf("tigge_check [options] grib_file grib_file ...\n"); printf(" -l: check local area model fields\n"); printf(" -v: check value ranges\n"); printf(" -w: warnings are treated as errors\n"); printf(" -g: write good gribs\n"); printf(" -b: write bad gribs\n"); printf(" -z: return 0 to calling shell\n"); printf(" -s: check s2s fields\n"); printf(" -r: check s2s reforecast fields\n"); printf(" -u: check uerra fields\n"); printf(" -c: check crra fields (-u must be also used in this case)\n"); exit(1); } int main(int argc, char** argv) { int i; int err = 0; for(i = 1; i < argc; i++) { if(argv[i][0] == '-') { switch(argv[i][1]) { case 'w': warnflg++; break; case 'z': zeroflg++; break; case 'v': valueflg++; break; case 'g': if(++i == argc) usage(); good = argv[i]; fgood = fopen(good,"w"); if(!fgood) { perror(good); exit(1); } break; case 'b': if(++i == argc) usage(); bad = argv[i]; fbad = fopen(bad,"w"); if(!fbad) { perror(bad); exit(1); } break; case 'l': is_lam=1; break; case 's': is_s2s=1; break; case 'r': is_s2s_refcst=1; break; case 'u': is_uerra=1; break; case 'c': is_crra=1; break; default: usage(); break; } } else { break; } } if(i == argc) usage(); for(; i < argc; i++) { error = 0; scan(argv[i]); if(error) err = 1; if(warning && warnflg) err = 1; } if(fgood && fclose(fgood)) { perror(good); exit(1); } if(fbad && fclose(fbad )) { perror(bad ); exit(1); } return zeroflg ? 0: err; }