/* v5d.c */

/* Vis5D version 5.0 */

/*
   Vis5D system for visualizing five dimensional gridded data sets
   Copyright (C) 1990 - 1997 Bill Hibbard, Johan Kellum, Brian Paul,
   Dave Santek, and Andre Battaiola.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 1, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/* this should be updated when the file version changes */
#define FILE_VERSION "4.3"

/*
 * New grid file format for VIS-5D:
 *
 * The header is a list of tagged items.  Each item has 3 parts:
 *    1. A tag which is a 4-byte integer identifying the type of item.
 *    2. A 4-byte integer indicating how many bytes of data follow.
 *    3. The binary data.
 *
 * If we need to add new information to a file header we just create a
 * new tag and add the code to read/write the information.
 *
 * If we're reading a header and find an unknown tag, we can use the
 * length field to skip ahead to the next tag.  Therefore, the file
 * format is forward (and backward) compatible.
 *
 * Grid data is stored as either:
 *     1-byte unsigned integers  (255=missing)
 *     2-byte unsigned integers  (65535=missing)
 *     4-byte IEEE floats        ( >1.0e30 = missing)
 *
 * All numeric values are stored in big endian order.  All floating point
 * values are in IEEE format.
 */

/*
 * Updates:
 *
 * April 13, 1995, brianp
 *   finished Cray support for 2-byte and 4-byte compress modes
 */

#include <grass/config.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <grass/gis.h>
#include "binio.h"
#include "v5d.h"
#include "vis5d.h"

#ifndef SEEK_SET
#define SEEK_SET 0
#endif
#ifndef SEEK_CUR
#define SEEK_CUR 1
#endif
#ifndef SEEK_END
#define SEEK_END 2
#endif

/*
 * Currently defined tags:
 * Note:  the notation a[i] doesn't mean a is an array of i elements,
 * rather it just refers to the ith element of a[].
 *
 * Tags marked as PHASED OUT should be readable but are no longer written.
 * Old tag numbers can't be reused!
 *
 */

/*      TAG NAME        VALUE       DATA (comments)                     */

/*----------------------------------------------------------------------*/
#define TAG_ID              0x5635440a /* hex encoding of "V5D\n"          */

/* general stuff 1000+ */
#define TAG_VERSION         1000 /* char*10 FileVersion              */
#define TAG_NUMTIMES        1001 /* int*4 NumTimes                   */
#define TAG_NUMVARS         1002 /* int*4 NumVars                    */
#define TAG_VARNAME         1003 /* int*4 var; char*10 VarName[var]  */

#define TAG_NR              1004 /* int*4 Nr                         */
#define TAG_NC              1005 /* int*4 Nc                         */
#define TAG_NL              1006 /* int*4 Nl  (Nl for all vars)      */
#define TAG_NL_VAR          1007 /* int*4 var; int*4 Nl[var]         */
#define TAG_LOWLEV_VAR      1008 /* int*4 var; int*4 LowLev[var]     */

#define TAG_TIME            1010 /* int*4 t;  int*4 TimeStamp[t]     */
#define TAG_DATE            1011 /* int*4 t;  int*4 DateStamp[t]     */

#define TAG_MINVAL          1012 /* int*4 var;  real*4 MinVal[var]   */
#define TAG_MAXVAL          1013 /* int*4 var;  real*4 MaxVal[var]   */

#define TAG_COMPRESS        1014 /* int*4 CompressMode; (#bytes/grid) */

#define TAG_UNITS           1015 /* int *4 var; char*20 Units[var]   */

/* vertical coordinate system 2000+ */
#define TAG_VERTICAL_SYSTEM 2000 /* int*4 VerticalSystem             */
#define TAG_VERT_ARGS       2100 /* int*4 n;  real*4 VertArgs[0..n-1] */

#define TAG_BOTTOMBOUND     2001 /* real*4 BottomBound     (PHASED OUT)  */
#define TAG_LEVINC          2002 /* real*4 LevInc      (PHASED OUT)      */
#define TAG_HEIGHT          2003 /* int*4 l;  real*4 Height[l] (PHASED OUT) */

/* projection 3000+ */
#define TAG_PROJECTION      3000 /* int*4 projection:                    */
                                 /*   0 = generic linear                 */
                                 /*   1 = cylindrical equidistant        */
                                 /*   2 = Lambert conformal/Polar Stereo */
                                 /*   3 = rotated equidistant            */
#define TAG_PROJ_ARGS       3100 /* int *4 n;  real*4 ProjArgs[0..n-1]   */

#define TAG_NORTHBOUND      3001 /* real*4 NorthBound       (PHASED OUT) */
#define TAG_WESTBOUND       3002 /* real*4 WestBound        (PHASED OUT) */
#define TAG_ROWINC          3003 /* real*4 RowInc           (PHASED OUT) */
#define TAG_COLINC          3004 /* real*4 ColInc           (PHASED OUT) */
#define TAG_LAT1            3005 /* real*4 Lat1             (PHASED OUT) */
#define TAG_LAT2            3006 /* real*4 Lat2             (PHASED OUT) */
#define TAG_POLE_ROW        3007 /* real*4 PoleRow          (PHASED OUT) */
#define TAG_POLE_COL        3008 /* real*4 PoleCol          (PHASED OUT) */
#define TAG_CENTLON         3009 /* real*4 CentralLon       (PHASED OUT) */
#define TAG_CENTLAT         3010 /* real*4 CentralLat       (PHASED OUT) */
#define TAG_CENTROW         3011 /* real*4 CentralRow       (PHASED OUT) */
#define TAG_CENTCOL         3012 /* real*4 CentralCol       (PHASED OUT) */
#define TAG_ROTATION        3013 /* real*4 Rotation         (PHASED OUT) */

#define TAG_END             9999

/**********************************************************************/

/*****                  Miscellaneous Functions                   *****/

/**********************************************************************/

float pressure_to_height(float pressure)
{
    return (float)DEFAULT_LOG_EXP * log((double)pressure / DEFAULT_LOG_SCALE);
}

float height_to_pressure(float height)
{
    return (float)DEFAULT_LOG_SCALE * exp((double)height / DEFAULT_LOG_EXP);
}

/*
 * Return current file position.
 * Input:  f - file descriptor
 */
static off_t ltell(int f)
{
    return lseek(f, 0, SEEK_CUR);
}

/*
 * Copy up to maxlen characters from src to dst stopping upon whitespace
 * in src.  Terminate dst with null character.
 * Return:  length of dst.
 */
static int copy_string2(char *dst, const char *src, int maxlen)
{
    int i;

    for (i = 0; i < maxlen; i++)
        dst[i] = src[i];
    for (i = maxlen - 1; i >= 0; i--) {
        if (dst[i] == ' ' || i == maxlen - 1)
            dst[i] = 0;
        else
            break;
    }
    return strlen(dst);
}

/*
 * Copy up to maxlen characters from src to dst stopping upon whitespace
 * in src.  Terminate dst with null character.
 * Return:  length of dst.
 */
static int copy_string(char *dst, const char *src, int maxlen)
{
    int i;

    for (i = 0; i < maxlen; i++) {
        if (src[i] == ' ' || i == maxlen - 1) {
            dst[i] = 0;
            break;
        }
        else {
            dst[i] = src[i];
        }
    }
    return i;
}

/*
 * Convert a date from YYDDD format to days since Jan 1, 1900.
 */
int v5dYYDDDtoDays(int yyddd)
{
    int iy, id, idays;

    iy = yyddd / 1000;
    id = yyddd - 1000 * iy;
    if (iy < 50)
        iy += 100; /* WLH 31 July 96 << 31 Dec 99 */
    idays = 365 * iy + (iy - 1) / 4 + id;

    return idays;
}

/*
 * Convert a time from HHMMSS format to seconds since midnight.
 */
int v5dHHMMSStoSeconds(int hhmmss)
{
    int h, m, s;

    h = hhmmss / 10000;
    m = (hhmmss / 100) % 100;
    s = hhmmss % 100;

    return s + m * 60 + h * 60 * 60;
}

/*
 * Convert a day since Jan 1, 1900 to YYDDD format.
 */
int v5dDaysToYYDDD(int days)
{
    int iy, id, iyyddd;

    iy = (4 * days) / 1461;
    id = days - (365 * iy + (iy - 1) / 4);
    if (iy > 99)
        iy = iy - 100; /* WLH 31 July 96 << 31 Dec 99 */
    /* iy = iy + 1900; is the right way to fix this, but requires
       changing all places where dates are printed - procrastinate */
    iyyddd = iy * 1000 + id;

    return iyyddd;
}

/*
 * Convert a time in seconds since midnight to HHMMSS format.
 */
int v5dSecondsToHHMMSS(int seconds)
{
    int hh, mm, ss;

    hh = seconds / (60 * 60);
    mm = (seconds / 60) % 60;
    ss = seconds % 60;
    return hh * 10000 + mm * 100 + ss;
}

