/*
  Copyright 2006, University Corporation for Atmospheric Research. See
  COPYRIGHT file for copying and redistribution conditions.

  This file is part of the NetCDF CF Library. 

  This file handles the libcf file stuff.

  Ed Hartnett, 9/1/06

  $Id: cfcvars.c,v 1.2 2009/09/01 16:56:46 ed Exp $
*/

#include <config.h>
#include <libcf.h>
#include <libcf_int.h>
#include <netcdf.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define NLAT_UNITS 6
#define NLON_UNITS 6
#define NPRES_UNITS 31
#define NLEVEL_UNITS 35
#define NTIME_UNITS 33
#define CF_POS_LEN 4

#define CF_PTOP "PTOP"

static char *vert_std_name[] = {
   STD_ATM_LN, STD_SIGMA, STD_HYBRID_SIGMA, STD_HYBRID_HEIGHT, STD_SLEVE, 
   STD_OCEAN_SIGMA, STD_OCEAN_S, STD_OCEAN_SIGMA_Z, STD_OCEAN_DBL_SIGMA
};

/* The Common Data Model defines these axis types. */
static char* axis_type_name[] = {"", "Lat", "Lon", "GeoX", "GeoY", "GeoZ", 
				 "Height", "Height", "Pressure", "Time", 
				 "RadialAzimuth", "RadialElevation", 
				 "RadialDistance"};

int
cf_test1(int i)
{
   printf("i = %d", i);
   return 0;
}

/* Define a coordinate dimension and variable with all the CF
 * recomended attribute accessories. */
static int 
def_coord_var(int ncid, const char *name, size_t len, nc_type xtype, 
	      const char *units, const char *axis, int positive_up, 
	      const char *standard_name, const char *formula_terms, 
	      int cdm_axis_type, int *dimidp, int *varidp)
{
   int did, vid;
   int ret;

   /* Name is required. */
   if (!name)
      return CF_EINVAL;

   /* There must be neither a dimension nor a variable of the chosen
    * name. */
   if (nc_inq_dimid(ncid, name, &did) != NC_EBADDIM ||
       nc_inq_varid(ncid, name, &vid) != NC_ENOTVAR)
      return CF_EEXISTS;

   /* Create a dimension. */
   if ((ret = nc_def_dim(ncid, name, len, &did)))
      return ret;

   /* Give the user back the dimid if he wants it. */
   if (dimidp)
      *dimidp = did;

   /* Create a variable. */
   if ((ret = nc_def_var(ncid, name, xtype, 1, &did, &vid)))
      return ret;

   /* Give the user back the varid if he wants it. */
   if (varidp)
      *varidp = vid;

   /* Create required attributes for this coordinate variable. */
   if (units)
      if ((ret = nc_put_att_text(ncid, vid, CF_UNITS, strlen(units) + 1, 
				 units)))
	 return ret;

   /* If an axis was provided, then use it. This is the "axis"
    * described in the CF document, section 4. It is a text string, X,
    * Y, Z or T.*/
   if (axis)
   {
      if ((ret = nc_put_att_text(ncid, vid, CF_AXIS, strlen(axis) + 1, 
				 axis)))
	 return ret;
      
      /* If this is a vertical axis, use the "positive" attribute to
       * indicate whether it is increasing upwards, or downwards. */
      if (!strncmp(axis, CF_LEVEL_AXIS, strlen(CF_LEVEL_AXIS)))
      {
	 if (positive_up)
	 {
	    if ((ret = nc_put_att_text(ncid, vid, CF_POSITIVE, 
				       strlen(CF_UP) + 1, CF_UP)))
	       return ret;
	 }
	 else
	 {
	    if ((ret = nc_put_att_text(ncid, vid, CF_POSITIVE, 
				       strlen(CF_DOWN) + 1, CF_DOWN)))
	       return ret;
	 }
      }
   }

   /* This is the CDM axis, which is somewhat different. */
   if (cdm_axis_type)
   {
      /* Is the axis type valid? */
      if (cdm_axis_type < 0 || cdm_axis_type > NCCF_RADDIST)
	 return NC_EINVAL;
      
      /* Now write the attribute which stores the axis type. */
      if ((ret = nc_put_att_text(ncid, vid, COORDINATE_AXIS_TYPE, 
				 strlen(axis_type_name[cdm_axis_type]) + 1, 
				 axis_type_name[cdm_axis_type])))
	 return ret;
   }

   /* If formula_terms was provided, then use it. */
   if (formula_terms)
      if ((ret = nc_put_att_text(ncid, vid, CF_FORMULA_TERMS, 
				 strlen(formula_terms) + 1, 
				 formula_terms)))
	 return ret;

   /* If standard_name was provided, then use it. */
   if (standard_name)
      if ((ret = nc_put_att_text(ncid, vid, CF_STANDARD_NAME, 
				 strlen(standard_name) + 1, 
				 standard_name)))
	 return ret;

   return CF_NOERR;
}

