1 /*********************************************************************
2  * Copyright 2005, UCAR/Unidata See COPYRIGHT file for copying and
3  * redistribution conditions.
4  *
5  * This runs the C++ tests for netCDF.
6  *
7  * $Id: nctst.cpp,v 1.28 2007/04/02 21:01:12 ed Exp $
8  *********************************************************************/
9 
10 #include <config.h>
11 #include <iostream>
12 using namespace std;
13 
14 #include <string.h>
15 #include "netcdfcpp.h"
16 
17 const char LAT[] = "lat";
18 const char LON[] = "lon";
19 const char FRTIME[] = "frtime";
20 const char TIMELEN1[] = "timelen";
21 const char P_NAME[] = "P";
22 const char PRES_MAX_WIND[] = "pressure at maximum wind";
23 const char LONG_NAME[] = "long_name";
24 const char UNITS[] = "units";
25 const char VALID_RANGE[] = "valid_range";
26 const char FILL_VALUE[] = "_FillValue";
27 const char DEGREES_NORTH[] = "degrees_north";
28 const char LONGITUDE[] = "longitude";
29 const char LATITUDE[] = "latitude";
30 const char HECTOPASCALS[] = "hectopascals";
31 const char DEGREES_EAST[] = "degrees_east";
32 const char HOURS[] = "hours";
33 const char FORECAST_TIME[] = "forecast time";
34 const char REFERENCE_TIME[] = "reference time";
35 const char REFTIME[] = "reftime";
36 const char TEXT_TIME[] = "text_time";
37 const char SCALARV[] = "scalarv";
38 const char SCALAR_ATT[] = "scalar_att";
39 const int SCALAR_VALUE = 1;
40 const char HISTORY[] = "history";
41 const char TITLE[] = "title";
42 const char HISTORY_STR[] = "created by Unidata LDM from NPS broadcast";
43 const char TITLE_STR[] = "NMC Global Product Set: Pressure at Maximum Wind";
44 
45 const int NC_ERR = 2;
46 const int NLATS = 4;
47 const int NLONS = 3;
48 const int NFRTIMES = 2;
49 const int TIMESTRINGLEN = 20;
50 const int NRANGES = 2;
51 
52 // These are data values written out by the gen() function, and read
53 // in again and checked by the read() function.
54 static float range[] = {0., 1500.};
55 static float lats[NLATS] = {-90, -87.5, -85, -82.5};
56 static float lons[NLONS] = {-180, -175, -170};
57 static int frtimes[NFRTIMES] = {12, 18};
58 static const char* s = "1992-3-21 12:00";
59 static float fill_value = -9999.0;
60 static float P_data[NFRTIMES][NLATS][NLONS] = {
61    {{950, 951, 952}, {953, 954, 955}, {956, 957, 958}, {959, 960, 961}},
62    {{962, 963, 964}, {965, 966, 967}, {968, 969, 970}, {971, 972, 973}}
63 };
64 
65 
66 // Check a string attribute to make sure it has the correct value.
67 int
check_string_att(NcAtt * att,const char * theName,const char * value)68 check_string_att(NcAtt *att, const char *theName, const char *value)
69 {
70    if (!att->is_valid() || strncmp(att->name(), theName, strlen(theName)) ||
71        att->type() != ncChar || att->num_vals() != (long)strlen(value))
72       return NC_ERR;
73 
74    char *value_in = att->as_string(0);
75    if (strncmp(value_in, value, strlen(value)))
76       return NC_ERR;
77    delete value_in;
78 
79    return 0;
80 }
81 
82 // Check the units and long_name attributes of a var to make sure they
83 // are what is expected.
84 int
check_u_ln_atts(NcVar * var,const char * units,const char * long_name)85 check_u_ln_atts(NcVar *var, const char *units, const char *long_name)
86 {
87    NcAtt *att;
88 
89    if (!(att = var->get_att(UNITS)))
90       return NC_ERR;
91    if (check_string_att(att, UNITS, units))
92       return NC_ERR;
93    delete att;
94 
95    if (!(att = var->get_att(LONG_NAME)))
96       return NC_ERR;
97    if (check_string_att(att, LONG_NAME, long_name))
98       return NC_ERR;
99    delete att;
100 
101    return 0;
102 }
103 
104 // This reads the netCDF file created by gen() and ensures that
105 // everything is there as expected.
read(const char * path,NcFile::FileFormat format)106 int read(const char* path, NcFile::FileFormat format)
107 {
108    NcAtt *att;
109 
110    // open the file
111    NcFile nc(path);
112 
113    if (!nc.is_valid())
114       return NC_ERR;
115 
116    // Check the format.
117    if (nc.get_format() != format)
118       return NC_ERR;
119 
120    // Check the numbers of things.
121    if (nc.num_dims() != 4 || nc.num_vars() != 6 ||
122        nc.num_atts() != 2)
123       return NC_ERR;
124 
125    // Check the global attributes.
126    if (!(att = nc.get_att(HISTORY)))
127       return NC_ERR;
128    if (check_string_att(att, HISTORY, HISTORY_STR))
129       return NC_ERR;
130    delete att;
131 
132    if (!(att = nc.get_att(TITLE)))
133       return NC_ERR;
134    if (check_string_att(att, TITLE, TITLE_STR))
135       return NC_ERR;
136    delete att;
137 
138    // Check the dimensions.
139    NcDim *latDim, *lonDim, *frtimeDim, *timeLenDim;
140 
141    if (!(latDim = nc.get_dim(LAT)))
142       return NC_ERR;
143    if (!latDim->is_valid() || strncmp(latDim->name(), LAT, strlen(LAT)) ||
144        latDim->size() != NLATS || latDim->is_unlimited())
145       return NC_ERR;
146 
147    if (!(lonDim = nc.get_dim(LON)))
148       return NC_ERR;
149    if (!lonDim->is_valid() || strncmp(lonDim->name(), LON, strlen(LON)) ||
150        lonDim->size() != NLONS || lonDim->is_unlimited())
151       return NC_ERR;
152 
153    if (!(frtimeDim = nc.get_dim(FRTIME)))
154       return NC_ERR;
155    if (!frtimeDim->is_valid() || strncmp(frtimeDim->name(), FRTIME, strlen(FRTIME)) ||
156        frtimeDim->size() != 2 || !frtimeDim->is_unlimited())
157       return NC_ERR;
158 
159    if (!(timeLenDim = nc.get_dim(TIMELEN1)))
160       return NC_ERR;
161    if (!timeLenDim->is_valid() || strncmp(timeLenDim->name(), TIMELEN1, strlen(TIMELEN1)) ||
162        timeLenDim->size() != TIMESTRINGLEN || timeLenDim->is_unlimited())
163       return NC_ERR;
164 
165    // Check the coordinate variables.
166    NcVar *latVar, *lonVar, *frtimeVar, *refTimeVar;
167 
168    // Get the latitude.
169    if (!(latVar = nc.get_var(LAT)))
170       return NC_ERR;
171 
172    // Check units and long name.
173    if (check_u_ln_atts(latVar, DEGREES_NORTH, LATITUDE))
174       return NC_ERR;
175 
176    // Get the longitude.
177    if (!(lonVar = nc.get_var(LON)))
178       return NC_ERR;
179 
180    // Check units and long name.
181    if (check_u_ln_atts(lonVar, DEGREES_EAST, LONGITUDE))
182       return NC_ERR;
183 
184    // Get the forecast time coordinate variable.
185    if (!(frtimeVar = nc.get_var(FRTIME)))
186       return NC_ERR;
187 
188    // Check units and long name.
189    if (check_u_ln_atts(frtimeVar, HOURS, FORECAST_TIME))
190       return NC_ERR;
191 
192    // Get the refTime coordinate variable.
193    if (!(refTimeVar = nc.get_var(REFTIME)))
194       return NC_ERR;
195 
196    // Check units and long name.
197    if (check_u_ln_atts(refTimeVar, TEXT_TIME, REFERENCE_TIME))
198       return NC_ERR;
199 
200    // Check the data variables.
201    NcVar *pVar, *scalarVar;
202 
203    if (!(pVar = nc.get_var(P_NAME)))
204       return NC_ERR;
205 
206    // Check units and long name.
207    if (check_u_ln_atts(pVar, HECTOPASCALS, PRES_MAX_WIND))
208       return NC_ERR;
209 
210    // Check the valid range, and check the values.
211    if (!(att = pVar->get_att(VALID_RANGE)))
212       return NC_ERR;
213    if (!att->is_valid() || strncmp(att->name(), VALID_RANGE, strlen(VALID_RANGE)) ||
214        att->type() != ncFloat || att->num_vals() != NRANGES)
215       return NC_ERR;
216    float range_in[NRANGES] = {att->as_float(0), att->as_float(1)};
217    if (range_in[0] != range[0] || range_in[1] != range[1])
218       return NC_ERR;
219    delete att;
220 
221    // Check the fill value, and check the value.
222    if (!(att = pVar->get_att(FILL_VALUE)))
223       return NC_ERR;
224    if (!att->is_valid() || strncmp(att->name(), FILL_VALUE, strlen(FILL_VALUE)) ||
225        att->type() != ncFloat || att->num_vals() != 1)
226       return NC_ERR;
227    float fill_value_in = att->as_float(0);
228    if (fill_value_in != fill_value)
229       return NC_ERR;
230    delete att;
231 
232    // Check the data in the pressure variable.
233    float P_data_in[NFRTIMES][NLATS][NLONS];
234    pVar->get(&P_data_in[0][0][0], NFRTIMES, NLATS, NLONS);
235    for (int f = 0; f < NFRTIMES; f++)
236       for (int la = 0; la < NLATS; la++)
237 	 for (int lo = 0; lo < NLONS; lo++)
238 	    if (P_data_in[f][la][lo] != P_data[f][la][lo])
239 	       return NC_ERR;
240 
241    // Get the scalar variable.
242    if (!(scalarVar = nc.get_var(SCALARV)))
243       return NC_ERR;
244 
245    // Check for the scalar attribute of the scalar variable and check its value.
246    if (!(att = scalarVar->get_att(SCALAR_ATT)))
247       return NC_ERR;
248    if (!att->is_valid() || strncmp(att->name(), SCALAR_ATT, strlen(SCALAR_ATT)) ||
249        att->type() != ncInt || att->num_vals() != 1)
250       return NC_ERR;
251    int value_in = att->as_int(0);
252    if (value_in != SCALAR_VALUE)
253       return NC_ERR;
254    delete att;
255 
256    // Check the value of the scalar variable.
257 
258 
259    return 0;
260 }
261 
gen(const char * path,NcFile::FileFormat format)262 int gen(const char* path, NcFile::FileFormat format)		// Generate a netCDF file
263 {
264 
265     NcFile nc(path, NcFile::Replace, NULL, 0, format); // Create, leave in define mode
266 
267     // Check if the file was opened successfully
268     if (! nc.is_valid()) {
269 	cerr << "can't create netCDF file " << path << "\n";
270 	return NC_ERR;
271     }
272 
273     // Create dimensions
274     NcDim* latd = nc.add_dim(LAT, NLATS);
275     NcDim* lond = nc.add_dim(LON, NLONS);
276     NcDim* frtimed = nc.add_dim(FRTIME); // unlimited dimension
277     NcDim* timelend = nc.add_dim(TIMELEN1, TIMESTRINGLEN);
278 
279     // Create variables and their attributes
280     NcVar* P = nc.add_var(P_NAME, ncFloat, frtimed, latd, lond);
281     P->add_att(LONG_NAME, PRES_MAX_WIND);
282     P->add_att(UNITS, HECTOPASCALS);
283     P->add_att(VALID_RANGE, NRANGES, range);
284     P->add_att(FILL_VALUE, fill_value);
285 
286     NcVar* lat = nc.add_var(LAT, ncFloat, latd);
287     lat->add_att(LONG_NAME, LATITUDE);
288     lat->add_att(UNITS, DEGREES_NORTH);
289 
290     NcVar* lon = nc.add_var(LON, ncFloat, lond);
291     lon->add_att(LONG_NAME, LONGITUDE);
292     lon->add_att(UNITS, DEGREES_EAST);
293 
294     NcVar* frtime = nc.add_var(FRTIME, ncLong, frtimed);
295     frtime->add_att(LONG_NAME, FORECAST_TIME);
296     frtime->add_att(UNITS, HOURS);
297 
298     NcVar* reftime = nc.add_var(REFTIME, ncChar, timelend);
299     reftime->add_att(LONG_NAME, REFERENCE_TIME);
300     reftime->add_att(UNITS, TEXT_TIME);
301 
302     NcVar* scalar = nc.add_var(SCALARV, ncInt);
303     scalar->add_att(SCALAR_ATT, SCALAR_VALUE);
304 
305     // Global attributes
306     nc.add_att(HISTORY, HISTORY_STR);
307     nc.add_att(TITLE, TITLE_STR);
308 
309     // Start writing data, implictly leaves define mode
310 
311     lat->put(lats, NLATS);
312 
313     lon->put(lons, NLONS);
314 
315     frtime->put(frtimes, NFRTIMES);
316 
317     reftime->put(s, strlen(s));
318 
319     // We could write all P data at once with P->put(&P_data[0][0][0], P->edges()),
320     // but instead we write one record at a time, to show use of setcur().
321     long rec = 0;                                      // start at zero-th
322     const long nrecs = 1;		               // # records to write
323     P->put(&P_data[0][0][0], nrecs, NLATS, NLONS);           // write zero-th record
324     P->set_cur(++rec);		                       // set to next record
325     P->put(&P_data[1][0][0], nrecs, NLATS, NLONS); // write next record
326 
327     // close of nc takes place in destructor
328     return 0;
329 }
330 
331 /*
332  * Convert pathname of netcdf file into name for CDL, by taking last component
333  * of path and stripping off any extension.  The returned string is in static
334  * storage, so copy it if you need to keep it.
335  */
336 static char*
cdl_name(const char * path)337 cdl_name(const char* path)
338 {
339     const char* cp = path + strlen(path);
340     while (*(cp-1) != '/' && cp != path) // assumes UNIX path separator
341 	cp--;
342 
343     static char np[NC_MAX_NAME];
344     strncpy(&np[0], cp, NC_MAX_NAME);
345 
346     char* ep = np + strlen(np);
347     while (*ep != '.' && ep != np)
348 	ep--;
349     if (*ep == '.')
350       *ep = '\0';
351     return np;
352 }
353 
354 // A derived class, just like NcFile except knows how to "dump" its
355 // dimensions, variables, global attributes, and data in ASCII form.
356 class DumpableNcFile : public NcFile
357 {
358   public:
DumpableNcFile(const char * path,NcFile::FileMode mode=ReadOnly)359     DumpableNcFile(const char* path, NcFile::FileMode mode = ReadOnly)
360 	: NcFile(path, mode) {} ;
361     void dumpdims( void );
362     void dumpvars( void );
363     void dumpgatts( void );
364     void dumpdata( void );
365 };
366 
dumpdims(void)367 void DumpableNcFile::dumpdims( void )
368 {
369 
370     for (int n=0; n < num_dims(); n++) {
371 	NcDim* dim = get_dim(n);
372 	cout << "\t" << dim->name() << " = " ;
373 	if (dim->is_unlimited())
374 	  cout << "UNLIMITED" << " ;\t " << "// " << dim->size() <<
375 	    " currently\n";
376 	else
377 	  cout << dim->size() << " ;\n";
378     }
379 }
380 
dumpatts(NcVar & var)381 void dumpatts(NcVar& var)
382 {
383     NcToken vname = var.name();
384     NcAtt* ap;
385     for(int n = 0; (ap = var.get_att(n)); n++) {
386 	cout << "\t\t" << vname << ":" << ap->name() << " = " ;
387 	NcValues* vals = ap->values();
388 	cout << *vals << " ;" << endl ;
389 	delete ap;
390 	delete vals;
391     }
392 }
393 
dumpvars(void)394 void DumpableNcFile::dumpvars( void )
395 {
396     int n;
397     static const char* types[] =
398       {"","byte","char","short","long","float","double"};
399     NcVar* vp;
400 
401     for(n = 0; (vp = get_var(n)); n++) {
402 	cout << "\t" << types[vp->type()] << " " << vp->name() ;
403 
404 	if (vp->num_dims() > 0) {
405 	    cout << "(";
406 	    for (int d = 0; d < vp->num_dims(); d++) {
407 		NcDim* dim = vp->get_dim(d);
408 		cout << dim->name();
409 		if (d < vp->num_dims()-1)
410 		  cout << ", ";
411 	    }
412 	    cout << ")";
413 	}
414 	cout << " ;\n";
415 	// now dump each of this variable's attributes
416 	dumpatts(*vp);
417     }
418 }
419 
dumpgatts(void)420 void DumpableNcFile::dumpgatts( void )
421 {
422     NcAtt* ap;
423     for(int n = 0; (ap = get_att(n)); n++) {
424 	cout << "\t\t" << ":" << ap->name() << " = " ;
425 	NcValues* vals = ap->values();
426 	cout << *vals << " ;" << endl ;
427 	delete vals;
428 	delete ap;
429     }
430 }
431 
dumpdata()432 void DumpableNcFile::dumpdata( )
433 {
434     NcVar* vp;
435     for (int n = 0; (vp = get_var(n)); n++) {
436 	cout << " " << vp->name() << " = ";
437 	NcValues* vals = vp->values();
438 	cout << *vals << " ;" << endl ;
439 	delete vals;
440     }
441 }
442 
dump(const char * path)443 void dump(const char* path)
444 {
445     DumpableNcFile nc(path);	// default is open in read-only mode
446 
447     cout << "netcdf " << cdl_name(path) << " {" << endl <<
448 	    "dimensions:" << endl ;
449 
450     nc.dumpdims();
451 
452     cout << "variables:" << endl;
453 
454     nc.dumpvars();
455 
456     if (nc.num_atts() > 0)
457       cout << "// global attributes" << endl ;
458 
459     nc.dumpgatts();
460 
461     cout << "data:" << endl;
462 
463     nc.dumpdata();
464 
465     cout << "}" << endl;
466 }
467 
468 /* Test everything for classic and 64-bit offsetfiles. If netcdf-4 is
469  * included, that means another whole round of testing. */
470 #ifdef USE_NETCDF4
471 #define NUM_FORMATS (4)
472 #else
473 #define NUM_FORMATS (2)
474 #endif
475 
476 int
main(void)477 main( void )	// test new netCDF interface
478 {
479 
480    cout << "*** Testing C++ API with " << NUM_FORMATS
481 	<< " different netCDF formats.\n";
482 
483    // Set up the format constants.
484    NcFile::FileFormat format[NUM_FORMATS] = {NcFile::Classic, NcFile::Offset64Bits
485 #ifdef USE_NETCDF4
486 					     , NcFile::Netcdf4, NcFile::Netcdf4Classic
487 #endif
488    };
489 
490    // Set up the file names.
491    char file_name[NUM_FORMATS][NC_MAX_NAME] =
492       {"nctst_classic.nc", "nctst_64bit_offset.nc"
493 #ifdef USE_NETCDF4
494        , "nctst_netcdf4.nc", "nctst_netcdf4_classic.nc"
495 #endif
496    };
497 
498    int errs = 0;
499    for (int i = 0; i < NUM_FORMATS; i++)
500    {
501       if (gen(file_name[i], format[i]) ||
502 	  read(file_name[i], format[i]))
503       {
504 	 cout << "*** FAILURE with file " << file_name[i] << "\n";
505 	 errs++;
506       }
507       else
508 	 cout << "*** SUCCESS with file " << file_name[i] << "\n";
509    }
510 
511    cout << "\n*** Total number of failures: " << errs << "\n";
512    if (errs)
513       cout << "*** nctst FAILURE!\n";
514    else
515       cout << "*** nctst SUCCESS!\n";
516 
517    return errs;
518 }
519