void v5dPrintStruct(const v5dstruct *v)
{
    static char day[7][10] = {"Sunday",   "Monday", "Tuesday", "Wednesday",
                              "Thursday", "Friday", "Saturday"};
    int time, var, i;
    int maxnl;

    maxnl = 0;
    for (var = 0; var < v->NumVars; var++) {
        if (v->Nl[var] + v->LowLev[var] > maxnl) {
            maxnl = v->Nl[var] + v->LowLev[var];
        }
    }

    if (v->FileFormat == 0) {
        if (v->FileVersion[0] == 0) {
            printf("File format: v5d  version: (4.0 or 4.1)\n");
        }
        else {
            printf("File format: v5d  version: %s\n", v->FileVersion);
        }
    }
    else {
        printf("File format: comp5d  (VIS-5D 3.3 or older)\n");
    }

    if (v->CompressMode == 1) {
        printf("Compression:  1 byte per gridpoint.\n");
    }
    else {
        printf("Compression:  %d bytes per gridpoint.\n", v->CompressMode);
    }
    printf("header size=%d\n", (int)v->FirstGridPos);
    printf("sizeof(v5dstruct)=%d\n", (int)sizeof(v5dstruct));
    printf("\n");

    printf("NumVars = %d\n", v->NumVars);

    printf("Var  Name       Units      Rows  Cols  Levels LowLev  MinVal       "
           "MaxVal\n");
    for (var = 0; var < v->NumVars; var++) {
        printf("%3d  %-10s %-10s %3d   %3d   %3d    %3d", var + 1,
               v->VarName[var], v->Units[var], v->Nr, v->Nc, v->Nl[var],
               v->LowLev[var]);
        if (v->MinVal[var] > v->MaxVal[var]) {
            printf("     MISSING      MISSING\n");
        }
        else {
            printf("     %-12g %-12g\n", v->MinVal[var], v->MaxVal[var]);
        }
    }

    printf("\n");

    printf("NumTimes = %d\n", v->NumTimes);
    printf("Step    Date(YYDDD)    Time(HH:MM:SS)   Day\n");
    for (time = 0; time < v->NumTimes; time++) {
        int i = v->TimeStamp[time];

        printf("%3d        %05d       %5d:%02d:%02d     %s\n", time + 1,
               v->DateStamp[time], i / 10000, (i / 100) % 100, i % 100,
               day[v5dYYDDDtoDays(v->DateStamp[time]) % 7]);
    }
    printf("\n");

    switch (v->VerticalSystem) {
    case 0:
        printf("Generic linear vertical coordinate system:\n");
        printf("\tBottom Bound: %f\n", v->VertArgs[0]);
        printf("\tIncrement between levels:  %f\n", v->VertArgs[1]);
        break;
    case 1:
        printf("Equally spaced levels in km:\n");
        printf("\tBottom Bound: %f\n", v->VertArgs[0]);
        printf("\tIncrement: %f\n", v->VertArgs[1]);
        break;
    case 2:
        printf("Unequally spaced levels in km:\n");
        printf("Level\tHeight(km)\n");
        for (i = 0; i < maxnl; i++) {
            printf("%3d     %10.3f\n", i + 1, v->VertArgs[i]);
        }
        break;
    case 3:
        printf("Unequally spaced levels in mb:\n");
        printf("Level\tPressure(mb)\n");
        for (i = 0; i < maxnl; i++) {
            printf("%3d     %10.3f\n", i + 1,
                   height_to_pressure(v->VertArgs[i]));
        }
        break;
    default:
        printf("Bad VerticalSystem value: %d\n", v->VerticalSystem);
    }
    printf("\n");

    switch (v->Projection) {
    case 0:
        printf("Generic linear projection:\n");
        printf("\tNorth Boundary: %f\n", v->ProjArgs[0]);
        printf("\tWest Boundary: %f\n", v->ProjArgs[1]);
        printf("\tRow Increment: %f\n", v->ProjArgs[2]);
        printf("\tColumn Increment: %f\n", v->ProjArgs[3]);
        break;
    case 1:
        printf("Cylindrical Equidistant projection:\n");
        printf("\tNorth Boundary: %f degrees\n", v->ProjArgs[0]);
        printf("\tWest Boundary: %f degrees\n", v->ProjArgs[1]);
        printf("\tRow Increment: %f degrees\n", v->ProjArgs[2]);
        printf("\tColumn Increment: %f degrees\n", v->ProjArgs[3]);
        /*
           printf("\tSouth Boundary: %f degrees\n",
           v->NorthBound - v->RowInc * (v->Nr-1) );
           printf("\tEast Boundary: %f degrees\n",
           v->WestBound - v->ColInc * (v->Nc-1) );
         */
        break;
    case 2:
        printf("Lambert Conformal projection:\n");
        printf("\tStandard Latitude 1: %f\n", v->ProjArgs[0]);
        printf("\tStandard Latitude 2: %f\n", v->ProjArgs[1]);
        printf("\tNorth/South Pole Row: %f\n", v->ProjArgs[2]);
        printf("\tNorth/South Pole Column: %f\n", v->ProjArgs[3]);
        printf("\tCentral Longitude: %f\n", v->ProjArgs[4]);
        printf("\tColumn Increment: %f km\n", v->ProjArgs[5]);
        break;
    case 3:
        printf("Stereographic:\n");
        printf("\tCenter Latitude: %f\n", v->ProjArgs[0]);
        printf("\tCenter Longitude: %f\n", v->ProjArgs[1]);
        printf("\tCenter Row: %f\n", v->ProjArgs[2]);
        printf("\tCenter Column: %f\n", v->ProjArgs[3]);
        printf("\tColumn Spacing: %f\n", v->ProjArgs[4]);
        break;
    case 4:
        /* WLH 4-21-95 */
        printf("Rotated equidistant projection:\n");
        printf("\tLatitude of grid(0,0): %f\n", v->ProjArgs[0]);
        printf("\tLongitude of grid(0,0): %f\n", v->ProjArgs[1]);
        printf("\tRow Increment: %f degress\n", v->ProjArgs[2]);
        printf("\tColumn Increment: %f degrees\n", v->ProjArgs[3]);
        printf("\tCenter Latitude: %f\n", v->ProjArgs[4]);
        printf("\tCenter Longitude: %f\n", v->ProjArgs[5]);
        printf("\tRotation: %f degrees\n", v->ProjArgs[6]);
        break;
    default:
        printf("Bad projection number: %d\n", v->Projection);
    }
}

/*
 * Compute the location of a compressed grid within a file.
 * Input:  v - pointer to v5dstruct describing the file header.
 *         time, var - which timestep and variable.
 * Return:  file offset in bytes
 */
static int grid_position(const v5dstruct *v, int time, int var)
{
    int i;
    off_t pos;

    assert(time >= 0);
    assert(var >= 0);
    assert(time < v->NumTimes);
    assert(var < v->NumVars);

    pos = v->FirstGridPos + time * v->SumGridSizes;
    for (i = 0; i < var; i++) {
        pos += v->GridSize[i];
    }

    return pos;
}

/*
 * Compute the ga and gb (de)compression values for a grid.
 * Input:  nr, nc, nl - size of grid
 *         data - the grid data
 *         ga, gb - arrays to store results.
 *         minval, maxval - pointer to floats to return min, max values
 *         compressmode - 1, 2 or 4 bytes per grid point
 * Output:  ga, gb - the (de)compression values
 *          minval, maxval - the min and max grid values
 * Side effect:  the MinVal[var] and MaxVal[var] fields in g may be
 *               updated with new values.
 */
static void compute_ga_gb(int nr, int nc, int nl, const float data[],
                          int compressmode, float ga[], float gb[],
                          float *minval, float *maxval)
{
#ifdef SIMPLE_COMPRESSION
    /*
     * Compute ga, gb values for whole grid.
     */
    int i, lev, allmissing, num;
    float min, max, a, b;

    min = 1.0e30;
    max = -1.0e30;
    num = nr * nc * nl;
    allmissing = 1;
    for (i = 0; i < num; i++) {
        if (!IS_MISSING(data[i])) {
            if (data[i] < min)
                min = data[i];
            if (data[i] > max)
                max = data[i];
            allmissing = 0;
        }
    }
    if (allmissing) {
        a = 1.0;
        b = 0.0;
    }
    else {
        a = (max - min) / 254.0;
        b = min;
    }

    /* return results */
    for (i = 0; i < nl; i++) {
        ga[i] = a;
        gb[i] = b;
    }

    *minval = min;
    *maxval = max;
#else
    /*
     * Compress grid on level-by-level basis.
     */
#define SMALLVALUE -1.0e30
#define BIGVALUE   1.0e30
#define ABS(x)     (((x) < 0.0) ? -(x) : (x))
    float gridmin, gridmax;
    float levmin[MAXLEVELS], levmax[MAXLEVELS];
    float d[MAXLEVELS], dmax;
    float ival, mval;
    int j, k, lev, nrnc;

    nrnc = nr * nc;

    /* find min and max for each layer and the whole grid */
    gridmin = BIGVALUE;
    gridmax = SMALLVALUE;
    j = 0;
    for (lev = 0; lev < nl; lev++) {
        float min, max;

        min = BIGVALUE;
        max = SMALLVALUE;
        for (k = 0; k < nrnc; k++) {
            if (!IS_MISSING(data[j]) && data[j] < min)
                min = data[j];
            if (!IS_MISSING(data[j]) && data[j] > max)
                max = data[j];
            j++;
        }
        if (min < gridmin)
            gridmin = min;
        if (max > gridmax)
            gridmax = max;
        levmin[lev] = min;
        levmax[lev] = max;
    }

    /* WLH 2-2-95 */
#ifdef KLUDGE
    /* if the grid minimum is within delt of 0.0, fudge all values */
    /* within delt of 0.0 to delt, and recalculate mins and maxes */
    {
        float delt;
        int nrncnl = nrnc * nl;

        delt = (gridmax - gridmin) / 100000.0;
        if (ABS(gridmin) < delt && gridmin != 0.0 && compressmode != 4) {
            float min, max;

            for (j = 0; j < nrncnl; j++) {
                if (!IS_MISSING(data[j]) && data[j] < delt)
                    data[j] = delt;
            }
            /* re-calculate min and max for each layer and the whole grid */
            gridmin = delt;
            for (lev = 0; lev < nl; lev++) {
                if (ABS(levmin[lev]) < delt)
                    levmin[lev] = delt;
                if (ABS(levmax[lev]) < delt)
                    levmax[lev] = delt;
            }
        }
    }
#endif

    /* find d[lev] and dmax = MAX( d[0], d[1], ... d[nl-1] ) */
    dmax = 0.0;
    for (lev = 0; lev < nl; lev++) {
        if (levmin[lev] >= BIGVALUE && levmax[lev] <= SMALLVALUE) {
            /* all values in the layer are MISSING */
            d[lev] = 0.0;
        }
        else {
            d[lev] = levmax[lev] - levmin[lev];
        }
        if (d[lev] > dmax)
            dmax = d[lev];
    }

    /*** Compute ga (scale) and gb (bias) for each grid level */
    if (dmax == 0.0) {

        /*** Special cases ***/
        if (gridmin == gridmax) {

            /*** whole grid is of same value ***/
            for (lev = 0; lev < nl; lev++) {
                ga[lev] = gridmin;
                gb[lev] = 0.0;
            }
        }
        else {

            /*** every layer is of a single value ***/
            for (lev = 0; lev < nl; lev++) {
                ga[lev] = levmin[lev];
                gb[lev] = 0.0;
            }
        }
    }
    else {

        /*** Normal cases ***/
        if (compressmode == 1) {
#define ORIGINAL
#ifdef ORIGINAL
            ival = dmax / 254.0;
            mval = gridmin;
            for (lev = 0; lev < nl; lev++) {
                ga[lev] = ival;
                gb[lev] = mval + ival * (int)((levmin[lev] - mval) / ival);
            }
#else
            for (lev = 0; lev < nl; lev++) {
                if (d[lev] == 0.0) {
                    ival = 1.0;
                }
                else {
                    ival = d[lev] / 254.0;
                }
                ga[lev] = ival;
                gb[lev] = levmin[lev];
            }
#endif
        }
        else if (compressmode == 2) {
            ival = dmax / 65534.0;
            mval = gridmin;
            for (lev = 0; lev < nl; lev++) {
                ga[lev] = ival;
                gb[lev] = mval + ival * (int)((levmin[lev] - mval) / ival);
            }
        }
        else {
            assert(compressmode == 4);
            for (lev = 0; lev < nl; lev++) {
                ga[lev] = 1.0;
                gb[lev] = 0.0;
            }
        }
    }

    /* update min, max values */
    *minval = gridmin;
    *maxval = gridmax;
#endif
}

/*
 * Compress a 3-D grid from floats to 1-byte unsigned integers.
 * Input: nr, nc, nl - size of grid
 *        compressmode - 1, 2 or 4 bytes per grid point
 *        data - array of [nr*nc*nl] floats
 *        compdata - pointer to array of [nr*nc*nl*compressmode] bytes
 *                   to put results into.
 *        ga, gb - pointer to arrays to put ga and gb decompression values
 *        minval, maxval - pointers to float to return min & max values
 * Output:  compdata - the compressed grid data
 *          ga, gb - the decompression values
 *          minval, maxval - the min and max grid values
 */