/* Inquire about a coordinate variable. */
static int 
inq_coord_var(int ncid, int nvalid, const char valid_units[][CF_MAX_LEN + 1], 
	      const char *valid_standard_name, char *name, size_t *lenp, 
	      nc_type *xtypep, size_t *ft_lenp, char *formula_terms, 
	      int *positive_upp, int *dimidp, int *varidp)
{
   int varid, nvars, dimid;
   nc_type att_type_in, xtype_in;
   size_t att_len_in, target_len_in;
   char *target_end;
   char target_in[NC_MAX_NAME + 1], var_name[NC_MAX_NAME + 1];
   char target[NC_MAX_NAME + 1];
   char pos_in[CF_POS_LEN + 1];
   int positive_found = 0, positive_up, vnum;
   int ret;

   /* How many vars are there? */
   if ((ret = nc_inq_nvars(ncid, &nvars)))
      return ret;

   /* If there are no valid units, and no standard name, I can't find
    * a coodinate variable! */
   if (!valid_units && !valid_standard_name)
      return CF_EINVAL;

   /* Decide what we are looking for. */
   if (valid_units)
      strcpy(target, CF_UNITS);
   else
      strcpy(target, CF_STANDARD_NAME);

   /* Search for a variable with an appropriate "units" or
    * "standard_name" attribute. */
   for (varid = 0; varid < nvars; varid++)
   {
      /* Is there's no units or standard_name attribute for this
       * var, move on. */
      if ((ret = nc_inq_att(ncid, varid, target, &att_type_in, 
			    &att_len_in)) == NC_ENOTATT)
	 continue;
      
      /* If we got some other error, we're totally hosed. */
      if (ret != NC_NOERR)
	 return ret;
      
      /* If the attribute is not a character string, or if it's longer
       * than NC_MAX_NAME, then what the heck is going on? Skip it. */
      if (att_type_in != NC_CHAR || att_len_in > NC_MAX_NAME)
	 continue;

      /* Get the value of the attribute. */
      if ((ret = nc_get_att_text(ncid, varid, target, target_in)))
	 return ret;

      /* We only care about this string before the first space. */
      target_end = index(target_in, ' ');
      if (target_end)
	 target_len_in = target_end - target_in - 1;
      else
	 target_len_in = att_len_in;
      
      if (valid_units)
      {
	 /* If the length and value matches on of our valid strings,
	  * then we have found our coordinate variable. */
	 for (vnum = 0; vnum < nvalid; vnum++)
	    if (target_len_in <= strlen(valid_units[vnum]) + 1 &&
		!strncmp(target_in, valid_units[vnum], strlen(valid_units[vnum])))
	       break;

	 /* If we found the coordinate var, stop looking. */
	 if (vnum < nvalid)
	    break;
      }
      else
      {
	 if (target_len_in <= strlen(valid_standard_name) + 1 &&
	     !strncmp(target_in, valid_standard_name, strlen(valid_standard_name)))
	    break;
      }

   } /* next varid*/
   
   /* Did we still not find our coordinate variable? */
   if (varid == nvars)
   {
      /* Let's look for an attribute named "positve". */
      for (varid = 0; varid < nvars; varid++)
      {
	 /* Is there's no "positive" attribute for this var, move on. */
	 if ((ret = nc_inq_att(ncid, varid, CF_POSITIVE, &att_type_in, 
			       &att_len_in)) == NC_ENOTATT)
	    continue;
      
	 /* If we got some other error, we're totally hosed. */
	 if (ret != NC_NOERR)
	    return ret;
      
	 /* If the "positive" attribute is not a character string,
	  * or it's too long, then what the heck is going on? Skip
	  * it. */
	 if (att_type_in != NC_CHAR || att_len_in > strlen(CF_DOWN) + 1)
	    continue;
      
	 /* Get the value of the "positive" attribute. */
	 if ((ret = nc_get_att_text(ncid, varid, CF_POSITIVE, pos_in)))
	    return ret;
	 if (strncasecmp(pos_in, CF_DOWN, strlen(CF_DOWN)) == 0)
	    positive_up = 0;
	 else if (strncasecmp(pos_in, CF_UP, strlen(CF_UP)) == 0)
	    positive_up = 1;
	 else
	    return CF_EBADVALUE;

	 /* Remember that we have already found and dealt with the
	  * "positive" attribute. */
	 positive_found++;
      
	 /* We found the coordinate var, stop looking. */
	 break;
      
      } /* next varid*/
      
      if (varid == nvars)
	 return CF_ENOTFOUND;
   }

   /* Does the user want the varid of this coordinate variable? */
   if (varidp)
      *varidp = varid;

   /* We need to know some stuff. */
   if ((ret = nc_inq_vartype(ncid, varid, &xtype_in)))
      return ret;

   /* Does the user want the type of this coordinate variable? */
   if (xtypep)
      *xtypep = xtype_in;

   /* Is there an interest in the formula_terms attribute? */
   if (ft_lenp || formula_terms)
   {
      ret = nc_inq_att(ncid, varid, CF_FORMULA_TERMS, &att_type_in, 
		       &att_len_in);
      if (ret == NC_ENOTATT)
      {
	 if (ft_lenp)
	    *ft_lenp = 0;
      }
      else if (ret == CF_NOERR)
      {
	 /* If this is not a character attribute, what the heck is
	  * it? */
	 if (att_type_in != NC_CHAR)
	 {
	    if (ft_lenp)
	       *ft_lenp = 0;
	 }
	 else
	 {
	    if (ft_lenp)
	       *ft_lenp = att_len_in;
	    if (formula_terms)
	       if ((ret = nc_get_att_text(ncid, varid, CF_FORMULA_TERMS, 
					  formula_terms)))
		  return ret;
	 }
      }	 
   }

   if (positive_upp)
      *positive_upp = positive_up;

   if (positive_upp)
   {
      if (!positive_found)
      {
	 /* Is there a "positive" attribute? */
	 ret = nc_inq_att(ncid, varid, CF_POSITIVE, &att_type_in, &att_len_in);
	 if (ret == NC_NOERR && att_len_in <= CF_POS_LEN + 1)
	 {
	    /* Get the contents of the attribute and see if it is "up" or
	     * "down". */
	    if ((ret = nc_get_att_text(ncid, varid, CF_POSITIVE, pos_in)))
	       return ret;
	    if (strncasecmp(pos_in, CF_DOWN, strlen(CF_DOWN)))
	       *positive_upp = 0;
	    else if (strncasecmp(pos_in, CF_UP, strlen(CF_UP)))
	       *positive_upp = 1;
	    else
	       return CF_EBADVALUE;
	 }
	 if (ret == NC_ENOTATT)
	 {
	    /* No "positive" attribute there. What should be done?
	     * According to the CF standards, assume down for pressure
	     * units, up otherwise. */
	    if (vnum < NPRES_UNITS)
	       *positive_upp = 0;
	    else
	       *positive_upp = 1;
	 }
      }
   }

   /* Get the name. */
   if ((ret = nc_inq_varname(ncid, varid, var_name)))
      return ret;

   /* Does the user want the name? */
   if (name)
      strcpy(name, var_name);

   /* Now that we have our coordianate variable, let's find the
    * associated dimension. It will have the same name. */
   if ((ret = nc_inq_dimid(ncid, var_name, &dimid)))
      return CF_ENODIM;

   /* Does the user want the dimid? */
   if (dimidp)
      *dimidp = dimid;

   /* Does the user want to know the length of this coordinate
    * axis? */
   if (lenp)
      if ((ret = nc_inq_dimlen(ncid, dimid, lenp)))
	 return ret;
   return CF_NOERR;
}

/* Define a latitude dimension and variable with all the CF
 * recomended attribute accessories.*/
int 
nccf_def_latitude(int ncid, size_t len, nc_type xtype, 
		  int *lat_dimidp, int *lat_varidp)
{
   return def_coord_var(ncid, CF_LATITUDE_NAME, len,
			xtype, CF_LATITUDE_UNITS, CF_LATITUDE_AXIS,
			0, CF_LATITUDE_NAME, NULL, 
			NCCF_LATITUDE, lat_dimidp, lat_varidp);
}

/* Inquire about a latitude dimension and variable with all the CF
 * recomended attribute accessories.*/
int 
nccf_inq_latitude(int ncid, size_t *lenp, nc_type *xtypep, 
		  int *lat_dimidp, int *lat_varidp)
{
   const char val[NLAT_UNITS][CF_MAX_LEN + 1] = {"degrees_north", "degree_north", 
						 "degree_N", "degrees_N", "degreeN",
						 "degreesN"};
   return inq_coord_var(ncid, NLAT_UNITS, val, NULL, NULL, lenp, xtypep, 
			NULL, NULL, NULL, lat_dimidp, lat_varidp);
}

/* Define a longitude dimension and variable with all the CF
 * recomended attribute accessories.*/
int 
nccf_def_longitude(int ncid, size_t len, nc_type xtype, 
		   int *lon_dimidp, int *lon_varidp)
{
   return def_coord_var(ncid, CF_LONGITUDE_NAME, len,
			xtype, CF_LONGITUDE_UNITS, CF_LONGITUDE_AXIS,
			0, CF_LONGITUDE_NAME, NULL, 
			NCCF_LONGITUDE, lon_dimidp, lon_varidp);
}

/* Inquire about a longitude dimension and variable with all the CF
 * recomended attribute accessories.*/
int 
nccf_inq_longitude(int ncid, size_t *lenp, nc_type *xtypep, 
		   int *lon_dimidp, int *lon_varidp)
{
   const char val[NLON_UNITS][CF_MAX_LEN + 1] = {
      "degrees_east", "degree_east", "degree_E", 
      "degrees_E", "degreeE", "degreesE"};

   return inq_coord_var(ncid, NLON_UNITS, val, NULL, NULL, lenp, xtypep, 
			NULL, NULL, NULL, lon_dimidp, lon_varidp);
}

