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.27 2006/08/20 21:03:45 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 |
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 |
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. |
106 |
int read(const char* path) |
107 |
{ |
108 |
NcAtt *att; |
109 |
|
110 |
// open the file |
111 |
NcFile nc(path); |
112 |
|
113 |
if (!nc.is_valid()) |
114 |
return 2; |
115 |
|
116 |
// Check the numbers of things. |
117 |
if (nc.num_dims() != 4 || nc.num_vars() != 6 || |
118 |
nc.num_atts() != 2) |
119 |
return NC_ERR; |
120 |
|
121 |
// Check the global attributes. |
122 |
if (!(att = nc.get_att(HISTORY))) |
123 |
return NC_ERR; |
124 |
if (check_string_att(att, HISTORY, HISTORY_STR)) |
125 |
return NC_ERR; |
126 |
delete att; |
127 |
|
128 |
if (!(att = nc.get_att(TITLE))) |
129 |
return NC_ERR; |
130 |
if (check_string_att(att, TITLE, TITLE_STR)) |
131 |
return NC_ERR; |
132 |
delete att; |
133 |
|
134 |
// Check the dimensions. |
135 |
NcDim *latDim, *lonDim, *frtimeDim, *timeLenDim; |
136 |
|
137 |
if (!(latDim = nc.get_dim(LAT))) |
138 |
return NC_ERR; |
139 |
if (!latDim->is_valid() || strncmp(latDim->name(), LAT, strlen(LAT)) || |
140 |
latDim->size() != NLATS || latDim->is_unlimited()) |
141 |
return NC_ERR; |
142 |
|
143 |
if (!(lonDim = nc.get_dim(LON))) |
144 |
return NC_ERR; |
145 |
if (!lonDim->is_valid() || strncmp(lonDim->name(), LON, strlen(LON)) || |
146 |
lonDim->size() != NLONS || lonDim->is_unlimited()) |
147 |
return NC_ERR; |
148 |
|
149 |
if (!(frtimeDim = nc.get_dim(FRTIME))) |
150 |
return NC_ERR; |
151 |
if (!frtimeDim->is_valid() || strncmp(frtimeDim->name(), FRTIME, strlen(FRTIME)) || |
152 |
frtimeDim->size() != 2 || !frtimeDim->is_unlimited()) |
153 |
return NC_ERR; |
154 |
|
155 |
if (!(timeLenDim = nc.get_dim(TIMELEN1))) |
156 |
return NC_ERR; |
157 |
if (!timeLenDim->is_valid() || strncmp(timeLenDim->name(), TIMELEN1, strlen(TIMELEN1)) || |
158 |
timeLenDim->size() != TIMESTRINGLEN || timeLenDim->is_unlimited()) |
159 |
return NC_ERR; |
160 |
|
161 |
// Check the coordinate variables. |
162 |
NcVar *latVar, *lonVar, *frtimeVar, *refTimeVar; |
163 |
|
164 |
// Get the latitude. |
165 |
if (!(latVar = nc.get_var(LAT))) |
166 |
return NC_ERR; |
167 |
|
168 |
// Check units and long name. |
169 |
if (check_u_ln_atts(latVar, DEGREES_NORTH, LATITUDE)) |
170 |
return NC_ERR; |
171 |
|
172 |
// Get the longitude. |
173 |
if (!(lonVar = nc.get_var(LON))) |
174 |
return NC_ERR; |
175 |
|
176 |
// Check units and long name. |
177 |
if (check_u_ln_atts(lonVar, DEGREES_EAST, LONGITUDE)) |
178 |
return NC_ERR; |
179 |
|
180 |
// Get the forecast time coordinate variable. |
181 |
if (!(frtimeVar = nc.get_var(FRTIME))) |
182 |
return NC_ERR; |
183 |
|
184 |
// Check units and long name. |
185 |
if (check_u_ln_atts(frtimeVar, HOURS, FORECAST_TIME)) |
186 |
return NC_ERR; |
187 |
|
188 |
// Get the refTime coordinate variable. |
189 |
if (!(refTimeVar = nc.get_var(REFTIME))) |
190 |
return NC_ERR; |
191 |
|
192 |
// Check units and long name. |
193 |
if (check_u_ln_atts(refTimeVar, TEXT_TIME, REFERENCE_TIME)) |
194 |
return NC_ERR; |
195 |
|
196 |
// Check the data variables. |
197 |
NcVar *pVar, *scalarVar; |
198 |
|
199 |
if (!(pVar = nc.get_var(P_NAME))) |
200 |
return NC_ERR; |
201 |
|
202 |
// Check units and long name. |
203 |
if (check_u_ln_atts(pVar, HECTOPASCALS, PRES_MAX_WIND)) |
204 |
return NC_ERR; |
205 |
|
206 |
// Check the valid range, and check the values. |
207 |
if (!(att = pVar->get_att(VALID_RANGE))) |
208 |
return NC_ERR; |
209 |
if (!att->is_valid() || strncmp(att->name(), VALID_RANGE, strlen(VALID_RANGE)) || |
210 |
att->type() != ncFloat || att->num_vals() != NRANGES) |
211 |
return NC_ERR; |
212 |
float range_in[NRANGES] = {att->as_float(0), att->as_float(1)}; |
213 |
if (range_in[0] != range[0] || range_in[1] != range[1]) |
214 |
return NC_ERR; |
215 |
delete att; |
216 |
|
217 |
// Check the fill value, and check the value. |
218 |
if (!(att = pVar->get_att(FILL_VALUE))) |
219 |
return NC_ERR; |
220 |
if (!att->is_valid() || strncmp(att->name(), FILL_VALUE, strlen(FILL_VALUE)) || |
221 |
att->type() != ncFloat || att->num_vals() != 1) |
222 |
return NC_ERR; |
223 |
float fill_value_in = att->as_float(0); |
224 |
if (fill_value_in != fill_value) |
225 |
return NC_ERR; |
226 |
delete att; |
227 |
|
228 |
// Check the data in the pressure variable. |
229 |
float P_data_in[NFRTIMES][NLATS][NLONS]; |
230 |
pVar->get(&P_data_in[0][0][0], NFRTIMES, NLATS, NLONS); |
231 |
for (int f = 0; f < NFRTIMES; f++) |
232 |
for (int la = 0; la < NLATS; la++) |
233 |
for (int lo = 0; lo < NLONS; lo++) |
234 |
if (P_data_in[f][la][lo] != P_data[f][la][lo]) |
235 |
return NC_ERR; |
236 |
|
237 |
// Get the scalar variable. |
238 |
if (!(scalarVar = nc.get_var(SCALARV))) |
239 |
return NC_ERR; |
240 |
|
241 |
// Check for the scalar attribute of the scalar variable and check its value. |
242 |
if (!(att = scalarVar->get_att(SCALAR_ATT))) |
243 |
return NC_ERR; |
244 |
if (!att->is_valid() || strncmp(att->name(), SCALAR_ATT, strlen(SCALAR_ATT)) || |
245 |
att->type() != ncInt || att->num_vals() != 1) |
246 |
return NC_ERR; |
247 |
int value_in = att->as_int(0); |
248 |
if (value_in != SCALAR_VALUE) |
249 |
return NC_ERR; |
250 |
delete att; |
251 |
|
252 |
// Check the value of the scalar variable. |
253 |
|
254 |
|
255 |
return 0; |
256 |
} |
257 |
|
258 |
int gen(const char* path, NcFile::FileFormat format) // Generate a netCDF file |
259 |
{ |
260 |
|
261 |
NcFile nc(path, NcFile::Replace, NULL, 0, format); // Create, leave in define mode |
262 |
|
263 |
// Check if the file was opened successfully |
264 |
if (! nc.is_valid()) { |
265 |
cerr << "can't create netCDF file " << path << "\n"; |
266 |
return NC_ERR; |
267 |
} |
268 |
|
269 |
// Create dimensions |
270 |
NcDim* latd = nc.add_dim(LAT, NLATS); |
271 |
NcDim* lond = nc.add_dim(LON, NLONS); |
272 |
NcDim* frtimed = nc.add_dim(FRTIME); // unlimited dimension |
273 |
NcDim* timelend = nc.add_dim(TIMELEN1, TIMESTRINGLEN); |
274 |
|
275 |
// Create variables and their attributes |
276 |
NcVar* P = nc.add_var(P_NAME, ncFloat, frtimed, latd, lond); |
277 |
P->add_att(LONG_NAME, PRES_MAX_WIND); |
278 |
P->add_att(UNITS, HECTOPASCALS); |
279 |
P->add_att(VALID_RANGE, NRANGES, range); |
280 |
P->add_att(FILL_VALUE, fill_value); |
281 |
|
282 |
NcVar* lat = nc.add_var(LAT, ncFloat, latd); |
283 |
lat->add_att(LONG_NAME, LATITUDE); |
284 |
lat->add_att(UNITS, DEGREES_NORTH); |
285 |
|
286 |
NcVar* lon = nc.add_var(LON, ncFloat, lond); |
287 |
lon->add_att(LONG_NAME, LONGITUDE); |
288 |
lon->add_att(UNITS, DEGREES_EAST); |
289 |
|
290 |
NcVar* frtime = nc.add_var(FRTIME, ncLong, frtimed); |
291 |
frtime->add_att(LONG_NAME, FORECAST_TIME); |
292 |
frtime->add_att(UNITS, HOURS); |
293 |
|
294 |
NcVar* reftime = nc.add_var(REFTIME, ncChar, timelend); |
295 |
reftime->add_att(LONG_NAME, REFERENCE_TIME); |
296 |
reftime->add_att(UNITS, TEXT_TIME); |
297 |
|
298 |
NcVar* scalar = nc.add_var(SCALARV, ncInt); |
299 |
scalar->add_att(SCALAR_ATT, SCALAR_VALUE); |
300 |
|
301 |
// Global attributes |
302 |
nc.add_att(HISTORY, HISTORY_STR); |
303 |
nc.add_att(TITLE, TITLE_STR); |
304 |
|
305 |
// Start writing data, implictly leaves define mode |
306 |
|
307 |
lat->put(lats, NLATS); |
308 |
|
309 |
lon->put(lons, NLONS); |
310 |
|
311 |
frtime->put(frtimes, NFRTIMES); |
312 |
|
313 |
reftime->put(s, strlen(s)); |
314 |
|
315 |
// We could write all P data at once with P->put(&P_data[0][0][0], P->edges()), |
316 |
// but instead we write one record at a time, to show use of setcur(). |
317 |
long rec = 0; // start at zero-th |
318 |
const long nrecs = 1; // # records to write |
319 |
P->put(&P_data[0][0][0], nrecs, NLATS, NLONS); // write zero-th record |
320 |
P->set_cur(++rec); // set to next record |
321 |
P->put(&P_data[1][0][0], nrecs, NLATS, NLONS); // write next record |
322 |
|
323 |
// close of nc takes place in destructor |
324 |
return 0; |
325 |
} |
326 |
|
327 |
/* |
328 |
* Convert pathname of netcdf file into name for CDL, by taking last component |
329 |
* of path and stripping off any extension. The returned string is in static |
330 |
* storage, so copy it if you need to keep it. |
331 |
*/ |
332 |
static char* |
333 |
cdl_name(const char* path) |
334 |
{ |
335 |
const char* cp = path + strlen(path); |
336 |
while (*(cp-1) != '/' && cp != path) // assumes UNIX path separator |
337 |
cp--; |
338 |
|
339 |
static char np[NC_MAX_NAME]; |
340 |
strncpy(&np[0], cp, NC_MAX_NAME); |
341 |
|
342 |
char* ep = np + strlen(np); |
343 |
while (*ep != '.' && ep != np) |
344 |
ep--; |
345 |
if (*ep == '.') |
346 |
*ep = '\0'; |
347 |
return np; |
348 |
} |
349 |
|
350 |
// A derived class, just like NcFile except knows how to "dump" its |
351 |
// dimensions, variables, global attributes, and data in ASCII form. |
352 |
class DumpableNcFile : public NcFile |
353 |
{ |
354 |
public: |
355 |
DumpableNcFile(const char* path, NcFile::FileMode mode = ReadOnly) |
356 |
: NcFile(path, mode) {} ; |
357 |
void dumpdims( void ); |
358 |
void dumpvars( void ); |
359 |
void dumpgatts( void ); |
360 |
void dumpdata( void ); |
361 |
}; |
362 |
|
363 |
void DumpableNcFile::dumpdims( void ) |
364 |
{ |
365 |
|
366 |
for (int n=0; n < num_dims(); n++) { |
367 |
NcDim* dim = get_dim(n); |
368 |
cout << "\t" << dim->name() << " = " ; |
369 |
if (dim->is_unlimited()) |
370 |
cout << "UNLIMITED" << " ;\t " << "// " << dim->size() << |
371 |
" currently\n"; |
372 |
else |
373 |
cout << dim->size() << " ;\n"; |
374 |
} |
375 |
} |
376 |
|
377 |
void dumpatts(NcVar& var) |
378 |
{ |
379 |
NcToken vname = var.name(); |
380 |
NcAtt* ap; |
381 |
for(int n = 0; (ap = var.get_att(n)); n++) { |
382 |
cout << "\t\t" << vname << ":" << ap->name() << " = " ; |
383 |
NcValues* vals = ap->values(); |
384 |
cout << *vals << " ;" << endl ; |
385 |
delete ap; |
386 |
delete vals; |
387 |
} |
388 |
} |
389 |
|
390 |
void DumpableNcFile::dumpvars( void ) |
391 |
{ |
392 |
int n; |
393 |
static const char* types[] = |
394 |
{"","byte","char","short","long","float","double"}; |
395 |
NcVar* vp; |
396 |
|
397 |
for(n = 0; (vp = get_var(n)); n++) { |
398 |
cout << "\t" << types[vp->type()] << " " << vp->name() ; |
399 |
|
400 |
if (vp->num_dims() > 0) { |
401 |
cout << "("; |
402 |
for (int d = 0; d < vp->num_dims(); d++) { |
403 |
NcDim* dim = vp->get_dim(d); |
404 |
cout << dim->name(); |
405 |
if (d < vp->num_dims()-1) |
406 |
cout << ", "; |
407 |
} |
408 |
cout << ")"; |
409 |
} |
410 |
cout << " ;\n"; |
411 |
// now dump each of this variable's attributes |
412 |
dumpatts(*vp); |
413 |
} |
414 |
} |
415 |
|
416 |
void DumpableNcFile::dumpgatts( void ) |
417 |
{ |
418 |
NcAtt* ap; |
419 |
for(int n = 0; (ap = get_att(n)); n++) { |
420 |
cout << "\t\t" << ":" << ap->name() << " = " ; |
421 |
NcValues* vals = ap->values(); |
422 |
cout << *vals << " ;" << endl ; |
423 |
delete vals; |
424 |
delete ap; |
425 |
} |
426 |
} |
427 |
|
428 |
void DumpableNcFile::dumpdata( ) |
429 |
{ |
430 |
NcVar* vp; |
431 |
for (int n = 0; (vp = get_var(n)); n++) { |
432 |
cout << " " << vp->name() << " = "; |
433 |
NcValues* vals = vp->values(); |
434 |
cout << *vals << " ;" << endl ; |
435 |
delete vals; |
436 |
} |
437 |
} |
438 |
|
439 |
void dump(const char* path) |
440 |
{ |
441 |
DumpableNcFile nc(path); // default is open in read-only mode |
442 |
|
443 |
cout << "netcdf " << cdl_name(path) << " {" << endl << |
444 |
"dimensions:" << endl ; |
445 |
|
446 |
nc.dumpdims(); |
447 |
|
448 |
cout << "variables:" << endl; |
449 |
|
450 |
nc.dumpvars(); |
451 |
|
452 |
if (nc.num_atts() > 0) |
453 |
cout << "// global attributes" << endl ; |
454 |
|
455 |
nc.dumpgatts(); |
456 |
|
457 |
cout << "data:" << endl; |
458 |
|
459 |
nc.dumpdata(); |
460 |
|
461 |
cout << "}" << endl; |
462 |
} |
463 |
|
464 |
/* Test everything for classic and 64-bit offsetfiles. If netcdf-4 is |
465 |
* included, that means another whole round of testing. */ |
466 |
#ifdef USE_NETCDF4 |
467 |
#define NUM_FORMATS (4) |
468 |
#else |
469 |
#define NUM_FORMATS (2) |
470 |
#endif |
471 |
|
472 |
int |
473 |
main( void ) // test new netCDF interface |
474 |
{ |
475 |
|
476 |
cout << "*** Testing C++ API with " << NUM_FORMATS |
477 |
<< " different netCDF formats.\n"; |
478 |
|
479 |
// Set up the format constants. |
480 |
NcFile::FileFormat format[NUM_FORMATS] = {NcFile::Classic, NcFile::Offset64Bits |
481 |
#ifdef USE_NETCDF4 |
482 |
, NcFile::Netcdf4, NcFile::Netcdf4Classic |
483 |
#endif |
484 |
}; |
485 |
|
486 |
// Set up the file names. |
487 |
char file_name[NUM_FORMATS][NC_MAX_NAME] = |
488 |
{"nctst_classic.nc", "nctst_64bit_offset.nc" |
489 |
#ifdef USE_NETCDF4 |
490 |
, "nctst_netcdf4.nc", "nctst_netcdf4_classic.nc" |
491 |
#endif |
492 |
}; |
493 |
|
494 |
int errs = 0; |
495 |
for (int i = 0; i < NUM_FORMATS; i++) |
496 |
{ |
497 |
if (gen(file_name[i], format[i]) || |
498 |
read(file_name[i])) |
499 |
{ |
500 |
cout << "*** FAILURE with file " << file_name[i] << "\n"; |
501 |
errs++; |
502 |
} |
503 |
else |
504 |
cout << "*** SUCCESS with file " << file_name[i] << "\n"; |
505 |
} |
506 |
|
507 |
cout << "\n*** Total number of failures: " << errs << "\n"; |
508 |
if (errs) |
509 |
cout << "*** nctst FAILURE!\n"; |
510 |
else |
511 |
cout << "*** nctst SUCCESS!\n"; |
512 |
|
513 |
return errs; |
514 |
} |