void v5dCompressGrid(int nr, int nc, int nl, int compressmode,
                     const float data[], void *compdata, float ga[], float gb[],
                     float *minval, float *maxval)
{
    int nrnc = nr * nc;
    int nrncnl = nr * nc * nl;
    V5Dubyte *compdata1 = (V5Dubyte *)compdata;
    V5Dushort *compdata2 = (V5Dushort *)compdata;

    /* compute ga, gb values */
    compute_ga_gb(nr, nc, nl, data, compressmode, ga, gb, minval, maxval);

    /* compress the data */
    if (compressmode == 1) {
        int i, lev, p;

        p = 0;
        for (lev = 0; lev < nl; lev++) {
            float one_over_a, b;

            b = gb[lev] - 0.0001; /* subtract an epsilon so the int((d-b)/a) */
            /* expr below doesn't get mis-truncated. */
            if (ga[lev] == 0.0) {
                one_over_a = 1.0;
            }
            else {
                one_over_a = 1.0 / ga[lev];
            }
            for (i = 0; i < nrnc; i++, p++) {
                if (IS_MISSING(data[p])) {
                    compdata1[p] = 255;
                }
                else {
                    compdata1[p] = (V5Dubyte)(int)((data[p] - b) * one_over_a);
                    if (compdata1[p] >= 255) {
                        compdata1[p] = (V5Dubyte)(int)(255.0 - .0001);
                    }
                }
            }
        }
    }

    else if (compressmode == 2) {
        int i, lev, p;

        p = 0;
        for (lev = 0; lev < nl; lev++) {
            float one_over_a, b;

            b = gb[lev] - 0.0001;
            if (ga[lev] == 0.0) {
                one_over_a = 1.0;
            }
            else {
                one_over_a = 1.0 / ga[lev];
            }
#ifdef _CRAY
            /* this is tricky because sizeof(V5Dushort)==8, not 2 */
            for (i = 0; i < nrnc; i++, p++) {
                V5Dushort compvalue;

                if (IS_MISSING(data[p])) {
                    compvalue = 65535;
                }
                else {
                    compvalue = (V5Dushort)(int)((data[p] - b) * one_over_a);
                }
                compdata1[p * 2 + 0] = compvalue >> 8;    /* upper byte */
                compdata1[p * 2 + 1] = compvalue & 0xffu; /* lower byte */
            }
#else
            for (i = 0; i < nrnc; i++, p++) {
                if (IS_MISSING(data[p])) {
                    compdata2[p] = 65535;
                }
                else {
                    compdata2[p] = (V5Dushort)(int)((data[p] - b) * one_over_a);
                }
            }
            /* TODO: byte-swapping on little endian??? */
#endif
        }
    }

    else {
        /* compressmode==4 */
#ifdef _CRAY
        cray_to_ieee_array(compdata, data, nrncnl);
#else
        /* other machines: just copy 4-byte IEEE floats */
        assert(sizeof(float) == 4);
        memcpy(compdata, data, nrncnl * 4);
        /* TODO: byte-swapping on little endian??? */
#endif
    }
}

/*
 * Decompress a 3-D grid from 1-byte integers to 4-byte floats.
 * Input:  nr, nc, nl - size of grid
 *         compdata - array of [nr*nr*nl*compressmode] bytes
 *         ga, gb - arrays of decompression factors
 *         compressmode - 1, 2 or 4 bytes per grid point
 *         data - address to put decompressed values
 * Output:  data - uncompressed floating point data values
 */
void v5dDecompressGrid(int nr, int nc, int nl, int compressmode, void *compdata,
                       float ga[], float gb[], float data[])
{
    int nrnc = nr * nc;
    int nrncnl = nr * nc * nl;
    V5Dubyte *compdata1 = (V5Dubyte *)compdata;
    V5Dushort *compdata2 = (V5Dushort *)compdata;

    if (compressmode == 1) {
        int p, i, lev;

        p = 0;
        for (lev = 0; lev < nl; lev++) {
            float a = ga[lev];
            float b = gb[lev];

            /* WLH 2-2-95 */
            float d, aa;
            int id;

            if (a > 0.0000000001) {
                d = b / a;
                id = floor(d);
                d = d - id;
                aa = a * 0.000001;
            }
            else {
                id = 1;
            }
            if (-254 <= id && id <= 0 && d < aa) {
                for (i = 0; i < nrnc; i++, p++) {
                    if (compdata1[p] == 255) {
                        data[p] = MISSING;
                    }
                    else {
                        data[p] = (float)(int)compdata1[p] * a + b;
                        if (fabs(data[p]) < aa)
                            data[p] = aa;
                    }
                }
            }
            else {
                for (i = 0; i < nrnc; i++, p++) {
                    if (compdata1[p] == 255) {
                        data[p] = MISSING;
                    }
                    else {
                        data[p] = (float)(int)compdata1[p] * a + b;
                    }
                }
            }
            /* end of WLH 2-2-95 */
        }
    }

    else if (compressmode == 2) {
        int p, i, lev;

        p = 0;
        for (lev = 0; lev < nl; lev++) {
            float a = ga[lev];
            float b = gb[lev];

#ifdef _CRAY
            /* this is tricky because sizeof(V5Dushort)==8, not 2 */
            for (i = 0; i < nrnc; i++, p++) {
                int compvalue;

                compvalue = (compdata1[p * 2] << 8) | compdata1[p * 2 + 1];
                if (compvalue == 65535) {
                    data[p] = MISSING;
                }
                else {
                    data[p] = (float)compvalue * a + b;
                }
            }
#else
            /* sizeof(V5Dushort)==2! */
            for (i = 0; i < nrnc; i++, p++) {
                if (compdata2[p] == 65535) {
                    data[p] = MISSING;
                }
                else {
                    data[p] = (float)(int)compdata2[p] * a + b;
                }
            }
#endif
        }
    }

    else {
        /* compressmode==4 */
#ifdef _CRAY
        ieee_to_cray_array(data, compdata, nrncnl);
#else
        /* other machines: just copy 4-byte IEEE floats */
        assert(sizeof(float) == 4);
        memcpy(data, compdata, nrncnl * 4);
#endif
    }
}

/*
 * Return the size (in bytes) of the 3-D grid specified by time and var.
 * Input:  v - pointer to v5dstruct describing the file
 *         time, var - which timestep and variable
 * Return:  number of data points.
 */
int v5dSizeofGrid(const v5dstruct *v, int time UNUSED, int var)
{
    return v->Nr * v->Nc * v->Nl[var] * v->CompressMode;
}

/*
 * Initialize a v5dstructure to reasonable initial values.
 * Input:  v - pointer to v5dstruct.
 */
void v5dInitStruct(v5dstruct *v)
{
    int i;

    /* set everything to zero */
    memset(v, 0, sizeof(v5dstruct));

    /* special cases */
    v->Projection = -1;
    v->VerticalSystem = -1;

    for (i = 0; i < MAXVARS; i++) {
        v->MinVal[i] = MISSING;
        v->MaxVal[i] = -MISSING;
        v->LowLev[i] = 0;
    }

    /* set file version */
    strcpy(v->FileVersion, FILE_VERSION);

    v->CompressMode = 1;
    v->FileDesc = -1;
}

/*
 * Return a pointer to a new, initialized v5dstruct.
 */
v5dstruct *v5dNewStruct(void)
{
    v5dstruct *v;

    v = (v5dstruct *)G_malloc(sizeof(v5dstruct));
    if (v) {
        v5dInitStruct(v);
    }
    return v;
}

/*
 * Free an initialized v5dstruct. (Todd Plessel)
 */
void v5dFreeStruct(v5dstruct *v)
{
    /*assert( v5dVerifyStruct( v ) ); */
    G_free(v);
    v = 0;
}

/*
 * Do some checking that the information in a v5dstruct is valid.
 * Input:  v - pointer to v5dstruct
 * Return:  1 = g is ok, 0 = g is invalid
 */