/* Define a verticle level coordinate variable and dimension. */
int 
nccf_def_lvl(int ncid, const char *name, size_t len, nc_type xtype, 
	     const char *units, int positive_up, 
	     const char *standard_name, const char *formula_terms, 
	     int cdm_axis_type, int *lvl_dimidp, int *lvl_varidp)
{
   return def_coord_var(ncid, name, len, xtype, units, CF_LEVEL_AXIS, 
			positive_up, standard_name, formula_terms, 
			cdm_axis_type, lvl_dimidp, lvl_varidp);
}

/* Inquire about a vertical dimension and coordinate variable. */
int 
nccf_inq_lvl(int ncid, char *name, size_t *lenp, nc_type *xtypep, 
	     size_t *ft_lenp, char *formula_terms, int *positive_upp, 
	     int *lon_dimidp, int *lon_varidp)
{
   const char val[NLEVEL_UNITS][CF_MAX_LEN + 1] = 
      {"bar", "standard_atmosphere", "technical_atmosphere", 
       "inch_H2O_39F", "inch_H2O_60F", "inch_Hg_32F", 
       "inch_Hg_60F", "millimeter_Hg_0C", "footH2O", "cmHg", 
       "cmH2O", "Pa", "inch_Hg", "inch_hg", "inHg", "in_Hg", 
       "in_hg", "millimeter_Hg", "mmHg", "mm_Hg", "mm_hg", 
       "torr", "foot_H2O", "ftH2O", "psi", "ksi", "barie", 
       "at", "atmosphere", "atm", "barye", "level", "layer", 
       "sigma_level", "sigma"};

   /* Find a coordinate var and dim with one of the units above. */
   return inq_coord_var(ncid, NLEVEL_UNITS, val, NULL, name, lenp, xtypep, 
			ft_lenp, formula_terms, positive_upp, lon_dimidp, 
			lon_varidp);
}

/* Define a time coordinate variable and dimension. */
int 
nccf_def_time(int ncid, const char *name, size_t len, nc_type xtype, 
	      const char *units, const char *standard_name, 
	      int *time_dimidp, int *time_varidp)
{
   return def_coord_var(ncid, name, len, xtype, units, CF_TIME_AXIS, 0, 
			standard_name, NULL, NCCF_TIME, time_dimidp, 
			time_varidp);
}

/* Inquire about a vertical dimension and coordinate variable. */
int 
nccf_inq_time(int ncid, char *name, size_t *lenp, nc_type *xtypep, 
	      int *time_dimidp, int *time_varidp)
{
   const char val[NTIME_UNITS][CF_MAX_LEN + 1] = {
      "second", "day", "hour", "minute", "s", 
      "sec", "shake", "sidereal_day", "sidereal_hour", 
      "sidereal_minute", "sidereal_second", 
      "sidereal_year", "tropical_year", "lunar_month", 
      "common_year", "leap_year", "Julian_year", 
      "Gregorian_year", "sidereal_month", "tropical_month", 
      "d", "min", "hr", "h", "fortnight", "week", "jiffy", 
      "jiffies", "year", "yr", "a", "eon", "month"};

   return inq_coord_var(ncid, NTIME_UNITS, val, NULL, NULL, lenp, xtypep, NULL, 
			NULL, NULL, time_dimidp, time_varidp);
}

/* Define one of the unitless vertical coordinate variables from
 * appendix D of the CF document. */
static int
def_vert_var(int ncid, const char *name, nc_type xtype, size_t len, 
	     int nterms, int *term_id, const char *ft_format, 
	     int *lvl_dimidp, int *lvl_varidp)
{
   char formula_terms[CF_MAX_FT_LEN + 1];
   char term_name[CF_MAX_FT_VARS][NC_MAX_NAME + 1];
   int v;
   int ret;

   if (strlen(name) > NC_MAX_NAME)
      return CF_EMAX_NAME;

   /* Find names of the vars to put in formula terms.*/
   for (v = 0; v < nterms; v++)
   {
      if (term_id[v] == -99)
	 strcpy(term_name[v], name);
      else
	 if ((ret = nc_inq_varname(ncid, term_id[v], term_name[v])))
	    return ret;
   }

   /* Assemble the formula terms attribute, depending on how many
    * terms are in use. */
   switch (nterms)
   {
      case 1:
	 sprintf(formula_terms, ft_format, term_name[0]);
	 break;
      case 2:
	 sprintf(formula_terms, ft_format, term_name[0], term_name[1]);
	 break;
      case 3:
	 sprintf(formula_terms, ft_format, term_name[0], term_name[1], 
		 term_name[2]);
	 break;
      case 4:
	 sprintf(formula_terms, ft_format, term_name[0], term_name[1], 
		 term_name[2], term_name[3]);
	 break;
      case 5:
	 sprintf(formula_terms, ft_format, term_name[0], term_name[1], 
		 term_name[2], term_name[3], term_name[4]);
	 break;
      case 6:
	 sprintf(formula_terms, ft_format, term_name[0], term_name[1], 
		 term_name[2], term_name[3], term_name[4], term_name[5]);
	 break;
      case 7:
	 sprintf(formula_terms, ft_format, term_name[0], term_name[1], 
		 term_name[2], term_name[3], term_name[4], term_name[5], 
		 term_name[6]);
	 break;
      default:
	 return CF_EINVAL;
   }
      
   /* Create the coordinate variable and dimension with this
    * formula_terms. */
   return def_coord_var(ncid, name, len, xtype, NULL, CF_LEVEL_AXIS, 
			0, STD_SIGMA, formula_terms, NCCF_GEOZ, 
			lvl_dimidp, lvl_varidp);
}

static int
nccf_set_ft(int ncid, int varid, int nterms, int *ft_varids, 
	    const char *ft_format)
{
   char formula_terms[CF_MAX_FT_LEN + 1];
   char term_name[CF_MAX_FT_VARS][NC_MAX_NAME + 1];
   int v;
   int ret;

   /* Find names of the vars to put in formula terms.*/
   for (v = 0; v < nterms; v++)
      if ((ret = nc_inq_varname(ncid, ft_varids[v], term_name[v])))
	 return ret;

   /* Assemble the formula terms attribute, depending on how many
    * terms are in use. */
   switch (nterms)
   {
      case 1:
	 sprintf(formula_terms, ft_format, term_name[0]);
	 break;
      case 2:
	 sprintf(formula_terms, ft_format, term_name[0], term_name[1]);
	 break;
      case 3:
	 sprintf(formula_terms, ft_format, term_name[0], term_name[1], 
		 term_name[2]);
	 break;
      case 4:
	 sprintf(formula_terms, ft_format, term_name[0], term_name[1], 
		 term_name[2], term_name[3]);
	 break;
      case 5:
	 sprintf(formula_terms, ft_format, term_name[0], term_name[1], 
		 term_name[2], term_name[3], term_name[4]);
	 break;
      case 6:
	 sprintf(formula_terms, ft_format, term_name[0], term_name[1], 
		 term_name[2], term_name[3], term_name[4], term_name[5]);
	 break;
      case 7:
	 sprintf(formula_terms, ft_format, term_name[0], term_name[1], 
		 term_name[2], term_name[3], term_name[4], term_name[5], 
		 term_name[6]);
	 break;
      default:
	 return CF_EINVAL;
   }

   /* Attatch the formula_terms attribute to the coordinate variable. */
   if ((ret = nc_put_att_text(ncid, varid, CF_FORMULA_TERMS, 
			      strlen(formula_terms) + 1, formula_terms)))
      return ret;

   return CF_NOERR;
}

/* Find out about one of the unitless vertical coordinates in appendix
 * D of the CF document. */
