bes Updated for version 3.21.1
The Backend Server (BES) is the lower two tiers of the Hyrax data server
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-memory 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 and 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 0 // Never do this. jhrg 2/28/22
66
67#define MODULE "nc"
68#define prolog std::string("ncdas::").append(__func__).append("() - ")
69
70using namespace libdap;
71
82static string print_attr(nc_type type, int loc, void *vals)
83{
84 ostringstream rep;
85 union {
86 char *cp;
87 char **stringp;
88 int16_t *sp;
89 uint16_t *usp;
90 int32_t *i;
91 uint32_t *ui;
92 float *fp;
93 double *dp;
94 } gp;
95
96 switch (type) {
97 case NC_UBYTE:
98 unsigned char uc;
99 gp.cp = (char *) vals;
100
101 uc = *(gp.cp + loc);
102 rep << (int) uc;
103 return rep.str();
104
105 case NC_BYTE:
106 if (NCRequestHandler::get_promote_byte_to_short()) {
107 signed char sc;
108 gp.cp = (char *) vals;
109
110 sc = *(gp.cp + loc);
111 rep << (int) sc;
112 return rep.str();
113 }
114 else {
115 unsigned char uc;
116 gp.cp = (char *) vals;
117
118 uc = *(gp.cp + loc);
119 rep << (int) uc;
120 return rep.str();
121 }
122
123 case NC_CHAR:
124 {
125 // Don't escape the special characters.
126 // Special characters will be handled in libdap4.
127 // KY 2022-08-25
128#if 0
129 return escattr(static_cast<const char*>(vals));
130#endif
131 string tmp_str = static_cast<const char*>(vals);
132 return tmp_str;
133 }
134
135 case NC_STRING:
136 gp.stringp = (char **) vals;
137 rep << *(gp.stringp + loc);
138 return rep.str();
139
140 case NC_SHORT:
141 gp.sp = (short *) vals;
142 rep << *(gp.sp + loc);
143 return rep.str();
144
145 case NC_USHORT:
146 gp.usp = (uint16_t *) vals;
147 rep << *(gp.usp + loc);
148 return rep.str();
149
150 case NC_INT:
151 gp.i = (int32_t *) vals; // warning: long int format, int arg (arg 3)
152 rep << *(gp.i + loc);
153 return rep.str();
154
155 case NC_UINT:
156 gp.ui = (uint32_t *) vals;
157 rep << *(gp.ui + loc);
158 return rep.str();
159
160 case NC_FLOAT: {
161 gp.fp = (float *) vals;
162 float valAtLoc = *(gp.fp + loc);
163
164 rep << std::showpoint;
165 rep << std::setprecision(9);
166
167 if (isnan(valAtLoc)) {
168 rep << "NaN";
169 }
170 else {
171 rep << valAtLoc;
172 }
173 // If there's no decimal point and the rep does not use scientific
174 // notation, add a decimal point. This little jaunt was taken because
175 // this code is modeled after older code and that's what it did. I'm
176 // trying to keep the same behavior as the old code without its
177 // problems. jhrg 8/11/2006
178 string tmp_value = rep.str();
179 if (tmp_value.find('.') == string::npos && tmp_value.find('e') == string::npos && tmp_value.find('E') == string::npos
180 && tmp_value.find("nan") == string::npos && tmp_value.find("NaN") == string::npos && tmp_value.find("NAN") == string::npos) rep << ".";
181 return rep.str();
182 }
183
184 case NC_DOUBLE: {
185 gp.dp = (double *) vals;
186 double valAtLoc = *(gp.dp + loc);
187
188 rep << std::showpoint;
189 rep << std::setprecision(16);
190
191 if (std::isnan(valAtLoc)) {
192 rep << "NaN";
193 }
194 else {
195 rep << valAtLoc;
196 }
197 string tmp_value = rep.str();
198 if (tmp_value.find('.') == string::npos && tmp_value.find('e') == string::npos && tmp_value.find('E') == string::npos
199 && tmp_value.find("nan") == string::npos && tmp_value.find("NaN") == string::npos && tmp_value.find("NAN") == string::npos) rep << ".";
200 return rep.str();
201 }
202
203 default:
204 if (NCRequestHandler::get_ignore_unknown_types())
205 cerr << "The netcdf handler tried to print an attribute that has an unrecognized type. (1)" << endl;
206 else
207 throw InternalErr(__FILE__, __LINE__, "The netcdf handler tried to print an attribute that has an unrecognized type. (1)");
208 break;
209 }
210
211 return "";
212}
213
220static string print_type(nc_type datatype)
221{
222 switch (datatype) {
223 case NC_STRING:
224 case NC_CHAR:
225 return "String";
226
227 case NC_UBYTE:
228 return "Byte";
229
230 case NC_BYTE:
231 if (NCRequestHandler::get_promote_byte_to_short()) {
232 return "Int16";
233 }
234 else {
235 return "Byte";
236 }
237
238 case NC_SHORT:
239 return "Int16";
240
241 case NC_INT:
242 return "Int32";
243
244 case NC_USHORT:
245 return "UInt16";
246
247 case NC_UINT:
248 return "UInt32";
249
250 case NC_FLOAT:
251 return "Float32";
252
253 case NC_DOUBLE:
254 return "Float64";
255
256 case NC_COMPOUND:
257 return "NC_COMPOUND";
258
259 // These are all new netcdf 4 types that we don't support yet
260 // as attributes. It's useful to have a print representation for
261 // them so that we can return useful information about why some
262 // information was elided or an exception thrown.
263 case NC_INT64:
264 return "NC_INT64";
265
266 case NC_UINT64:
267 return "NC_UINT64";
268
269 case NC_VLEN:
270 return "NC_VLEN";
271 case NC_OPAQUE:
272 return "NC_OPAQUE";
273 case NC_ENUM:
274 return "NC_ENUM";
275
276 default:
277 if (NCRequestHandler::get_ignore_unknown_types())
278 cerr << "The netcdf handler tried to print an attribute that has an unrecognized type. (2)" << endl;
279 else
280 throw InternalErr(__FILE__, __LINE__, "The netcdf handler tried to print an attribute that has an unrecognized type. (2)");
281 break;
282 }
283
284 return "";
285}
286
291static void append_values(int ncid, int v, int len, nc_type datatype, char *attrname, AttrTable *at)
292{
293 size_t size;
294 int errstat = nc_inq_type(ncid, datatype, 0, &size);
295 if (errstat != NC_NOERR) throw Error(errstat, "Could not get the size for the type.");
296
297 vector<char> value((len + 1) * size);
298 errstat = nc_get_att(ncid, v, attrname, value.data());
299 if (errstat != NC_NOERR) {
300 throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
301 }
302
303 // If the datatype is NC_CHAR then we have a string. netCDF 3
304 // represents strings as arrays of char, but we represent them as X
305 // strings. So... Add the null and set the length to 1
306 if (datatype == NC_CHAR) {
307 value[len] = '\0';
308 len = 1;
309 }
310
311 // add all the attributes in the array
312 for (int loc = 0; loc < len; loc++) {
313 string print_rep = print_attr(datatype, loc, value.data());
314 at->append_attr(attrname, print_type(datatype), print_rep);
315 }
316}
317
329static void read_attributes_netcdf4(int ncid, int varid, int natts, AttrTable *at)
330{
331 BESDEBUG(MODULE, prolog << "In read_attributes_netcdf4" << endl);
332
333 for (int attr_num = 0; attr_num < natts; ++attr_num) {
334 int errstat = NC_NOERR;
335 // Get the attribute name
336 char attrname[MAX_NC_NAME];
337 errstat = nc_inq_attname(ncid, varid, attr_num, attrname);
338 if (errstat != NC_NOERR) throw Error(errstat, "Could not get the name for attribute " + long_to_string(attr_num));
339
340 // Get datatype and len; len is the number of values.
341 nc_type datatype;
342 size_t len;
343 errstat = nc_inq_att(ncid, varid, attrname, &datatype, &len);
344 if (errstat != NC_NOERR) throw Error(errstat, "Could not get the name for attribute '" + string(attrname) + "'");
345
346 BESDEBUG(MODULE, prolog << "nc_inq_att returned datatype = " << datatype << " for '" << attrname << "'" << endl);
347
348 if (datatype >= NC_FIRSTUSERTYPEID) {
349 char type_name[NC_MAX_NAME + 1];
350 size_t size;
351 nc_type base_type;
352 size_t nfields;
353 int class_type;
354 errstat = nc_inq_user_type(ncid, datatype, type_name, &size, &base_type, &nfields, &class_type);
355 if (errstat != NC_NOERR)
356 throw(InternalErr(__FILE__, __LINE__, "Could not get information about a user-defined type (" + long_to_string(errstat) + ")."));
357
358 BESDEBUG(MODULE, prolog << "Before switch(class_type)" << endl);
359 switch (class_type) {
360 case NC_COMPOUND: {
361 // Make recursive attrs work?
362 vector<unsigned char> values((len + 1) * size);
363
364 int errstat = nc_get_att(ncid, varid, attrname, values.data());
365 if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
366
367 for (size_t i = 0; i < nfields; ++i) {
368 char field_name[NC_MAX_NAME + 1];
369 nc_type field_typeid;
370 size_t field_offset;
371 nc_inq_compound_field(ncid, datatype, i, field_name, &field_offset, &field_typeid, 0, 0);
372
373 at->append_attr(field_name, print_type(field_typeid), print_attr(field_typeid, 0, values.data() + field_offset));
374 }
375 break;
376 }
377
378 case NC_VLEN:
379 if (NCRequestHandler::get_ignore_unknown_types())
380 cerr << "in build_user_defined; found a vlen." << endl;
381 else
382 throw Error("The netCDF handler does not yet support the NC_VLEN type.");
383 break;
384
385 case NC_OPAQUE: {
386 vector<unsigned char> values((len + 1) * size);
387
388 int errstat = nc_get_att(ncid, varid, attrname, values.data());
389 if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
390
391 for (size_t i = 0; i < size; ++i)
392 at->append_attr(attrname, print_type(NC_BYTE), print_attr(NC_BYTE, i, values.data()));
393
394 break;
395 }
396
397 case NC_ENUM: {
398#if 0
399 nc_type basetype;
400 size_t base_size, num_members;
401 errstat = nc_inq_enum(ncid, datatype, 0/*char *name*/, &basetype, &base_size, &num_members);
402 if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the size of the enum base type for '") + attrname + string("'"));
403#endif
404 vector<unsigned char> values((len + 1) * size);
405
406 int errstat = nc_get_att(ncid, varid, attrname, values.data());
407 if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
408
409 for (size_t i = 0; i < len; ++i)
410 at->append_attr(attrname, print_type(base_type), print_attr(base_type, i, values.data()));
411
412 break;
413 }
414
415 default:
416 throw InternalErr(__FILE__, __LINE__, "Expected one of NC_COMPOUND, NC_VLEN, NC_OPAQUE or NC_ENUM");
417 }
418
419 BESDEBUG(MODULE, prolog << "After switch(class-type)" << endl);
420 }
421 else {
422 switch (datatype) {
423 case NC_STRING:
424 case NC_BYTE:
425 case NC_CHAR:
426 case NC_SHORT:
427 case NC_INT:
428 case NC_FLOAT:
429 case NC_DOUBLE:
430 case NC_UBYTE:
431 case NC_USHORT:
432 case NC_UINT:
433 BESDEBUG(MODULE, prolog << "Before append_values ..." << endl);
434 append_values(ncid, varid, len, datatype, attrname, at);
435 BESDEBUG(MODULE, prolog << "After append_values ..." << endl);
436 break;
437
438 case NC_INT64:
439 case NC_UINT64: {
440 string note = "Attribute elided: Unsupported attribute type ";
441 note += "(" + print_type(datatype) + ")";
442 at->append_attr(attrname, "String", note);
443 break;
444 }
445
446 case NC_COMPOUND:
447 case NC_VLEN:
448 case NC_OPAQUE:
449 case NC_ENUM:
450 throw InternalErr(__FILE__, __LINE__, "user-defined attribute type not recognized as such!");
451
452 default:
453 throw InternalErr(__FILE__, __LINE__, "Unrecognized attribute type.");
454 }
455 }
456 }
457 BESDEBUG(MODULE, prolog << "Exiting read_attributes_netcdf4" << endl);
458}
459
469void nc_read_dataset_attributes(DAS &das, const string &filename)
470{
471 BESDEBUG(MODULE, prolog << "In nc_read_dataset_attributes" << endl);
472
473 int ncid, errstat;
474 errstat = nc_open(filename.c_str(), NC_NOWRITE, &ncid);
475 if (errstat != NC_NOERR) throw Error(errstat, "NetCDF handler: Could not open " + filename + ".");
476
477 // how many variables? how many global attributes?
478 int nvars, ngatts;
479 errstat = nc_inq(ncid, (int *) 0, &nvars, &ngatts, (int *) 0);
480 if (errstat != NC_NOERR) throw Error(errstat, "NetCDF handler: Could not inquire about netcdf file: " + path_to_filename(filename) + ".");
481
482 // for each variable
483 char varname[MAX_NC_NAME];
484 int natts = 0;
485 nc_type var_type;
486 for (int varid = 0; varid < nvars; ++varid) {
487 BESDEBUG(MODULE, prolog << "Top of for loop; for each var..." << endl);
488
489 errstat = nc_inq_var(ncid, varid, varname, &var_type, (int*) 0, (int*) 0, &natts);
490 if (errstat != NC_NOERR) throw Error(errstat, "Could not get information for variable: " + long_to_string(varid));
491
492 AttrTable *attr_table_ptr = das.get_table(varname);
493 if (!attr_table_ptr) attr_table_ptr = das.add_table(varname, new AttrTable);
494
495 read_attributes_netcdf4(ncid, varid, natts, attr_table_ptr);
496
497 // Add a special attribute for string lengths
498 if (var_type == NC_CHAR) {
499 // number of dimensions and size of Nth dimension
500 int num_dim;
501 int vdimids[MAX_VAR_DIMS]; // variable dimension ids
502 errstat = nc_inq_var(ncid, varid, (char *) 0, (nc_type *) 0, &num_dim, vdimids, (int *) 0);
503 if (errstat != NC_NOERR)
504 throw Error(errstat, string("NetCDF handler: Could not read information about a NC_CHAR variable while building the DAS."));
505
506 if (num_dim == 0) {
507 // a scalar NC_CHAR is stuffed into a string of length 1
508 int size = 1;
509 string print_rep = print_attr(NC_INT, 0, (void *) &size);
510 attr_table_ptr->append_attr("string_length", print_type(NC_INT), print_rep);
511 }
512 else {
513 // size_t *dim_sizes = new size_t[num_dim];
514 vector<size_t> dim_sizes(num_dim);
515 for (int i = 0; i < num_dim; ++i) {
516 if ((errstat = nc_inq_dimlen(ncid, vdimids[i], &dim_sizes[i])) != NC_NOERR) {
517 throw Error(errstat,
518 string("NetCDF handler: Could not read dimension information about the variable `") + varname + string("'."));
519 }
520 }
521
522 // add attribute
523 string print_rep = print_attr(NC_INT, 0, (void *) (&dim_sizes[num_dim - 1]));
524 attr_table_ptr->append_attr("string_length", print_type(NC_INT), print_rep);
525 }
526 }
527 else if (is_user_defined_type(ncid, var_type)) {
528 //var_type >= NC_FIRSTUSERTYPEID) {
529 vector<char> name(MAX_NC_NAME + 1);
530 int class_type;
531 errstat = nc_inq_user_type(ncid, var_type, name.data(), 0, 0, 0, &class_type);
532 if (errstat != NC_NOERR)
533 throw(InternalErr(__FILE__, __LINE__, "Could not get information about a user-defined type (" + long_to_string(errstat) + ")."));
534
535 switch (class_type) {
536 case NC_OPAQUE: {
537 attr_table_ptr->append_attr("DAP2_OriginalNetCDFBaseType", print_type(NC_STRING), "NC_OPAQUE");
538 attr_table_ptr->append_attr("DAP2_OriginalNetCDFTypeName", print_type(NC_STRING), name.data());
539 break;
540 }
541
542 case NC_ENUM: {
543 //vector<char> name(MAX_NC_NAME + 1);
544 nc_type base_nc_type;
545 size_t base_size, num_members;
546 errstat = nc_inq_enum(ncid, var_type, 0/*name.data()*/, &base_nc_type, &base_size, &num_members);
547 if (errstat != NC_NOERR)
548 throw(InternalErr(__FILE__, __LINE__, "Could not get information about an enum(" + long_to_string(errstat) + ")."));
549
550 // If the base type is a 64-bit int, bail with an error or
551 // a message about unsupported types
552 if (base_nc_type == NC_INT64 || base_nc_type == NC_UINT64) {
553 if (NCRequestHandler::get_ignore_unknown_types())
554 cerr << "An Enum uses 64-bit integers, but this handler does not support that type." << endl;
555 else
556 throw Error("An Enum uses 64-bit integers, but this handler does not support that type.");
557 break;
558 }
559
560 for (size_t i = 0; i < num_members; ++i) {
561 vector<char> member_name(MAX_NC_NAME + 1);
562 vector<char> member_value(base_size);
563 errstat = nc_inq_enum_member(ncid, var_type, i, member_name.data(), member_value.data());
564 if (errstat != NC_NOERR)
565 throw(InternalErr(__FILE__, __LINE__, "Could not get information about an enum value (" + long_to_string(errstat) + ")."));
566 attr_table_ptr->append_attr("DAP2_EnumValues", print_type(base_nc_type), print_attr(base_nc_type, 0, member_value.data()));
567 attr_table_ptr->append_attr("DAP2_EnumNames", print_type(NC_STRING), member_name.data());
568 }
569
570 attr_table_ptr->append_attr("DAP2_OriginalNetCDFBaseType", print_type(NC_STRING), "NC_ENUM");
571 attr_table_ptr->append_attr("DAP2_OriginalNetCDFTypeName", print_type(NC_STRING), name.data());
572
573 break;
574 }
575
576 default:
577 break;
578 }
579 }
580 }
581
582 BESDEBUG(MODULE, prolog << "Starting global attributes" << endl);
583
584 // global attributes
585 if (ngatts > 0) {
586 AttrTable *attr_table_ptr = das.add_table("NC_GLOBAL", new AttrTable);
587 read_attributes_netcdf4(ncid, NC_GLOBAL, ngatts, attr_table_ptr);
588 }
589
590 // Add unlimited dimension name in DODS_EXTRA attribute table
591 int xdimid;
592 char dimname[MAX_NC_NAME];
593 nc_type datatype = NC_CHAR;
594 if ((errstat = nc_inq(ncid, (int *) 0, (int *) 0, (int *) 0, &xdimid)) != NC_NOERR)
595 throw InternalErr(__FILE__, __LINE__, string("NetCDF handler: Could not access variable information: ") + nc_strerror(errstat));
596 if (xdimid != -1) {
597 if ((errstat = nc_inq_dim(ncid, xdimid, dimname, (size_t *) 0)) != NC_NOERR)
598 throw InternalErr(__FILE__, __LINE__, string("NetCDF handler: Could not access dimension information: ") + nc_strerror(errstat));
599 string print_rep = print_attr(datatype, 0, dimname);
600 AttrTable *attr_table_ptr = das.add_table("DODS_EXTRA", new AttrTable);
601 attr_table_ptr->append_attr("Unlimited_Dimension", print_type(datatype), print_rep);
602 }
603
604 if (nc_close(ncid) != NC_NOERR) throw InternalErr(__FILE__, __LINE__, "NetCDF handler: Could not close the dataset!");
605
606 BESDEBUG(MODULE, prolog << "Exiting nc_read_dataset_attributes" << endl);
607}
STL class.
STL class.