int v5dVerifyStruct(const v5dstruct *v)
{
    int var, i, invalid, maxnl;

    invalid = 0;

    if (!v)
        return 0;

    /* Number of variables */
    if (v->NumVars < 0) {
        printf("Invalid number of variables: %d\n", v->NumVars);
        invalid = 1;
    }
    else if (v->NumVars > MAXVARS) {
        printf("Too many variables: %d  (Maximum is %d)\n", v->NumVars,
               MAXVARS);
        invalid = 1;
    }

    /* Variable Names */
    for (i = 0; i < v->NumVars; i++) {
        if (v->VarName[i][0] == 0) {
            printf("Missing variable name: VarName[%d]=\"\"\n", i);
            invalid = 1;
        }
    }

    /* Number of timesteps */
    if (v->NumTimes < 0) {
        printf("Invalid number of timesteps: %d\n", v->NumTimes);
        invalid = 1;
    }
    else if (v->NumTimes > MAXTIMES) {
        printf("Too many timesteps: %d  (Maximum is %d)\n", v->NumTimes,
               MAXTIMES);
        invalid = 1;
    }

    /* Make sure timestamps are increasing */
    for (i = 1; i < v->NumTimes; i++) {
        int date0 = v5dYYDDDtoDays(v->DateStamp[i - 1]);
        int date1 = v5dYYDDDtoDays(v->DateStamp[i]);
        int time0 = v5dHHMMSStoSeconds(v->TimeStamp[i - 1]);
        int time1 = v5dHHMMSStoSeconds(v->TimeStamp[i]);

        if (time1 <= time0 && date1 <= date0) {
            printf("Timestamp for step %d must be later than step %d\n", i,
                   i - 1);
            invalid = 1;
        }
    }

    /* Rows */
    if (v->Nr < 2) {
        printf("Too few rows: %d (2 is minimum)\n", v->Nr);
        invalid = 1;
    }
    else if (v->Nr > MAXROWS) {
        printf("Too many rows: %d (%d is maximum)\n", v->Nr, MAXROWS);
        invalid = 1;
    }

    /* Columns */
    if (v->Nc < 2) {
        printf("Too few columns: %d (2 is minimum)\n", v->Nc);
        invalid = 1;
    }
    else if (v->Nc > MAXCOLUMNS) {
        printf("Too many columns: %d (%d is maximum)\n", v->Nc, MAXCOLUMNS);
        invalid = 1;
    }

    /* Levels */
    maxnl = 0;
    for (var = 0; var < v->NumVars; var++) {
        if (v->LowLev[var] < 0) {
            printf("Low level cannot be negative for var %s: %d\n",
                   v->VarName[var], v->LowLev[var]);
            invalid = 1;
        }
        if (v->Nl[var] < 1) {
            printf("Too few levels for var %s: %d (1 is minimum)\n",
                   v->VarName[var], v->Nl[var]);
            invalid = 1;
        }
        if (v->Nl[var] + v->LowLev[var] > MAXLEVELS) {
            printf("Too many levels for var %s: %d (%d is maximum)\n",
                   v->VarName[var], v->Nl[var] + v->LowLev[var], MAXLEVELS);
            invalid = 1;
        }
        if (v->Nl[var] + v->LowLev[var] > maxnl) {
            maxnl = v->Nl[var] + v->LowLev[var];
        }
    }

    if (v->CompressMode != 1 && v->CompressMode != 2 && v->CompressMode != 4) {
        printf("Bad CompressMode: %d (must be 1, 2 or 4)\n", v->CompressMode);
        invalid = 1;
    }

    switch (v->VerticalSystem) {
    case 0:
    case 1:
        if (v->VertArgs[1] == 0.0) {
            printf("Vertical level increment is zero, must be non-zero\n");
            invalid = 1;
        }
        break;
    case 2:
        /* Check that Height values increase upward */
        for (i = 1; i < maxnl; i++) {
            if (v->VertArgs[i] <= v->VertArgs[i - 1]) {
                printf("Height[%d]=%f <= Height[%d]=%f, level heights must "
                       "increase\n",
                       i, v->VertArgs[i], i - 1, v->VertArgs[i - 1]);
                invalid = 1;
                break;
            }
        }
        break;
    case 3:
        /* Check that Pressure values decrease upward */
        for (i = 1; i < maxnl; i++) {
            if (v->VertArgs[i] <= v->VertArgs[i - 1]) {
                printf("Pressure[%d]=%f >= Pressure[%d]=%f, level pressures "
                       "must decrease\n",
                       i, height_to_pressure(v->VertArgs[i]), i - 1,
                       height_to_pressure(v->VertArgs[i - 1]));
                invalid = 1;
                break;
            }
        }
        break;
    default:
        printf("VerticalSystem = %d, must be in 0..3\n", v->VerticalSystem);
        invalid = 1;
    }

    switch (v->Projection) {
    case 0: /* Generic */
        if (v->ProjArgs[2] == 0.0) {
            printf("Row Increment (ProjArgs[2]) can't be zero\n");
            invalid = 1;
        }
        if (v->ProjArgs[3] == 0.0) {
            printf("Column increment (ProjArgs[3]) can't be zero\n");
            invalid = 1;
        }
        break;
    case 1: /* Cylindrical equidistant */
        if (v->ProjArgs[2] < 0.0) {
            printf("Row Increment (ProjArgs[2]) = %g  (must be >=0.0)\n",
                   v->ProjArgs[2]);
            invalid = 1;
        }
        if (v->ProjArgs[3] <= 0.0) {
            printf("Column Increment (ProjArgs[3]) = %g  (must be >=0.0)\n",
                   v->ProjArgs[3]);
            invalid = 1;
        }
        break;
    case 2: /* Lambert Conformal */
        if (v->ProjArgs[0] < -90.0 || v->ProjArgs[0] > 90.0) {
            printf("Lat1 (ProjArgs[0]) out of range: %g\n", v->ProjArgs[0]);
            invalid = 1;
        }
        if (v->ProjArgs[1] < -90.0 || v->ProjArgs[1] > 90.0) {
            printf("Lat2 (ProjArgs[1] out of range: %g\n", v->ProjArgs[1]);
            invalid = 1;
        }
        if (v->ProjArgs[5] <= 0.0) {
            printf("ColInc (ProjArgs[5]) = %g  (must be >=0.0)\n",
                   v->ProjArgs[5]);
            invalid = 1;
        }
        break;
    case 3: /* Stereographic */
        if (v->ProjArgs[0] < -90.0 || v->ProjArgs[0] > 90.0) {
            printf("Central Latitude (ProjArgs[0]) out of range: ");
            printf("%g  (must be in +/-90)\n", v->ProjArgs[0]);
            invalid = 1;
        }
        if (v->ProjArgs[1] < -180.0 || v->ProjArgs[1] > 180.0) {
            printf("Central Longitude (ProjArgs[1]) out of range: ");
            printf("%g  (must be in +/-180)\n", v->ProjArgs[1]);
            invalid = 1;
        }
        if (v->ProjArgs[4] < 0) {
            printf("Column spacing (ProjArgs[4]) = %g  (must be positive)\n",
                   v->ProjArgs[4]);
            invalid = 1;
        }
        break;
    case 4: /* Rotated */
        /* WLH 4-21-95 */
        if (v->ProjArgs[2] <= 0.0) {
            printf("Row Increment (ProjArgs[2]) = %g  (must be >=0.0)\n",
                   v->ProjArgs[2]);
            invalid = 1;
        }
        if (v->ProjArgs[3] <= 0.0) {
            printf("Column Increment = (ProjArgs[3]) %g  (must be >=0.0)\n",
                   v->ProjArgs[3]);
            invalid = 1;
        }
        if (v->ProjArgs[4] < -90.0 || v->ProjArgs[4] > 90.0) {
            printf("Central Latitude (ProjArgs[4]) out of range: ");
            printf("%g  (must be in +/-90)\n", v->ProjArgs[4]);
            invalid = 1;
        }
        if (v->ProjArgs[5] < -180.0 || v->ProjArgs[5] > 180.0) {
            printf("Central Longitude (ProjArgs[5]) out of range: ");
            printf("%g  (must be in +/-180)\n", v->ProjArgs[5]);
            invalid = 1;
        }
        if (v->ProjArgs[6] < -180.0 || v->ProjArgs[6] > 180.0) {
            printf("Central Longitude (ProjArgs[6]) out of range: ");
            printf("%g  (must be in +/-180)\n", v->ProjArgs[6]);
            invalid = 1;
        }
        break;
    default:
        printf("Projection = %d, must be in 0..4\n", v->Projection);
        invalid = 1;
    }

    return !invalid;
}

/*
 * Get the McIDAS file number and grid number associated with the grid
 * identified by time and var.
 * Input:  v - v5d grid struct
 *         time, var - timestep and variable of grid
 * Output:  mcfile, mcgrid - McIDAS grid file number and grid number
 */
int v5dGetMcIDASgrid(v5dstruct *v, int time, int var, int *mcfile, int *mcgrid)
{
    if (time < 0 || time >= v->NumTimes) {
        printf("Bad time argument to v5dGetMcIDASgrid: %d\n", time);
        return 0;
    }
    if (var < 0 || var >= v->NumVars) {
        printf("Bad var argument to v5dGetMcIDASgrid: %d\n", var);
        return 0;
    }

    *mcfile = (int)v->McFile[time][var];
    *mcgrid = (int)v->McGrid[time][var];
    return 1;
}

/*
 * Set the McIDAS file number and grid number associated with the grid
 * identified by time and var.
 * Input:  v - v5d grid struct
 *         time, var - timestep and variable of grid
 *         mcfile, mcgrid - McIDAS grid file number and grid number
 * Return:  1 = ok, 0 = error (bad time or var)
 */
int v5dSetMcIDASgrid(v5dstruct *v, int time, int var, int mcfile, int mcgrid)
{
    if (time < 0 || time >= v->NumTimes) {
        printf("Bad time argument to v5dSetMcIDASgrid: %d\n", time);
        return 0;
    }
    if (var < 0 || var >= v->NumVars) {
        printf("Bad var argument to v5dSetMcIDASgrid: %d\n", var);
        return 0;
    }

    v->McFile[time][var] = (short)mcfile;
    v->McGrid[time][var] = (short)mcgrid;
    return 1;
}

/**********************************************************************/

/*****                    Input Functions                         *****/

/**********************************************************************/

/*
 * Read the header from a COMP* file and return results in the v5dstruct.
 * Input:  f - the file descriptor
 *         v - pointer to a v5dstruct.
 * Return:  1 = ok, 0 = error.
 */
static int read_comp_header(int f, v5dstruct *v)
{
    unsigned int id;

    /* reset file position to start of file */
    lseek(f, 0, SEEK_SET);

    /* read file ID */
    read_int4(f, (int *)&id);

    if (id == 0x80808080 || id == 0x80808081) {
        /* Older COMP5D format */
        int gridtimes, gridparms;
        int i, j, it, iv, nl;
        off_t gridsize;
        float hgttop, hgtinc;

        /*char *compgrid; */

        if (id == 0x80808080) {
            /* 20 vars, 300 times */
            gridtimes = 300;
            gridparms = 20;
        }
        else {
            /* 30 vars, 400 times */
            gridtimes = 400;
            gridparms = 30;
        }

        v->FirstGridPos = 12 * 4 + 8 * gridtimes + 4 * gridparms;

        read_int4(f, &v->NumTimes);
        read_int4(f, &v->NumVars);
        read_int4(f, &v->Nr);
        read_int4(f, &v->Nc);
        read_int4(f, &nl);
        for (i = 0; i < v->NumVars; i++) {
            v->Nl[i] = nl;
            v->LowLev[i] = 0;
        }
        read_float4(f, &v->ProjArgs[0]);
        read_float4(f, &v->ProjArgs[1]);
        read_float4(f, &hgttop);
        read_float4(f, &v->ProjArgs[2]);
        read_float4(f, &v->ProjArgs[3]);
        read_float4(f, &hgtinc);
        /*
           for (i=0;i<nl;i++) {
           v->Height[nl-i-1] = hgttop - i * hgtinc;
           }
         */
        v->VerticalSystem = 1;
        v->VertArgs[0] = hgttop - hgtinc * (nl - 1);
        v->VertArgs[1] = hgtinc;

        /* read dates and times */
        for (i = 0; i < gridtimes; i++) {
            read_int4(f, &j);
            v->DateStamp[i] = v5dDaysToYYDDD(j);
        }
        for (i = 0; i < gridtimes; i++) {
            read_int4(f, &j);
            v->TimeStamp[i] = v5dSecondsToHHMMSS(j);
        }

        /* read variable names */
        for (i = 0; i < gridparms; i++) {
            char name[4];

            read_bytes(f, name, 4);
            /* remove trailing spaces, if any */
            for (j = 3; j > 0; j--) {
                if (name[j] == ' ' || name[j] == 0)
                    name[j] = 0;
                else
                    break;
            }
            strncpy(v->VarName[i], name, 4);
            v->VarName[i][4] = 0;
        }

        gridsize = (((off_t)v->Nr * v->Nc * nl + 3) / 4) * 4;
        for (i = 0; i < v->NumVars; i++) {
            v->GridSize[i] = 8 + gridsize;
        }
        v->SumGridSizes = (8 + gridsize) * v->NumVars;

        /* read the grids and their ga,gb values to find min and max values */

        for (i = 0; i < v->NumVars; i++) {
            v->MinVal[i] = 999999.9;
            v->MaxVal[i] = -999999.9;
        }

        /*compgrid = (char *) G_malloc ( gridsize ); */

        for (it = 0; it < v->NumTimes; it++) {
            for (iv = 0; iv < v->NumVars; iv++) {
                float ga, gb;
                float min, max;

                read_float4(f, &ga);
                read_float4(f, &gb);

                /* skip ahead by 'gridsize' bytes */
                if (lseek(f, gridsize, SEEK_CUR) == -1) {
                    printf("Error:  Unexpected end of file, ");
                    printf("file may be corrupted.\n");
                    return 0;
                }
                min = -(125.0 + gb) / ga;
                max = (125.0 - gb) / ga;
                if (min < v->MinVal[iv])
                    v->MinVal[iv] = min;
                if (max > v->MaxVal[iv])
                    v->MaxVal[iv] = max;
            }
        }

        /*G_free ( compgrid ); */

        /* done */
    }
    else if (id == 0x80808082 || id == 0x80808083) {
        /* Newer COMP5D format */
        int gridtimes;
        off_t gridsize;
        int it, iv, nl, i, j;
        float delta;

        read_int4(f, &gridtimes);
        read_int4(f, &v->NumVars);
        read_int4(f, &v->NumTimes);
        read_int4(f, &v->Nr);
        read_int4(f, &v->Nc);
        read_int4(f, &nl);
        for (i = 0; i < v->NumVars; i++) {
            v->Nl[i] = nl;
        }

        read_float4(f, &v->ProjArgs[2]);
        read_float4(f, &v->ProjArgs[3]);

        /* Read height and determine if equal spacing */
        v->VerticalSystem = 1;
        for (i = 0; i < nl; i++) {
            read_float4(f, &v->VertArgs[i]);
            if (i == 1) {
                delta = v->VertArgs[1] - v->VertArgs[0];
            }
            else if (i > 1) {
                if (delta != (v->VertArgs[i] - v->VertArgs[i - 1])) {
                    v->VerticalSystem = 2;
                }
            }
        }
        if (v->VerticalSystem == 1) {
            v->VertArgs[1] = delta;
        }

        /* read variable names */
        for (iv = 0; iv < v->NumVars; iv++) {
            char name[8];

            read_bytes(f, name, 8);

            /* remove trailing spaces, if any */
            for (j = 7; j > 0; j--) {
                if (name[j] == ' ' || name[j] == 0)
                    name[j] = 0;
                else
                    break;
            }
            strncpy(v->VarName[iv], name, 8);
            v->VarName[iv][8] = 0;
        }

        for (iv = 0; iv < v->NumVars; iv++) {
            read_float4(f, &v->MinVal[iv]);
        }
        for (iv = 0; iv < v->NumVars; iv++) {
            read_float4(f, &v->MaxVal[iv]);
        }
        for (it = 0; it < gridtimes; it++) {
            read_int4(f, &j);
            v->TimeStamp[it] = v5dSecondsToHHMMSS(j);
        }
        for (it = 0; it < gridtimes; it++) {
            read_int4(f, &j);
            v->DateStamp[it] = v5dDaysToYYDDD(j);
        }
        for (it = 0; it < gridtimes; it++) {
            float nlat;

            read_float4(f, &nlat);
            if (it == 0)
                v->ProjArgs[0] = nlat;
        }
        for (it = 0; it < gridtimes; it++) {
            float wlon;

            read_float4(f, &wlon);
            if (it == 0)
                v->ProjArgs[1] = wlon;
        }

        /* calculate grid storage sizes */
        if (id == 0x80808082) {
            gridsize = nl * 2 * 4 + (((off_t)v->Nr * v->Nc * nl + 3) / 4) * 4;
        }
        else {
            /* McIDAS grid and file numbers present */
            gridsize = 8 + nl * 2 * 4 + ((v->Nr * v->Nc * nl + 3) / 4) * 4;
        }
        for (i = 0; i < v->NumVars; i++) {
            v->GridSize[i] = gridsize;
        }
        v->SumGridSizes = gridsize * v->NumVars;

        /* read McIDAS numbers??? */

        /* size (in bytes) of all header info */
        v->FirstGridPos =
            9 * 4 + v->Nl[0] * 4 + v->NumVars * 16 + gridtimes * 16;
    }

    v->CompressMode = 1; /* one byte per grid point */
    v->Projection = 1;   /* Cylindrical equidistant */
    v->FileVersion[0] = 0;

    return 1;
}