static int 
inq_vert_var(int ncid, const char *standard_name, int nterms, 
	     const char *ft_format, int *termids, char *name, 
	     nc_type *xtypep, size_t *lenp, int *lvl_dimidp, 
	     int *lvl_varidp)
{
   char formula_terms[CF_MAX_FT_LEN + 1];
   char term_name[FT_MAX_TERMS][CF_MAX_FT_LEN + 1];
   size_t ft_len;
   int t;
   int ret;
   
   /* Get some of the info, including the formula_terms attribute. */
   if ((ret = inq_coord_var(ncid, 0, NULL, standard_name, name, lenp, 
			    xtypep, &ft_len, formula_terms, NULL, 
			    lvl_dimidp, lvl_varidp)))
      return ret;
   
   /* Was the formula_terms attribute too big? */
   if (ft_len > CF_MAX_FT_LEN)
      return CF_EMAXFT;

   /* Parse the formula_terms attribute, depending on the number of
    * terms in use. */
   switch (nterms)
   {
      case 1:
	 if (sscanf(formula_terms, ft_format, term_name[0]) != nterms)
	    return CF_EBADFT;
	 break;
      case 2:
	 if (sscanf(formula_terms, ft_format, term_name[0], 
		    term_name[1]) != nterms)
	    return CF_EBADFT;
	 break;
      case 3:
	 if (sscanf(formula_terms, ft_format, term_name[0], term_name[1], 
		    term_name[2]) != nterms)
	    return CF_EBADFT;
	 break;
      case 4:
	 if (sscanf(formula_terms, ft_format, term_name[0], term_name[1], 
		    term_name[2], term_name[3]) != nterms)
	    return CF_EBADFT;
	 break;
      case 5:
	 if (sscanf(formula_terms, ft_format, term_name[0], term_name[1], 
		    term_name[2], term_name[3], term_name[4]) != nterms)
	    return CF_EBADFT;
	 break;
      case 6:
	 if (sscanf(formula_terms, ft_format, term_name[0], term_name[1], 
		    term_name[2], term_name[3], term_name[4], 
		    term_name[5]) != nterms)
	    return CF_EBADFT;
	 break;
      case 7:
	 if (sscanf(formula_terms, ft_format, term_name[0], term_name[1], 
		    term_name[2], term_name[3], term_name[4], term_name[5], 
		    term_name[6]) != nterms)
	    return CF_EBADFT;
	 break;
      default:
	 return CF_EINVAL;
   }

   /* Convert the names of the terms into variable ids, and return
    * them in the termids array. */
   if (termids)
      for (t = 0; t < nterms; t++)
	 if ((ret = nc_inq_varid(ncid, term_name[t], &termids[t])))
	    return ret;

   return CF_NOERR;
}

/*
  Atmosphere natural log pressure coordinate

  standard_name = "atmosphere_ln_pressure_coordinate"

  Definition:

  p(k) = p0 * exp(-lev(k))

  where p(k) is the pressure at gridpoint (k), p0 is a reference
  pressure, lev(k) is the dimensionless coordinate at vertical gridpoint
  (k).

  The format for the formula_terms attribute is

  formula_terms = "p0: var1 lev: var2"
*/
int 
nccf_def_lvl_atm_ln(int ncid, const char *name, nc_type xtype, size_t len, 
		    int *lvl_dimidp, int *lvl_varidp)
{
   return def_coord_var(ncid, name, len, xtype, NULL, CF_LEVEL_AXIS, 
			0, STD_ATM_LN, NULL, NCCF_GEOZ, lvl_dimidp, 
			lvl_varidp);
}

int 
nccf_def_ft_atm_ln(int ncid, int varid, int pref_varid)
{
   int ft_varids[FT_MAX_TERMS];
   
   ft_varids[0] = pref_varid;

   return nccf_set_ft(ncid, varid, FT_ATM_LN_TERMS, ft_varids, FT_ATM_LN_FORMAT);
}

/* Atmosphere natural log pressure coordinate. */
int 
nccf_inq_lvl_atm_ln(int ncid, char *name, nc_type *xtypep, size_t *lenp, 
		    int *pref_varidp, int *lvl_dimidp, int *lvl_varidp)
{
   int termids[FT_ATM_LN_TERMS];
   int ret;

   /* Get most of the info from inq_vert_var. */
   if ((ret = inq_vert_var(ncid, STD_ATM_LN, FT_ATM_LN_TERMS, FT_ATM_LN_FORMAT, 
			   termids, name, xtypep, lenp, lvl_dimidp, lvl_varidp)))
      return ret;

   /* Pull out the varids for atm_ln level coordinate formula terms. */
   if (pref_varidp)
      *pref_varidp = termids[0];

   return CF_NOERR;
}

/* Create a unitless vertical dimension/variable from appendix D of
 * the CF standard. */
int 
nccf_def_lvl_vert(int ncid, int lvl_type, const char *name, nc_type xtype, 
		  size_t len, int *lvl_dimidp, int *lvl_varidp)
{
   /* Must be a known level type... */
   if (lvl_type < 0 || lvl_type >= CF_NUM_VERT)
      return CF_EINVAL;

   /* Do the deed! */
   return def_coord_var(ncid, name, len, xtype, NULL, CF_LEVEL_AXIS, 0, 
			vert_std_name[lvl_type], NULL, NCCF_GEOZ, lvl_dimidp, 
			lvl_varidp);
}

/* Define a vertical sigma coordinate dimension. 
   From the CF doc:

   Atmosphere sigma coordinate:  
   float lev(lev) ;
   lev:long_name = "sigma at layer midpoints" ;
   lev:positive = "down" ;
   lev:standard_name = "atmosphere_sigma_coordinate" ;
   lev:formula_terms = "sigma: lev ps: PS ptop: PTOP" ;
*/
int 
nccf_def_lvl_sigma(int ncid, const char *name, nc_type xtype, size_t len, 
		   int *lvl_dimidp, int *lvl_varidp)
{
   return def_coord_var(ncid, name, len, xtype, NULL, CF_LEVEL_AXIS, 
			0, STD_SIGMA, NULL, NCCF_GEOZ, lvl_dimidp, 
			lvl_varidp);
}

/* Set the formula_terms to contain information about this level. */
int
nccf_def_ft_sigma(int ncid, int lvl_vid, int ps_vid, int p0_vid)
{
   int ft_varids[FT_MAX_TERMS];
   
   ft_varids[0] = lvl_vid;
   ft_varids[1] = ps_vid;
   ft_varids[2] = p0_vid;
   return nccf_set_ft(ncid, lvl_vid, FT_SIGMA_TERMS, ft_varids, FT_SIGMA_FORMAT);
}

