mirror of https://github.com/ecmwf/eccodes.git
1553 lines
44 KiB
C++
Executable File
1553 lines
44 KiB
C++
Executable File
/*
|
|
* (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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <grib_api.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <sys/types.h>
|
|
|
|
#ifndef ECCODES_ON_WINDOWS
|
|
#include <dirent.h>
|
|
#else
|
|
#include <direct.h>
|
|
#include <io.h>
|
|
|
|
#ifdef _MSC_VER
|
|
# define strcasecmp _stricmp
|
|
#endif
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#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) && (max<values[i]))) max = values[i];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
min = max = values[0];
|
|
for(i = 1; i < count ; i++)
|
|
{
|
|
if(min>values[i]) min = values[i];
|
|
if(max<values[i]) max = values[i];
|
|
}
|
|
}
|
|
free(values);
|
|
}
|
|
|
|
check_parameter(h,min,max);
|
|
|
|
check_packing(h);
|
|
|
|
/* Section 1 */
|
|
|
|
CHECK(ge(h,"gribMasterTablesVersionNumber",4));
|
|
CHECK(eq(h,"versionNumberOfGribLocalTables",0)); /* Local tables not used */
|
|
|
|
CHECK(eq(h,"significanceOfReferenceTime",1)); /* Start of forecast */
|
|
|
|
if (!is_s2s){
|
|
/* todo check for how many years back the reforecast is done? Is it coded in the grib??? */
|
|
/* Check if the date is OK */
|
|
{
|
|
long date = get(h,"date");
|
|
/* CHECK(date > 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;
|
|
}
|