/*
 * Read a compressed grid from a COMP* file.
 * Return:  1 = ok, 0 = error.
 */
static int read_comp_grid(v5dstruct *v, int time, int var, float *ga, float *gb,
                          void *compdata)
{
    unsigned int pos;
    V5Dubyte bias;
    int i, n, nl;
    int f;
    V5Dubyte *compdata1 = (V5Dubyte *)compdata;

    f = v->FileDesc;

    /* move to position in file */
    pos = grid_position(v, time, var);
    lseek(f, pos, SEEK_SET);

    if (v->FileFormat == 0x80808083) {
        /* read McIDAS grid and file numbers */
        int mcfile, mcgrid;

        read_int4(f, &mcfile);
        read_int4(f, &mcgrid);
        v->McFile[time][var] = (short)mcfile;
        v->McGrid[time][var] = (short)mcgrid;
    }

    nl = v->Nl[var];

    if (v->FileFormat == 0x80808080 || v->FileFormat == 0x80808081) {
        /* single ga,gb pair for whole grid */
        float a, b;

        read_float4(f, &a);
        read_float4(f, &b);
        /* convert a, b to new v5d ga, gb values */
        for (i = 0; i < nl; i++) {
            if (a == 0.0) {
                ga[i] = gb[i] = 0.0;
            }
            else {
                gb[i] = (b + 128.0) / -a;
                ga[i] = 1.0 / a;
            }
        }
        bias = 128;
    }
    else {
        /* read ga, gb arrays */
        read_float4_array(f, ga, v->Nl[var]);
        read_float4_array(f, gb, v->Nl[var]);

        /* convert ga, gb values to v5d system */
        for (i = 0; i < nl; i++) {
            if (ga[i] == 0.0) {
                ga[i] = gb[i] = 0.0;
            }
            else {
                /*gb[i] = (gb[i]+125.0) / -ga[i]; */
                gb[i] = (gb[i] + 128.0) / -ga[i];
                ga[i] = 1.0 / ga[i];
            }
        }
        bias = 128; /* 125 ??? */
    }

    /* read compressed grid data */
    n = v->Nr * v->Nc * v->Nl[var];
    if (read_bytes(f, compdata1, n) != n)
        return 0;

    /* convert data values to v5d system */
    n = v->Nr * v->Nc * v->Nl[var];
    for (i = 0; i < n; i++) {
        compdata1[i] += bias;
    }

    return 1;
}

/*
 * Read a v5d file header.
 * Input:  f - file opened for reading.
 *         v - pointer to v5dstruct to store header info into.
 * Return:  1 = ok, 0 = error.
 */
static int read_v5d_header(v5dstruct *v)
{
#define SKIP(N) lseek(f, N, SEEK_CUR)
    int end_of_header = 0;
    unsigned int id;
    int idlen, var, numargs;
    int f;

    f = v->FileDesc;

    /* first try to read the header id */
    read_int4(f, (int *)&id);
    read_int4(f, &idlen);
    if (id == TAG_ID && idlen == 0) {
        /* this is a v5d file */
        v->FileFormat = 0;
    }
    else if (id >= 0x80808080 && id <= 0x80808083) {
        /* this is an old COMP* file */
        v->FileFormat = id;
        return read_comp_header(f, v);
    }
    else {
        /* unknown file type */
        printf("Error: not a v5d file\n");
        return 0;
    }

    v->CompressMode = 1; /* default */

    while (!end_of_header) {
        int tag, length;
        int i, var, time, nl, lev;

        if (read_int4(f, &tag) < 1 || read_int4(f, &length) < 1) {
            printf("Error while reading header, premature EOF\n");
            return 0;
        }

        switch (tag) {
        case TAG_VERSION:
            assert(length == 10);
            read_bytes(f, v->FileVersion, 10);
            /* Check if reading a file made by a future version of Vis5D */
            if (strcmp(v->FileVersion, FILE_VERSION) > 0) {
                G_warning("Trying to read a version %s file, you should "
                          "upgrade Vis5D",
                          v->FileVersion);
            }
            break;
        case TAG_NUMTIMES:
            assert(length == 4);
            read_int4(f, &v->NumTimes);
            break;
        case TAG_NUMVARS:
            assert(length == 4);
            read_int4(f, &v->NumVars);
            break;
        case TAG_VARNAME:
            assert(length == 14); /* 1 int + 10 char */
            read_int4(f, &var);
            read_bytes(f, v->VarName[var], 10);
            break;
        case TAG_NR:
            /* Number of rows for all variables */
            assert(length == 4);
            read_int4(f, &v->Nr);
            break;
        case TAG_NC:
            /* Number of columns for all variables */
            assert(length == 4);
            read_int4(f, &v->Nc);
            break;
        case TAG_NL:
            /* Number of levels for all variables */
            assert(length == 4);
            read_int4(f, &nl);
            for (i = 0; i < v->NumVars; i++) {
                v->Nl[i] = nl;
            }
            break;
        case TAG_NL_VAR:
            /* Number of levels for one variable */
            assert(length == 8);
            read_int4(f, &var);
            read_int4(f, &v->Nl[var]);
            break;
        case TAG_LOWLEV_VAR:
            /* Lowest level for one variable */
            assert(length == 8);
            read_int4(f, &var);
            read_int4(f, &v->LowLev[var]);
            break;

        case TAG_TIME:
            /* Time stamp for 1 timestep */
            assert(length == 8);
            read_int4(f, &time);
            read_int4(f, &v->TimeStamp[time]);
            break;
        case TAG_DATE:
            /* Date stamp for 1 timestep */
            assert(length == 8);
            read_int4(f, &time);
            read_int4(f, &v->DateStamp[time]);
            break;

        case TAG_MINVAL:
            /* Minimum value for a variable */
            assert(length == 8);
            read_int4(f, &var);
            read_float4(f, &v->MinVal[var]);
            break;
        case TAG_MAXVAL:
            /* Maximum value for a variable */
            assert(length == 8);
            read_int4(f, &var);
            read_float4(f, &v->MaxVal[var]);
            break;
        case TAG_COMPRESS:
            /* Compress mode */
            assert(length == 4);
            read_int4(f, &v->CompressMode);
            break;
        case TAG_UNITS:
            /* physical units */
            assert(length == 24);
            read_int4(f, &var);
            read_bytes(f, v->Units[var], 20);
            break;

            /*
             * Vertical coordinate system
             */
        case TAG_VERTICAL_SYSTEM:
            assert(length == 4);
            read_int4(f, &v->VerticalSystem);
            if (v->VerticalSystem < 0 || v->VerticalSystem > 3) {
                printf("Error: bad vertical coordinate system: %d\n",
                       v->VerticalSystem);
            }
            break;
        case TAG_VERT_ARGS:
            read_int4(f, &numargs);
            assert(numargs <= MAXVERTARGS);
            read_float4_array(f, v->VertArgs, numargs);
            assert(length == numargs * 4 + 4);
            break;
        case TAG_HEIGHT:
            /* height of a grid level */
            assert(length == 8);
            read_int4(f, &lev);
            read_float4(f, &v->VertArgs[lev]);
            break;
        case TAG_BOTTOMBOUND:
            assert(length == 4);
            read_float4(f, &v->VertArgs[0]);
            break;
        case TAG_LEVINC:
            assert(length == 4);
            read_float4(f, &v->VertArgs[1]);
            break;

            /*
             * Map projection information
             */
        case TAG_PROJECTION:
            assert(length == 4);
            read_int4(f, &v->Projection);
            if (v->Projection < 0 || v->Projection > 4) { /* WLH 4-21-95 */
                printf("Error while reading header, bad projection (%d)\n",
                       v->Projection);
                return 0;
            }
            break;
        case TAG_PROJ_ARGS:
            read_int4(f, &numargs);
            assert(numargs <= MAXPROJARGS);
            read_float4_array(f, v->ProjArgs, numargs);
            assert(length == 4 * numargs + 4);
            break;
        case TAG_NORTHBOUND:
            assert(length == 4);
            if (v->Projection == 0 || v->Projection == 1 ||
                v->Projection == 4) {
                read_float4(f, &v->ProjArgs[0]);
            }
            else {
                SKIP(4);
            }
            break;
        case TAG_WESTBOUND:
            assert(length == 4);
            if (v->Projection == 0 || v->Projection == 1 ||
                v->Projection == 4) {
                read_float4(f, &v->ProjArgs[1]);
            }
            else {
                SKIP(4);
            }
            break;
        case TAG_ROWINC:
            assert(length == 4);
            if (v->Projection == 0 || v->Projection == 1 ||
                v->Projection == 4) {
                read_float4(f, &v->ProjArgs[2]);
            }
            else {
                SKIP(4);
            }
            break;
        case TAG_COLINC:
            assert(length == 4);
            if (v->Projection == 0 || v->Projection == 1 ||
                v->Projection == 4) {
                read_float4(f, &v->ProjArgs[3]);
            }
            else if (v->Projection == 2) {
                read_float4(f, &v->ProjArgs[5]);
            }
            else if (v->Projection == 3) {
                read_float4(f, &v->ProjArgs[4]);
            }
            else {
                SKIP(4);
            }
            break;
        case TAG_LAT1:
            assert(length == 4);
            if (v->Projection == 2) {
                read_float4(f, &v->ProjArgs[0]);
            }
            else {
                SKIP(4);
            }
            break;
        case TAG_LAT2:
            assert(length == 4);
            if (v->Projection == 2) {
                read_float4(f, &v->ProjArgs[1]);
            }
            else {
                SKIP(4);
            }
            break;
        case TAG_POLE_ROW:
            assert(length == 4);
            if (v->Projection == 2) {
                read_float4(f, &v->ProjArgs[2]);
            }
            else {
                SKIP(4);
            }
            break;
        case TAG_POLE_COL:
            assert(length == 4);
            if (v->Projection == 2) {
                read_float4(f, &v->ProjArgs[3]);
            }
            else {
                SKIP(4);
            }
            break;
        case TAG_CENTLON:
            assert(length == 4);
            if (v->Projection == 2) {
                read_float4(f, &v->ProjArgs[4]);
            }
            else if (v->Projection == 3) {
                read_float4(f, &v->ProjArgs[1]);
            }
            else if (v->Projection == 4) { /* WLH 4-21-95 */
                read_float4(f, &v->ProjArgs[5]);
            }
            else {
                SKIP(4);
            }
            break;
        case TAG_CENTLAT:
            assert(length == 4);
            if (v->Projection == 3) {
                read_float4(f, &v->ProjArgs[0]);
            }
            else if (v->Projection == 4) { /* WLH 4-21-95 */
                read_float4(f, &v->ProjArgs[4]);
            }
            else {
                SKIP(4);
            }
            break;
        case TAG_CENTROW:
            assert(length == 4);
            if (v->Projection == 3) {
                read_float4(f, &v->ProjArgs[2]);
            }
            else {
                SKIP(4);
            }
            break;
        case TAG_CENTCOL:
            assert(length == 4);
            if (v->Projection == 3) {
                read_float4(f, &v->ProjArgs[3]);
            }
            else {
                SKIP(4);
            }
            break;
        case TAG_ROTATION:
            assert(length == 4);
            if (v->Projection == 4) { /* WLH 4-21-95 */
                read_float4(f, &v->ProjArgs[6]);
            }
            else {
                SKIP(4);
            }
            break;

        case TAG_END:
            /* end of header */
            end_of_header = 1;
            lseek(f, length, SEEK_CUR);
            break;

        default:
            /* unknown tag, skip to next tag */
            printf("Unknown tag: %d  length=%d\n", tag, length);
            lseek(f, length, SEEK_CUR);
            break;
        }
    }

    v5dVerifyStruct(v);

    /* Now we're ready to read the grid data */

    /* Save current file pointer */
    v->FirstGridPos = ltell(f);

    /* compute grid sizes */
    v->SumGridSizes = 0;
    for (var = 0; var < v->NumVars; var++) {
        v->GridSize[var] = 8 * v->Nl[var] + v5dSizeofGrid(v, 0, var);
        v->SumGridSizes += v->GridSize[var];
    }

    return 1;
#undef SKIP
}

