bes  Updated for version 3.20.8
ncdas.cc
1 // -*- mode: c++; c-basic-offset:4 -*-
2 
3 // This file is part of nc_handler, a data handler for the OPeNDAP data
4 // server.
5 
6 // Copyright (c) 2002,2003 OPeNDAP, Inc.
7 // Author: James Gallagher <jgallagher@opendap.org>
8 //
9 // This is free software; you can redistribute it and/or modify it under the
10 // terms of the GNU Lesser General Public License as published by the Free
11 // Software Foundation; either version 2.1 of the License, or (at your
12 // option) any later version.
13 //
14 // This software is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17 // License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 //
23 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
24 
25 // (c) COPYRIGHT URI/MIT 1994-1996
26 // Please read the full copyright statement in the file COPYRIGHT.
27 //
28 // Authors:
29 // reza Reza Nekovei (reza@intcomm.net)
30 
31 // This file contains functions which read the variables and their attributes
32 // from a netcdf file and build the in-memeory DAS. These functions form the
33 // core of the server-side software necessary to extract the DAS from a
34 // netcdf data file.
35 //
36 // It also contains test code which will print the in-memory DAS to
37 // stdout. It uses both the DAS class as well as the netcdf library.
38 // In addition, parts of these functions were taken from the netcdf program
39 // ncdump, from the netcdf standard distribution (ver 2.3.2)
40 //
41 // jhrg 9/23/94
42 
43 #include "config_nc.h"
44 
45 #include <iostream>
46 #include <string>
47 #include <sstream>
48 #include <iomanip>
49 #include <vector>
50 
51 #include <cmath>
52 
53 #include <netcdf.h>
54 
55 #include <util.h>
56 #include <escaping.h>
57 #include <DAS.h>
58 
59 #include <BESDebug.h>
60 
61 #include "NCRequestHandler.h"
62 #include "nc_util.h"
63 
64 #define ATTR_STRING_QUOTE_FIX
65 
66 #define NETCDF_VERSION 4
67 
68 #if NETCDF_VERSION >= 4
69 #define READ_ATTRIBUTES_MACRO read_attributes_netcdf4
70 #else
71 #define READ_ATTRIBUTES_MACRO read_attributes_netcdf3
72 #endif
73 
74 #define MODULE "nc"
75 #define prolog std::string("ncdas::").append(__func__).append("() - ")
76 
77 using namespace libdap;
78 
89 static string print_attr(nc_type type, int loc, void *vals)
90 {
91  ostringstream rep;
92  union {
93  char *cp;
94  char **stringp;
95  int16_t *sp;
96  uint16_t *usp;
97  int32_t *i;
98  uint32_t *ui;
99  float *fp;
100  double *dp;
101  } gp;
102 
103  switch (type) {
104 #if NETCDF_VERSION >= 4
105  case NC_UBYTE:
106  unsigned char uc;
107  gp.cp = (char *) vals;
108 
109  uc = *(gp.cp + loc);
110  rep << (int) uc;
111  return rep.str();
112 #endif
113 
114  case NC_BYTE:
115  if (NCRequestHandler::get_promote_byte_to_short()) {
116  signed char sc;
117  gp.cp = (char *) vals;
118 
119  sc = *(gp.cp + loc);
120  rep << (int) sc;
121  return rep.str();
122  }
123  else {
124  unsigned char uc;
125  gp.cp = (char *) vals;
126 
127  uc = *(gp.cp + loc);
128  rep << (int) uc;
129  return rep.str();
130  }
131 
132  case NC_CHAR:
133 #ifndef ATTR_STRING_QUOTE_FIX
134  rep << "\"" << escattr(static_cast<const char*>(vals)) << "\"";
135  return rep.str();
136 #else
137  return escattr(static_cast<const char*>(vals));
138 #endif
139 
140 #if NETCDF_VERSION >= 4
141  case NC_STRING:
142  gp.stringp = (char **) vals;
143  rep << *(gp.stringp + loc);
144  return rep.str();
145 #endif
146 
147  case NC_SHORT:
148  gp.sp = (short *) vals;
149  rep << *(gp.sp + loc);
150  return rep.str();
151 
152 #if NETCDF_VERSION >= 4
153  case NC_USHORT:
154  gp.usp = (uint16_t *) vals;
155  rep << *(gp.usp + loc);
156  return rep.str();
157 #endif
158 
159  case NC_INT:
160  gp.i = (int32_t *) vals; // warning: long int format, int arg (arg 3)
161  rep << *(gp.i + loc);
162  return rep.str();
163 
164 #if NETCDF_VERSION >= 4
165  case NC_UINT:
166  gp.ui = (uint32_t *) vals;
167  rep << *(gp.ui + loc);
168  return rep.str();
169 #endif
170 
171  case NC_FLOAT: {
172  gp.fp = (float *) vals;
173  float valAtLoc = *(gp.fp + loc);
174 
175  rep << std::showpoint;
176  rep << std::setprecision(9);
177 
178  if (isnan(valAtLoc)) {
179  rep << "NaN";
180  }
181  else {
182  rep << valAtLoc;
183  }
184  // If there's no decimal point and the rep does not use scientific
185  // notation, add a decimal point. This little jaunt was taken because
186  // this code is modeled after older code and that's what it did. I'm
187  // trying to keep the same behavior as the old code without it's
188  // problems. jhrg 8/11/2006
189  string tmp_value = rep.str();
190  if (tmp_value.find('.') == string::npos && tmp_value.find('e') == string::npos && tmp_value.find('E') == string::npos
191  && tmp_value.find("nan") == string::npos && tmp_value.find("NaN") == string::npos && tmp_value.find("NAN") == string::npos) rep << ".";
192  return rep.str();
193  }
194 
195  case NC_DOUBLE: {
196  gp.dp = (double *) vals;
197  double valAtLoc = *(gp.dp + loc);
198 
199  rep << std::showpoint;
200  rep << std::setprecision(16);
201 
202  if (std::isnan(valAtLoc)) {
203  rep << "NaN";
204  }
205  else {
206  rep << valAtLoc;
207  }
208  string tmp_value = rep.str();
209  if (tmp_value.find('.') == string::npos && tmp_value.find('e') == string::npos && tmp_value.find('E') == string::npos
210  && tmp_value.find("nan") == string::npos && tmp_value.find("NaN") == string::npos && tmp_value.find("NAN") == string::npos) rep << ".";
211  return rep.str();
212  }
213 
214  default:
215  if (NCRequestHandler::get_ignore_unknown_types())
216  cerr << "The netcdf handler tried to print an attribute that has an unrecognized type. (1)" << endl;
217  else
218  throw InternalErr(__FILE__, __LINE__, "The netcdf handler tried to print an attribute that has an unrecognized type. (1)");
219  break;
220  }
221 
222  return "";
223 }
224 
231 static string print_type(nc_type datatype)
232 {
233  switch (datatype) {
234 #if NETCDF_VERSION >= 4
235  case NC_STRING:
236 #endif
237  case NC_CHAR:
238  return "String";
239 
240 #if NETCDF_VERSION >= 4
241  case NC_UBYTE:
242  return "Byte";
243 #endif
244  case NC_BYTE:
245  if (NCRequestHandler::get_promote_byte_to_short()) {
246  return "Int16";
247  }
248  else {
249  return "Byte";
250  }
251 
252  case NC_SHORT:
253  return "Int16";
254 
255  case NC_INT:
256  return "Int32";
257 
258 #if NETCDF_VERSION >= 4
259  case NC_USHORT:
260  return "UInt16";
261 
262  case NC_UINT:
263  return "UInt32";
264 #endif
265 
266  case NC_FLOAT:
267  return "Float32";
268 
269  case NC_DOUBLE:
270  return "Float64";
271 
272 #if NETCDF_VERSION >= 4
273  case NC_COMPOUND:
274  return "NC_COMPOUND";
275 #endif
276 
277 #if NETCDF_VERSION >= 4
278  // These are all new netcdf 4 types that we don't support yet
279  // as attributes. It's useful to have a print representation for
280  // them so that we can return useful information about why some
281  // information was elided or an exception thrown.
282  case NC_INT64:
283  return "NC_INT64";
284 
285  case NC_UINT64:
286  return "NC_UINT64";
287 
288  case NC_VLEN:
289  return "NC_VLEN";
290  case NC_OPAQUE:
291  return "NC_OPAQUE";
292  case NC_ENUM:
293  return "NC_ENUM";
294 #endif
295  default:
296  if (NCRequestHandler::get_ignore_unknown_types())
297  cerr << "The netcdf handler tried to print an attribute that has an unrecognized type. (2)" << endl;
298  else
299  throw InternalErr(__FILE__, __LINE__, "The netcdf handler tried to print an attribute that has an unrecognized type. (2)");
300  break;
301  }
302 
303  return "";
304 }
305 
310 static void append_values(int ncid, int v, int len, nc_type datatype, char *attrname, AttrTable *at)
311 {
312  size_t size;
313  int errstat;
314 #if NETCDF_VERSION >= 4
315  errstat = nc_inq_type(ncid, datatype, 0, &size);
316  if (errstat != NC_NOERR) throw Error(errstat, "Could not get the size for the type.");
317 #else
318  size = nctypelen(datatype);
319 #endif
320 
321  vector<char> value((len + 1) * size);
322  errstat = nc_get_att(ncid, v, attrname, &value[0]);
323  if (errstat != NC_NOERR) {
324  throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
325  }
326 
327  // If the datatype is NC_CHAR then we have a string. netCDF 3
328  // represents strings as arrays of char, but we represent them as X
329  // strings. So... Add the null and set the length to 1
330  if (datatype == NC_CHAR) {
331  value[len] = '\0';
332  len = 1;
333  }
334 
335  // add all the attributes in the array
336  for (int loc = 0; loc < len; loc++) {
337  string print_rep = print_attr(datatype, loc, &value[0]);
338  at->append_attr(attrname, print_type(datatype), print_rep);
339  }
340 }
341 
353 static void read_attributes_netcdf3(int ncid, int v, int natts, AttrTable *at)
354 {
355  char attrname[MAX_NC_NAME];
356  nc_type datatype;
357  size_t len;
358  int errstat = NC_NOERR;
359 
360  for (int a = 0; a < natts; ++a) {
361  errstat = nc_inq_attname(ncid, v, a, attrname);
362  if (errstat != NC_NOERR) {
363  string msg = "Could not get the name for attribute ";
364  msg += long_to_string(a);
365  throw Error(errstat, msg);
366  }
367 
368  // len is the number of values. Attributes in netcdf can be scalars or
369  // vectors
370  errstat = nc_inq_att(ncid, v, attrname, &datatype, &len);
371  if (errstat != NC_NOERR) {
372  string msg = "Could not get the name for attribute '";
373  msg += attrname + string("'");
374  throw Error(errstat, msg);
375  }
376 
377  switch (datatype) {
378  case NC_BYTE:
379  case NC_CHAR:
380  case NC_SHORT:
381  case NC_INT:
382  case NC_FLOAT:
383  case NC_DOUBLE:
384  append_values(ncid, v, len, datatype, attrname, at);
385  break;
386 
387  default:
388  if (NCRequestHandler::get_ignore_unknown_types())
389  cerr << "Unrecognized attribute type." << endl;
390  else
391  throw InternalErr(__FILE__, __LINE__, "Unrecognized attribute type.");
392  break;
393  }
394  }
395 }
396 
397 #if NETCDF_VERSION >= 4
398 
410 static void read_attributes_netcdf4(int ncid, int varid, int natts, AttrTable *at)
411 {
412  BESDEBUG(MODULE, prolog << "In read_attributes_netcdf4" << endl);
413 
414  for (int attr_num = 0; attr_num < natts; ++attr_num) {
415  int errstat = NC_NOERR;
416  // Get the attribute name
417  char attrname[MAX_NC_NAME];
418  errstat = nc_inq_attname(ncid, varid, attr_num, attrname);
419  if (errstat != NC_NOERR) throw Error(errstat, "Could not get the name for attribute " + long_to_string(attr_num));
420 
421  // Get datatype and len; len is the number of values.
422  nc_type datatype;
423  size_t len;
424  errstat = nc_inq_att(ncid, varid, attrname, &datatype, &len);
425  if (errstat != NC_NOERR) throw Error(errstat, "Could not get the name for attribute '" + string(attrname) + "'");
426 
427  BESDEBUG(MODULE, prolog << "nc_inq_att returned datatype = " << datatype << " for '" << attrname << "'" << endl);
428 
429  //if (is_user_defined_type(ncid, datatype)) {
430  if (datatype >= NC_FIRSTUSERTYPEID) {
431  char type_name[NC_MAX_NAME + 1];
432  size_t size;
433  nc_type base_type;
434  size_t nfields;
435  int class_type;
436  errstat = nc_inq_user_type(ncid, datatype, type_name, &size, &base_type, &nfields, &class_type);
437  if (errstat != NC_NOERR)
438  throw(InternalErr(__FILE__, __LINE__, "Could not get information about a user-defined type (" + long_to_string(errstat) + ")."));
439 
440  BESDEBUG(MODULE, prolog << "Before switch(class_type)" << endl);
441  switch (class_type) {
442  case NC_COMPOUND: {
443  // Make recursive attrs work?
444  vector<unsigned char> values((len + 1) * size);
445 
446  int errstat = nc_get_att(ncid, varid, attrname, &values[0]);
447  if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
448 
449  for (size_t i = 0; i < nfields; ++i) {
450  char field_name[NC_MAX_NAME + 1];
451  nc_type field_typeid;
452  size_t field_offset;
453  nc_inq_compound_field(ncid, datatype, i, field_name, &field_offset, &field_typeid, 0, 0);
454 
455  at->append_attr(field_name, print_type(field_typeid), print_attr(field_typeid, 0, &values[0] + field_offset));
456  }
457  break;
458  }
459 
460  case NC_VLEN:
461  if (NCRequestHandler::get_ignore_unknown_types())
462  cerr << "in build_user_defined; found a vlen." << endl;
463  else
464  throw Error("The netCDF handler does not yet support the NC_VLEN type.");
465  break;
466 
467  case NC_OPAQUE: {
468  vector<unsigned char> values((len + 1) * size);
469 
470  int errstat = nc_get_att(ncid, varid, attrname, &values[0]);
471  if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
472 
473  for (size_t i = 0; i < size; ++i)
474  at->append_attr(attrname, print_type(NC_BYTE), print_attr(NC_BYTE, i, &values[0]));
475 
476  break;
477  }
478 
479  case NC_ENUM: {
480 #if 0
481  nc_type basetype;
482  size_t base_size, num_members;
483  errstat = nc_inq_enum(ncid, datatype, 0/*char *name*/, &basetype, &base_size, &num_members);
484  if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the size of the enum base type for '") + attrname + string("'"));
485 #endif
486  vector<unsigned char> values((len + 1) * size);
487 
488  int errstat = nc_get_att(ncid, varid, attrname, &values[0]);
489  if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
490 
491  for (size_t i = 0; i < len; ++i)
492  at->append_attr(attrname, print_type(base_type), print_attr(base_type, i, &values[0]));
493 
494  break;
495  }
496 
497  default:
498  throw InternalErr(__FILE__, __LINE__, "Expected one of NC_COMPOUND, NC_VLEN, NC_OPAQUE or NC_ENUM");
499  }
500 
501  BESDEBUG(MODULE, prolog << "After switch(class-type)" << endl);
502  }
503  else {
504  switch (datatype) {
505  case NC_STRING:
506  case NC_BYTE:
507  case NC_CHAR:
508  case NC_SHORT:
509  case NC_INT:
510  case NC_FLOAT:
511  case NC_DOUBLE:
512  case NC_UBYTE:
513  case NC_USHORT:
514  case NC_UINT:
515  BESDEBUG(MODULE, prolog << "Before append_values ..." << endl);
516  append_values(ncid, varid, len, datatype, attrname, at);
517  BESDEBUG(MODULE, prolog << "After append_values ..." << endl);
518  break;
519 
520  case NC_INT64:
521  case NC_UINT64: {
522  string note = "Attribute edlided: Unsupported attribute type ";
523  note += "(" + print_type(datatype) + ")";
524  at->append_attr(attrname, "String", note);
525  break;
526  }
527 
528  case NC_COMPOUND:
529  case NC_VLEN:
530  case NC_OPAQUE:
531  case NC_ENUM:
532  throw InternalErr(__FILE__, __LINE__, "user-defined attribute type not recognized as such!");
533 
534  default:
535  throw InternalErr(__FILE__, __LINE__, "Unrecognized attribute type.");
536  }
537  }
538  }
539  BESDEBUG(MODULE, prolog << "Exiting read_attributes_netcdf4" << endl);
540 }
541 #endif
542 
552 void nc_read_dataset_attributes(DAS &das, const string &filename)
553 {
554  BESDEBUG(MODULE, prolog << "In nc_read_dataset_attributes" << endl);
555 
556  int ncid, errstat;
557  errstat = nc_open(filename.c_str(), NC_NOWRITE, &ncid);
558  if (errstat != NC_NOERR) throw Error(errstat, "NetCDF handler: Could not open " + filename + ".");
559 
560  // how many variables? how many global attributes?
561  int nvars, ngatts;
562  errstat = nc_inq(ncid, (int *) 0, &nvars, &ngatts, (int *) 0);
563  if (errstat != NC_NOERR) throw Error(errstat, "NetCDF handler: Could not inquire about netcdf file: " + path_to_filename(filename) + ".");
564 
565  // for each variable
566  char varname[MAX_NC_NAME];
567  int natts = 0;
568  nc_type var_type;
569  for (int varid = 0; varid < nvars; ++varid) {
570  BESDEBUG(MODULE, prolog << "Top of for loop; for each var..." << endl);
571 
572  errstat = nc_inq_var(ncid, varid, varname, &var_type, (int*) 0, (int*) 0, &natts);
573  if (errstat != NC_NOERR) throw Error(errstat, "Could not get information for variable: " + long_to_string(varid));
574 
575  AttrTable *attr_table_ptr = das.get_table(varname);
576  if (!attr_table_ptr) attr_table_ptr = das.add_table(varname, new AttrTable);
577 
578  READ_ATTRIBUTES_MACRO(ncid, varid, natts, attr_table_ptr);
579 
580  // Add a special attribute for string lengths
581  if (var_type == NC_CHAR) {
582  // number of dimensions and size of Nth dimension
583  int num_dim;
584  int vdimids[MAX_VAR_DIMS]; // variable dimension ids
585  errstat = nc_inq_var(ncid, varid, (char *) 0, (nc_type *) 0, &num_dim, vdimids, (int *) 0);
586  if (errstat != NC_NOERR)
587  throw Error(errstat, string("NetCDF handler: Could not read information about a NC_CHAR variable while building the DAS."));
588 
589  if (num_dim == 0) {
590  // a scalar NC_CHAR is stuffed into a string of length 1
591  int size = 1;
592  string print_rep = print_attr(NC_INT, 0, (void *) &size);
593  attr_table_ptr->append_attr("string_length", print_type(NC_INT), print_rep);
594  }
595  else {
596  // size_t *dim_sizes = new size_t[num_dim];
597  vector<size_t> dim_sizes(num_dim);
598  for (int i = 0; i < num_dim; ++i) {
599  if ((errstat = nc_inq_dimlen(ncid, vdimids[i], &dim_sizes[i])) != NC_NOERR) {
600  throw Error(errstat,
601  string("NetCDF handler: Could not read dimension information about the variable `") + varname + string("'."));
602  }
603  }
604 
605  // add attribute
606  string print_rep = print_attr(NC_INT, 0, (void *) (&dim_sizes[num_dim - 1]));
607  attr_table_ptr->append_attr("string_length", print_type(NC_INT), print_rep);
608  }
609  }
610 
611 #if NETCDF_VERSION >= 4
612  else if (is_user_defined_type(ncid, var_type)) {
613  //var_type >= NC_FIRSTUSERTYPEID) {
614  vector<char> name(MAX_NC_NAME + 1);
615  int class_type;
616  errstat = nc_inq_user_type(ncid, var_type, &name[0], 0, 0, 0, &class_type);
617  if (errstat != NC_NOERR)
618  throw(InternalErr(__FILE__, __LINE__, "Could not get information about a user-defined type (" + long_to_string(errstat) + ")."));
619 
620  switch (class_type) {
621  case NC_OPAQUE: {
622  attr_table_ptr->append_attr("DAP2_OriginalNetCDFBaseType", print_type(NC_STRING), "NC_OPAQUE");
623  attr_table_ptr->append_attr("DAP2_OriginalNetCDFTypeName", print_type(NC_STRING), &name[0]);
624  break;
625  }
626 
627  case NC_ENUM: {
628  //vector<char> name(MAX_NC_NAME + 1);
629  nc_type base_nc_type;
630  size_t base_size, num_members;
631  errstat = nc_inq_enum(ncid, var_type, 0/*&name[0]*/, &base_nc_type, &base_size, &num_members);
632  if (errstat != NC_NOERR)
633  throw(InternalErr(__FILE__, __LINE__, "Could not get information about an enum(" + long_to_string(errstat) + ")."));
634 
635  // If the base type is a 64-bit int, bail with an error or
636  // a message about unsupported types
637  if (base_nc_type == NC_INT64 || base_nc_type == NC_UINT64) {
638  if (NCRequestHandler::get_ignore_unknown_types())
639  cerr << "An Enum uses 64-bit integers, but this handler does not support that type." << endl;
640  else
641  throw Error("An Enum uses 64-bit integers, but this handler does not support that type.");
642  break;
643  }
644 
645  for (size_t i = 0; i < num_members; ++i) {
646  vector<char> member_name(MAX_NC_NAME + 1);
647  vector<char> member_value(base_size);
648  errstat = nc_inq_enum_member(ncid, var_type, i, &member_name[0], &member_value[0]);
649  if (errstat != NC_NOERR)
650  throw(InternalErr(__FILE__, __LINE__, "Could not get information about an enum value (" + long_to_string(errstat) + ")."));
651  attr_table_ptr->append_attr("DAP2_EnumValues", print_type(base_nc_type), print_attr(base_nc_type, 0, &member_value[0]));
652  attr_table_ptr->append_attr("DAP2_EnumNames", print_type(NC_STRING), &member_name[0]);
653  }
654 
655  attr_table_ptr->append_attr("DAP2_OriginalNetCDFBaseType", print_type(NC_STRING), "NC_ENUM");
656  attr_table_ptr->append_attr("DAP2_OriginalNetCDFTypeName", print_type(NC_STRING), &name[0]);
657 
658  break;
659  }
660 
661  default:
662  break;
663  }
664  }
665 #endif // NETCDF_VERSION >= 4
666  }
667 
668  BESDEBUG(MODULE, prolog << "Starting global attributes" << endl);
669 
670  // global attributes
671  if (ngatts > 0) {
672  AttrTable *attr_table_ptr = das.add_table("NC_GLOBAL", new AttrTable);
673  READ_ATTRIBUTES_MACRO(ncid, NC_GLOBAL, ngatts, attr_table_ptr);
674  }
675 
676  // Add unlimited dimension name in DODS_EXTRA attribute table
677  int xdimid;
678  char dimname[MAX_NC_NAME];
679  nc_type datatype = NC_CHAR;
680  if ((errstat = nc_inq(ncid, (int *) 0, (int *) 0, (int *) 0, &xdimid)) != NC_NOERR)
681  throw InternalErr(__FILE__, __LINE__, string("NetCDF handler: Could not access variable information: ") + nc_strerror(errstat));
682  if (xdimid != -1) {
683  if ((errstat = nc_inq_dim(ncid, xdimid, dimname, (size_t *) 0)) != NC_NOERR)
684  throw InternalErr(__FILE__, __LINE__, string("NetCDF handler: Could not access dimension information: ") + nc_strerror(errstat));
685  string print_rep = print_attr(datatype, 0, dimname);
686  AttrTable *attr_table_ptr = das.add_table("DODS_EXTRA", new AttrTable);
687  attr_table_ptr->append_attr("Unlimited_Dimension", print_type(datatype), print_rep);
688  }
689 
690  if (nc_close(ncid) != NC_NOERR) throw InternalErr(__FILE__, __LINE__, "NetCDF handler: Could not close the dataset!");
691 
692  BESDEBUG(MODULE, prolog << "Exiting nc_read_dataset_attributes" << endl);
693 }
string print_attr(hid_t type, int loc, void *sm_buf)
Definition: h5get.cc:706