Reading and writing NetCDF files

Arjen Markus (6 july 2009) NetCDF files are a popular way of distributing scientific data (results of numerical models or satellite data for instance). The library (found at for instance http://www.unidata.ucar.edu/software/netcdf/ ) is easy enough to use and there exist (at least?) two Tcl interfaces to it. Unfortunately, one has not been maintained over the years and the other is part of NAP, a very nice extension, but it means you have to deal with NAP's data model. If you just want to store and retrieve data structured as a block, then the geographical coordinates may be a hindrance.

So, here are the very humble beginnings of an extension that uses Critcl to read and write NetCDF files in a more basic way. I intend to add a higher-level API (and note: it does not yet retrieve the actual data ...)


# bindnetcdf.tcl --
#     Humble start of Tcl bindings for NetCDF
#
package require critcl

critcl::config keepsrc 1
critcl::config force 1

#
# NOTE: Windows-specific!
#
critcl::clibraries netcdf.lib

#critcl::config I [pwd]

set env(HOME) .

critcl::ccode {
#include <stdlib.h>
#include "netcdf.h"
}

critcl::cproc nc_create {Tcl_Interp* ip char* fileName} ok {

    int ncid;
    int retval;

    retval = nc_create( fileName, NC_CLOBBER, &ncid );

    if ( retval == 0 ) {
        Tcl_SetObjResult( ip, Tcl_NewIntObj(ncid) );
        return TCL_OK;
    } else {
        Tcl_SetResult( ip, (char *) nc_strerror(retval), TCL_VOLATILE );
        return TCL_ERROR;
    }
}

critcl::cproc nc_close {Tcl_Interp* ip int ncid} ok {

    int retval;

    retval = nc_close( ncid );

    if ( retval == 0 ) {
        Tcl_SetObjResult( ip, Tcl_NewIntObj(ncid) );
        return TCL_OK;
    } else {
        Tcl_SetResult( ip, (char *) nc_strerror(retval), TCL_VOLATILE );
        return TCL_ERROR;
    }
}

critcl::cproc nc_enddef {Tcl_Interp* ip int ncid} ok {

    int retval;

    retval = nc_enddef( ncid );

    if ( retval == 0 ) {
        return TCL_OK;
    } else {
        Tcl_SetResult( ip, (char *) nc_strerror(retval), TCL_VOLATILE );
        return TCL_ERROR;
    }
}

critcl::cproc nc_def_dim {Tcl_Interp* ip int ncid char* dimName int dim} ok {

    int retval;
    int dimid;

    if ( dim <= 0 ) {
        dim = NC_UNLIMITED;
    }

    retval = nc_def_dim( ncid, dimName, dim, &dimid );

    if ( retval == 0 ) {
        Tcl_SetObjResult( ip, Tcl_NewIntObj(dimid) );
        return TCL_OK;
    } else {
        Tcl_SetResult( ip, (char *) nc_strerror(retval), TCL_VOLATILE );
        return TCL_ERROR;
    }
}

critcl::cproc nc_def_var {Tcl_Interp* ip int ncid char* varName char* type
                          Tcl_Obj* dims} ok {

    int retval;
    int typeCode;
    int varid;
    int dimid;
    int dimids[10];
    int ndim;
    int i;
    Tcl_Obj *elemObj;

    /* Extract the IDs of the dimension variables
    */
    retval = Tcl_ListObjLength( ip, dims, &ndim );
    if ( retval != TCL_OK ) {
        return retval;
    }

    if ( ndim > 10 ) {
        Tcl_SetResult( ip, "Too many dimensions specified!", TCL_STATIC );
        return TCL_ERROR;
    }

    for ( i = 0; i < ndim; i ++ ) {
        retval = Tcl_ListObjIndex( ip, dims, i, &elemObj );
        if ( retval != TCL_OK ) {
            return retval;
        }
        retval = Tcl_GetIntFromObj( ip, elemObj, &dimids[i] );
        if ( retval != TCL_OK ) {
            return retval;
        }
    }

    typeCode = -1;
    if ( strcmp( type, "int"    ) == 0 ) typeCode = NC_INT;
    if ( strcmp( type, "long"   ) == 0 ) typeCode = NC_LONG;
    if ( strcmp( type, "float"  ) == 0 ) typeCode = NC_FLOAT;
    if ( strcmp( type, "double" ) == 0 ) typeCode = NC_DOUBLE;

    if ( typeCode == -1 ) {
        Tcl_SetResult( ip, "Invalid type specified!", TCL_STATIC );
        return TCL_ERROR;
    }

    retval = nc_def_var( ncid, varName, typeCode, ndim, dimids, &varid );

    if ( retval == 0 ) {
        Tcl_SetObjResult( ip, Tcl_NewIntObj(varid) );
        return TCL_OK;
    } else {
        Tcl_SetResult( ip, (char *) nc_strerror(retval), TCL_VOLATILE );
        return TCL_ERROR;
    }
}

critcl::cproc nc_put_att_text {Tcl_Interp* ip int ncid int varid
                  char* attName char* attValue} ok {

    int retval;

    retval = nc_put_att_text( ncid, varid, attName, strlen(attValue), attValue);

    if ( retval == 0 ) {
        return TCL_OK;
    } else {
        Tcl_SetResult( ip, (char *) nc_strerror(retval), TCL_VOLATILE );
        return TCL_ERROR;
    }
    return TCL_ERROR;
}

critcl::cproc nc_get_var {Tcl_Interp* ip int ncid char* varName} ok {

    /* TODO
    int retval;
    int dimid;

    retval = nc_get_dim( ncid, varName, &varid );

    if ( retval == 0 ) {
        Tcl_SetObjResult( ip, Tcl_NewIntObj(varid) );
        return TCL_OK;
    } else {
        Tcl_SetResult( ip, (char *) nc_strerror(retval), TCL_VOLATILE );
        return TCL_ERROR;
    }
    */
    return TCL_ERROR;
}

critcl::cproc nc_inq_varname {Tcl_Interp* ip int ncid int varid} ok {

    int retval;
    int dimid;

    char varName[NC_MAX_NAME+1];

    retval = nc_inq_varname( ncid, varid, &varName );

    if ( retval == 0 ) {
        Tcl_SetObjResult( ip, Tcl_NewStringObj(varName, -1) );
        return TCL_OK;
    } else {
        Tcl_SetResult( ip, (char *) nc_strerror(retval), TCL_VOLATILE );
        return TCL_ERROR;
    }
    return TCL_ERROR;
}

# main --
#     Test the thing
#
set ncid  [nc_create "somefile.nc"]

set xdimid [nc_def_dim $ncid "X" 10]
set ydimid [nc_def_dim $ncid "Y" 20]

set xvarid [nc_def_var $ncid "X" float $xdimid]
set yvarid [nc_def_var $ncid "Y" float $ydimid]
set salin  [nc_def_var $ncid "Salinity" float [list $xdimid $ydimid]]

puts "$xdimid -- $ydimid"

catch {
nc_put_att_text $ncid $xdimid name "X-coordinate"
} msg
puts "NC: $msg"

set err 0
set i   0
while { $err == 0 } {
    set err [catch {
        puts [nc_inq_varname $ncid $i]
    } msg]
    incr i
}

nc_close $ncid

cdftcl is a Tcl/Tk interface for netCDF. It allows the use of "wildcards" (*) or ranges (1-4) in the subscript notation, and use of name references instead of variable IDs.


Xconv is a program designed to convert model output into a format suitable for use in various plotting packages. It can read and write NetCDF files. It can be used to see what fields are contained within a data file and to look at the data values, either directly at the numerical values or at a grid box fill plot of the data. Xconv can also be used to manipulate the input data before it is converted into the output format.

Also available is convsh, which allows scripts to be written to automate various xconv tasks. Convsh uses the tcl scripting language, plus various extensions for reading, writing and manipulating data files.

http://www.ukca.ac.uk/images/3/38/UKCA_vn8.4_Tutorial_Xconv_paO3surf.png

http://www.ukca.ac.uk/images/7/7f/Xconv_Trans_N96.png


arjen - 2018-03-18 11:17:39

As part of my mathemaTcl project I have written a (fairly basic) interface to the NetCDF library. Plenty of limitations to overcome, but it is possible to read and examine NetCDF files and retrieve data from them.