/*
 * Open a v5d file for reading.
 * Input:  filename - name of v5d file to open
 *         v - pointer to a v5dstruct in which to put header info or NULL
 *             if a struct should be dynamically allocated.
 * Return:  NULL if error, else v or a pointer to a new v5dstruct if v was NULL
 */
v5dstruct *v5dOpenFile(const char *filename, v5dstruct *v)
{
    int fd;

    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        /* error */
        return 0;
    }

    if (v) {
        v5dInitStruct(v);
    }
    else {
        v = v5dNewStruct();
        if (!v) {
            return NULL;
        }
    }

    v->FileDesc = fd;
    v->Mode = 'r';
    if (read_v5d_header(v)) {
        return v;
    }
    else {
        return NULL;
    }
}

/*
 * Read a compressed grid from a v5d file.
 * Input:  v - pointer to v5dstruct describing the file
 *         time, var - which timestep and variable
 *         ga, gb - arrays to store grid (de)compression values
 *         compdata - address of where to store compressed grid data.
 * Return:  1 = ok, 0 = error.
 */
int v5dReadCompressedGrid(v5dstruct *v, int time, int var, float *ga, float *gb,
                          void *compdata)
{
    int pos, n, k = 0;

    if (time < 0 || time >= v->NumTimes) {
        printf("Error in v5dReadCompressedGrid: bad timestep argument (%d)\n",
               time);
        return 0;
    }
    if (var < 0 || var >= v->NumVars) {
        printf("Error in v5dReadCompressedGrid: bad var argument (%d)\n", var);
        return 0;
    }

    if (v->FileFormat) {
        /* old COMP* file */
        return read_comp_grid(v, time, var, ga, gb, compdata);
    }

    /* move to position in file */
    pos = grid_position(v, time, var);
    lseek(v->FileDesc, pos, SEEK_SET);

    /* read ga, gb arrays */
    read_float4_array(v->FileDesc, ga, v->Nl[var]);
    read_float4_array(v->FileDesc, gb, v->Nl[var]);

    /* read compressed grid data */
    n = v->Nr * v->Nc * v->Nl[var];
    if (v->CompressMode == 1) {
        k = read_block(v->FileDesc, compdata, n, 1) == n;
    }
    else if (v->CompressMode == 2) {
        k = read_block(v->FileDesc, compdata, n, 2) == n;
    }
    else if (v->CompressMode == 4) {
        k = read_block(v->FileDesc, compdata, n, 4) == n;
    }
    if (!k) {
        /* error */
        printf("Error in v5dReadCompressedGrid: read failed, bad file?\n");
    }
    return k;

    /*
       n = v->Nr * v->Nc * v->Nl[var] * v->CompressMode;
       if (read( v->FileDesc, compdata, n )==n)
       return 1;
       else
       return 0;
     */
}

/*
 * Read a grid from a v5d file, decompress it and return it.
 * Input:  v - pointer to v5dstruct describing file header
 *         time, var - which timestep and variable.
 *         data - address of buffer to put grid data
 * Output:  data - the grid data
 * Return:  1 = ok, 0 = error.
 */
int v5dReadGrid(v5dstruct *v, int time, int var, float data[])
{
    float ga[MAXLEVELS], gb[MAXLEVELS];
    void *compdata;
    int bytes = 0;

    if (time < 0 || time >= v->NumTimes) {
        printf("Error in v5dReadGrid: bad timestep argument (%d)\n", time);
        return 0;
    }
    if (var < 0 || var >= v->NumVars) {
        printf("Error in v5dReadGrid: bad variable argument (%d)\n", var);
        return 0;
    }

    /* allocate compdata buffer */
    if (v->CompressMode == 1) {
        bytes = v->Nr * v->Nc * v->Nl[var] * (int)sizeof(unsigned char);
    }
    else if (v->CompressMode == 2) {
        bytes = v->Nr * v->Nc * v->Nl[var] * (int)sizeof(unsigned short);
    }
    else if (v->CompressMode == 4) {
        bytes = v->Nr * v->Nc * v->Nl[var] * (int)sizeof(float);
    }
    compdata = (void *)G_malloc(bytes);
    if (!compdata) {
        printf("Error in v5dReadGrid: out of memory (needed %d bytes)\n",
               bytes);
        return 0;
    }

    /* read the compressed data */
    if (!v5dReadCompressedGrid(v, time, var, ga, gb, compdata)) {
        return 0;
    }

    /* decompress the data */
    v5dDecompressGrid(v->Nr, v->Nc, v->Nl[var], v->CompressMode, compdata, ga,
                      gb, data);

    /* free compdata */
    G_free(compdata);
    return 1;
}

/**********************************************************************/

/*****                   Output Functions                         *****/

/**********************************************************************/

static int write_tag(v5dstruct *v, int tag, int length, int newfile)
{
    if (!newfile) {
        /* have to check that there's room in header to write this tagged item
         */
        if (v->CurPos + 8 + length > v->FirstGridPos) {
            printf("Error: out of header space!\n");
            /* Out of header space! */
            return 0;
        }
    }

    if (write_int4(v->FileDesc, tag) == 0)
        return 0;
    if (write_int4(v->FileDesc, length) == 0)
        return 0;
    v->CurPos += 8 + length;
    return 1;
}

/*
 * Write the information in the given v5dstruct as a v5d file header.
 * Note that the current file position is restored when this function
 * returns normally.
 * Input:  f - file already open for writing
 *         v - pointer to v5dstruct
 * Return:  1 = ok, 0 = error.
 */
static int write_v5d_header(v5dstruct *v)
{
    int var, time, filler, maxnl;
    int f;
    int newfile;

    if (v->FileFormat != 0) {
        printf("Error: v5d library can't write comp5d format files.\n");
        return 0;
    }

    f = v->FileDesc;

    if (!v5dVerifyStruct(v))
        return 0;

    /* Determine if we're writing to a new file */
    if (v->FirstGridPos == 0) {
        newfile = 1;
    }
    else {
        newfile = 0;
    }

    /* compute grid sizes */
    v->SumGridSizes = 0;
    for (var = 0; var < v->NumVars; var++) {
        v->GridSize[var] = 8 * v->Nl[var] + v5dSizeofGrid(v, 0, var);
        v->SumGridSizes += v->GridSize[var];
    }

    /* set file pointer to start of file */
    lseek(f, 0, SEEK_SET);
    v->CurPos = 0;

    /*
     * Write the tagged header info
     */
#define WRITE_TAG(V, T, L)            \
    if (!write_tag(V, T, L, newfile)) \
        return 0;

    /* ID */
    WRITE_TAG(v, TAG_ID, 0);

    /* File Version */
    WRITE_TAG(v, TAG_VERSION, 10);
    write_bytes(f, FILE_VERSION, 10);

    /* Number of timesteps */
    WRITE_TAG(v, TAG_NUMTIMES, 4);
    write_int4(f, v->NumTimes);

    /* Number of variables */
    WRITE_TAG(v, TAG_NUMVARS, 4);
    write_int4(f, v->NumVars);

    /* Names of variables */
    for (var = 0; var < v->NumVars; var++) {
        WRITE_TAG(v, TAG_VARNAME, 14);
        write_int4(f, var);
        write_bytes(f, v->VarName[var], 10);
    }

    /* Physical Units */
    for (var = 0; var < v->NumVars; var++) {
        WRITE_TAG(v, TAG_UNITS, 24);
        write_int4(f, var);
        write_bytes(f, v->Units[var], 20);
    }

    /* Date and time of each timestep */
    for (time = 0; time < v->NumTimes; time++) {
        WRITE_TAG(v, TAG_TIME, 8);
        write_int4(f, time);
        write_int4(f, v->TimeStamp[time]);
        WRITE_TAG(v, TAG_DATE, 8);
        write_int4(f, time);
        write_int4(f, v->DateStamp[time]);
    }

    /* Number of rows */
    WRITE_TAG(v, TAG_NR, 4);
    write_int4(f, v->Nr);

    /* Number of columns */
    WRITE_TAG(v, TAG_NC, 4);
    write_int4(f, v->Nc);

    /* Number of levels, compute maxnl */
    maxnl = 0;
    for (var = 0; var < v->NumVars; var++) {
        WRITE_TAG(v, TAG_NL_VAR, 8);
        write_int4(f, var);
        write_int4(f, v->Nl[var]);
        WRITE_TAG(v, TAG_LOWLEV_VAR, 8);
        write_int4(f, var);
        write_int4(f, v->LowLev[var]);
        if (v->Nl[var] + v->LowLev[var] > maxnl) {
            maxnl = v->Nl[var] + v->LowLev[var];
        }
    }

    /* Min/Max values */
    for (var = 0; var < v->NumVars; var++) {
        WRITE_TAG(v, TAG_MINVAL, 8);
        write_int4(f, var);
        write_float4(f, v->MinVal[var]);
        WRITE_TAG(v, TAG_MAXVAL, 8);
        write_int4(f, var);
        write_float4(f, v->MaxVal[var]);
    }

    /* Compress mode */
    WRITE_TAG(v, TAG_COMPRESS, 4);
    write_int4(f, v->CompressMode);

    /* Vertical Coordinate System */
    WRITE_TAG(v, TAG_VERTICAL_SYSTEM, 4);
    write_int4(f, v->VerticalSystem);
    WRITE_TAG(v, TAG_VERT_ARGS, 4 + 4 * MAXVERTARGS);
    write_int4(f, MAXVERTARGS);
    write_float4_array(f, v->VertArgs, MAXVERTARGS);

    /* Map Projection */
    WRITE_TAG(v, TAG_PROJECTION, 4);
    write_int4(f, v->Projection);
    WRITE_TAG(v, TAG_PROJ_ARGS, 4 + 4 * MAXPROJARGS);
    write_int4(f, MAXPROJARGS);
    write_float4_array(f, v->ProjArgs, MAXPROJARGS);

    /* write END tag */
    if (newfile) {
        /* We're writing to a brand new file.  Reserve 10000 bytes */
        /* for future header growth. */
        WRITE_TAG(v, TAG_END, 10000);
        lseek(f, 10000, SEEK_CUR);

        /* Let file pointer indicate where first grid is stored */
        v->FirstGridPos = ltell(f);
    }
    else {
        /* we're rewriting a header */
        filler = v->FirstGridPos - ltell(f);
        WRITE_TAG(v, TAG_END, filler - 8);
    }

#undef WRITE_TAG

    return 1;
}