int 
nccf_inq_lvl_vert(int ncid, char *name, nc_type *xtypep, size_t *lenp, 
		  int *lvl_typep, int *lvl_dimidp, int *lvl_varidp)
{
   char dim_name[NC_MAX_NAME + 1];
   int varid, d, ndims, v;
   size_t name_len;
   char standard_name[NC_MAX_NAME + 1], var_name[NC_MAX_NAME + 1];
   size_t dim_len;
   nc_type var_type;
   int ret;

   /* Loop thru vars in file to see if any has a standard name that
    * matches one of the standard names of the appendix D vertical
    * dimensions.*/
   if ((ret = nc_inq_ndims(ncid, &ndims)))
      return ret;
   for (d = 0; d < ndims; d++)
   {
      /* Any var with same name as this dim? */
      if ((ret = nc_inq_dimname(ncid, d, dim_name)))
	 return ret;
      ret = nc_inq_varid(ncid, dim_name, &varid);
      if (ret == NC_ENOTVAR)
	 continue; 
      else if (ret)
	 return ret;
      else
      {
	 /* Find length of standard_name attribute, if it exists. */
	 ret = nc_inq_attlen(ncid, varid, CF_STANDARD_NAME, &name_len);
	 if (ret == NC_ENOTATT)
	    continue;
	 else if (ret)
	    return ret;

	 /* If the name is not too long, read it in. */
	 if (name_len > NC_MAX_NAME)
	    return CF_EMAX_NAME;
	 if ((ret = nc_get_att_text(ncid, varid, CF_STANDARD_NAME, standard_name)))
	    return ret;

	 /* Compare the standard name to our list of unitless vert
	  * dimension standard names. */
	 for (v = 0; v < CF_NUM_VERT; v++)
	 {
	    if (!strncmp(vert_std_name[v], standard_name, strlen(vert_std_name[v])))
	       break;
	 }
	 if (v < CF_NUM_VERT)
	    break;
      }
   }

   /* If we went thru all dimensions, I guess we didn't find a
    * unitless vertical coordinate variable. Boo hoo! */
   if (d == ndims)
      return CF_ENOTFOUND;

   /* Learn the type and name from the coordinate variable. */
   if ((ret = nc_inq_var(ncid, v, var_name, &var_type, NULL, NULL, NULL)))
      return ret;

   /* Learn the length from the coordinate dimension. */
   if ((ret = nc_inq_dim(ncid, d, NULL, &dim_len)))
      return ret;

   /* Give the user the results they are interested in. */
   if (name)
      strcpy(name, var_name);

   if (xtypep)
      *xtypep = var_type;

   if (lenp)
      *lenp = dim_len;

   if (lvl_dimidp)
      *lvl_dimidp = d;

   if (lvl_typep)
      *lvl_typep = v;

   if (lvl_varidp)
      *lvl_varidp = varid;

   return CF_NOERR;
}

/* Inquire about atmosphere sigma coordinate. */
int 
nccf_inq_lvl_sigma(int ncid, char *name, nc_type *xtypep, size_t *lenp, 
		   int *ps_varidp, int *ptop_varidp, int *lvl_dimidp, 
		   int *lvl_varidp)
{
   int termids[FT_SIGMA_TERMS];
   int ret;

   /* Get most of the info from inq_vert_var. */
   if ((ret = inq_vert_var(ncid, STD_SIGMA, FT_SIGMA_TERMS, FT_SIGMA_FORMAT, 
			   termids, name, xtypep, lenp, lvl_dimidp, lvl_varidp)))
      return ret;

   /* Pull out the varids for sigma level coordinate formula terms. */
   if (ps_varidp)
      *ps_varidp = termids[1];
   if (ptop_varidp)
      *ptop_varidp = termids[2];

   return CF_NOERR;
}

/*
  Atmosphere hybrid sigma pressure coordinate

  standard_name = "atmosphere_hybrid_sigma_pressure_coordinate"

  Definition:

  p(n,k,j,i) = a(k)*p0 + b(k)*ps(n,j,i)

  or

  p(n,k,j,i) = ap(k) + b(k)*ps(n,j,i)

  where p(n,k,j,i) is the pressure at gridpoint (n,k,j,i), a(k) or ap(k)
  and b(k) are components of the hybrid coordinate at level k, p0 is a
  reference pressure, and ps(n,j,i) is the surface pressure at
  horizontal gridpoint (j,i) and time (n). The choice of whether a(k) or
  ap(k) is used depends on model formulation; the former is a
  dimensionless fraction, the latter a pressure value. In both
  formulations, b(k) is a dimensionless fraction.

  The format for the formula_terms attribute is

  formula_terms = "a: var1 b: var2 ps: var3 p0: var4"

  where a is replaced by ap if appropriate.

  The hybrid sigma-pressure coordinate for level k is defined as
  a(k)+b(k) or ap(k)/p0+b(k), as appropriate.

*/
int 
nccf_def_lvl_hybrid_sigma(int ncid, const char *name, nc_type xtype, size_t len, 
			  int *lvl_dimidp, int *lvl_varidp)
{
   /* Create the coordinate variable and dimension with no
    * formula_terms. */
   return def_coord_var(ncid, name, len, xtype, NULL, CF_LEVEL_AXIS, 
			0, STD_HYBRID_SIGMA, NULL, NCCF_GEOZ, lvl_dimidp, 
			lvl_varidp);
}

/* Set the formula_terms atribute to contain all this information. */
int
nccf_def_ft_hybrid_sigma(int ncid, int lvl_vid, int a_vid, int b_vid, int ps_vid, 
			 int p0_vid)
{
   int ft_varids[FT_MAX_TERMS];
   
   ft_varids[0] = a_vid;
   ft_varids[1] = b_vid;
   ft_varids[2] = ps_vid;
   ft_varids[3] = p0_vid;
   return nccf_set_ft(ncid, lvl_vid, FT_HYBRID_SIGMA_TERMS, ft_varids, 
		      FT_HYBRID_SIGMA_FORMAT);
}

/* #define CF_4D 4 */
/* int */
/* nccf_def_hybrid_sigma(int ncid, char names[CF_4D][NC_MAX_NAME+1], int *lens,  */
/* 		      nc_type *types, int *dids, int *vids, int *term_vids)  */
/* { */
/*    return nccf_def_coord_system(ncid, CF_VERT_HYBRID_SIGMA, names, lens, types, time_units,  */
/* 				FT_HYBRID_SIGMA_TERMS, term_names, term_types, term_ndims, */
/* 				term_dimids, did, vids, term_vids); */
/* } */

/* int */
/* nccf_def_coord_system(int ncid, int vert_axis_type, char name[CF_4D][NC_MAX_NAME+1],  */
/* 		      int *lens, nc_type *types, char *time_units, int nterms,  */
/* 		      char *term_names, nc_type *term_types, int *term_ndims,  */
/* 		      int *term_dimds, int *dids, int *vids, int *term_vids)  */
/* { */
/*    int t; */
/*    int ret; */

/*    /\* Create the latitude and longitude dimensions and variables, with */
/*     * appropriate attributes. *\/ */
/*    if ((ret = nccf_def_latitude(ncid, lens[0], types[0], &did[0], &vid[0]))) */
/*       return ret; */
/*    if ((ret = nccf_def_longitude(ncid, lens[1], types[1], &did[1], &vid[1]))) */
/*       return ret; */

/*    /\* Create the vertical axis dimension and variable. *\/ */
/*    if (def_coord_var(ncid, names[2], lens[2], types[2], NULL, CF_LEVEL_AXIS,  */
/* 		     0, vert_axis_type, NULL, NCCF_GEOZ, &did[2], &vid[2]))  */
/*       return ret; */

/*    /\* Now define the time coordinate variable and dimension. *\/ */
/*    if ((ret = nccf_def_time(ncid, names[3], lens[3], types[3], time_units,  */
/* 			    "time", &did[3], &vid[3]))) */
/*       return ret; */

/*    /\* Before we can set up the formula_terms attribute, we need to */
/*     * define some additional variables specific to this type of */
/*     * vertical dimension. *\/ */
/*    for (t = 0; t < nterms; t++) */
/*    { */
/*       if ((ret = nc_def_var(ncid, term_names[t], term_types[t], 1, did, &a_vid))) */
/* 	 return ret; */
/*    } */

/*    if ((ret = nc_def_var(ncid, "b", NC_FLOAT, 1, did, &b_vid))) */
/*       return ret; */
/*    if ((ret = nc_def_var(ncid, "ps", NC_FLOAT, 2, did, &ps_vid))) */
/*       return ret; */
/*    if ((ret = nc_def_var(ncid, "P0", NC_FLOAT, 0, NULL, &p0_vid))) */
/*       return ret; */

