mirror of
https://github.com/Unidata/netcdf-c.git
synced 2024-12-03 08:01:25 +08:00
2375 lines
67 KiB
C
2375 lines
67 KiB
C
/** \file
|
|
Attribute functions
|
|
|
|
These functions read and write attributes.
|
|
|
|
Copyright 2008 University Corporation for Atmospheric
|
|
Research/Unidata. See \ref copyright file for more info. */
|
|
|
|
#include <config.h>
|
|
#include <stdio.h>
|
|
#ifdef HAVE_GETOPT_H
|
|
#include <getopt.h>
|
|
#endif
|
|
#ifndef _WIN32
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#ifdef HAVE_LOCALE_H
|
|
#include <locale.h>
|
|
#endif /* HAVE_LOCALE_H */
|
|
#include <netcdf.h>
|
|
#include "nctime.h" /* new iso time and calendar stuff */
|
|
#include "ncdump.h"
|
|
#include "dumplib.h"
|
|
#include "vardata.h"
|
|
#include "indent.h"
|
|
#include "isnan.h"
|
|
#include "cdl.h"
|
|
#include "utils.h"
|
|
|
|
#define int64_t long long
|
|
#define uint64_t unsigned long long
|
|
|
|
#define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0)
|
|
|
|
char *progname;
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
#define USAGE "\
|
|
[-c] Coordinate variable data and header information\n\
|
|
[-h] Header information only, no data\n\
|
|
[-v var1[,...]] Data for variable(s) <var1>,... only\n\
|
|
[-b [c|f]] Brief annotations for C or Fortran indices in data\n\
|
|
[-f [c|f]] Full annotations for C or Fortran indices in data\n\
|
|
[-l len] Line length maximum in data section (default 80)\n\
|
|
[-n name] Name for netCDF (default derived from file name)\n\
|
|
[-p n[,n]] Display floating-point values with less precision\n\
|
|
[-k] Output kind of netCDF file\n\
|
|
[-x] Output XML (NcML) instead of CDL\n\
|
|
[-s] Output special (virtual) attributes\n\
|
|
[-t] Output time data as date-time strings\n\
|
|
[-w] Without client-side caching of variables for DAP URLs\n\
|
|
file Name of netCDF file\n"
|
|
|
|
(void) fprintf(stderr,
|
|
"%s [-c|-h] [-v ...] [[-b|-f] [c|f]] [-l len] [-n name] [-p n[,n]] [-k] [-x] [-s] [-t] [-w] file\n%s",
|
|
progname,
|
|
USAGE);
|
|
|
|
(void) fprintf(stderr,
|
|
"netcdf library version %s\n",
|
|
nc_inq_libvers());
|
|
}
|
|
|
|
|
|
/*
|
|
* convert pathname of netcdf file into name for cdl unit, by taking
|
|
* last component of path and stripping off any extension.
|
|
* DMH: add code to handle OPeNDAP url.
|
|
* DMH: I think this also works for UTF8.
|
|
*/
|
|
static char *
|
|
name_path(const char *path)
|
|
{
|
|
const char *cp;
|
|
char *new;
|
|
char *sp;
|
|
|
|
#ifdef vms
|
|
#define FILE_DELIMITER ']'
|
|
#endif
|
|
#if defined(WIN32) || defined(msdos)
|
|
#define FILE_DELIMITER '\\'
|
|
#endif
|
|
#ifndef FILE_DELIMITER /* default to unix */
|
|
#define FILE_DELIMITER '/'
|
|
#endif
|
|
|
|
#ifdef USE_DAP
|
|
/* See if this is a url */
|
|
{
|
|
char* base;
|
|
extern int nc__testurl(const char*,char**);
|
|
if(nc__testurl(path,&base)) {
|
|
return base; /* Looks like a url */
|
|
}
|
|
/* else fall thru and treat like a file path */
|
|
}
|
|
#endif /*USE_DAP*/
|
|
|
|
cp = strrchr(path, FILE_DELIMITER);
|
|
if (cp == 0) /* no delimiter */
|
|
cp = path;
|
|
else /* skip delimeter */
|
|
cp++;
|
|
new = (char *) emalloc((unsigned) (strlen(cp)+1));
|
|
(void) strncpy(new, cp, strlen(cp) + 1); /* copy last component of path */
|
|
if ((sp = strrchr(new, '.')) != NULL)
|
|
*sp = '\0'; /* strip off any extension */
|
|
return new;
|
|
}
|
|
|
|
/* Return primitive type name */
|
|
static const char *
|
|
prim_type_name(nc_type type)
|
|
{
|
|
switch (type) {
|
|
case NC_BYTE:
|
|
return "byte";
|
|
case NC_CHAR:
|
|
return "char";
|
|
case NC_SHORT:
|
|
return "short";
|
|
case NC_INT:
|
|
return "int";
|
|
case NC_FLOAT:
|
|
return "float";
|
|
case NC_DOUBLE:
|
|
return "double";
|
|
#ifdef USE_NETCDF4
|
|
case NC_UBYTE:
|
|
return "ubyte";
|
|
case NC_USHORT:
|
|
return "ushort";
|
|
case NC_UINT:
|
|
return "uint";
|
|
case NC_INT64:
|
|
return "int64";
|
|
case NC_UINT64:
|
|
return "uint64";
|
|
case NC_STRING:
|
|
return "string";
|
|
case NC_VLEN:
|
|
return "vlen";
|
|
case NC_OPAQUE:
|
|
return "opaque";
|
|
case NC_COMPOUND:
|
|
return "compound";
|
|
#endif /* USE_NETCDF4 */
|
|
default:
|
|
error("prim_type_name: bad type %d", type);
|
|
return "bogus";
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Remove trailing zeros (after decimal point) but not trailing decimal
|
|
* point from ss, a string representation of a floating-point number that
|
|
* might include an exponent part.
|
|
*/
|
|
static void
|
|
tztrim(char *ss)
|
|
{
|
|
char *cp, *ep;
|
|
|
|
cp = ss;
|
|
if (*cp == '-')
|
|
cp++;
|
|
while(isdigit((int)*cp) || *cp == '.')
|
|
cp++;
|
|
if (*--cp == '.')
|
|
return;
|
|
ep = cp+1;
|
|
while (*cp == '0')
|
|
cp--;
|
|
cp++;
|
|
if (cp == ep)
|
|
return;
|
|
while (*ep)
|
|
*cp++ = *ep++;
|
|
*cp = '\0';
|
|
return;
|
|
}
|
|
|
|
|
|
/* Return file type string */
|
|
static const char *
|
|
kind_string(int kind)
|
|
{
|
|
switch (kind) {
|
|
case NC_FORMAT_CLASSIC:
|
|
return "classic";
|
|
case NC_FORMAT_64BIT:
|
|
return "64-bit offset";
|
|
case NC_FORMAT_NETCDF4:
|
|
return "netCDF-4";
|
|
case NC_FORMAT_NETCDF4_CLASSIC:
|
|
return "netCDF-4 classic model";
|
|
default:
|
|
error("unrecognized file format: %d");
|
|
return "unrecognized";
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Emit kind of netCDF file
|
|
*/
|
|
static void
|
|
do_nckind(int ncid, const char *path)
|
|
{
|
|
int nc_kind;
|
|
|
|
/*nc_set_log_level(3);*/
|
|
|
|
NC_CHECK( nc_inq_format(ncid, &nc_kind) );
|
|
printf ("%s\n", kind_string(nc_kind));
|
|
NC_CHECK( nc_close(ncid) );
|
|
}
|
|
|
|
|
|
/*
|
|
* Emit initial line of output for NcML
|
|
*/
|
|
static void
|
|
pr_initx(int ncid, const char *path)
|
|
{
|
|
printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<netcdf xmlns=\"http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2\" location=\"%s\">\n",
|
|
path);
|
|
}
|
|
|
|
|
|
/*
|
|
* Print attribute string, for text attributes.
|
|
*/
|
|
static void
|
|
pr_att_string(
|
|
int kind,
|
|
size_t len,
|
|
const char *string
|
|
)
|
|
{
|
|
int iel;
|
|
const char *cp;
|
|
const char *sp;
|
|
unsigned char uc;
|
|
|
|
cp = string;
|
|
printf ("\"");
|
|
/* adjust len so trailing nulls don't get printed */
|
|
sp = cp + len - 1;
|
|
while (len != 0 && *sp-- == '\0')
|
|
len--;
|
|
for (iel = 0; iel < len; iel++)
|
|
switch (uc = *cp++ & 0377) {
|
|
case '\b':
|
|
printf ("\\b");
|
|
break;
|
|
case '\f':
|
|
printf ("\\f");
|
|
break;
|
|
case '\n':
|
|
/* Only generate linebreaks after embedded newlines for
|
|
* classic, 64-bit offset, or classic model files. For
|
|
* netCDF-4 files, don't generate linebreaks, because that
|
|
* would create an extra string in a list of strings. */
|
|
if (kind != NC_FORMAT_NETCDF4) {
|
|
printf ("\\n\",\n\t\t\t\"");
|
|
} else {
|
|
printf("\\n");
|
|
}
|
|
break;
|
|
case '\r':
|
|
printf ("\\r");
|
|
break;
|
|
case '\t':
|
|
printf ("\\t");
|
|
break;
|
|
case '\v':
|
|
printf ("\\v");
|
|
break;
|
|
case '\\':
|
|
printf ("\\\\");
|
|
break;
|
|
case '\'':
|
|
printf ("\\'");
|
|
break;
|
|
case '\"':
|
|
printf ("\\\"");
|
|
break;
|
|
default:
|
|
if (iscntrl(uc))
|
|
printf ("\\%03o",uc);
|
|
else
|
|
printf ("%c",uc);
|
|
break;
|
|
}
|
|
printf ("\"");
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* Print NcML attribute string, for text attributes.
|
|
*/
|
|
static void
|
|
pr_attx_string(
|
|
size_t len,
|
|
const char *string
|
|
)
|
|
{
|
|
int iel;
|
|
const char *cp;
|
|
const char *sp;
|
|
unsigned char uc;
|
|
|
|
cp = string;
|
|
printf ("\"");
|
|
/* adjust len so trailing nulls don't get printed */
|
|
sp = cp + len - 1;
|
|
while (len != 0 && *sp-- == '\0')
|
|
len--;
|
|
for (iel = 0; iel < len; iel++)
|
|
switch (uc = *cp++ & 0377) {
|
|
case '\"':
|
|
printf (""");
|
|
break;
|
|
case '<':
|
|
printf ("<");
|
|
break;
|
|
case '>':
|
|
printf (">");
|
|
break;
|
|
case '&':
|
|
printf ("&");
|
|
break;
|
|
case '\n':
|
|
printf ("
");
|
|
break;
|
|
case '\r':
|
|
printf ("
");
|
|
break;
|
|
case '\t':
|
|
printf ("	");
|
|
break;
|
|
default:
|
|
if (iscntrl(uc))
|
|
printf ("&#%d;",uc);
|
|
else
|
|
printf ("%c",uc);
|
|
break;
|
|
}
|
|
printf ("\"");
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* Print list of attribute values, for attributes of primitive types.
|
|
* Attribute values must be printed with explicit type tags for
|
|
* netCDF-3 primitive types, because CDL doesn't require explicit
|
|
* syntax to declare such attribute types.
|
|
*/
|
|
static void
|
|
pr_att_valgs(
|
|
int kind,
|
|
nc_type type,
|
|
size_t len,
|
|
const void *vals
|
|
)
|
|
{
|
|
int iel;
|
|
signed char sc;
|
|
short ss;
|
|
int ii;
|
|
char gps[PRIM_LEN];
|
|
float ff;
|
|
double dd;
|
|
#ifdef USE_NETCDF4
|
|
unsigned char uc;
|
|
unsigned short us;
|
|
unsigned int ui;
|
|
int64_t i64;
|
|
uint64_t ui64;
|
|
char *stringp;
|
|
#endif /* USE_NETCDF4 */
|
|
char *delim = ", "; /* delimiter between output values */
|
|
|
|
if (type == NC_CHAR) {
|
|
char *cp = (char *) vals;
|
|
pr_att_string(kind, len, cp);
|
|
return;
|
|
}
|
|
/* else */
|
|
for (iel = 0; iel < len; iel++) {
|
|
if (iel == len - 1)
|
|
delim = "";
|
|
switch (type) {
|
|
case NC_BYTE:
|
|
sc = ((signed char *) vals)[iel];
|
|
printf ("%db%s", sc, delim);
|
|
break;
|
|
case NC_SHORT:
|
|
ss = ((short *) vals)[iel];
|
|
printf ("%ds%s", ss, delim);
|
|
break;
|
|
case NC_INT:
|
|
ii = ((int *) vals)[iel];
|
|
printf ("%d%s", ii, delim);
|
|
break;
|
|
case NC_FLOAT:
|
|
ff = ((float *) vals)[iel];
|
|
if(isfinite(ff)) {
|
|
int res;
|
|
res = snprintf(gps, PRIM_LEN, float_att_fmt, ff);
|
|
assert(res < PRIM_LEN);
|
|
tztrim(gps); /* trim trailing 0's after '.' */
|
|
printf ("%s%s", gps, delim);
|
|
} else {
|
|
if(isnan(ff)) {
|
|
printf("NaNf%s", delim);
|
|
} else if(isinf(ff)) {
|
|
if(ff < 0.0f) {
|
|
printf("-");
|
|
}
|
|
printf("Infinityf%s", delim);
|
|
}
|
|
}
|
|
break;
|
|
case NC_DOUBLE:
|
|
dd = ((double *) vals)[iel];
|
|
if(isfinite(dd)) {
|
|
int res;
|
|
res = snprintf(gps, PRIM_LEN, double_att_fmt, dd);
|
|
assert(res < PRIM_LEN);
|
|
tztrim(gps);
|
|
printf ("%s%s", gps, delim);
|
|
} else {
|
|
if(isnan(dd)) {
|
|
printf("NaN%s", delim);
|
|
} else if(isinf(dd)) {
|
|
if(dd < 0.0) {
|
|
printf("-");
|
|
}
|
|
printf("Infinity%s", delim);
|
|
}
|
|
}
|
|
break;
|
|
#ifdef USE_NETCDF4
|
|
case NC_UBYTE:
|
|
uc = ((unsigned char *) vals)[iel];
|
|
printf ("%uUB%s", uc, delim);
|
|
break;
|
|
case NC_USHORT:
|
|
us = ((unsigned short *) vals)[iel];
|
|
printf ("%uUS%s", us, delim);
|
|
break;
|
|
case NC_UINT:
|
|
ui = ((unsigned int *) vals)[iel];
|
|
printf ("%uU%s", ui, delim);
|
|
break;
|
|
case NC_INT64:
|
|
i64 = ((int64_t *) vals)[iel];
|
|
printf ("%lldL%s", i64, delim);
|
|
break;
|
|
case NC_UINT64:
|
|
ui64 = ((uint64_t *) vals)[iel];
|
|
printf ("%lluUL%s", ui64, delim);
|
|
break;
|
|
case NC_STRING:
|
|
stringp = ((char **) vals)[iel];
|
|
pr_att_string(kind, strlen(stringp), stringp);
|
|
printf("%s", delim);
|
|
break;
|
|
#endif /* USE_NETCDF4 */
|
|
default:
|
|
error("pr_att_vals: bad type");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Print list of numeric attribute values to string for use in NcML output.
|
|
* Unlike CDL, NcML makes type explicit, so don't need type suffixes.
|
|
*/
|
|
static void
|
|
pr_att_valsx(
|
|
nc_type type,
|
|
size_t len,
|
|
const double *vals,
|
|
char *attvals, /* returned string */
|
|
size_t attvalslen /* size of attvals buffer, assumed
|
|
large enough to hold all len
|
|
blank-separated values */
|
|
)
|
|
{
|
|
int iel;
|
|
float ff;
|
|
double dd;
|
|
int ii;
|
|
#ifdef USE_NETCDF4
|
|
unsigned int ui;
|
|
int64_t i64;
|
|
uint64_t ui64;
|
|
#endif /* USE_NETCDF4 */
|
|
|
|
attvals[0]='\0';
|
|
if (len == 0)
|
|
return;
|
|
for (iel = 0; iel < len; iel++) {
|
|
char gps[PRIM_LEN];
|
|
int res;
|
|
switch (type) {
|
|
case NC_BYTE:
|
|
case NC_SHORT:
|
|
case NC_INT:
|
|
ii = vals[iel];
|
|
res = snprintf(gps, PRIM_LEN, "%d", ii);
|
|
assert(res < PRIM_LEN);
|
|
(void) strlcat(attvals, gps, attvalslen);
|
|
(void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
|
|
break;
|
|
#ifdef USE_NETCDF4
|
|
case NC_UBYTE:
|
|
case NC_USHORT:
|
|
case NC_UINT:
|
|
ui = vals[iel];
|
|
res = snprintf(gps, PRIM_LEN, "%u", ui);
|
|
assert(res < PRIM_LEN);
|
|
(void) strlcat(attvals, gps, attvalslen);
|
|
(void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
|
|
break;
|
|
case NC_INT64:
|
|
i64 = vals[iel];
|
|
res = snprintf(gps, PRIM_LEN, "%lld", i64);
|
|
assert(res < PRIM_LEN);
|
|
(void) strlcat(attvals, gps, attvalslen);
|
|
(void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
|
|
break;
|
|
case NC_UINT64:
|
|
ui64 = vals[iel];
|
|
res = snprintf(gps, PRIM_LEN, "%llu", ui64);
|
|
assert(res < PRIM_LEN);
|
|
(void) strlcat(attvals, gps, attvalslen);
|
|
(void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
|
|
break;
|
|
#endif /* USE_NETCDF4 */
|
|
case NC_FLOAT:
|
|
ff = vals[iel];
|
|
res = snprintf(gps, PRIM_LEN, float_attx_fmt, ff);
|
|
assert(res < PRIM_LEN);
|
|
tztrim(gps); /* trim trailing 0's after '.' */
|
|
(void) strlcat(attvals, gps, attvalslen);
|
|
(void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
|
|
break;
|
|
case NC_DOUBLE:
|
|
dd = vals[iel];
|
|
res = snprintf(gps, PRIM_LEN, double_att_fmt, dd);
|
|
assert(res < PRIM_LEN);
|
|
tztrim(gps); /* trim trailing 0's after '.' */
|
|
(void) strlcat(attvals, gps, attvalslen);
|
|
(void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
|
|
break;
|
|
default:
|
|
error("pr_att_valsx: bad type");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Print a variable attribute
|
|
*/
|
|
static void
|
|
pr_att(
|
|
int ncid,
|
|
int kind,
|
|
int varid,
|
|
const char *varname,
|
|
int ia
|
|
)
|
|
{
|
|
ncatt_t att; /* attribute */
|
|
|
|
NC_CHECK( nc_inq_attname(ncid, varid, ia, att.name) );
|
|
NC_CHECK( nc_inq_att(ncid, varid, att.name, &att.type, &att.len) );
|
|
att.tinfo = get_typeinfo(att.type);
|
|
|
|
indent_out();
|
|
printf ("\t\t");
|
|
#ifdef USE_NETCDF4
|
|
if (is_user_defined_type(att.type) || att.type == NC_STRING)
|
|
#else
|
|
if (is_user_defined_type(att.type))
|
|
#endif
|
|
{
|
|
/* TODO: omit next two lines if att_type_name not needed
|
|
* because print_type_name() looks it up */
|
|
char att_type_name[NC_MAX_NAME + 1];
|
|
get_type_name(ncid, att.type, att_type_name);
|
|
|
|
/* printf ("\t\t%s ", att_type_name); */
|
|
/* ... but handle special characters in CDL names with escapes */
|
|
print_type_name(ncid, att.type);
|
|
printf(" ");
|
|
}
|
|
/* printf ("\t\t%s:%s = ", varname, att.name); */
|
|
print_name(varname);
|
|
printf(":");
|
|
print_name(att.name);
|
|
printf(" = ");
|
|
|
|
if (att.len == 0) { /* show 0-length attributes as empty strings */
|
|
att.type = NC_CHAR;
|
|
}
|
|
|
|
if (! is_user_defined_type(att.type) ) {
|
|
att.valgp = (void *) emalloc((att.len + 1) * att.tinfo->size );
|
|
NC_CHECK( nc_get_att(ncid, varid, att.name, att.valgp ) );
|
|
if(att.type == NC_CHAR) /* null-terminate retrieved text att value */
|
|
((char *)att.valgp)[att.len] = '\0';
|
|
pr_att_valgs(kind, att.type, att.len, att.valgp);
|
|
#ifdef USE_NETCDF4
|
|
/* If NC_STRING, need to free all the strings also */
|
|
if(att.type == NC_STRING) {
|
|
nc_free_string(att.len, att.valgp);
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
free(att.valgp);
|
|
}
|
|
#ifdef USE_NETCDF4
|
|
else /* User-defined type. */
|
|
{
|
|
char type_name[NC_MAX_NAME + 1];
|
|
size_t type_size, nfields;
|
|
nc_type base_nc_type;
|
|
int class, i;
|
|
void *data;
|
|
|
|
NC_CHECK( nc_inq_user_type(ncid, att.type, type_name, &type_size,
|
|
&base_nc_type, &nfields, &class));
|
|
switch(class)
|
|
{
|
|
case NC_VLEN:
|
|
/* because size returned for vlen is base type size, but we
|
|
* need space to read array of vlen structs into ... */
|
|
data = emalloc((att.len + 1) * sizeof(nc_vlen_t));
|
|
break;
|
|
case NC_OPAQUE:
|
|
data = emalloc((att.len + 1) * type_size);
|
|
break;
|
|
case NC_ENUM:
|
|
/* a long long is ample for all base types */
|
|
data = emalloc((att.len + 1) * sizeof(int64_t));
|
|
break;
|
|
case NC_COMPOUND:
|
|
data = emalloc((att.len + 1) * type_size);
|
|
break;
|
|
default:
|
|
error("unrecognized class of user defined type: %d", class);
|
|
}
|
|
|
|
NC_CHECK( nc_get_att(ncid, varid, att.name, data));
|
|
|
|
switch(class) {
|
|
case NC_VLEN:
|
|
pr_any_att_vals(&att, data);
|
|
free(data);
|
|
break;
|
|
case NC_OPAQUE: {
|
|
char *sout = emalloc(2 * type_size + strlen("0X") + 1);
|
|
unsigned char *cp = data;
|
|
for (i = 0; i < att.len; i++) {
|
|
(void) ncopaque_val_as_hex(type_size, sout, cp);
|
|
printf("%s%s", sout, i < att.len-1 ? ", " : "");
|
|
cp += type_size;
|
|
}
|
|
free(sout);
|
|
}
|
|
break;
|
|
case NC_ENUM: {
|
|
int64_t value;
|
|
for (i = 0; i < att.len; i++) {
|
|
char enum_name[NC_MAX_NAME + 1];
|
|
switch(base_nc_type)
|
|
{
|
|
case NC_BYTE:
|
|
value = *((char *)data + i);
|
|
break;
|
|
case NC_UBYTE:
|
|
value = *((unsigned char *)data + i);
|
|
break;
|
|
case NC_SHORT:
|
|
value = *((short *)data + i);
|
|
break;
|
|
case NC_USHORT:
|
|
value = *((unsigned short *)data + i);
|
|
break;
|
|
case NC_INT:
|
|
value = *((int *)data + i);
|
|
break;
|
|
case NC_UINT:
|
|
value = *((unsigned int *)data + i);
|
|
break;
|
|
case NC_INT64:
|
|
value = *((int64_t *)data + i);
|
|
break;
|
|
case NC_UINT64:
|
|
value = *((uint64_t *)data + i);
|
|
break;
|
|
}
|
|
NC_CHECK( nc_inq_enum_ident(ncid, att.type, value,
|
|
enum_name));
|
|
/* printf("%s%s", enum_name, i < att.len-1 ? ", " : ""); */
|
|
print_name(enum_name);
|
|
printf("%s", i < att.len-1 ? ", " : "");
|
|
}
|
|
}
|
|
break;
|
|
case NC_COMPOUND:
|
|
pr_any_att_vals(&att, data);
|
|
free(data);
|
|
break;
|
|
default:
|
|
error("unrecognized class of user defined type: %d", class);
|
|
}
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
|
|
printf (" ;\n");
|
|
}
|
|
|
|
/* Common code for printing attribute name */
|
|
static void
|
|
pr_att_name(
|
|
int ncid,
|
|
const char *varname,
|
|
const char *attname
|
|
)
|
|
{
|
|
indent_out();
|
|
printf ("\t\t");
|
|
print_name(varname);
|
|
printf(":");
|
|
print_name(attname);
|
|
}
|
|
|
|
/*
|
|
* Print special _Format global attribute, a virtual attribute not
|
|
* actually stored in the file.
|
|
*/
|
|
static void
|
|
pr_att_global_format(
|
|
int ncid,
|
|
int kind
|
|
)
|
|
{
|
|
pr_att_name(ncid, "", NC_ATT_FORMAT);
|
|
printf(" = ");
|
|
printf("\"%s\"", kind_string(kind));
|
|
printf (" ;\n");
|
|
}
|
|
|
|
|
|
#ifdef USE_NETCDF4
|
|
/*
|
|
* Print special reserved variable attributes, such as _Chunking,
|
|
* _DeflateLevel, ... These are virtual, not real, attributes
|
|
* generated from the result of inquire calls. They are of primitive
|
|
* type to fit into the classic model. Currently, these only exist
|
|
* for netCDF-4 data.
|
|
*/
|
|
static void
|
|
pr_att_specials(
|
|
int ncid,
|
|
int kind,
|
|
int varid,
|
|
const ncvar_t *varp
|
|
)
|
|
{
|
|
/* No special variable attributes for classic or 64-bit offset data */
|
|
if(kind == 1 || kind == 2)
|
|
return;
|
|
/* _Chunking */
|
|
if (varp->ndims > 0) { /* no chunking for scalar variables */
|
|
int contig = 0;
|
|
NC_CHECK( nc_inq_var_chunking(ncid, varid, &contig, NULL ) );
|
|
if(contig == 1) {
|
|
pr_att_name(ncid, varp->name, NC_ATT_STORAGE);
|
|
printf(" = \"contiguous\" ;\n");
|
|
} else {
|
|
size_t *chunkp;
|
|
int i;
|
|
pr_att_name(ncid, varp->name, NC_ATT_STORAGE);
|
|
printf(" = \"chunked\" ;\n");
|
|
chunkp = (size_t *) emalloc(sizeof(size_t) * (varp->ndims + 1) );
|
|
NC_CHECK( nc_inq_var_chunking(ncid, varid, NULL, chunkp) );
|
|
/* print chunking, even if it is default */
|
|
pr_att_name(ncid, varp->name, NC_ATT_CHUNKING);
|
|
printf(" = ");
|
|
for(i = 0; i < varp->ndims; i++) {
|
|
printf("%lu%s", (unsigned long)chunkp[i], i+1 < varp->ndims ? ", " : " ;\n");
|
|
}
|
|
free(chunkp);
|
|
}
|
|
}
|
|
|
|
/*_Deflate, _Shuffle */
|
|
{
|
|
int shuffle=NC_NOSHUFFLE, deflate=0, deflate_level=0;
|
|
NC_CHECK( nc_inq_var_deflate(ncid, varid, &shuffle,
|
|
&deflate, &deflate_level) );
|
|
if(deflate != 0) {
|
|
pr_att_name(ncid, varp->name, NC_ATT_DEFLATE);
|
|
printf(" = %d ;\n", deflate_level);
|
|
}
|
|
if(shuffle != NC_NOSHUFFLE) {
|
|
pr_att_name(ncid, varp->name, NC_ATT_SHUFFLE);
|
|
printf(" = \"true\" ;\n");
|
|
}
|
|
}
|
|
/* _Checksum */
|
|
{
|
|
int fletcher32 = 0;
|
|
NC_CHECK( nc_inq_var_fletcher32(ncid, varid, &fletcher32) );
|
|
if(fletcher32 != 0) {
|
|
pr_att_name(ncid, varp->name, NC_ATT_CHECKSUM);
|
|
printf(" = \"true\" ;\n");
|
|
}
|
|
}
|
|
/* _Endianness */
|
|
if(varp->tinfo->size > 1) /* Endianness is meaningless for 1-byte types */
|
|
{
|
|
int endianness = 0;
|
|
NC_CHECK( nc_inq_var_endian(ncid, varid, &endianness) );
|
|
if(endianness != 0) {
|
|
pr_att_name(ncid, varp->name, NC_ATT_ENDIANNESS);
|
|
printf(" = ");
|
|
switch (endianness) {
|
|
case NC_ENDIAN_LITTLE:
|
|
printf("\"little\"");
|
|
break;
|
|
case NC_ENDIAN_BIG:
|
|
printf("\"big\"");
|
|
break;
|
|
case NC_ENDIAN_NATIVE:
|
|
printf("\"native\"");
|
|
break;
|
|
default:
|
|
error("pr_att_specials: bad endianness: %d", endianness);
|
|
break;
|
|
}
|
|
printf(" ;\n");
|
|
}
|
|
}
|
|
{
|
|
int no_fill = 0;
|
|
/* Don't get the fill_value, it's set explicitly with
|
|
* _FillValue attribute, because nc_def_var_fill() creates a
|
|
* _FillValue attribute, if needed, and it's value gets
|
|
* displayed elsewhere as a normal (not special virtual)
|
|
* attribute. */
|
|
NC_CHECK( nc_inq_var_fill(ncid, varid, &no_fill, NULL) );
|
|
if(no_fill != 0) {
|
|
pr_att_name(ncid, varp->name, NC_ATT_NOFILL);
|
|
printf(" = \"true\" ;\n");
|
|
}
|
|
}
|
|
/* TODO: handle _Nbit when inquire function is available */
|
|
|
|
/* TODO: handle _ScaleOffset when inquire is available */
|
|
|
|
/* TODO: handle _Szip when szip inquire function is available */
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
|
|
|
|
/*
|
|
* Print a variable attribute for NcML
|
|
*/
|
|
static void
|
|
pr_attx(
|
|
int ncid,
|
|
int varid,
|
|
int ia
|
|
)
|
|
{
|
|
ncatt_t att; /* attribute */
|
|
char *attvals;
|
|
int attvalslen = 0;
|
|
|
|
NC_CHECK( nc_inq_attname(ncid, varid, ia, att.name) );
|
|
NC_CHECK( nc_inq_att(ncid, varid, att.name, &att.type, &att.len) );
|
|
att.tinfo = get_typeinfo(att.type);
|
|
|
|
/* Put attribute values into a single string, with blanks in between */
|
|
|
|
switch (att.type) {
|
|
case NC_CHAR:
|
|
attvals = (char *) emalloc(att.len + 1);
|
|
attvalslen = att.len;
|
|
attvals[att.len] = '\0';
|
|
NC_CHECK( nc_get_att_text(ncid, varid, att.name, attvals ) );
|
|
break;
|
|
#ifdef USE_NETCDF4
|
|
case NC_STRING:
|
|
/* TODO: this only prints first string value, need to handle
|
|
multiple strings? */
|
|
attvals = (char *) emalloc(att.len + 1);
|
|
attvals[att.len] = '\0';
|
|
NC_CHECK( nc_get_att_text(ncid, varid, att.name, attvals ) );
|
|
break;
|
|
case NC_VLEN:
|
|
/* TODO */
|
|
break;
|
|
case NC_OPAQUE:
|
|
/* TODO */
|
|
break;
|
|
case NC_COMPOUND:
|
|
/* TODO */
|
|
break;
|
|
#endif /* USE_NETCDF4 */
|
|
default:
|
|
att.vals = (double *) emalloc((att.len + 1) * sizeof(double));
|
|
NC_CHECK( nc_get_att_double(ncid, varid, att.name, att.vals ) );
|
|
attvalslen = 20*att.len; /* max 20 chars for each value and blank separator */
|
|
attvals = (char *) emalloc(attvalslen + 1);
|
|
pr_att_valsx(att.type, att.len, att.vals, attvals, attvalslen);
|
|
free(att.vals);
|
|
break;
|
|
}
|
|
|
|
/* Don't output type for string attributes, since that's default type */
|
|
if(att.type == NC_CHAR
|
|
#ifdef USE_NETCDF4
|
|
|| att.type == NC_CHAR
|
|
#endif /* USE_NETCDF4 */
|
|
) {
|
|
/* TODO: XML-ish escapes for special chars in names */
|
|
printf ("%s <attribute name=\"%s\" value=",
|
|
varid != NC_GLOBAL ? " " : "",
|
|
att.name);
|
|
/* print attvals as a string with XML escapes */
|
|
pr_attx_string(attvalslen, attvals);
|
|
} else { /* non-string attribute */
|
|
char att_type_name[NC_MAX_NAME + 1];
|
|
get_type_name(ncid, att.type, att_type_name);
|
|
/* TODO: print full type name with group prefix, when needed */
|
|
printf ("%s <attribute name=\"%s\" type=\"%s\" value=\"",
|
|
varid != NC_GLOBAL ? " " : "",
|
|
att.name,
|
|
att_type_name);
|
|
printf("%s\"",attvals);
|
|
}
|
|
printf (" />\n");
|
|
free (attvals);
|
|
}
|
|
|
|
|
|
/* Print optional NcML attribute for a variable's shape */
|
|
static void
|
|
pr_shape(ncvar_t* varp, ncdim_t *dims)
|
|
{
|
|
char *shape;
|
|
int shapelen = 0;
|
|
int id;
|
|
|
|
if (varp->ndims == 0)
|
|
return;
|
|
for (id = 0; id < varp->ndims; id++) {
|
|
shapelen += strlen(dims[varp->dims[id]].name) + 1;
|
|
}
|
|
shape = (char *) emalloc(shapelen + 1);
|
|
shape[0] = '\0';
|
|
for (id = 0; id < varp->ndims; id++) {
|
|
/* TODO: XML-ish escapes for special chars in dim names */
|
|
strlcat(shape, dims[varp->dims[id]].name, shapelen);
|
|
strlcat(shape, id < varp->ndims-1 ? " " : "", shapelen);
|
|
}
|
|
printf (" shape=\"%s\"", shape);
|
|
free(shape);
|
|
}
|
|
|
|
#ifdef USE_NETCDF4
|
|
|
|
|
|
/* Print an enum type declaration */
|
|
static void
|
|
print_enum_type(int ncid, nc_type typeid) {
|
|
char type_name[NC_MAX_NAME + 1];
|
|
size_t type_size;
|
|
nc_type base_nc_type;
|
|
size_t type_nfields;
|
|
int type_class;
|
|
char base_type_name[NC_MAX_NAME + 1];
|
|
int f;
|
|
int64_t memval;
|
|
char memname[NC_MAX_NAME + 1];
|
|
/* extra space for escapes, and punctuation */
|
|
#define SAFE_BUF_LEN 4*NC_MAX_NAME+30
|
|
char safe_buf[SAFE_BUF_LEN];
|
|
char *delim;
|
|
int64_t data; /* space for data of any primitive type */
|
|
char *esc_btn;
|
|
char *esc_tn;
|
|
char *esc_mn;
|
|
int res;
|
|
|
|
NC_CHECK( nc_inq_user_type(ncid, typeid, type_name, &type_size, &base_nc_type,
|
|
&type_nfields, &type_class) );
|
|
|
|
get_type_name(ncid, base_nc_type, base_type_name);
|
|
indent_out();
|
|
esc_btn = escaped_name(base_type_name);
|
|
esc_tn = escaped_name(type_name);
|
|
res = snprintf(safe_buf, SAFE_BUF_LEN,"%s enum %s {", esc_btn, esc_tn);
|
|
assert(res < SAFE_BUF_LEN);
|
|
free(esc_btn);
|
|
free(esc_tn);
|
|
lput(safe_buf);
|
|
delim = ", ";
|
|
for (f = 0; f < type_nfields; f++) {
|
|
if (f == type_nfields - 1)
|
|
delim = "} ;\n";
|
|
NC_CHECK( nc_inq_enum_member(ncid, typeid, f, memname, &data) );
|
|
switch (base_nc_type) {
|
|
case NC_BYTE:
|
|
memval = *(char *)&data;
|
|
break;
|
|
case NC_SHORT:
|
|
memval = *(short *)&data;
|
|
break;
|
|
case NC_INT:
|
|
memval = *(int *)&data;
|
|
break;
|
|
#ifdef USE_NETCDF4
|
|
case NC_UBYTE:
|
|
memval = *(unsigned char *)&data;
|
|
break;
|
|
case NC_USHORT:
|
|
memval = *(unsigned short *)&data;
|
|
break;
|
|
case NC_UINT:
|
|
memval = *(unsigned int *)&data;
|
|
break;
|
|
case NC_INT64:
|
|
memval = *(int64_t *)&data;
|
|
break;
|
|
case NC_UINT64:
|
|
memval = *(uint64_t *)&data;
|
|
break;
|
|
#endif /* USE_NETCDF4 */
|
|
default:
|
|
error("Bad base type for enum!");
|
|
break;
|
|
}
|
|
esc_mn = escaped_name(memname);
|
|
res = snprintf(safe_buf, SAFE_BUF_LEN, "%s = %lld%s", memname,
|
|
memval, delim);
|
|
assert(res < SAFE_BUF_LEN);
|
|
free(esc_mn);
|
|
lput(safe_buf);
|
|
}
|
|
}
|
|
|
|
|
|
/* Print a user-defined type declaration */
|
|
static void
|
|
print_ud_type(int ncid, nc_type typeid) {
|
|
|
|
char type_name[NC_MAX_NAME + 1];
|
|
char base_type_name[NC_MAX_NAME + 1];
|
|
size_t type_nfields, type_size;
|
|
nc_type base_nc_type;
|
|
int f, type_class;
|
|
|
|
NC_CHECK( nc_inq_user_type(ncid, typeid, type_name, &type_size, &base_nc_type,
|
|
&type_nfields, &type_class) );
|
|
switch(type_class) {
|
|
case NC_VLEN:
|
|
/* TODO: don't bother getting base_type_name if
|
|
* print_type_name looks it up anyway */
|
|
get_type_name(ncid, base_nc_type, base_type_name);
|
|
indent_out();
|
|
/* printf("%s(*) %s ;\n", base_type_name, type_name); */
|
|
print_type_name(ncid, base_nc_type);
|
|
printf("(*) ");
|
|
print_type_name(ncid, typeid);
|
|
printf(" ;\n");
|
|
break;
|
|
case NC_OPAQUE:
|
|
indent_out();
|
|
/* printf("opaque(%d) %s ;\n", (int)type_size, type_name); */
|
|
printf("opaque(%d) ", (int)type_size);
|
|
print_type_name(ncid, typeid);
|
|
printf(" ;\n");
|
|
break;
|
|
case NC_ENUM:
|
|
print_enum_type(ncid, typeid);
|
|
break;
|
|
case NC_COMPOUND:
|
|
{
|
|
char field_name[NC_MAX_NAME + 1];
|
|
char field_type_name[NC_MAX_NAME + 1];
|
|
size_t field_offset;
|
|
nc_type field_type;
|
|
int field_ndims;
|
|
int d;
|
|
|
|
indent_out();
|
|
/* printf("compound %s {\n", type_name); */
|
|
printf("compound ");
|
|
print_type_name(ncid, typeid);
|
|
printf(" {\n");
|
|
for (f = 0; f < type_nfields; f++)
|
|
{
|
|
NC_CHECK( nc_inq_compound_field(ncid, typeid, f, field_name,
|
|
&field_offset, &field_type,
|
|
&field_ndims, NULL) );
|
|
/* TODO: don't bother if field_type_name not needed here */
|
|
get_type_name(ncid, field_type, field_type_name);
|
|
indent_out();
|
|
/* printf(" %s %s", field_type_name, field_name); */
|
|
printf(" ");
|
|
print_type_name(ncid, field_type);
|
|
printf(" ");
|
|
print_name(field_name);
|
|
if (field_ndims > 0) {
|
|
int *field_dim_sizes = (int *) emalloc((field_ndims + 1) * sizeof(int));
|
|
NC_CHECK( nc_inq_compound_field(ncid, typeid, f, NULL,
|
|
NULL, NULL, NULL,
|
|
field_dim_sizes) );
|
|
printf("(");
|
|
for (d = 0; d < field_ndims-1; d++)
|
|
printf("%d, ", field_dim_sizes[d]);
|
|
printf("%d)", field_dim_sizes[field_ndims-1]);
|
|
free(field_dim_sizes);
|
|
}
|
|
printf(" ;\n");
|
|
}
|
|
indent_out();
|
|
/* printf("}; // %s\n", type_name); */
|
|
printf("}; // ");
|
|
print_type_name(ncid, typeid);
|
|
printf("\n");
|
|
}
|
|
break;
|
|
default:
|
|
error("Unknown class of user-defined type!");
|
|
}
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
|
|
static void
|
|
get_fill_info(int ncid, int varid, ncvar_t *vp) {
|
|
ncatt_t att; /* attribute */
|
|
int nc_status; /* return from netcdf calls */
|
|
void *fillvalp = NULL;
|
|
|
|
vp->has_fillval = 1; /* by default, but turn off for bytes */
|
|
|
|
/* get _FillValue attribute */
|
|
nc_status = nc_inq_att(ncid,varid,_FillValue,&att.type,&att.len);
|
|
fillvalp = emalloc(vp->tinfo->size + 1);
|
|
if(nc_status == NC_NOERR &&
|
|
att.type == vp->type && att.len == 1) {
|
|
NC_CHECK(nc_get_att(ncid, varid, _FillValue, fillvalp));
|
|
} else {
|
|
switch (vp->type) {
|
|
case NC_BYTE:
|
|
/* don't do default fill-values for bytes, too risky */
|
|
vp->has_fillval = 0;
|
|
free(fillvalp);
|
|
fillvalp = 0;
|
|
break;
|
|
case NC_CHAR:
|
|
*(char *)fillvalp = NC_FILL_CHAR;
|
|
break;
|
|
case NC_SHORT:
|
|
*(short *)fillvalp = NC_FILL_SHORT;
|
|
break;
|
|
case NC_INT:
|
|
*(int *)fillvalp = NC_FILL_INT;
|
|
break;
|
|
case NC_FLOAT:
|
|
*(float *)fillvalp = NC_FILL_FLOAT;
|
|
break;
|
|
case NC_DOUBLE:
|
|
*(double *)fillvalp = NC_FILL_DOUBLE;
|
|
break;
|
|
#ifdef USE_NETCDF4
|
|
case NC_UBYTE:
|
|
/* don't do default fill-values for bytes, too risky */
|
|
vp->has_fillval = 0;
|
|
free(fillvalp);
|
|
fillvalp = 0;
|
|
break;
|
|
case NC_USHORT:
|
|
*(unsigned short *)fillvalp = NC_FILL_USHORT;
|
|
break;
|
|
case NC_UINT:
|
|
*(unsigned int *)fillvalp = NC_FILL_UINT;
|
|
break;
|
|
case NC_INT64:
|
|
*(int64_t *)fillvalp = NC_FILL_INT64;
|
|
break;
|
|
case NC_UINT64:
|
|
*(uint64_t *)fillvalp = NC_FILL_UINT64;
|
|
break;
|
|
case NC_STRING:
|
|
*((char **)fillvalp) = NC_FILL_STRING;
|
|
break;
|
|
#endif /* USE_NETCDF4 */
|
|
default: /* no default fill values for NC_NAT
|
|
or user-defined types */
|
|
vp->has_fillval = 0;
|
|
free(fillvalp);
|
|
fillvalp = 0;
|
|
break;
|
|
}
|
|
}
|
|
vp->fillvalp = fillvalp;
|
|
}
|
|
|
|
|
|
/* Check for optional "calendar" attribute and return specified
|
|
* calendar type, if present. */
|
|
cdCalenType
|
|
calendar_type(int ncid, int varid) {
|
|
int ctype;
|
|
int stat;
|
|
ncatt_t catt;
|
|
static struct {
|
|
char* attname;
|
|
int type;
|
|
} calmap[] = {
|
|
{"gregorian", cdMixed},
|
|
{"standard", cdMixed}, /* synonym */
|
|
{"proleptic_gregorian", cdStandard},
|
|
{"noleap", cdNoLeap},
|
|
{"no_leap", cdNoLeap},
|
|
{"365_day", cdNoLeap}, /* synonym */
|
|
{"allleap", cd366},
|
|
{"all_leap", cd366}, /* synonym */
|
|
{"366_day", cd366}, /* synonym */
|
|
{"360_day", cd360},
|
|
{"julian", cdJulian},
|
|
{"none", cdClim} /* TODO: test this */
|
|
};
|
|
#define CF_CAL_ATT_NAME "calendar"
|
|
int ncals = (sizeof calmap)/(sizeof calmap[0]);
|
|
ctype = cdMixed; /* default mixed Gregorian/Julian ala udunits */
|
|
stat = nc_inq_att(ncid, varid, CF_CAL_ATT_NAME, &catt.type, &catt.len);
|
|
if(stat == NC_NOERR && catt.type == NC_CHAR && catt.len > 0) {
|
|
char *calstr = (char *)emalloc(catt.len + 1);
|
|
int itype;
|
|
NC_CHECK(nc_get_att(ncid, varid, CF_CAL_ATT_NAME, calstr));
|
|
calstr[catt.len] = '\0';
|
|
for(itype = 0; itype < ncals; itype++) {
|
|
if(strncmp(calstr, calmap[itype].attname, catt.len) == 0) {
|
|
ctype = calmap[itype].type;
|
|
break;
|
|
}
|
|
}
|
|
free(calstr);
|
|
}
|
|
return ctype;
|
|
}
|
|
|
|
|
|
static void
|
|
get_timeinfo(int ncid, int varid, ncvar_t *vp) {
|
|
ncatt_t uatt; /* units attribute */
|
|
int nc_status; /* return from netcdf calls */
|
|
char *units;
|
|
|
|
vp->has_timeval = false; /* by default, turn on if criteria met */
|
|
vp->timeinfo = 0;
|
|
|
|
/* time variables must have appropriate units attribute */
|
|
nc_status = nc_inq_att(ncid, varid, "units", &uatt.type, &uatt.len);
|
|
if(nc_status == NC_NOERR && uatt.type == NC_CHAR) { /* TODO: NC_STRING? */
|
|
units = emalloc(uatt.len + 1);
|
|
NC_CHECK(nc_get_att(ncid, varid, "units", units));
|
|
units[uatt.len] = '\0';
|
|
/* check for calendar attribute (not required even for time vars) */
|
|
vp->timeinfo = (timeinfo_t *)emalloc(sizeof(timeinfo_t));
|
|
memset((void*)vp->timeinfo,0,sizeof(timeinfo_t));
|
|
vp->timeinfo->calendar = calendar_type(ncid, varid);
|
|
/* Parse relative units, returning the unit and base component time. */
|
|
if(cdParseRelunits(vp->timeinfo->calendar, units,
|
|
&vp->timeinfo->unit, &vp->timeinfo->origin) != 0) {
|
|
/* error parsing units so just treat as not a time variable */
|
|
free(vp->timeinfo);
|
|
free(units);
|
|
vp->timeinfo = NULL;
|
|
return;
|
|
}
|
|
/* Currently this gets reparsed for every value, need function
|
|
* like cdRel2Comp that resuses parsed units? */
|
|
vp->timeinfo->units = strdup(units);
|
|
vp->has_timeval = true;
|
|
free(units);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Recursively dump the contents of a group. (Recall that only
|
|
* netcdf-4 format files can have groups. On all other formats, there
|
|
* is just a root group, so recursion will not take place.)
|
|
*
|
|
* ncid: id of open file (first call) or group (subsequent recursive calls)
|
|
* path: file path name (first call) or NULL if called for a group
|
|
* specp: formatting spec
|
|
*/
|
|
static void
|
|
do_ncdump_rec(int ncid, const char *path, fspec_t* specp)
|
|
{
|
|
int ndims; /* number of dimensions */
|
|
int nvars; /* number of variables */
|
|
int ngatts; /* number of global attributes */
|
|
int xdimid; /* id of unlimited dimension */
|
|
int varid; /* variable id */
|
|
ncdim_t *dims; /* dimensions */
|
|
size_t *vdims=0; /* dimension sizes for a single variable */
|
|
ncvar_t var; /* variable */
|
|
int id; /* dimension number per variable */
|
|
int ia; /* attribute number */
|
|
int iv; /* variable number */
|
|
vnode* vlist = 0; /* list for vars specified with -v option */
|
|
char type_name[NC_MAX_NAME + 1];
|
|
int kind; /* strings output differently for nc4 files */
|
|
char dim_name[NC_MAX_NAME + 1];
|
|
#ifdef USE_NETCDF4
|
|
int *dimids_grp; /* dimids of the dims in this group. */
|
|
int *unlimids; /* dimids of unlimited dimensions in this group */
|
|
int d_grp, ndims_grp;
|
|
int ntypes, *typeids;
|
|
int nunlim;
|
|
#else
|
|
int dimid; /* dimension id */
|
|
#endif /* USE_NETCDF4 */
|
|
|
|
/*
|
|
* If any vars were specified with -v option, get list of
|
|
* associated variable ids relative to this group. Assume vars
|
|
* specified with syntax like "grp1/grp2/varname" or
|
|
* "/grp1/grp2/varname" if they are in groups.
|
|
*/
|
|
if (specp->nlvars > 0) {
|
|
vlist = newvlist(); /* list for vars specified with -v option */
|
|
for (iv=0; iv < specp->nlvars; iv++) {
|
|
if(nc_inq_gvarid(ncid, specp->lvars[iv], &varid) == NC_NOERR)
|
|
varadd(vlist, varid);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_NETCDF4
|
|
/* Are there any user defined types in this group? */
|
|
NC_CHECK( nc_inq_typeids(ncid, &ntypes, NULL) );
|
|
if (ntypes)
|
|
{
|
|
int t;
|
|
|
|
typeids = emalloc((ntypes + 1) * sizeof(int));
|
|
NC_CHECK( nc_inq_typeids(ncid, &ntypes, typeids) );
|
|
indent_out();
|
|
printf("types:\n");
|
|
indent_more();
|
|
for (t = 0; t < ntypes; t++)
|
|
{
|
|
print_ud_type(ncid, typeids[t]); /* print declaration of user-defined type */
|
|
}
|
|
indent_less();
|
|
free(typeids);
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
|
|
/*
|
|
* get number of dimensions, number of variables, number of global
|
|
* atts, and dimension id of unlimited dimension, if any
|
|
*/
|
|
NC_CHECK( nc_inq(ncid, &ndims, &nvars, &ngatts, &xdimid) );
|
|
/* get dimension info */
|
|
dims = (ncdim_t *) emalloc((ndims + 1) * sizeof(ncdim_t));
|
|
if (ndims > 0) {
|
|
indent_out();
|
|
printf ("dimensions:\n");
|
|
}
|
|
|
|
#ifdef USE_NETCDF4
|
|
/* In netCDF-4 files, dimids will not be sequential because they
|
|
* may be defined in various groups, and we are only looking at one
|
|
* group at a time. */
|
|
|
|
/* Find the number of dimids defined in this group. */
|
|
NC_CHECK( nc_inq_ndims(ncid, &ndims_grp) );
|
|
dimids_grp = (int *)emalloc((ndims_grp + 1) * sizeof(int));
|
|
|
|
/* Find the dimension ids in this group. */
|
|
NC_CHECK( nc_inq_dimids(ncid, 0, dimids_grp, 0) );
|
|
|
|
/* Find the number of unlimited dimensions and get their IDs */
|
|
NC_CHECK( nc_inq_unlimdims(ncid, &nunlim, NULL) );
|
|
unlimids = (int *)emalloc((nunlim + 1) * sizeof(int));
|
|
NC_CHECK( nc_inq_unlimdims(ncid, &nunlim, unlimids) );
|
|
|
|
/* For each dimension defined in this group, get and print out info. */
|
|
for (d_grp = 0; d_grp < ndims_grp; d_grp++)
|
|
{
|
|
int dimid = dimids_grp[d_grp];
|
|
int is_unlimited = 0;
|
|
int uld;
|
|
int stat;
|
|
|
|
for (uld = 0; uld < nunlim; uld++) {
|
|
if(dimid == unlimids[uld]) {
|
|
is_unlimited = 1;
|
|
break;
|
|
}
|
|
}
|
|
stat = nc_inq_dim(ncid, dimid, dims[d_grp].name, &dims[d_grp].size);
|
|
if (stat == NC_EDIMSIZE && SIZEOF_SIZE_T < 8) {
|
|
error("dimension \"%s\" too large for 32-bit platform, try 64-bit version", dims[d_grp].name);
|
|
} else {
|
|
NC_CHECK (stat);
|
|
}
|
|
indent_out();
|
|
printf ("\t");
|
|
print_name(dims[d_grp].name);
|
|
printf (" = ");
|
|
if(SIZEOF_SIZE_T >= 8) {
|
|
if (is_unlimited) {
|
|
printf ("UNLIMITED ; // (%lu currently)\n",
|
|
(unsigned long)dims[d_grp].size);
|
|
} else {
|
|
printf ("%lu ;\n", (unsigned long)dims[d_grp].size);
|
|
}
|
|
} else { /* 32-bit platform */
|
|
if (is_unlimited) {
|
|
printf ("UNLIMITED ; // (%u currently)\n",
|
|
(unsigned int)dims[d_grp].size);
|
|
} else {
|
|
printf ("%u ;\n", (unsigned int)dims[d_grp].size);
|
|
}
|
|
}
|
|
}
|
|
if(unlimids)
|
|
free(unlimids);
|
|
if(dimids_grp)
|
|
free(dimids_grp);
|
|
#else /* not using netCDF-4 */
|
|
for (dimid = 0; dimid < ndims; dimid++) {
|
|
NC_CHECK( nc_inq_dim(ncid, dimid, dims[dimid].name, &dims[dimid].size) );
|
|
indent_out();
|
|
printf ("\t");
|
|
print_name(dims[dimid].name);
|
|
printf (" = ");
|
|
if (dimid == xdimid) {
|
|
printf ("UNLIMITED ; // (%u currently)\n",
|
|
(unsigned int)dims[dimid].size);
|
|
} else {
|
|
printf ("%u ;\n", (unsigned int)dims[dimid].size);
|
|
}
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
|
|
if (nvars > 0) {
|
|
indent_out();
|
|
printf ("variables:\n");
|
|
}
|
|
/* Because netCDF-4 can have a string attribute with multiple
|
|
* string values, we can't output strings with embedded newlines
|
|
* as what look like multiple strings, as we do for classic and
|
|
* 64-bit offset files. So we need to know the output file type
|
|
* to know how to print strings with embedded newlines. */
|
|
NC_CHECK( nc_inq_format(ncid, &kind) );
|
|
|
|
/* For each var, get and print out info. */
|
|
|
|
memset((void*)&var,0,sizeof(var));
|
|
|
|
for (varid = 0; varid < nvars; varid++) {
|
|
NC_CHECK( nc_inq_varndims(ncid, varid, &var.ndims) );
|
|
if(var.dims != NULL) free(var.dims);
|
|
var.dims = (int *) emalloc((var.ndims + 1) * sizeof(int));
|
|
NC_CHECK( nc_inq_var(ncid, varid, var.name, &var.type, 0,
|
|
var.dims, &var.natts) );
|
|
/* TODO: don't bother if type name not needed here */
|
|
get_type_name(ncid, var.type, type_name);
|
|
var.tinfo = get_typeinfo(var.type);
|
|
indent_out();
|
|
/* printf ("\t%s %s", type_name, var.name); */
|
|
printf ("\t");
|
|
/* TODO: if duplicate type name and not just inherited, print
|
|
* full type name. */
|
|
print_type_name (ncid, var.type);
|
|
printf (" ");
|
|
print_name (var.name);
|
|
if (var.ndims > 0)
|
|
printf ("(");
|
|
for (id = 0; id < var.ndims; id++) {
|
|
/* This dim may be in a parent group, so let's look up the
|
|
* name. */
|
|
NC_CHECK( nc_inq_dimname(ncid, var.dims[id], dim_name) );
|
|
#ifdef USE_NETCDF4
|
|
/* Subtlety: The following code block is needed because
|
|
* nc_inq_dimname() currently returns only a simple dimension
|
|
* name, without a prefix identifying the group it came from.
|
|
* That's OK unless the dimid identifies a dimension in an
|
|
* ancestor group that has the same simple name as a
|
|
* dimension in the current group (or some intermediate
|
|
* group), in which case the simple name is ambiguous. This
|
|
* code tests for that case and provides an absolute dimname
|
|
* only in the case where a simple name would be
|
|
* ambiguous. */
|
|
{
|
|
int dimid_test; /* to see if dimname is ambiguous */
|
|
int locid; /* group id where dimension is defined */
|
|
NC_CHECK( nc_inq_dimid(ncid, dim_name, &dimid_test) );
|
|
locid = ncid;
|
|
while(var.dims[id] != dimid_test) { /* not in locid, try ancestors */
|
|
int parent_id;
|
|
NC_CHECK( nc_inq_grp_parent(locid, &parent_id) );
|
|
locid = parent_id;
|
|
NC_CHECK( nc_inq_dimid(locid, dim_name, &dimid_test) );
|
|
}
|
|
/* dimid is in group locid, prefix dimname with group name if needed */
|
|
if(locid != ncid) {
|
|
size_t len;
|
|
char *locname; /* the group name */
|
|
NC_CHECK( nc_inq_grpname_full(locid, &len, NULL) );
|
|
locname = emalloc(len + 1);
|
|
NC_CHECK( nc_inq_grpname_full(locid, &len, locname) );
|
|
print_name (locname);
|
|
if(strcmp("/", locname) != 0) { /* not the root group */
|
|
printf("/"); /* ensure a trailing slash */
|
|
}
|
|
free(locname);
|
|
}
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
print_name (dim_name);
|
|
printf ("%s", id < var.ndims-1 ? ", " : ")");
|
|
}
|
|
printf (" ;\n");
|
|
|
|
/* print variable attributes */
|
|
for (ia = 0; ia < var.natts; ia++) { /* print ia-th attribute */
|
|
pr_att(ncid, kind, varid, var.name, ia);
|
|
}
|
|
#ifdef USE_NETCDF4
|
|
/* Print special (virtual) attributes, if option specified */
|
|
if (specp->special_atts) {
|
|
pr_att_specials(ncid, kind, varid, &var);
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
}
|
|
|
|
/* get global attributes */
|
|
if (ngatts > 0 || specp->special_atts) {
|
|
printf ("\n");
|
|
indent_out();
|
|
if (path != NULL) /* top-level, root group */
|
|
printf("// global attributes:\n");
|
|
else
|
|
printf("// group attributes:\n");
|
|
}
|
|
for (ia = 0; ia < ngatts; ia++) { /* print ia-th global attribute */
|
|
pr_att(ncid, kind, NC_GLOBAL, "", ia);
|
|
}
|
|
if (path != NULL && specp->special_atts) { /* output special attribute
|
|
* for format variant */
|
|
pr_att_global_format(ncid, kind);
|
|
}
|
|
|
|
/* output variable data */
|
|
if (! specp->header_only) {
|
|
if (nvars > 0) {
|
|
indent_out();
|
|
printf ("data:\n");
|
|
}
|
|
for (varid = 0; varid < nvars; varid++) {
|
|
int no_data;
|
|
/* if var list specified, test for membership */
|
|
if (specp->nlvars > 0 && ! varmember(vlist, varid))
|
|
continue;
|
|
NC_CHECK( nc_inq_varndims(ncid, varid, &var.ndims) );
|
|
if(var.dims != NULL) free(var.dims);
|
|
var.dims = (int *) emalloc((var.ndims + 1) * sizeof(int));
|
|
NC_CHECK( nc_inq_var(ncid, varid, var.name, &var.type, 0,
|
|
var.dims, &var.natts) );
|
|
var.tinfo = get_typeinfo(var.type);
|
|
/* If coords-only option specified, don't get data for
|
|
* non-coordinate vars */
|
|
if (specp->coord_vals && !iscoordvar(ncid,varid)) {
|
|
continue;
|
|
}
|
|
/* Collect variable's dim sizes */
|
|
if (vdims) {
|
|
free(vdims);
|
|
vdims = 0;
|
|
}
|
|
vdims = (size_t *) emalloc((var.ndims + 1) * SIZEOF_SIZE_T);
|
|
no_data = 0;
|
|
for (id = 0; id < var.ndims; id++) {
|
|
size_t len;
|
|
NC_CHECK( nc_inq_dimlen(ncid, var.dims[id], &len) );
|
|
if(len == 0) {
|
|
no_data = 1;
|
|
}
|
|
vdims[id] = len;
|
|
}
|
|
/* Don't get data for record variables if no records have
|
|
* been written yet */
|
|
if (no_data) {
|
|
free(vdims);
|
|
vdims = 0;
|
|
continue;
|
|
}
|
|
if(var.fillvalp != NULL) free(var.fillvalp);
|
|
get_fill_info(ncid, varid, &var); /* sets has_fillval, fillvalp mmbrs */
|
|
if(var.timeinfo != NULL) {
|
|
if(var.timeinfo->units) free(var.timeinfo->units);
|
|
free(var.timeinfo);
|
|
}
|
|
get_timeinfo(ncid, varid, &var); /* sets has_timeval, timeinfo mmbrs */
|
|
/* printf format used to print each value */
|
|
var.fmt = get_fmt(ncid, varid, var.type);
|
|
var.locid = ncid;
|
|
set_tostring_func(&var, specp);
|
|
if (vardata(&var, vdims, ncid, varid, specp) == -1) {
|
|
error("can't output data for variable %s", var.name);
|
|
NC_CHECK(
|
|
nc_close(ncid) );
|
|
goto done;
|
|
}
|
|
}
|
|
if (vdims) {
|
|
free(vdims);
|
|
vdims = 0;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_NETCDF4
|
|
/* For netCDF-4 compiles, check to see if the file has any
|
|
* groups. If it does, this function is called recursively on each
|
|
* of them. */
|
|
{
|
|
int g, numgrps, *ncids;
|
|
char group_name[NC_MAX_NAME + 1];
|
|
|
|
/* See how many groups there are. */
|
|
NC_CHECK( nc_inq_grps(ncid, &numgrps, NULL) );
|
|
|
|
/* Allocate memory to hold the list of group ids. */
|
|
ncids = emalloc((numgrps + 1) * sizeof(int));
|
|
|
|
/* Get the list of group ids. */
|
|
NC_CHECK( nc_inq_grps(ncid, NULL, ncids) );
|
|
|
|
/* Call this function for each group. */
|
|
for (g = 0; g < numgrps; g++)
|
|
{
|
|
NC_CHECK( nc_inq_grpname(ncids[g], group_name) );
|
|
printf ("\n");
|
|
indent_out();
|
|
/* printf ("group: %s {\n", group_name); */
|
|
printf ("group: ");
|
|
print_name (group_name);
|
|
printf (" {\n");
|
|
indent_more();
|
|
do_ncdump_rec(ncids[g], NULL, specp);
|
|
indent_out();
|
|
/* printf ("} // group %s\n", group_name); */
|
|
printf ("} // group ");
|
|
print_name (group_name);
|
|
printf ("\n");
|
|
indent_less();
|
|
}
|
|
|
|
free(ncids);
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
|
|
done:
|
|
if(var.dims != NULL) free(var.dims);
|
|
if(var.fillvalp != NULL) free(var.fillvalp);
|
|
if(var.timeinfo != NULL) {
|
|
if(var.timeinfo->units) free(var.timeinfo->units);
|
|
free(var.timeinfo);
|
|
}
|
|
if (dims)
|
|
free(dims);
|
|
if (vlist)
|
|
free(vlist);
|
|
}
|
|
|
|
|
|
static void
|
|
do_ncdump(int ncid, const char *path, fspec_t* specp)
|
|
{
|
|
char* esc_specname;
|
|
/* output initial line */
|
|
indent_init();
|
|
indent_out();
|
|
esc_specname=escaped_name(specp->name);
|
|
printf ("netcdf %s {\n", esc_specname);
|
|
free(esc_specname);
|
|
do_ncdump_rec(ncid, path, specp);
|
|
indent_out();
|
|
printf ("}\n");
|
|
NC_CHECK( nc_close(ncid) );
|
|
}
|
|
|
|
|
|
static void
|
|
do_ncdumpx(int ncid, const char *path, fspec_t* specp)
|
|
{
|
|
int ndims; /* number of dimensions */
|
|
int nvars; /* number of variables */
|
|
int ngatts; /* number of global attributes */
|
|
int xdimid; /* id of unlimited dimension */
|
|
int dimid; /* dimension id */
|
|
int varid; /* variable id */
|
|
ncdim_t *dims; /* dimensions */
|
|
ncvar_t var; /* variable */
|
|
int ia; /* attribute number */
|
|
int iv; /* variable number */
|
|
vnode* vlist = 0; /* list for vars specified with -v option */
|
|
|
|
/*
|
|
* If any vars were specified with -v option, get list of associated
|
|
* variable ids
|
|
*/
|
|
if (specp->nlvars > 0) {
|
|
vlist = newvlist(); /* list for vars specified with -v option */
|
|
for (iv=0; iv < specp->nlvars; iv++) {
|
|
NC_CHECK( nc_inq_varid(ncid, specp->lvars[iv], &varid) );
|
|
varadd(vlist, varid);
|
|
}
|
|
}
|
|
|
|
/* output initial line */
|
|
pr_initx(ncid, path);
|
|
|
|
/*
|
|
* get number of dimensions, number of variables, number of global
|
|
* atts, and dimension id of unlimited dimension, if any
|
|
*/
|
|
/* TODO: print names with XML-ish escapes fopr special chars */
|
|
NC_CHECK( nc_inq(ncid, &ndims, &nvars, &ngatts, &xdimid) );
|
|
/* get dimension info */
|
|
dims = (ncdim_t *) emalloc((ndims + 1) * sizeof(ncdim_t));
|
|
for (dimid = 0; dimid < ndims; dimid++) {
|
|
NC_CHECK( nc_inq_dim(ncid, dimid, dims[dimid].name, &dims[dimid].size) );
|
|
if (dimid == xdimid)
|
|
printf(" <dimension name=\"%s\" length=\"%d\" isUnlimited=\"true\" />\n",
|
|
dims[dimid].name, (int)dims[dimid].size);
|
|
else
|
|
printf (" <dimension name=\"%s\" length=\"%d\" />\n",
|
|
dims[dimid].name, (int)dims[dimid].size);
|
|
}
|
|
|
|
/* get global attributes */
|
|
for (ia = 0; ia < ngatts; ia++)
|
|
pr_attx(ncid, NC_GLOBAL, ia); /* print ia-th global attribute */
|
|
|
|
/* get variable info, with variable attributes */
|
|
memset((void*)&var,0,sizeof(var));
|
|
for (varid = 0; varid < nvars; varid++) {
|
|
NC_CHECK( nc_inq_varndims(ncid, varid, &var.ndims) );
|
|
if(var.dims != NULL) free(var.dims);
|
|
var.dims = (int *) emalloc((var.ndims + 1) * sizeof(int));
|
|
NC_CHECK( nc_inq_var(ncid, varid, var.name, &var.type, 0,
|
|
var.dims, &var.natts) );
|
|
printf (" <variable name=\"%s\"", var.name);
|
|
pr_shape(&var, dims);
|
|
|
|
/* handle one-line variable elements that aren't containers
|
|
for attributes or data values, since they need to be
|
|
rendered as <variable ... /> instead of <variable ..>
|
|
... </variable> */
|
|
if (var.natts == 0) {
|
|
if (
|
|
/* header-only specified */
|
|
(specp->header_only) ||
|
|
/* list of variables specified and this variable not in list */
|
|
(specp->nlvars > 0 && !varmember(vlist, varid)) ||
|
|
/* coordinate vars only and this is not a coordinate variable */
|
|
(specp->coord_vals && !iscoordvar(ncid, varid)) ||
|
|
/* this is a record variable, but no records have been written */
|
|
(isrecvar(ncid,varid) && dims[xdimid].size == 0)
|
|
) {
|
|
printf (" type=\"%s\" />\n", prim_type_name(var.type));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* else nest attributes values, data values in <variable> ... </variable> */
|
|
printf (" type=\"%s\">\n", prim_type_name(var.type));
|
|
|
|
/* get variable attributes */
|
|
for (ia = 0; ia < var.natts; ia++) {
|
|
pr_attx(ncid, varid, ia); /* print ia-th attribute */
|
|
}
|
|
printf (" </variable>\n");
|
|
}
|
|
|
|
printf ("</netcdf>\n");
|
|
NC_CHECK(
|
|
nc_close(ncid) );
|
|
if (vlist)
|
|
free(vlist);
|
|
if(dims)
|
|
free(dims);
|
|
}
|
|
|
|
|
|
static void
|
|
make_lvars(char *optarg, fspec_t* fspecp)
|
|
{
|
|
char *cp = optarg;
|
|
int nvars = 1;
|
|
char ** cpp;
|
|
|
|
/* compute number of variable names in comma-delimited list */
|
|
fspecp->nlvars = 1;
|
|
while (*cp++)
|
|
if (*cp == ',')
|
|
nvars++;
|
|
|
|
fspecp->lvars = (char **) emalloc(nvars * sizeof(char*));
|
|
|
|
cpp = fspecp->lvars;
|
|
/* copy variable names into list */
|
|
for (cp = strtok(optarg, ",");
|
|
cp != NULL;
|
|
cp = strtok((char *) NULL, ",")) {
|
|
size_t bufsiz = strlen(cp) + 1;
|
|
|
|
*cpp = (char *) emalloc(bufsiz);
|
|
strncpy(*cpp, cp, bufsiz);
|
|
cpp++;
|
|
}
|
|
fspecp->nlvars = nvars;
|
|
}
|
|
|
|
|
|
/*
|
|
* Extract the significant-digits specifiers from the (deprecated and
|
|
* undocumented) -d argument on the command-line and update the
|
|
* default data formats appropriately. This only exists because an
|
|
* old version of ncdump supported the "-d" flag which did not
|
|
* override the C_format attributes (if any).
|
|
*/
|
|
static void
|
|
set_sigdigs(const char *optarg)
|
|
{
|
|
char *ptr1 = 0;
|
|
char *ptr2 = 0;
|
|
int flt_digits = FLT_DIGITS; /* default floating-point digits */
|
|
int dbl_digits = DBL_DIGITS; /* default double-precision digits */
|
|
|
|
if (optarg != 0 && (int) strlen(optarg) > 0 && optarg[0] != ',')
|
|
flt_digits = (int)strtol(optarg, &ptr1, 10);
|
|
|
|
if (flt_digits < 1 || flt_digits > 20) {
|
|
error("unreasonable value for float significant digits: %d",
|
|
flt_digits);
|
|
}
|
|
if (ptr1 && *ptr1 == ',') {
|
|
dbl_digits = (int)strtol(ptr1+1, &ptr2, 10);
|
|
if (ptr2 == ptr1+1 || dbl_digits < 1 || dbl_digits > 20) {
|
|
error("unreasonable value for double significant digits: %d",
|
|
dbl_digits);
|
|
}
|
|
}
|
|
set_formats(flt_digits, dbl_digits);
|
|
}
|
|
|
|
|
|
/*
|
|
* Extract the significant-digits specifiers from the -p argument on the
|
|
* command-line, set flags so we can override C_format attributes (if any),
|
|
* and update the default data formats appropriately.
|
|
*/
|
|
static void
|
|
set_precision(const char *optarg)
|
|
{
|
|
char *ptr1 = 0;
|
|
char *ptr2 = 0;
|
|
int flt_digits = FLT_DIGITS; /* default floating-point digits */
|
|
int dbl_digits = DBL_DIGITS; /* default double-precision digits */
|
|
|
|
if (optarg != 0 && (int) strlen(optarg) > 0 && optarg[0] != ',') {
|
|
flt_digits = (int)strtol(optarg, &ptr1, 10);
|
|
float_precision_specified = 1;
|
|
}
|
|
|
|
if (flt_digits < 1 || flt_digits > 20) {
|
|
error("unreasonable value for float significant digits: %d",
|
|
flt_digits);
|
|
}
|
|
if (ptr1 && *ptr1 == ',') {
|
|
dbl_digits = (int) strtol(ptr1+1, &ptr2, 10);
|
|
double_precision_specified = 1;
|
|
if (ptr2 == ptr1+1 || dbl_digits < 1 || dbl_digits > 20) {
|
|
error("unreasonable value for double significant digits: %d",
|
|
dbl_digits);
|
|
}
|
|
}
|
|
set_formats(flt_digits, dbl_digits);
|
|
}
|
|
|
|
/* Determine whether a variable named varname exists in any group in
|
|
an open netCDF file with id ncid. If so, return the count of how
|
|
many matching variables were found, else return a count of 0. The
|
|
variable name can be absolute such as "/foo" or "/GRP1/GRP1A/foo",
|
|
in which case there is only one group to look in, given by the path
|
|
from the root group. Alternatively, the variable name can be
|
|
relative, such as "foo" or "GRPA/GRPB/foo", in which case every
|
|
group is examined for a variable with that relative name. */
|
|
size_t
|
|
nc_inq_varname_count(int ncid, char *varname) {
|
|
/*
|
|
count = 0;
|
|
status = nc_inq_gvarid(ncid, varname, varid);
|
|
if (status == NC_NOERR)
|
|
count++;
|
|
for each subgroup gid {
|
|
count += nc_inq_varname_count(gid, varname);
|
|
}
|
|
return count;
|
|
*/
|
|
size_t count = 0;
|
|
int varid;
|
|
/* look in this group */
|
|
int status = nc_inq_gvarid(ncid, varname, &varid);
|
|
#ifdef USE_NETCDF4
|
|
int numgrps;
|
|
int *ncids;
|
|
int g;
|
|
#endif
|
|
|
|
if (status == NC_NOERR)
|
|
count++;
|
|
|
|
#ifdef USE_NETCDF4
|
|
/* if this group has subgroups, call recursively on each of them */
|
|
NC_CHECK( nc_inq_grps(ncid, &numgrps, NULL) );
|
|
|
|
/* Allocate memory to hold the list of group ids. */
|
|
ncids = emalloc((numgrps + 1) * sizeof(int));
|
|
|
|
/* Get the list of group ids. */
|
|
NC_CHECK( nc_inq_grps(ncid, NULL, ncids) );
|
|
|
|
/* Call this function for each group. */
|
|
for (g = 0; g < numgrps; g++) {
|
|
count += nc_inq_varname_count(ncids[g], varname);
|
|
}
|
|
free(ncids);
|
|
#endif /* USE_NETCDF4 */
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
/* Check if any variable names specified with "-v var1,...,varn" are
|
|
* missing. Returns 0 if no missing variables detected, otherwise
|
|
* exits. */
|
|
static int
|
|
missing_vars(int ncid, fspec_t *specp) {
|
|
int iv;
|
|
for (iv=0; iv < specp->nlvars; iv++) {
|
|
if(nc_inq_varname_count(ncid, specp->lvars[iv]) == 0) {
|
|
error("%s: No such variable", specp->lvars[iv]);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define DAP_CLIENT_CACHE_DIRECTIVE "[cache]"
|
|
/* replace path string with same string prefixed by
|
|
* DAP_CLIENT_NCDUMP_DIRECTIVE */
|
|
static
|
|
void adapt_url_for_cache(char **pathp) {
|
|
char prefix[] = DAP_CLIENT_CACHE_DIRECTIVE;
|
|
char* path = *pathp;
|
|
char *tmp_path = strdup(path);
|
|
path = (char *)emalloc(strlen(prefix) + strlen(tmp_path) + 1);
|
|
path[0] = '\0';
|
|
strncat(path, prefix, strlen(prefix));
|
|
strncat(path, tmp_path, strlen(tmp_path));
|
|
free(tmp_path);
|
|
*pathp = path;
|
|
return;
|
|
}
|
|
|
|
/**
|
|
The ncdump tool generates the CDL text representation of a netCDF
|
|
dataset on standard output, optionally excluding some or all of the
|
|
variable data in the output. The output from ncdump is intended to be
|
|
acceptable as input to ncgen. Thus ncdump and ncgen can be used as
|
|
inverses to transform data representation between binary and text
|
|
representations.
|
|
|
|
As of netCDF version 4.1, ncdump can also access DAP data sources if
|
|
DAP support is enabled in the underlying netCDF library. Instead of
|
|
specifying a file name as argument to ncdump, the user specifies a URL
|
|
to a DAP source.
|
|
|
|
ncdump may also be used as a simple browser for netCDF datasets, to
|
|
display the dimension names and lengths; variable names, types, and
|
|
shapes; attribute names and values; and optionally, the values of data
|
|
for all variables or selected variables in a netCDF dataset.
|
|
|
|
ncdump defines a default format used for each type of netCDF variable
|
|
data, but this can be overridden if a C_format attribute is defined
|
|
for a netCDF variable. In this case, ncdump will use the C_format
|
|
attribute to format values for that variable. For example, if
|
|
floating-point data for the netCDF variable Z is known to be accurate
|
|
to only three significant digits, it might be appropriate to use this
|
|
variable attribute:
|
|
|
|
\code
|
|
Z:C_format = "%.3g"
|
|
\endcode
|
|
|
|
Ncdump uses '_' to represent data values that are equal to the
|
|
_FillValue attribute for a variable, intended to represent data that
|
|
has not yet been written. If a variable has no _FillValue attribute,
|
|
the default fill value for the variable type is used unless the
|
|
variable is of byte type.
|
|
|
|
UNIX syntax for invoking ncdump:
|
|
|
|
\code
|
|
ncdump [ -c | -h] [-v var1,...] [-b lang] [-f lang]
|
|
[-l len] [ -p fdig[,ddig]] [ -s ] [ -n name] [input-file]
|
|
\endcode
|
|
|
|
where:
|
|
|
|
-c Show the values of coordinate variables (variables that are also
|
|
dimensions) as well as the declarations of all dimensions, variables,
|
|
and attribute values. Data values of non-coordinate variables are not
|
|
included in the output. This is often the most suitable option to use
|
|
for a brief look at the structure and contents of a netCDF dataset.
|
|
|
|
-h Show only the header information in the output, that is, output
|
|
only the declarations for the netCDF dimensions, variables, and
|
|
attributes of the input file, but no data values for any
|
|
variables. The output is identical to using the '-c' option except
|
|
that the values of coordinate variables are not included. (At most
|
|
one of '-c' or '-h' options may be present.)
|
|
|
|
-v var1,... The output will include data values for the specified
|
|
variables, in addition to the declarations of all dimensions,
|
|
variables, and attributes. One or more variables must be specified by
|
|
name in the comma-delimited list following this option. The list must
|
|
be a single argument to the command, hence cannot contain blanks or
|
|
other white space characters. The named variables must be valid
|
|
netCDF variables in the input-file. The default, without this option
|
|
and in the absence of the '-c' or '-h' options, is to include data
|
|
values for all variables in the output.
|
|
|
|
-b lang A brief annotation in the form of a CDL comment (text
|
|
beginning with the characters '//') will be included in the data
|
|
section of the output for each 'row' of data, to help identify data
|
|
values for multidimensional variables. If lang begins with 'C' or
|
|
'c', then C language conventions will be used (zero-based indices,
|
|
last dimension varying fastest). If lang begins with 'F' or 'f', then
|
|
FORTRAN language conventions will be used (one-based indices, first
|
|
dimension varying fastest). In either case, the data will be
|
|
presented in the same order; only the annotations will differ. This
|
|
option may be useful for browsing through large volumes of
|
|
multidimensional data.
|
|
|
|
-f lang Full annotations in the form of trailing CDL comments (text
|
|
beginning with the characters '//') for every data value (except
|
|
individual characters in character arrays) will be included in the
|
|
data section. If lang begins with 'C' or 'c', then C language
|
|
conventions will be used (zero-based indices, last dimension varying
|
|
fastest). If lang begins with 'F' or 'f', then FORTRAN language
|
|
conventions will be used (one-based indices, first dimension varying
|
|
fastest). In either case, the data will be presented in the same
|
|
order; only the annotations will differ. This option may be useful
|
|
for piping data into other filters, since each data value appears on
|
|
a separate line, fully identified. (At most one of '-b' or '-f'
|
|
options may be present.)
|
|
|
|
-l len Changes the default maximum line length (80) used in formatting
|
|
lists of non-character data values.
|
|
|
|
-p float_digits[,double_digits] Specifies default precision (number of
|
|
significant digits) to use in displaying floating-point or double
|
|
precision data values for attributes and variables. If specified, this
|
|
value overrides the value of the C_format attribute, if any, for a
|
|
variable. Floating-point data will be displayed with float_digits
|
|
significant digits. If double_digits is also specified,
|
|
double-precision values will be displayed with that many significant
|
|
digits. In the absence of any '-p' specifications, floating-point and
|
|
double-precision data are displayed with 7 and 15 significant digits
|
|
respectively. CDL files can be made smaller if less precision is
|
|
required. If both floating-point and double precisions are specified,
|
|
the two values must appear separated by a comma (no blanks) as a
|
|
single argument to the command.
|
|
|
|
-n name CDL requires a name for a netCDF dataset, for use by 'ncgen
|
|
-b' in generating a default netCDF dataset name. By default, ncdump
|
|
constructs this name from the last component of the file name of the
|
|
input netCDF dataset by stripping off any extension it has. Use the
|
|
'-n' option to specify a different name. Although the output file name
|
|
used by 'ncgen -b' can be specified, it may be wise to have ncdump
|
|
change the default name to avoid inadvertently overwriting a valuable
|
|
netCDF dataset when using ncdump, editing the resulting CDL file, and
|
|
using 'ncgen -b' to generate a new netCDF dataset from the edited CDL
|
|
file.
|
|
|
|
-s Specifies that special virtual attributes should be output for the
|
|
file format variant and for variable properties such as compression,
|
|
chunking, and other properties specific to the format implementation
|
|
that are primarily related to performance rather than the logical
|
|
schema of the data. All the special virtual attributes begin with '_'
|
|
followed by an upper-case letter. Currently they include the global
|
|
attribute “_Format” and the variable attributes “_Fletcher32”,
|
|
“_ChunkSizes”, “_Endianness”, “_DeflateLevel”, “_Shuffle”, “_Storage”,
|
|
and “_NoFill”. The ncgen utility recognizes these attributes and
|
|
supports them appropriately.
|
|
|
|
-t Controls display of time data, if stored in a variable that uses a
|
|
udunits compliant time representation such as “days since 1970-01-01”
|
|
or “seconds since 2009-03-15 12:01:17”. If this option is specified,
|
|
time values are displayed as human-readable date-time strings rather
|
|
than numerical values, interpreted in terms of a “calendar” variable
|
|
attribute, if specified. Calendar attribute values interpreted with
|
|
this option include the CF Conventions values “gregorian” or
|
|
“standard”, “proleptic_gregorian”, “noleap” or “365_day”, “all_leap”
|
|
or “366_day”, “360_day”, and “julian”.
|
|
|
|
\section Examples
|
|
|
|
Look at the structure of the data in the netCDF dataset foo.nc:
|
|
|
|
\code
|
|
ncdump -c foo.nc
|
|
\endcode
|
|
|
|
Produce an annotated CDL version of the structure and data in the
|
|
netCDF dataset foo.nc, using C-style indexing for the annotations:
|
|
|
|
\code
|
|
ncdump -b c foo.nc > foo.cdl
|
|
\endcode
|
|
|
|
Output data for only the variables uwind and vwind from the netCDF
|
|
dataset foo.nc, and show the floating-point data with only three
|
|
significant digits of precision:
|
|
|
|
\code
|
|
ncdump -v uwind,vwind -p 3 foo.nc
|
|
\endcode
|
|
|
|
Produce a fully-annotated (one data value per line) listing of the
|
|
data for the variable omega, using FORTRAN conventions for indices,
|
|
and changing the netCDF dataset name in the resulting CDL file to
|
|
omega:
|
|
|
|
\code
|
|
ncdump -v omega -f fortran -n omega foo.nc > Z.cdl
|
|
\endcode
|
|
|
|
Examine the translated DDS for the DAP source from the specified URL.
|
|
|
|
\code
|
|
ncdump -h http://test.opendap.org:8080/dods/dts/test.01
|
|
\endcode
|
|
*/
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
static fspec_t fspec = /* defaults, overridden on command line */
|
|
{
|
|
0, /* construct netcdf name from file name */
|
|
false, /* print header info only, no data? */
|
|
false, /* just print coord vars? */
|
|
false, /* brief comments in data section? */
|
|
false, /* full annotations in data section? */
|
|
false, /* human-readable output for date-time values */
|
|
false, /* output special attributes, eg chunking? */
|
|
LANG_C, /* language conventions for indices */
|
|
0, /* if -v specified, number of variables */
|
|
false, /* for DAP URLs, client-side cache used */
|
|
0 /* if -v specified, list of variable names */
|
|
};
|
|
int c;
|
|
int i;
|
|
int max_len = 80; /* default maximum line length */
|
|
int nameopt = 0;
|
|
boolean xml_out = false; /* if true, output NcML instead of CDL */
|
|
boolean kind_out = false; /* if true, just output kind of netCDF file */
|
|
|
|
#ifdef HAVE_LOCALE_H
|
|
setlocale(LC_ALL, "C"); /* CDL may be ambiguous with other locales */
|
|
#endif /* HAVE_LOCALE_H */
|
|
opterr = 1;
|
|
progname = argv[0];
|
|
set_formats(FLT_DIGITS, DBL_DIGITS); /* default for float, double data */
|
|
|
|
/* If the user called ncdump without arguments, print the usage
|
|
* message and return peacefully. */
|
|
if (argc <= 1)
|
|
{
|
|
usage();
|
|
#ifdef vms
|
|
exit(EXIT_SUCCESS);
|
|
#else
|
|
return EXIT_SUCCESS;
|
|
#endif
|
|
}
|
|
|
|
while ((c = getopt(argc, argv, "b:cd:f:hjkl:n:p:stv:xw")) != EOF)
|
|
switch(c) {
|
|
case 'h': /* dump header only, no data */
|
|
fspec.header_only = true;
|
|
break;
|
|
case 'c': /* header, data only for coordinate dims */
|
|
fspec.coord_vals = true;
|
|
break;
|
|
case 'n': /*
|
|
* provide different name than derived from
|
|
* file name
|
|
*/
|
|
fspec.name = optarg;
|
|
nameopt = 1;
|
|
break;
|
|
case 'b': /* brief comments in data section */
|
|
fspec.brief_data_cmnts = true;
|
|
switch (tolower(optarg[0])) {
|
|
case 'c':
|
|
fspec.data_lang = LANG_C;
|
|
break;
|
|
case 'f':
|
|
fspec.data_lang = LANG_F;
|
|
break;
|
|
default:
|
|
error("invalid value for -b option: %s", optarg);
|
|
}
|
|
break;
|
|
case 'f': /* full comments in data section */
|
|
fspec.full_data_cmnts = true;
|
|
switch (tolower(optarg[0])) {
|
|
case 'c':
|
|
fspec.data_lang = LANG_C;
|
|
break;
|
|
case 'f':
|
|
fspec.data_lang = LANG_F;
|
|
break;
|
|
default:
|
|
error("invalid value for -f option: %s", optarg);
|
|
}
|
|
break;
|
|
case 'l': /* maximum line length */
|
|
max_len = (int) strtol(optarg, 0, 0);
|
|
if (max_len < 10) {
|
|
error("unreasonably small line length specified: %d", max_len);
|
|
}
|
|
break;
|
|
case 'v': /* variable names */
|
|
/* make list of names of variables specified */
|
|
make_lvars (optarg, &fspec);
|
|
break;
|
|
case 'd': /* specify precision for floats (deprecated, undocumented) */
|
|
set_sigdigs(optarg);
|
|
break;
|
|
case 'p': /* specify precision for floats, overrides attribute specs */
|
|
set_precision(optarg);
|
|
break;
|
|
case 'x': /* XML output (NcML) */
|
|
xml_out = true;
|
|
break;
|
|
case 'k': /* just output what kind of netCDF file */
|
|
kind_out = true;
|
|
break;
|
|
case 't': /* human-readable strings for time values */
|
|
fspec.iso_times = true;
|
|
break;
|
|
case 's': /* output special (virtual) attributes for
|
|
* netCDF-4 files and variables, including
|
|
* _DeflateLevel, _Chunking, _Endianness,
|
|
* _Format, _Checksum, _NoFill */
|
|
fspec.special_atts = true;
|
|
break;
|
|
case 'w': /* with client-side cache for DAP URLs */
|
|
fspec.with_cache = true;
|
|
break;
|
|
case '?':
|
|
usage();
|
|
return 0;
|
|
}
|
|
|
|
set_max_len(max_len);
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
/* If no file arguments left or more than one, print usage message. */
|
|
if (argc != 1)
|
|
{
|
|
usage();
|
|
return 0;
|
|
}
|
|
|
|
i = 0;
|
|
|
|
init_epsilons();
|
|
|
|
{
|
|
char *path = strdup(argv[i]);
|
|
if(!path)
|
|
error("out of memory copying argument %s", argv[i]);
|
|
if (!nameopt)
|
|
fspec.name = name_path(path);
|
|
if (argc > 0) {
|
|
int ncid, nc_status;
|
|
/* If path is a URL, prefix with client-side directive to
|
|
* make ncdump reasonably efficient */
|
|
#ifdef USE_DAP
|
|
if(fspec.with_cache) /* by default, don't use cache directive */
|
|
{
|
|
extern int nc__testurl(const char*,char**);
|
|
/* See if this is a url */
|
|
if(nc__testurl(path, NULL)) {
|
|
adapt_url_for_cache(&path);
|
|
}
|
|
/* else fall thru and treat like a file path */
|
|
}
|
|
#endif /*USE_DAP*/
|
|
nc_status = nc_open(path, NC_NOWRITE, &ncid);
|
|
if (nc_status != NC_NOERR) {
|
|
error("%s: %s", path, nc_strerror(nc_status));
|
|
}
|
|
if (kind_out) {
|
|
do_nckind(ncid, path);
|
|
} else {
|
|
/* Initialize list of types. */
|
|
init_types(ncid);
|
|
/* Check if any vars in -v don't exist */
|
|
if(missing_vars(ncid, &fspec))
|
|
return EXIT_FAILURE;
|
|
if (xml_out) {
|
|
do_ncdumpx(ncid, path, &fspec);
|
|
} else {
|
|
do_ncdump(ncid, path, &fspec);
|
|
}
|
|
}
|
|
}
|
|
free(path);
|
|
}
|
|
#ifdef vms
|
|
exit(EXIT_SUCCESS);
|
|
#else
|
|
return EXIT_SUCCESS;
|
|
#endif
|
|
}
|
|
END_OF_MAIN();
|