/*
 * Open a v5d file for writing.  If the named file already exists,
 * it will be deleted.
 * Input:  filename - name of v5d file to create.
 *         v - pointer to v5dstruct with the header info to write.
 * Return:  1 = ok, 0 = error.
 */
int v5dCreateFile(const char *filename, v5dstruct *v)
{
    mode_t mask;
    int fd;

    mask = 0666;
    fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, mask);
    if (fd == -1) {
        printf("Error in v5dCreateFile: open failed\n");
        v->FileDesc = -1;
        v->Mode = 0;
        return 0;
    }
    else {
        /* ok */
        v->FileDesc = fd;
        v->Mode = 'w';
        /* write header and return status */
        return write_v5d_header(v);
    }
}

/*
 * Open a v5d file for updating/appending and read the header info.
 * Input:  filename - name of v5d file to open for updating.
 *         v - pointer to v5dstruct in which the file header info will be
 *             put.  If v is NULL a v5dstruct will be allocated and returned.
 * Return:  NULL if error, else v or a pointer to a new v5dstruct if v as NULL
 */
v5dstruct *v5dUpdateFile(const char *filename, v5dstruct *v)
{
    int fd;

    fd = open(filename, O_RDWR);
    if (fd == -1) {
        return NULL;
    }

    if (!v) {
        v = v5dNewStruct();
        if (!v) {
            return NULL;
        }
    }

    v->FileDesc = fd;
    v->Mode = 'w';

    if (read_v5d_header(v)) {
        return v;
    }
    else {
        return NULL;
    }
}

/*
 * Write a compressed grid to a v5d file.
 * Input:  v - pointer to v5dstruct describing the file
 *         time, var - which timestep and variable
 *         ga, gb - the GA and GB (de)compression value arrays
 *         compdata - address of array of compressed data values
 * Return:  1 = ok, 0 = error.
 */
int v5dWriteCompressedGrid(const v5dstruct *v, int time, int var,
                           const float *ga, const float *gb,
                           const void *compdata)
{
    int pos, n, k;

    /* simple error checks */
    if (v->Mode != 'w') {
        printf("Error in v5dWriteCompressedGrid: file opened for reading,");
        printf(" not writing.\n");
        return 0;
    }
    if (time < 0 || time >= v->NumTimes) {
        printf("Error in v5dWriteCompressedGrid: bad timestep argument (%d)\n",
               time);
        return 0;
    }
    if (var < 0 || var >= v->NumVars) {
        printf("Error in v5dWriteCompressedGrid: bad variable argument (%d)\n",
               var);
        return 0;
    }

    /* move to position in file */
    pos = grid_position(v, time, var);
    if (lseek(v->FileDesc, pos, SEEK_SET) < 0) {
        /* lseek failed, return error */
        printf("Error in v5dWrite[Compressed]Grid: seek failed, disk full?\n");
        return 0;
    }

    /* write ga, gb arrays */
    k = 0;
    if (write_float4_array(v->FileDesc, ga, v->Nl[var]) == v->Nl[var] &&
        write_float4_array(v->FileDesc, gb, v->Nl[var]) == v->Nl[var]) {
        /* write compressed grid data (k=1=OK, k=0=Error) */
        n = v->Nr * v->Nc * v->Nl[var];
        if (v->CompressMode == 1) {
            k = write_block(v->FileDesc, compdata, n, 1) == n;
        }
        else if (v->CompressMode == 2) {
            k = write_block(v->FileDesc, compdata, n, 2) == n;
        }
        else if (v->CompressMode == 4) {
            k = write_block(v->FileDesc, compdata, n, 4) == n;
        }
    }

    if (k == 0) {
        /* Error while writing */
        printf("Error in v5dWrite[Compressed]Grid: write failed, disk full?\n");
    }
    return k;

    /*
       n = v->Nr * v->Nc * v->Nl[var] * v->CompressMode;
       if (write_bytes( v->FileDesc, compdata, n )!=n) {
       printf("Error in v5dWrite[Compressed]Grid: write failed, disk full?\n");
       return 0;
       }
       else {
       return 1;
       }
     */
}

/*
 * Compress a grid and write it to a v5d file.
 * Input:  v - pointer to v5dstruct describing the file
 *         time, var - which timestep and variable (starting at 0)
 *         data - address of uncompressed grid data
 * Return:  1 = ok, 0 = error.
 */
int v5dWriteGrid(v5dstruct *v, int time, int var, const float data[])
{
    float ga[MAXLEVELS], gb[MAXLEVELS];
    void *compdata;
    int n, bytes = 0;
    float min, max;

    if (v->Mode != 'w') {
        printf("Error in v5dWriteGrid: file opened for reading,");
        printf(" not writing.\n");
        return 0;
    }
    if (time < 0 || time >= v->NumTimes) {
        printf("Error in v5dWriteGrid: bad timestep argument (%d)\n", time);
        return 0;
    }
    if (var < 0 || var >= v->NumVars) {
        printf("Error in v5dWriteGrid: bad variable argument (%d)\n", var);
        return 0;
    }

    /* allocate compdata buffer */
    if (v->CompressMode == 1) {
        bytes = v->Nr * v->Nc * v->Nl[var] * (int)sizeof(unsigned char);
    }
    else if (v->CompressMode == 2) {
        bytes = v->Nr * v->Nc * v->Nl[var] * (int)sizeof(unsigned short);
    }
    else if (v->CompressMode == 4) {
        bytes = v->Nr * v->Nc * v->Nl[var] * (int)sizeof(float);
    }
    compdata = (void *)G_malloc(bytes);
    if (!compdata) {
        printf("Error in v5dWriteGrid: out of memory (needed %d bytes)\n",
               bytes);
        return 0;
    }

    /* compress the grid data */
    v5dCompressGrid(v->Nr, v->Nc, v->Nl[var], v->CompressMode, data, compdata,
                    ga, gb, &min, &max);

    /* update min and max value */
    if (min < v->MinVal[var])
        v->MinVal[var] = min;
    if (max > v->MaxVal[var])
        v->MaxVal[var] = max;

    /* write the compressed grid */
    n = v5dWriteCompressedGrid(v, time, var, ga, gb, compdata);

    /* free compdata */
    G_free(compdata);

    return n;
}

/*
 * Close a v5d file which was opened with open_v5d_file() or
 * create_v5d_file().
 * Input: f - file descriptor
 * Return:  1 = ok, 0 = error
 */
int v5dCloseFile(v5dstruct *v)
{
    int status = 1;

    if (v->Mode == 'w') {
        /* rewrite header because writing grids updates the minval and */
        /* maxval fields */
        lseek(v->FileDesc, 0, SEEK_SET);
        status = write_v5d_header(v);
        lseek(v->FileDesc, 0, SEEK_END);
        close(v->FileDesc);
    }
    else if (v->Mode == 'r') {
        /* just close the file */
        close(v->FileDesc);
    }
    else {
        printf("Error in v5dCloseFile: bad v5dstruct argument\n");
        return 0;
    }
    v->FileDesc = -1;
    v->Mode = 0;
    return status;
}

/**********************************************************************/

/*****           Simple v5d file writing functions.               *****/

/**********************************************************************/

static v5dstruct *Simple = NULL;

/*
 * Create a new v5d file specifying both a map projection and vertical
 * coordinate system.  See README file for argument details.
 * Return:  1 = ok, 0 = error.
 */
int v5dCreate(const char *name, int numtimes, int numvars, int nr, int nc,
              const int nl[], const char varname[MAXVARS][10],
              const int timestamp[], const int datestamp[], int compressmode,
              int projection, const float proj_args[], int vertical,
              const float vert_args[])
{
    int var, time, maxnl, i;

    /* initialize the v5dstruct */
    Simple = v5dNewStruct();

    Simple->NumTimes = numtimes;
    Simple->NumVars = numvars;
    Simple->Nr = nr;
    Simple->Nc = nc;
    maxnl = nl[0];
    for (var = 0; var < numvars; var++) {
        if (nl[var] > maxnl) {
            maxnl = nl[var];
        }
        Simple->Nl[var] = nl[var];
        Simple->LowLev[var] = 0;
        strncpy(Simple->VarName[var], varname[var], 10);
        Simple->VarName[var][9] = 0;
    }

    /* time and date for each timestep */
    for (time = 0; time < numtimes; time++) {
        Simple->TimeStamp[time] = timestamp[time];
        Simple->DateStamp[time] = datestamp[time];
    }

    Simple->CompressMode = compressmode;

    /* Map projection and vertical coordinate system */
    Simple->Projection = projection;
    memcpy(Simple->ProjArgs, proj_args, MAXPROJARGS * sizeof(float));

    Simple->VerticalSystem = vertical;
    if (vertical == 3) {
        /* convert pressures to heights */
        for (i = 0; i < MAXVERTARGS; i++) {
            if (vert_args[i] > 0.000001) {
                Simple->VertArgs[i] = pressure_to_height(vert_args[i]);
            }
            else
                Simple->VertArgs[i] = 0.0;
        }
    }
    else {
        memcpy(Simple->VertArgs, vert_args, MAXVERTARGS * sizeof(float));
    }

    /* create the file */
    if (v5dCreateFile(name, Simple) == 0) {
        printf("Error in v5dCreateSimpleFile: unable to create %s\n", name);
        return 0;
    }
    else {
        return 1;
    }
}

/*
 * Create a new v5d file using minimal information.
 * Return:  1 = ok, 0 = error.  See README file for argument details.
 */