/*    if ((ret = nccf_def_ft_hybrid_sigma(ncid, lvl_vid, a_vid, b_vid, ps_vid,  */
/* 				       p0_vid))) */
/*       return ret; */

/*    return CF_NOERR; */
/* } */

/* #define A_NDIMS 1 */
/* #define B_NDIMS 1 */
/* #define P0_NDIMS 0 */
/* #define PS_NDIMS 3 */
/* int  */
/* nccf_def_lvl_hybrid_sigma_full(int ncid, const char *name, nc_type xtype, size_t len,  */
/* 			       const char *a_name, nc_type a_type, int *a_varid, const char *a_units, */
/* 			       const char *b_name, nc_type b_type, int *b_varid, const char *b_units, */
/* 			       const char *ps_name, nc_type ps_type, int *ps_varid, const char *ps_units, */
/* 			       const char *p0_name, nc_type p0_type, int *p0_varid, const char *p0_units, */
/* 			       int *lvl_dimidp, int *lvl_varidp) */
/* { */
/*    int termids[FT_HYBRID_SIGMA_TERMS] = {a_varid, b_varid, ps_varid, p0_varid}; */
/*    int ret; */

/*    /\* Create the coordinate variable and dimension without */
/*     * formula_terms. *\/ */
/*    if ((ret = def_coord_var(ncid, name, len, xtype, NULL, CF_LEVEL_AXIS,  */
/* 			    0, STD_SIGMA, NULL, NCCF_GEOZ,  */
/* 			    lvl_dimidp, lvl_varidp))) */
/*       return ret; */

/*    if ((ret = nc_def_var(ncid, a_name, a_type, A_NDIMS,  */
   
/* } */

/* Inquire about atmosphere hybrid_sigma coordinate. */
int 
nccf_inq_lvl_hybrid_sigma(int ncid, char *name, nc_type *xtypep, size_t *lenp, 
			  int *a_varidp, int *b_varidp, int *ps_varidp, 
			  int *p0_varidp, int *lvl_dimidp, int *lvl_varidp)
{
   int termids[FT_HYBRID_SIGMA_TERMS];
   int ret;

   /* Get most of the info from inq_vert_var. */
   if ((ret = inq_vert_var(ncid, STD_HYBRID_SIGMA, FT_HYBRID_SIGMA_TERMS, 
			   FT_HYBRID_SIGMA_FORMAT, termids, name, xtypep, 
			   lenp, lvl_dimidp, lvl_varidp)))
      return ret;

   /* Pull out the varids for hybrid_sigma level coordinate formula terms. */
   if (a_varidp)
      *a_varidp = termids[0];
   if (b_varidp)
      *b_varidp = termids[1];
   if (ps_varidp)
      *ps_varidp = termids[2];
   if (p0_varidp)
      *p0_varidp = termids[3];

   return CF_NOERR;
}

/* Atmosphere hybrid height coordinate

   standard_name = "atmosphere_hybrid_height_coordinate"

   Definition:
   z(n,k,j,i) = a(k) + b(k)*orog(n,j,i)

   where z(n,k,j,i) is the height above the geoid (approximately mean
   sea level) at gridpoint (k,j,i) and time (n), orog(n,j,i) is the
   height of the surface above the geoid at (j,i) and time (n), and
   a(k) and b(k) are the coordinates which define hybrid height level
   k. a(k) has the dimensions of height and b(k) is dimensionless.

   The format for the formula_terms attribute is
   formula_terms = "a: var1 b: var2 orog: var3"

   There is no dimensionless hybrid height coordinate. The hybrid
   height is best approximated as a(k) if a level-dependent constant
   is needed.
*/
int 
nccf_def_lvl_hybrid_height(int ncid, const char *name, nc_type xtype, size_t len, 
			   int *lvl_dimidp, int *lvl_varidp)
{
   /* Create the coordinate variable and dimension with no
    * formula_terms. */
   return def_coord_var(ncid, name, len, xtype, NULL, CF_LEVEL_AXIS, 
			0, STD_HYBRID_HEIGHT, NULL, NCCF_GEOZ, lvl_dimidp, 
			lvl_varidp);
}

int 
nccf_def_ft_hybrid_height(int ncid, int varid, int a_varid, int b_varid, 
			  int orog_varid)
{
   int ft_varids[FT_HYBRID_HEIGHT_TERMS];

   ft_varids[0] = a_varid;
   ft_varids[1] = b_varid;
   ft_varids[2] = orog_varid;
   return nccf_set_ft(ncid, varid, FT_HYBRID_HEIGHT_TERMS, ft_varids, 
		      FT_HYBRID_HEIGHT_FORMAT);
}

/* Inquire about atmosphere hybrid_height coordinate. */
int 
nccf_inq_lvl_hybrid_height(int ncid, char *name, nc_type *xtypep, size_t *lenp, 
			   int *a_varidp, int *b_varidp, int *orog_varidp, 
			   int *lvl_dimidp, int *lvl_varidp)
{
   int termids[FT_HYBRID_HEIGHT_TERMS];
   int ret;

   /* Get most of the info from inq_vert_var. */
   if ((ret = inq_vert_var(ncid, STD_HYBRID_HEIGHT, FT_HYBRID_HEIGHT_TERMS, 
			   FT_HYBRID_HEIGHT_FORMAT, termids, name, xtypep, 
			   lenp, lvl_dimidp, lvl_varidp)))
      return ret;

   /* Pull out the varids for hybrid_height level coordinate formula terms. */
   if (a_varidp)
      *a_varidp = termids[1];
   if (b_varidp)
      *b_varidp = termids[2];
   if (orog_varidp)
      *orog_varidp = termids[3];

   return CF_NOERR;
}

/* Atmosphere smooth level vertical (SLEVE) coordinate

   standard_name = "atmosphere_sleve_coordinate"

   Definition:
   z(n,k,j,i) = a(k)*ztop + b1(k)*zsurf1(n,j,i) + b2(k)*zsurf2(n,j,i)

   where z(n,k,j,i) is the height above the geoid (approximately mean
   sea level) at gridpoint (k,j,i) and time (n), ztop is the height of
   the top of the model, and a(k), b1(k) and b2(k) are the
   dimensionless coordinates which define hybrid height level
   k. zsurf1(n,j,i) and zsurf2(n,j,i) are respectively the large and
   small scale parts of the topography. See Schaer et al [SCH02] for
   details.

   The format for the formula_terms attribute is

   formula_terms = "a: var1 b1: var2 b2: var3 ztop: var4 zsurf1: var5
   zsurf2: var6"

   The hybrid height coordinate for level k is defined as a(k)*ztop.
*/
int 
nccf_def_lvl_sleve(int ncid, const char *name, nc_type xtype, size_t len, 
		   int *lvl_dimidp, int *lvl_varidp)
{
   /* Create the coordinate variable and dimension with no
    * formula_terms. */
   return def_coord_var(ncid, name, len, xtype, NULL, CF_LEVEL_AXIS, 
			0, STD_SLEVE, NULL, NCCF_GEOZ, lvl_dimidp, 
			lvl_varidp);
}

