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