Version 0 of Reading and writing NetCDF files

Updated 2009-07-06 07:20:19 by arjen

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