int 
nccf_def_ft_sleve(int ncid, int varid, int a_varid, int b1_varid, int b2_varid, 
		  int ztop_varid, int zsurf1_varid, int zsurf2_varid)
{
   int ft_varids[FT_SLEVE_TERMS];

   ft_varids[0] = a_varid;
   ft_varids[1] = b1_varid;
   ft_varids[2] = b2_varid;
   ft_varids[3] = ztop_varid;
   ft_varids[4] = zsurf1_varid;
   ft_varids[5] = zsurf2_varid;
   return nccf_set_ft(ncid, varid, FT_SLEVE_TERMS, ft_varids, FT_SLEVE_FORMAT);
}

/* Inquire about atmosphere sleve coordinate. */
int 
nccf_inq_lvl_sleve(int ncid, char *name, nc_type *xtypep, size_t *lenp, 
		   int *a_varidp, int *b1_varidp, int *b2_varidp, int *ztop_varidp, 
		   int *zsurf1_varidp, int *zsurf2_varidp, int *lvl_dimidp, 
		   int *lvl_varidp)
{
   int termids[FT_SLEVE_TERMS];
   int ret;

   /* Get most of the info from inq_vert_var. */
   if ((ret = inq_vert_var(ncid, STD_SLEVE, FT_SLEVE_TERMS, FT_SLEVE_FORMAT, 
			   termids, name, xtypep, lenp, lvl_dimidp, lvl_varidp)))
      return ret;

   /* Pull out the varids for sleve level coordinate formula terms. */
   if (a_varidp)
      *a_varidp = termids[0];
   if (b1_varidp)
      *b1_varidp = termids[1];
   if (b2_varidp)
      *b2_varidp = termids[2];
   if (ztop_varidp)
      *ztop_varidp = termids[3];
   if (zsurf1_varidp)
      *zsurf1_varidp = termids[4];
   if (zsurf2_varidp)
      *zsurf2_varidp = termids[5];

   return CF_NOERR;
}

/* Ocean sigma coordinate

   standard_name = "ocean_sigma_coordinate"

   Definition:
   z(n,k,j,i) = eta(n,j,i) + sigma(k) * (depth(j,i)+eta(n,j,i))

   where z(n,k,j,i) is height, positive upwards, relative to ocean
   datum (e.g. mean sea level) at gridpoint (n,k,j,i), eta(n,j,i) is
   the height of the ocean surface, positive upwards, relative to
   ocean datum at gridpoint (n,j,i), sigma(k) is the dimensionless
   coordinate at vertical gridpoint (k), and depth(j,i) is the
   distance from ocean datum to sea floor (positive value) at
   horizontal gridpoint (j,i).

   The format for the formula_terms attribute is
   formula_terms = "sigma: var1 eta: var2 depth: var3"
*/
int 
nccf_def_lvl_ocean_sigma(int ncid, const char *name, nc_type xtype, size_t len, 
			 int *lvl_dimidp, int *lvl_varidp)
{
   /* Create the coordinate variable and dimension with no
    * formula_terms. */
   return def_coord_var(ncid, name, len, xtype, NULL, CF_LEVEL_AXIS, 
			0, STD_OCEAN_SIGMA, NULL, NCCF_GEOZ, lvl_dimidp, 
			lvl_varidp);
}

int 
nccf_def_ft_ocean_sigma(int ncid, int varid, int eta_varid, int depth_varid)
{
   int ft_varids[FT_OCEAN_SIGMA_TERMS];

   ft_varids[0] = varid;
   ft_varids[1] = eta_varid;
   ft_varids[2] = depth_varid;
   return nccf_set_ft(ncid, varid, FT_OCEAN_SIGMA_TERMS, ft_varids, 
		      FT_OCEAN_SIGMA_FORMAT);
}

/* Inquire about atmosphere ocean_sigma coordinate. */
int 
nccf_inq_lvl_ocean_sigma(int ncid, char *name, nc_type *xtypep, size_t *lenp, 
			 int *eta_varidp, int *depth_varidp, int *lvl_dimidp, 
			 int *lvl_varidp)
{
   int termids[FT_OCEAN_SIGMA_TERMS];
   int ret;

   /* Get most of the info from inq_vert_var. */
   if ((ret = inq_vert_var(ncid, STD_OCEAN_SIGMA, FT_OCEAN_SIGMA_TERMS, FT_OCEAN_SIGMA_FORMAT, 
			   termids, name, xtypep, lenp, lvl_dimidp, lvl_varidp)))
      return ret;

   /* Pull out the varids for ocean_sigma level coordinate formula terms. */
   if (eta_varidp)
      *eta_varidp = termids[1];
   if (depth_varidp)
      *depth_varidp = termids[2];

   return CF_NOERR;
}

/* Ocean s-coordinate

   standard_name = "ocean_s_coordinate"

   z(n,k,j,i) = eta(n,j,i)*(1+s(k)) + depth_c*s(k) +
   (depth(j,i)-depth_c)*C(k)

   C(k) = (1-b)*sinh(a*s(k))/sinh(a) + 
   b*[tanh(a*(s(k)+0.5))/(2*tanh(0.5*a)) - 0.5]

   where z(n,k,j,i) is height, positive upwards, relative to ocean
   datum (e.g. mean sea level) at gridpoint (n,k,j,i), eta(n,j,i) is
   the height of the ocean surface, positive upwards, relative to
   ocean datum at gridpoint (n,j,i), s(k) is the dimensionless
   coordinate at vertical gridpoint (k), and depth(j,i) is the
   distance from ocean datum to sea floor (positive value) at
   horizontal gridpoint (j,i). The constants a, b, and depth_c control
   the stretching.

   The format for the formula_terms attribute is

   formula_terms = "s: var1 eta: var2 depth: var3 a: var4 b: var5
   depth_c: var6"
*/
int 
nccf_def_lvl_ocean_s(int ncid, const char *name, nc_type xtype, size_t len, 
		     int *lvl_dimidp, int *lvl_varidp)
{
   /* Create the coordinate variable and dimension with no
    * formula_terms. */
   return def_coord_var(ncid, name, len, xtype, NULL, CF_LEVEL_AXIS, 
			0, STD_OCEAN_S, NULL, NCCF_GEOZ, lvl_dimidp, 
			lvl_varidp);
}

int 
nccf_def_ft_ocean_s(int ncid, int varid, int eta_varid, int depth_varid, 
		    int a_varid, int b_varid, int depth_c_varid)
{
   int ft_varids[FT_OCEAN_S_TERMS];

   ft_varids[0] = varid;
   ft_varids[1] = eta_varid;
   ft_varids[2] = depth_varid;
   ft_varids[3] = a_varid;
   ft_varids[4] = b_varid;
   ft_varids[5] = depth_c_varid;
   return nccf_set_ft(ncid, varid, FT_OCEAN_S_TERMS, ft_varids, 
		      FT_OCEAN_S_FORMAT);
}

/* Inquire about atmosphere ocean_s coordinate. */
int 
nccf_inq_lvl_ocean_s(int ncid, char *name, nc_type *xtypep, size_t *lenp, 
		     int *eta_varidp, int *depth_varidp, int *a_varidp, int *b_varidp, 
		     int *depth_c_varidp, int *lvl_dimidp, int *lvl_varidp)
{
   int termids[FT_OCEAN_S_TERMS];
   int ret;

   /* Get most of the info from inq_vert_var. */
   if ((ret = inq_vert_var(ncid, STD_OCEAN_S, FT_OCEAN_S_TERMS, FT_OCEAN_S_FORMAT, 
			   termids, name, xtypep, lenp, lvl_dimidp, lvl_varidp)))
      return ret;

   /* Pull out the varids for ocean_s level coordinate formula terms. */
   if (eta_varidp)
      *eta_varidp = termids[1];
   if (depth_varidp)
      *depth_varidp = termids[2];
   if (a_varidp)
      *a_varidp = termids[3];
   if (b_varidp)
      *b_varidp = termids[4];
   if (depth_c_varidp)
      *depth_c_varidp = termids[5];

   return CF_NOERR;
}