int v5dCreateSimple(const char *name, int numtimes, int numvars, int nr, int nc,
                    int nl, const char varname[MAXVARS][10],
                    const int timestamp[], const int datestamp[],
                    float northlat, float latinc, float westlon, float loninc,
                    float bottomhgt, float hgtinc)
{
    int nlvar[MAXVARS];
    int compressmode, projection, vertical;
    float proj_args[100], vert_args[MAXLEVELS];
    int i;

    for (i = 0; i < numvars; i++) {
        nlvar[i] = nl;
    }

    compressmode = 1;

    projection = 1;
    proj_args[0] = northlat;
    proj_args[1] = westlon;
    proj_args[2] = latinc;
    proj_args[3] = loninc;

    vertical = 1;
    vert_args[0] = bottomhgt;
    vert_args[1] = hgtinc;

    return v5dCreate(name, numtimes, numvars, nr, nc, nlvar, varname, timestamp,
                     datestamp, compressmode, projection, proj_args, vertical,
                     vert_args);
}

/*
 * Set lowest levels for each variable (other than default of 0).
 * Input: lowlev - array [NumVars] of ints
 * Return:  1 = ok, 0 = error
 */
int v5dSetLowLev(int lowlev[])
{
    int var;

    if (Simple) {
        for (var = 0; var < Simple->NumVars; var++) {
            Simple->LowLev[var] = lowlev[var];
        }
        return 1;
    }
    else {
        printf("Error: must call v5dCreate before v5dSetLowLev\n");
        return 0;
    }
}

/*
 * Set the units for a variable.
 * Input:  var - a variable in [1,NumVars]
 *         units - a string
 * Return:  1 = ok, 0 = error
 */
int v5dSetUnits(int var, const char *units)
{
    if (Simple) {
        if (var >= 1 && var <= Simple->NumVars) {
            strncpy(Simple->Units[var - 1], units, 19);
            Simple->Units[var - 1][19] = 0;
            return 1;
        }
        else {
            printf("Error: bad variable number in v5dSetUnits\n");
            return 0;
        }
    }
    else {
        printf("Error: must call v5dCreate before v5dSetUnits\n");
        return 0;
    }
}

/*
 * Write a grid to a v5d file.
 * Input:  time - timestep in [1,NumTimes]
 *         var - timestep in [1,NumVars]
 *         data - array [nr*nc*nl] of floats
 * Return:  1 = ok, 0 = error
 */
int v5dWrite(int time, int var, const float data[])
{
    if (Simple) {
        if (time < 1 || time > Simple->NumTimes) {
            printf("Error in v5dWrite: bad timestep number: %d\n", time);
            return 0;
        }
        if (var < 1 || var > Simple->NumVars) {
            printf("Error in v5dWrite: bad variable number: %d\n", var);
        }
        return v5dWriteGrid(Simple, time - 1, var - 1, data);
    }
    else {
        printf("Error: must call v5dCreate before v5dWrite\n");
        return 0;
    }
}

/*
 * Close a v5d file after the last grid has been written to it.
 * Return:  1 = ok, 0 = error
 */
int v5dClose(void)
{
    if (Simple) {
        int ok = v5dCloseFile(Simple);

        v5dFreeStruct(Simple);
        return ok;
    }
    else {
        printf("Error: v5dClose: no file to close\n");
        return 0;
    }
}

/**********************************************************************/

/*****                FORTRAN-callable simple output              *****/

/**********************************************************************/

/*
 * Create a v5d file.  See README file for argument descriptions.
 * Return:  1 = ok, 0 = error.
 */
#ifdef UNDERSCORE
int v5dcreate_
#else
#ifdef _CRAY
int V5DCREATE
#else
int v5dcreate
#endif
#endif

    (const char *name, const int *numtimes, const int *numvars, const int *nr,
     const int *nc, const int nl[], const char varname[][10],
     const int timestamp[], const int datestamp[], const int *compressmode,
     const int *projection, const float proj_args[], const int *vertical,
     const float vert_args[])
{
    char filename[100];
    char names[MAXVARS][10];
    int i, maxnl, args;

    /* copy name to filename and remove trailing spaces if any */
    copy_string(filename, name, 100);

    /*
     * Check for uninitialized arguments
     */
    if (*numtimes < 1) {
        printf("Error: numtimes invalid\n");
        return 0;
    }
    if (*numvars < 1) {
        printf("Error: numvars invalid\n");
        return 0;
    }
    if (*nr < 2) {
        printf("Error: nr invalid\n");
        return 0;
    }
    if (*nc < 2) {
        printf("Error: nc invalid\n");
        return 0;
    }
    maxnl = 0;
    for (i = 0; i < *numvars; i++) {
        if (nl[i] < 1) {
            printf("Error: nl(%d) invalid\n", i + 1);
            return 0;
        }
        if (nl[i] > maxnl) {
            maxnl = nl[i];
        }
    }

    for (i = 0; i < *numvars; i++) {
        if (copy_string2(names[i], varname[i], 10) == 0) {
            printf("Error: uninitialized varname(%d)\n", i + 1);
            return 0;
        }
    }

    for (i = 0; i < *numtimes; i++) {
        if (timestamp[i] < 0) {
            printf("Error: times(%d) invalid\n", i + 1);
            return 0;
        }
        if (datestamp[i] < 0) {
            printf("Error: dates(%d) invalid\n", i + 1);
            return 0;
        }
    }

    if (*compressmode != 1 && *compressmode != 2 && *compressmode != 4) {
        printf("Error: compressmode invalid\n");
        return 0;
    }

    switch (*projection) {
    case 0:
        args = 4;
        break;
    case 1:
        args = 0;
        if (IS_MISSING(proj_args[0])) {
            printf("Error: northlat (proj_args(1)) invalid\n");
            return 0;
        }
        if (IS_MISSING(proj_args[1])) {
            printf("Error: westlon (proj_args(2)) invalid\n");
            return 0;
        }
        if (IS_MISSING(proj_args[2])) {
            printf("Error: latinc (proj_args(3)) invalid\n");
            return 0;
        }
        if (IS_MISSING(proj_args[3])) {
            printf("Error: loninc (proj_args(4)) invalid\n");
            return 0;
        }
        break;
    case 2:
        args = 6;
        break;
    case 3:
        args = 5;
        break;
    case 4:
        args = 7;
        break;
    default:
        args = 0;
        printf("Error: projection invalid\n");
        return 0;
    }
    for (i = 0; i < args; i++) {
        if (IS_MISSING(proj_args[i])) {
            printf("Error: proj_args(%d) invalid\n", i + 1);
            return 0;
        }
    }

    switch (*vertical) {
    case 0:
        /* WLH 31 Oct 96  -  just fall through
           args = 4;
           break;
         */
    case 1:
        args = 0;
        if (IS_MISSING(vert_args[0])) {
            printf("Error: bottomhgt (vert_args(1)) invalid\n");
            return 0;
        }
        if (IS_MISSING(vert_args[1])) {
            printf("Error: hgtinc (vert_args(2)) invalid\n");
            return 0;
        }
        break;
    case 2:
    case 3:
        args = maxnl;
        break;
    default:
        args = 0;
        printf("Error: vertical invalid\n");
        return 0;
    }
    for (i = 0; i < args; i++) {
        if (IS_MISSING(vert_args[i])) {
            printf("Error: vert_args(%d) invalid\n", i + 1);
            return 0;
        }
    }

    return v5dCreate(filename, *numtimes, *numvars, *nr, *nc, nl,
                     (const char(*)[10])names, timestamp, datestamp,
                     *compressmode, *projection, proj_args, *vertical,
                     vert_args);
}

/*
 * Create a simple v5d file.  See README file for argument descriptions.
 * Return:  1 = ok, 0 = error.
 */
#ifdef UNDERSCORE
int v5dcreatesimple_
#else
#ifdef _CRAY
int V5DCREATESIMPLE
#else
int v5dcreatesimple
#endif
#endif

    (const char *name, const int *numtimes, const int *numvars, const int *nr,
     const int *nc, const int *nl, const char varname[][10],
     const int timestamp[], const int datestamp[], const float *northlat,
     const float *latinc, const float *westlon, const float *loninc,
     const float *bottomhgt, const float *hgtinc)
{
    int compressmode, projection, vertical;
    float projarg[100], vertarg[MAXLEVELS];
    int varnl[MAXVARS];
    int i;

    for (i = 0; i < MAXVARS; i++) {
        varnl[i] = *nl;
    }

    compressmode = 1;

    projection = 1;
    projarg[0] = *northlat;
    projarg[1] = *westlon;
    projarg[2] = *latinc;
    projarg[3] = *loninc;

    vertical = 1;
    vertarg[0] = *bottomhgt;
    vertarg[1] = *hgtinc;

#ifdef UNDERSCORE
    return v5dcreate_
#else
#ifdef _CRAY
    return V5DCREATE
#else

    return v5dcreate
#endif
#endif
        (name, numtimes, numvars, nr, nc, varnl, varname, timestamp, datestamp,
         &compressmode, &projection, projarg, &vertical, vertarg);
}

/*
 * Set lowest levels for each variable (other than default of 0).
 * Input: lowlev - array [NumVars] of ints
 * Return:  1 = ok, 0 = error
 */
#ifdef UNDERSCORE
int v5dsetlowlev_
#else
#ifdef _CRAY
int V5DSETLOWLEV
#else
int v5dsetlowlev
#endif
#endif
    (int *lowlev)
{
    return v5dSetLowLev(lowlev);
}

/*
 * Set the units for a variable.
 * Input: var - variable number in [1,NumVars]
 *        units - a character string
 * Return:  1 = ok, 0 = error
 */
#ifdef UNDERSCORE
int v5dsetunits_
#else
#ifdef _CRAY
int V5DSETUNITS
#else
int v5dsetunits
#endif
#endif
    (int *var, char *name)
{
    return v5dSetUnits(*var, name);
}

/*
 * Write a grid of data to the file.
 * Input:  time - timestep in [1,NumTimes]
 *         var - timestep in [1,NumVars]
 *         data - array [nr*nc*nl] of floats
 * Return:  1 = ok, 0 = error
 */
#ifdef UNDERSCORE
int v5dwrite_
#else
#ifdef _CRAY
int V5DWRITE
#else
int v5dwrite
#endif
#endif
    (const int *time, const int *var, const float *data)
{
    return v5dWrite(*time, *var, data);
}

/*
 * Specify the McIDAS GR3D file number and grid number which correspond
 * to the grid specified by time and var.
 * Input:  time, var - timestep and variable of grid (starting at 1)
 *         mcfile, mcgrid - McIDAS grid file number and grid number
 * Return:  1 = ok, 0 = error (bad time or var)
 */
#ifdef UNDERSCORE
int v5dmcfile_
#else
#ifdef _CRAY
int V5DMCFILE
#else
int v5dmcfile
#endif
#endif
    (const int *time, const int *var, const int *mcfile, const int *mcgrid)
{
    if (*time < 1 || *time > Simple->NumTimes) {
        printf("Bad time argument to v5dSetMcIDASgrid: %d\n", *time);
        return 0;
    }
    if (*var < 1 || *var > Simple->NumVars) {
        printf("Bad var argument to v5dSetMcIDASgrid: %d\n", *var);
        return 0;
    }

    Simple->McFile[*time - 1][*var - 1] = (short)*mcfile;
    Simple->McGrid[*time - 1][*var - 1] = (short)*mcgrid;
    return 1;
}

/*
 * Close a simple v5d file.
 */
#ifdef UNDERSCORE
int v5dclose_(void)
#else
#ifdef _CRAY
int V5DCLOSE(void)
#else
int v5dclose(void)
#endif
#endif
{
    return v5dClose();
}