/* Ocean sigma over z coordinate

   standard_name = "ocean_sigma_z_coordinate"

   for k <= nsigma:
   z(n,k,j,i) = eta(n,j,i) + sigma(k)*(min(depth_c,depth(j,i))+eta(n,j,i))
 
   for k > nsigma:
   z(n,k,j,i) = zlev(k)

   where z(n,k,j,i) is height, positive upwards, relative to ocean
   datum (e.g. mean sea level) at gridpoint (n,k,j,i), eta(n,j,i) is
   the height of the ocean surface, positive upwards, relative to
   ocean datum at gridpoint (n,j,i), sigma(k) is the dimensionless
   coordinate at vertical gridpoint (k) for k <= nsigma, and
   depth(j,i) is the distance from ocean datum to sea floor (positive
   value) at horizontal gridpoint (j,i). Above depth depth_c there are
   nsigma layers.

   The format for the formula_terms attribute is

   formula_terms = "sigma: var1 eta: var2 depth: var3 depth_c: var4
   nsigma: var5 zlev: var6"
*/
int 
nccf_def_lvl_ocean_sigma_z(int ncid, const char *name, nc_type xtype, size_t len, 
			   int *lvl_dimidp, int *lvl_varidp)
{
   /* Create the coordinate variable and dimension with no
    * formula_terms. */
   return def_coord_var(ncid, name, len, xtype, NULL, CF_LEVEL_AXIS, 
			0, STD_OCEAN_SIGMA_Z, NULL, NCCF_GEOZ, lvl_dimidp, 
			lvl_varidp);

}

int 
nccf_def_ft_ocean_sigma_z(int ncid, int varid, int eta_varid, int depth_varid, 
			  int depth_c_varid, int nsigma_varid, int zlev_varid)
{
   int ft_varids[FT_OCEAN_SIGMA_Z_TERMS];

   ft_varids[0] = varid;
   ft_varids[1] = eta_varid;
   ft_varids[2] = depth_varid;
   ft_varids[3] = depth_c_varid;
   ft_varids[4] = nsigma_varid;
   ft_varids[5] = zlev_varid;
   return nccf_set_ft(ncid, varid, FT_OCEAN_SIGMA_Z_TERMS, ft_varids, 
		      FT_OCEAN_SIGMA_Z_FORMAT);
}

/* Inquire about atmosphere ocean_sigma_z coordinate. */
int 
nccf_inq_lvl_ocean_sigma_z(int ncid, char *name, nc_type *xtypep, size_t *lenp, 
			   int *eta_varidp, int *depth_varidp, int *depth_c_varidp, 
			   int *nsigma_varidp, int *zlev_varidp, int *lvl_dimidp, 
			   int *lvl_varidp)
{
   int termids[FT_OCEAN_SIGMA_Z_TERMS];
   int ret;

   /* Get most of the info from inq_vert_var. */
   if ((ret = inq_vert_var(ncid, STD_OCEAN_SIGMA_Z, FT_OCEAN_SIGMA_Z_TERMS, 
			   FT_OCEAN_SIGMA_Z_FORMAT, termids, name, xtypep, 
			   lenp, lvl_dimidp, lvl_varidp)))
      return ret;

   /* Pull out the varids for ocean_sigma_z level coordinate formula terms. */
   if (eta_varidp)
      *eta_varidp = termids[1];
   if (depth_varidp)
      *depth_varidp = termids[2];
   if (depth_c_varidp)
      *depth_c_varidp = termids[3];
   if (nsigma_varidp)
      *nsigma_varidp = termids[4];
   if (zlev_varidp)
      *zlev_varidp = termids[5];

   return CF_NOERR;
}

/*
  Ocean double sigma coordinate

  standard_name = "ocean_double_sigma_coordinate"

  Definition:

  for k <= k_c

  z(k,j,i)= sigma(k)*f(j,i)

  for k > k_c

  z(k,j,i)= f(j,i) + (sigma(k)-1)*(depth(j,i)-f(j,i))

  f(j,i)= 0.5*(z1+ z2) + 0.5*(z1-z2)* tanh(2*a/(z1-z2)*(depth(j,i)-href))

  where z(k,j,i) is height, positive upwards, relative to ocean datum
  (e.g. mean sea level) at gridpoint (k,j,i), sigma(k) is the
  dimensionless coordinate at vertical gridpoint (k) for k <= k_c, and
  depth(j,i) is the distance from ocean datum to sea floor (positive
  value) at horizontal gridpoint (j,i).  z1, z2, a, and href are
  constants.

  The format for the formula_terms attribute is:

  formula_terms = "sigma: var1 depth: var2 z1: var3 z2: var4 a: var5 
  href: var6 k_c: var7"
*/

int 
nccf_def_lvl_ocean_dbl_sigma(int ncid, const char *name, nc_type xtype, size_t len, 
			     int *lvl_dimidp, int *lvl_varidp)
{
   /* Create the coordinate variable and dimension with no
    * formula_terms. */
   return def_coord_var(ncid, name, len, xtype, NULL, CF_LEVEL_AXIS, 
			0, STD_OCEAN_DBL_SIGMA, NULL, NCCF_GEOZ, lvl_dimidp, 
			lvl_varidp);
}

int 
nccf_def_ft_ocean_dbl_sigma(int ncid, int varid, int depth_varid, int z1_varid, 
			    int z2_varid, int a_varid, int href_varid, int k_c_varid)
{
   int ft_varids[FT_OCEAN_DBL_SIGMA_TERMS];

   ft_varids[0] = varid;
   ft_varids[1] = depth_varid;
   ft_varids[2] = z1_varid;
   ft_varids[3] = z2_varid;
   ft_varids[4] = a_varid;
   ft_varids[5] = href_varid;
   ft_varids[6] = k_c_varid;
   return nccf_set_ft(ncid, varid, FT_OCEAN_DBL_SIGMA_TERMS, ft_varids, 
		      FT_OCEAN_DBL_SIGMA_FORMAT);
}

/* Inquire about atmosphere ocean_dbl_sigma coordinate. */
int 
nccf_inq_lvl_ocean_dbl_sigma(int ncid, char *name, nc_type *xtypep, size_t *lenp, 
			     int *depth_varidp, int *z1_varidp, int *z2_varidp, 
			     int *a_varidp, int *href_varidp, int *k_c_varidp, 
			     int *lvl_dimidp, int *lvl_varidp)
{
   int termids[FT_OCEAN_DBL_SIGMA_TERMS];
   int ret;

   /* Get most of the info from inq_vert_var. */
   if ((ret = inq_vert_var(ncid, STD_OCEAN_DBL_SIGMA, FT_OCEAN_DBL_SIGMA_TERMS, 
			   FT_OCEAN_DBL_SIGMA_FORMAT, termids, name, xtypep, 
			   lenp, lvl_dimidp, lvl_varidp)))
      return ret;

   /* Pull out the varids for ocean_dbl_sigma level coordinate formula
    * terms. */
   if (depth_varidp)
      *depth_varidp = termids[1];
   if (z1_varidp)
      *z1_varidp = termids[2];
   if (z2_varidp)
      *z2_varidp = termids[3];
   if (a_varidp)
      *a_varidp = termids[4];
   if (href_varidp)
      *href_varidp = termids[5];
   if (k_c_varidp)
      *k_c_varidp = termids[6];

   return CF_NOERR;
}

