#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <limits.h>
#include <getopt.h>
#include <mpi.h>
#include "compearth.h"
#include "parmt_utils.h"
#ifdef PARMT_USE_INTEL
#include <mkl_cblas.h>
#else
#include <cblas.h>
#endif
#include "parmt_polarity.h"
#include "parmt_postProcess.h"
#include "parmt_mtsearch.h"
#include "iscl/array/array.h"
#include "iscl/memory/memory.h"
#include "iscl/os/os.h"

#define PROGRAM_NAME "postmt"
#define OUTDIR "postprocess"
#define WAVOUT_DIR "obsest"

static int parseArguments(int argc, char *argv[], char iniFile[PATH_MAX]);
static void printUsage(void);
int parmt_freeData(struct parmtData_struct *data);

int main(int argc, char *argv[])
{
    struct parmtGeneralParms_struct parms;
    struct parmtData_struct data;
    struct parmtPolarityParms_struct polarityParms;
    struct polarityData_struct polarityData;
    FILE *ofl;
    char fname[PATH_MAX];
    char iniFile[PATH_MAX];
    char programNameIn[256];
    bool lpol;
    double U[9], Muse[6], Mned[6], lam[3], *betas, *deps, *depMPDF, *depMagMPDF,
           *G, *gammas, *kappas,
           *sigmas, *thetas, *M0s, *phi, *var, dip, epoch,
           lagTime, Mw, phiLoc, xnorm, xsum;
    int ierr, iobs, imtopt, jb, jg, jk, jloc, jm, joptLoc,
        js, jt, k, lag, myid, nb, ng, nk, nlags, nlocs,
        nm, nmt, npmax, npts, nprocs, ns, nt,
        provided;
    bool ldefault;
    // Start MPI
    MPI_Init_thread(&argc, &argv, MPI_THREAD_FUNNELED, &provided);
    MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
    // Initialize
    depMPDF = NULL;
    depMagMPDF = NULL;
    betas = NULL;
    deps = NULL;
    gammas = NULL;
    kappas = NULL;
    sigmas = NULL;
    thetas = NULL;
    M0s = NULL;
    phi = NULL;
    memset(&parms, 0, sizeof(struct parmtGeneralParms_struct));
    memset(&data, 0, sizeof(struct parmtData_struct));
    memset(&polarityParms, 0, sizeof(struct parmtPolarityParms_struct));
    memset(&polarityData, 0, sizeof(struct polarityData_struct));
    // Parse the input arguments
    ierr = parseArguments(argc, argv, iniFile);
    if (ierr != 0)
    {
        if (ierr ==-2){return 0;}
        printf("%s: Error parsing arguments\n", PROGRAM_NAME);
        return EXIT_FAILURE;
    }
    // Load the ini file - from this we should be able to deduce the archive
    ierr = parmt_utils_readGeneralParms(iniFile, &parms);
    if (ierr != 0)
    {
        printf("%s: Error reading general parameters\n", PROGRAM_NAME);
        return EXIT_FAILURE;
    }
    if (!os_path_isfile(parms.postmtFile))
    {
        printf("%s: Archive file %s doesn't exist\n",
               PROGRAM_NAME, parms.postmtFile);
        return EXIT_FAILURE;
    }
    ierr += parmt_utils_readPolarityParms(iniFile, &polarityParms);

    // TODO make this a config
    if (!os_path_isdir(OUTDIR))
    {
        os_makedirs(OUTDIR);
    }
    if (!os_path_isdir(WAVOUT_DIR))
    {
        os_makedirs(WAVOUT_DIR);
    }
    // Load the data
    printf("%s: Reading data...\n", PROGRAM_NAME);
    ierr = utils_dataArchive_readAllWaveforms(parms.dataFile, &data);
    if (ierr != 0)
    {
        printf("%s: Error reading data\n", PROGRAM_NAME);
        goto ERROR;
    }
    data.est = (struct sacData_struct *)
               calloc((size_t) data.nobs, sizeof(struct sacData_struct));
    // Read the archive
    printf("%s: Reading archive %s...\n", PROGRAM_NAME, parms.postmtFile);
printf("%s %s %s\n", parms.resultsDir, parms.projnm, parms.resultsFileSuffix);
    ierr = parmt_io_readObjfnArchive64f(
                   //parms.resultsDir, parms.projnm, parms.resultsFileSuffix,
                   parms.postmtFile, //parms.parmtArchive,
                   programNameIn,
                   &nlocs, &deps,
                   &nm, &M0s,
                   &nb, &betas,
                   &ng, &gammas,
                   &nk, &kappas,
                   &ns, &sigmas,
                   &nt, &thetas,
                   &nmt, &phi);
    if (ierr != 0)
    {
        printf("%s: Error loading archive\n", PROGRAM_NAME);
        goto ERROR;
    }
/*
double *phi1;
    ierr = parmt_io_readObjfnArchive64f(
                   "bw", parms.projnm, "bodyWaves",
                   &nlocs, &deps,
                   &nm, &M0s,
                   &nb, &betas,
                   &ng, &gammas,
                   &nk, &kappas,
                   &ns, &sigmas,
                   &nt, &thetas,
                   &nmt, &phi1);
for (int imt=0; imt<nmt; imt++)
{
  phi[imt] = (12.0*phi1[imt] + 11*phi[imt]);
}
        ierr = parmt_io_createObjfnArchive64f("joint", parms.projnm,
                                              "joint",
                                              25, //data.nobs,
                                              nlocs, deps,
                                              nm, M0s,
                                              nb, betas,
                                              ng, gammas,
                                              nk, kappas,
                                              ns, sigmas,
                                              nt, thetas);
        ierr = parmt_io_writeObjectiveFunction64f(
                   "joint", parms.projnm, "joint",
                   nmt, phi);
return 0;
*/
    // Get the optimum moment tensor for the waveforms
    ierr = marginal_getOptimum(nlocs, nm, nb,
                               ng, nk, ns, nt,
                               phi,
                               &jloc, &jm, &jb, &jg,
                               &jk, &js, &jt);
    if (ierr != 0)
    {
        printf("%s: Failed to get optimum\n", PROGRAM_NAME);
        goto ERROR;
    }
    imtopt = jloc*nm*nb*ng*nk*ns*nt
           +      jm*nb*ng*nk*ns*nt
           +         jb*ng*nk*ns*nt
           +            jg*nk*ns*nt
           +               jk*ns*nt
           +                  js*nt
           +                     jt;
//printf("%d %e\n", jm, M0s[jm]);
//getchar();
    compearth_m02mw(1, CE_KANAMORI_1978, &M0s[jm], &Mw);
    joptLoc = jloc;
//printf("%d %d %d %d %d %d %d\n", jloc, jm, jg, jb, jk, js, jt);
//jloc =4; jm = 5; jg =1; jb=0; jk=11; js=2; jt=4;

    printf("%s: Optimum information:\n", PROGRAM_NAME);
    printf("        Value: %f\n", phi[imtopt]);
    printf("        Depth: %f (km)\n", deps[jloc]);
    printf("        Magnitude: %f\n", Mw);
    printf("        Lune longitude: %f (deg)\n", gammas[jg]*180.0/M_PI);
    printf("        Lune latitude: %f (deg)\n", (M_PI_2 - betas[jb])*180.0/M_PI);
    printf("        Strike: %f (deg)\n", kappas[jk]*180.0/M_PI);
    printf("        Slip: %f (deg)\n", sigmas[js]*180.0/M_PI);
    printf("        Dip: %f (deg)\n", thetas[jt]*180.0/M_PI);
    // Display the moment tensor in USE format which is useful for obspy and gmt
        double gammaOpt = gammas[jg]*180.0/M_PI;
        double deltaOpt = 90.0-betas[jb]*180.0/M_PI;
        double kappaOpt = kappas[jk]*180.0/M_PI;
        double thetaOpt = thetas[jt]*180.0/M_PI;
        double sigmaOpt = sigmas[js]*180.0/M_PI;
        compearth_TT2CMT(1, &gammaOpt,
                         &deltaOpt,
                         &M0s[jm],
                         &kappaOpt,
                         &thetaOpt,
                         &sigmaOpt,
                         Muse, lam, U);

/*
    compearth_tt2cmt(gammas[jg]*180.0/M_PI,
                     (M_PI/2.0 - betas[jb])*180.0/M_PI,
                     M0s[jm],
                     kappas[jk]*180.0/M_PI,
                     thetas[jt]*180.0/M_PI,
                     sigmas[js]*180.0/M_PI,
                     Muse, lam, U);
*/
/*
Muse[0] = 28200000000000000.;  // mrr
Muse[1] = 283100000000000000.; // mtt
Muse[2] =-311300000000000000.; // mpp
Muse[3] =-243300000000000000.; // mrt
Muse[4] =-149900000000000000.; // mrp
Muse[5] = 467500000000000000.; // mtp
compearth_convertMT(1, CE_USE,  CE_NED, Muse, Mned);
*/
    ierr = parmt_discretizeMT64f(1, &gammas[jg],
                                 1, &betas[jb],
                                 1, &M0s[jm],
                                 1, &kappas[jk],
                                 1, &thetas[jt],
                                 1, &sigmas[js],
                                 6, 1, Mned);

    printf("mtUSE =[%.6e,%.6e,%.6e,%.6e,%.6e,%.6e]\n",
           Muse[0], Muse[1], Muse[2], Muse[3], Muse[4], Muse[5]);
    printf("mtNED =[%.6e,%.6e,%.6e,%.6e,%.6e,%.6e]\n",
           Mned[0], Mned[1], Mned[2], Mned[3], Mned[4], Mned[5]);
//printf("%e\n", M0s[jm]);
//compearth_CMT2m0(1, 1, Mned, &M0s[jm]);
//printf("%e\n", M0s[jm]);
//getchar();
    // Compute the polarities
    lpol = true;
    ierr = parmt_polarity_computeTTimesGreens(MPI_COMM_WORLD, polarityParms,
                                              data, &polarityData);
    if (ierr != 0)
    {
        printf("%s: Error computing polarity Green's functions\n",
               PROGRAM_NAME);
        ierr = 1;
        lpol = false;
    }
    if (lpol && polarityData.nPolarity > 0)
    {
        double *Gpol = memory_calloc64f(6*polarityData.nPolarity);
        double *est = memory_calloc64f(polarityData.nPolarity);
        double phiPol;
        int ipol, kt;
        kt = jloc*polarityData.nPolarity;
printf("G\n");
        for (ipol=0; ipol<polarityData.nPolarity; ipol++)
        {
            Gpol[6*ipol+0] = polarityData.Gxx[kt+ipol];
            Gpol[6*ipol+1] = polarityData.Gyy[kt+ipol];
            Gpol[6*ipol+2] = polarityData.Gzz[kt+ipol];
            Gpol[6*ipol+3] = polarityData.Gxy[kt+ipol];
            Gpol[6*ipol+4] = polarityData.Gxz[kt+ipol];
            Gpol[6*ipol+5] = polarityData.Gyz[kt+ipol];
printf("%f %f %f %f %f %f\n", Gpol[6*ipol+0], Gpol[6*ipol+1], Gpol[6*ipol+2],
                     Gpol[6*ipol+3], Gpol[6*ipol+4], Gpol[6*ipol+5]);
        }
        cblas_dgemv(CblasRowMajor, CblasNoTrans,
                    polarityData.nPolarity, 6, 1.0, Gpol, 6,
                    Mned, 1, 0.0, est, 1);
        for (ipol=0; ipol<polarityData.nPolarity; ipol++)
        {
            est[ipol] = copysign(1.0, est[ipol]);
            printf("Polarity: obs %f; est %f\n",
                   polarityData.polarity[ipol], est[ipol]);
        }

    }
    // Write the output file

    struct globalMapOpts_struct globalMap;
    memset(&globalMap, 0, sizeof(struct globalMapOpts_struct));
    strcpy(globalMap.outputScript, "gmtScripts/globalMap.sh");
    strcpy(globalMap.psFile, "globalMap.ps");
    array_copy64f_work(6, Muse, globalMap.mts);
    globalMap.basis = CE_USE;
    globalMap.evla = data.data[0].header.evla;
    globalMap.evlo = data.data[0].header.evlo;
    globalMap.evdp = deps[jloc];
    globalMap.lwantMT = true;
    globalMap.lwantPolarity = true;
printf("%f %f\n", globalMap.mts[0], globalMap.mts[1]);
    ierr = postmt_gmtHelper_writeGlobalMap(globalMap, data.nobs, data.data);
/*
printf("overriding ned and joptloc\n");
joptLoc=5;
Mned[0] = 8.526325e+15;
Mned[1] =-8.707938e+16;
Mned[2] = 2.226786e+17;
Mned[3] =-5.768950e+17;
Mned[4] =-3.469420e+17;
Mned[5] =-2.756277e+17;
*/
    // Compute the scaling factor
    xnorm = marginal_computeNormalization(nlocs, deps,
                                          nm, M0s,
                                          nb, betas,
                                          ng, gammas,
                                          nk, kappas,
                                          ns, sigmas,
                                          nt, thetas,
                                          phi, &ierr);
    if (xnorm <= 0.0 || ierr != 0)
    {
        printf("%s: Failed to compute normalization factor - setting to 1\n",
               PROGRAM_NAME);
        ierr = 0;
        xnorm = 1.0;
    }
    printf("%s: Scaling factor: %e\n", PROGRAM_NAME, xnorm);
    cblas_dscal(nmt, 1.0/xnorm, phi, 1);
    printf("%s: Computing pure histograms\n", PROGRAM_NAME);

    double *locHist, *magHist, *betaHist, *gammaHist, *kappaHist, *sigmaHist, *thetaHist;
    ierr = postmt_gmtHelper_makeRegularHistograms(nlocs, nm,
                                                  nb, ng, nk, ns, nt,
                                                  nmt, phi,
                                                  &locHist, &magHist, &betaHist,
                                                  &gammaHist, &kappaHist, &sigmaHist,
                                                  &thetaHist);
    int i;
/*
    printf("Depths\n");
    for (i=0; i<nlocs; i++)
    {
        printf("%f %f\n", deps[i], locHist[i]);
    }
    printf("Magnitudes\n");
    for (i=0; i<nm; i++)
    {
        compearth_m02mw(1, CE_KANAMORI_1978, &M0s[i], &Mw);
        printf("%f %f\n", Mw, magHist[i]);
    }
    printf("Gammas\n");
    for (i=0; i<ng; i++)
    {
        printf("%f %f\n", gammas[i]*180.0/M_PI, gammaHist[i]);
    }
*/
    printf("Betas\n");
    for (i=0; i<nb; i++)
    {
        printf("%f %f\n", 90.0 - betas[i]*180.0/M_PI, betaHist[i]);
    }
/*
    printf("Kappas\n");
    for (i=0; i<nk; i++)
    {
        printf("%f %f\n", kappas[i]*180.0/M_PI, kappaHist[i]);
    }
    printf("Sigmas\n");
    for (i=0; i<ns; i++)
    {
        printf("%f %f\n", sigmas[i]*180.0/M_PI, sigmaHist[i]);
    }
    printf("Thetas\n");
    for (i=0; i<nt; i++)
    {
        printf("%f %f\n", thetas[i]*180.0/M_PI, thetaHist[i]);
    }
*/
const char *scriptFile = "gmtScripts/boxes.sh";
const char *psFile = "boxes.ps";
ierr = postmt_gmtHelper_writeBetaBoxes(false, false, scriptFile, psFile,
                                       nb, jb, betas, betaHist);
ierr = postmt_gmtHelper_writeGammaBoxes(true, false, scriptFile, psFile,
                                        ng, jg, gammas, gammaHist);
ierr = postmt_gmtHelper_writeKappaBoxes(true, false, scriptFile, psFile,
                                        nk, jk, kappas, kappaHist);
ierr = postmt_gmtHelper_writeSigmaBoxes(true, false, scriptFile, psFile,
                                        ns, js, sigmas, sigmaHist);
ierr = postmt_gmtHelper_writeThetaBoxes(true, false, scriptFile, psFile,
                                        nt, jt, thetas, thetaHist);
ierr = postmt_gmtHelper_writeMagnitudeBoxes(true, false, scriptFile, psFile,
                                            nm, jm, M0s, magHist);
ierr = postmt_gmtHelper_writeDepthBoxes(true, true, scriptFile, psFile,
                                        nlocs, joptLoc, deps, locHist);

    // Start the post-processing - compute the depgh magnitude mPDFs
    printf("%s: Computing the depth MPDF's\n", PROGRAM_NAME);
    depMagMPDF = memory_calloc64f(nm*nlocs);
    depMPDF = memory_calloc64f(nlocs);
    ierr = marginal_computeDepthMPDF(nlocs,
                                     nm, M0s,
                                     nb, betas,
                                     ng, gammas,
                                     nk, kappas,
                                     ns, sigmas,
                                     nt, thetas,
                                     phi, depMagMPDF, depMPDF);
    if (ierr != 0)
    {
        printf("%s: Error computing depthMPDF\n", PROGRAM_NAME);
        goto ERROR;
    }
    // Print the station list for my edification
    for (k=0; k<data.nobs; k++)
    {
        printf("%8.3f %8.3f %6s\n", data.data[k].header.stlo,
                                    data.data[k].header.stla,
                                    data.data[k].header.kstnm);
    }
int im, iloc;
    for (im=0; im<nm; im++)
    {
        compearth_m02mw(1, CE_KANAMORI_1978, &M0s[im], &Mw);
        printf("Writing magnitude: %f\n", Mw);
        memset(fname, 0, PATH_MAX*sizeof(char));
        sprintf(fname, "%s/%s_dep.%d.txt", OUTDIR, parms.projnm, im+1);
        ofl = fopen(fname, "w");
        for (iloc=0; iloc<nlocs; iloc++)
        {
            jloc = im*nlocs + iloc;
            fprintf(ofl, "%e %e\n", deps[iloc], depMagMPDF[jloc]);
        }
        fclose(ofl);
    }
    memset(fname, 0, PATH_MAX*sizeof(char));
    sprintf(fname, "%s/%s_dep.txt", OUTDIR, parms.projnm);
    ofl = fopen(fname, "w");
    for (iloc=0; iloc<nlocs; iloc++)
    {
        fprintf(ofl, "%e %e\n", deps[iloc], depMPDF[iloc]);
    }
    fclose(ofl);
    // Compute the optimal synthetics
    xsum = 0.0;
    for (iobs=0; iobs<data.nobs; iobs++)
    {
        // Extract the depth waveform
        npts = data.data[iobs].npts;
        npmax = npts;
        G = memory_calloc64f(6*npmax);
        var = memory_calloc64f(npmax);
        ierr = parmt_utils_setDataOnG(iobs, joptLoc, npmax, data, G);
        if (ierr != 0)
        {
            printf("%s: Failed to set data on G\n", PROGRAM_NAME);
            goto ERROR;
        }
        // Compute the lag time
        nlags = 0;
        lag = 0;
        if (parms.lwantLags)
        {
            //lagTime = data.data[iobs].header.user0;
            //if (lagTime < 0.0){lagTime = parms.defaultMaxLagTime;}
            lagTime = parmt_utils_getLagTime(data.data[iobs],
                                             parms.defaultMaxLagTime,
                                             &ldefault);
            nlags = (int) (lagTime/data.data[iobs].header.delta + 0.5);
        }
        ierr = parmt_mtSearchL164f(6, 1,
                                   npts, 1,
                                   nlags, parms.lwantLags,
                                   parms.lrescale,
                                   &G[0*npmax], &G[1*npmax], &G[2*npmax],
                                   &G[3*npmax], &G[4*npmax], &G[5*npmax],
                                   NULL, Mned, data.data[iobs].data,
                                   &phiLoc, var, &lag);
        if (ierr != 0)
        {
            printf("%s: Error computing lags for waveform %d\n",
                   PROGRAM_NAME, iobs);
            goto ERROR;
        }
        // Compute the synthetic
        k = iobs*data.nlocs + joptLoc;
        parmt_utils_sacGrnsToEst(data.data[iobs],
                                 data.sacGxx[k], data.sacGyy[k],
                                 data.sacGzz[k], data.sacGxy[k],
                                 data.sacGxz[k], data.sacGyz[k],
                                 Mned,
                                 parms.lrescale,
                                 &data.est[iobs]);
        // Fix the timing
        ierr = sacio_getEpochalStartTime(data.est[iobs].header, &epoch);
        epoch = epoch + (double) lag*data.data[iobs].header.delta;
        sacio_setEpochalStartTime(epoch, &data.est[iobs].header);
        // Write the file
        memset(fname, 0, PATH_MAX*sizeof(char));
        sprintf(fname, "%s/%s.%s.%s.%s.SAC",
                WAVOUT_DIR,
                data.data[iobs].header.knetwk,
                data.data[iobs].header.kstnm,
                data.data[iobs].header.kcmpnm,
                data.data[iobs].header.khole);
        sacio_writeTimeSeriesFile(fname, data.data[iobs]);
        memset(fname, 0, PATH_MAX*sizeof(char));
        sprintf(fname, "%s/%s.%s.%s.%s.EST.SAC",
                WAVOUT_DIR,
                data.data[iobs].header.knetwk,
                data.data[iobs].header.kstnm,
                data.data[iobs].header.kcmpnm,
                data.data[iobs].header.khole);
        sacio_writeTimeSeriesFile(fname, data.est[iobs]);
        printf("Waveform %6s has lag %+4d=%+8.3e (s) and a fit of %f\n",
               data.data[iobs].header.kstnm,
               lag, lag*data.data[iobs].header.delta, phiLoc);
        memory_free64f(&G);
        memory_free64f(&var);
        xsum = xsum + phiLoc;
    }
    printf("Average %f\n", xsum/(double) data.nobs );
/*
double *db = memory_calloc64f(nb);
double *dg = memory_calloc64f(ng);
double *dt = memory_calloc64f(nt);
postprocess_computeBetaCellSpacing(nb, betas, db);
 printf("\n");
postprocess_computeGammaCellSpacing(ng, gammas, dg);
printf("\n");
postprocess_computeThetaCellSpacing(nt, thetas, dt);
*/
    // Free memory
ERROR:;
    parmt_freeData(&data);
    memory_free64f(&depMagMPDF);
    memory_free64f(&depMPDF);
    memory_free64f(&deps);
    memory_free64f(&M0s);
    memory_free64f(&betas);
    memory_free64f(&gammas);
    memory_free64f(&kappas);
    memory_free64f(&sigmas);
    memory_free64f(&thetas);
    memory_free64f(&phi);
    iscl_finalize();
    MPI_Finalize();
    return ierr;
}
//============================================================================//
/*!
 * @brief Parses the input arguments to get the ini file anme
 */
static int parseArguments(int argc, char *argv[], char iniFile[PATH_MAX])
{
    bool linFile;
    memset(iniFile, 0, PATH_MAX*sizeof(char));
    while (true)
    {
        static struct option longOptions[] =
        {
            {"help", no_argument, 0, '?'},
            {"help", no_argument, 0, 'h'},
            {"ini_file", required_argument, 0, 'i'},
            {0, 0, 0, 0}
        };
        int c, optionIndex;
        c = getopt_long(argc, argv, "?hi:",
                        longOptions, &optionIndex);
        if (c ==-1){break;}
        if (c == 'i')
        {
            strcpy(iniFile, (const char *) optarg);
            linFile = true;
        }
        else if (c == 'h' || c == '?')
        {
            printUsage();
            return -2;
        }
        else
        {
            printf("%s: Unknown options: %s\n",
                   PROGRAM_NAME, argv[optionIndex]);
        }
    }
    if (!linFile)
    {
        printf("%s: Error must specify ini file\n\n", PROGRAM_NAME);
        printUsage();
        return -1;
    }
    return 0;
}

static void printUsage(void)
{
    printf("Usage:\n   postmt -i input_file\n\n");
    printf("Required arguments:\n");
    printf("   -i input_file specifies the initialization file\n");
    printf("\n");
    printf("Optional arguments:\n");
    printf("   -h displays this message\n");
    return;
}

int parmt_freeData(struct parmtData_struct *data)
{
    int i;
    if (data->nobs > 0 && data->nlocs > 0 &&
        data->sacGxx != NULL && data->sacGyy != NULL && data->sacGzz != NULL &&
        data->sacGxy != NULL && data->sacGxz != NULL && data->sacGyz != NULL)
    {
        for (i=0; i<data->nobs*data->nlocs; i++)
        {
            sacio_freeData(&data->sacGxx[i]);
            sacio_freeData(&data->sacGyy[i]);
            sacio_freeData(&data->sacGzz[i]);
            sacio_freeData(&data->sacGxy[i]);
            sacio_freeData(&data->sacGxz[i]);
            sacio_freeData(&data->sacGyz[i]);
        }
        free(data->sacGxx);
        free(data->sacGyy);
        free(data->sacGzz);
        free(data->sacGxy);
        free(data->sacGxz);
        free(data->sacGyz);
    }
    if (data->nobs > 0 && data->data != NULL)
    {
        for (i=0; i<data->nobs; i++)
        {
            sacio_freeData(&data->data[i]);
        }
        free(data->data);
    }
    if (data->nobs > 0 && data->est != NULL)
    {
        free(data->est);
    }
    memset(data, 0, sizeof(struct parmtData_struct));
    return 0;
}


#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>
#include "cimple_polytope_library.h"

/**
 * "Constructor" Dynamically allocates the space a polytope needs
 */
struct polytope *polytope_alloc(size_t k,
                                size_t n)
{

    struct polytope *return_polytope = malloc (sizeof (struct polytope));

    return_polytope->H = gsl_matrix_alloc(k, n);
    if (return_polytope->H == NULL) {
        free (return_polytope);
        return NULL;
    }

    return_polytope->G = gsl_vector_alloc(k);
    if (return_polytope->H == NULL) {
        free (return_polytope);
        return NULL;
    }

    return_polytope->chebyshev_center = malloc(n* sizeof (double));
    if (return_polytope->chebyshev_center == NULL) {
        free (return_polytope);
        return NULL;
    }

    return return_polytope;
};

/**
 * "Destructor" Deallocates the dynamically allocated memory of the polytope
 */
void polytope_free(polytope *polytope)
{
    gsl_matrix_free(polytope->H);
    gsl_vector_free(polytope->G);
    free(polytope->chebyshev_center);
    free(polytope);
};

/**
 * "Constructor" Dynamically allocates the space a polytope needs
 */
struct cell *cell_alloc(size_t k,
                        size_t n,
                        int time_horizon)
{

    struct cell *return_cell = malloc (sizeof (struct cell));

    return_cell->safe_mode = malloc(sizeof(polytope)*time_horizon);
    if (return_cell->safe_mode == NULL) {
        free (return_cell);
        return NULL;
    }

    return_cell->polytope_description = polytope_alloc(k,n);
    if (return_cell->polytope_description == NULL) {
        free (return_cell);
        return NULL;
    }
    return return_cell;
};

/**
 * "Destructor" Deallocates the dynamically allocated memory of the region of polytopes
 */
void cell_free(cell *cell)
{
    polytope_free(cell->polytope_description);
    free(cell->safe_mode);
    free(cell);

};

/**
 * "Constructor" Dynamically allocates the space a region of polytope needs
 */
struct abstract_state *abstract_state_alloc(size_t *k,
                                            size_t k_hull,
                                            size_t n,
                                            int transitions_in_count,
                                            int transitions_out_count,
                                            int cells_count,
                                            int time_horizon)
{

    struct abstract_state *return_abstract_state = malloc (sizeof (struct abstract_state));

    /*
     * Default values: at initialization safe mode is not yet computed.
     * Thus it is assumed that the state does not contain an invariant set => invariant_set = NULL
     * next_state (next state the system has to transition to reach an invariant set) is unknown at initialization.
     */

    return_abstract_state->next_state = NULL;
    return_abstract_state->invariant_set = NULL;
//    return_abstract_state->distance_invariant_set = INFINITY;

    return_abstract_state->cells = malloc(sizeof(cell)*cells_count);
    if (return_abstract_state->cells == NULL) {
        free (return_abstract_state);
        return NULL;
    }

    for(int i = 0; i < cells_count; i++){
        return_abstract_state->cells[i] = cell_alloc(*(k+i), n, time_horizon);
        if (return_abstract_state->cells[i] == NULL) {
            free (return_abstract_state);
            return NULL;
        }
    }

    return_abstract_state->cells_count = cells_count;

    return_abstract_state->transitions_in = malloc(sizeof(struct abstract_state) * transitions_in_count);
    if (return_abstract_state->transitions_in == NULL) {
        free (return_abstract_state);
        return NULL;
    }

    return_abstract_state->transitions_in_count = transitions_in_count;

    return_abstract_state->transitions_out = malloc(sizeof(struct abstract_state) * transitions_out_count);
    if (return_abstract_state->transitions_out == NULL) {
        free (return_abstract_state);
        return NULL;
    }

    return_abstract_state->transitions_out_count = transitions_out_count;

    return_abstract_state->convex_hull = polytope_alloc(k_hull, n);
    if (return_abstract_state->convex_hull == NULL) {
        free (return_abstract_state);
        return NULL;
    }

    return return_abstract_state;
};

/**
 * "Destructor" Deallocates the dynamically allocated memory of the region of polytopes
 */
void abstract_state_free(abstract_state * abstract_state){
    polytope_free(abstract_state->convex_hull);
    for(int i = 0; i< abstract_state->cells_count; i++){
        cell_free(abstract_state->cells[i]);
    }
    free(abstract_state->transitions_out);
    free(abstract_state->transitions_in);
    free(abstract_state->cells);
    free(abstract_state);

};

/**
 * Converts two C arrays to a polytope consistent of a left side matrix (i.e. H) and right side vector (i.e. G)
 */
void polytope_from_arrays(polytope *polytope,
                          double *left_side,
                          double *right_side,
                          double *cheby,
                          char*name)
{

    gsl_matrix_from_array(polytope->H, left_side, name);
    gsl_vector_from_array(polytope->G, right_side, name);
    for(int i = 0; i < polytope->H->size2; i++){
        polytope->chebyshev_center[i] = cheby[i];
    }
};

/**
 * Converts a polytope in gsl form to cdd constraint form
 */
dd_PolyhedraPtr polytope_to_cdd(polytope *original,
                                dd_ErrorType *err)
{
    dd_PolyhedraPtr new;
    dd_MatrixPtr constraints;
    constraints = dd_CreateMatrix(original->H->size1, (original->H->size2+1));
    for (size_t k = 0; k < (original->H->size1); k++) {
        double value = gsl_vector_get(original->G, k);
        dd_set_d(constraints->matrix[k][0],value);
    }
    for(size_t i = 0; i<original->H->size1; i++){
        for (size_t j = 1; j < (original->H->size2+1); j++) {
            double value = gsl_matrix_get(original->H, i, j-1);
            dd_set_d(constraints->matrix[i][j],-1*value);
        }
    }
    constraints->representation=dd_Inequality;
    new = dd_DDMatrix2Poly(constraints, err);
    dd_FreeMatrix(constraints);
    return new;
};

/**
 * Converts a polytope in cdd constraint form to gsl form
 */
polytope * cdd_to_polytope(dd_PolyhedraPtr *original)
{

    dd_MatrixPtr constraints;
    constraints = dd_CopyInequalities(*original);
    polytope *new = polytope_alloc(constraints->rowsize, constraints->colsize-1);
    for (size_t k = 0; k < (constraints->rowsize); k++) {
        double value = dd_get_d(constraints->matrix[k][0]);
        gsl_vector_set(new->G, k, value);
    }
    for(size_t i = 0; i<constraints->rowsize; i++){
        for (size_t j = 0; j < (constraints->colsize-1); j++) {
            double value = dd_get_d(constraints->matrix[i][j+1]);
            gsl_matrix_set(new->H,i,j,-value);
        }
    }
    dd_FreeMatrix(constraints);
    return new;

};

/**
 * Generate a polytope representing a scaled unit cube
 */
polytope * polytope_scaled_unit_cube(double scale,
                                     int dimensions)
{

    dd_PolyhedraPtr cube = cdd_scaled_unit_cube(scale, dimensions);
    polytope * return_cube = cdd_to_polytope(&cube);
    dd_FreePolyhedra(cube);

    return return_cube;
};

/**
 * Checks whether a state is in a certain polytope
 */
bool polytope_check_state(polytope *polytope,
                         gsl_vector *x)
{
    gsl_vector * result = gsl_vector_alloc(polytope->G->size);
    gsl_blas_dgemv(CblasNoTrans, 1.0, polytope->H, x, 0.0, result);
    for(size_t i = 0; i< polytope->G->size; i++){
        if(gsl_vector_get(result, i) > gsl_vector_get(polytope->G, i)){
            gsl_vector_free(result);
            return false;
        }
    }
    gsl_vector_free(result);
    return true;
};

/**
 * Checks whether polytope P1 \ issubset P2
 */
bool polytope_is_subset(polytope *P1,
                        polytope *P2)
{
    dd_ErrorType err = dd_NoError;
    dd_PolyhedraPtr first = polytope_to_cdd(P1, &err);
    dd_MatrixPtr verticesFirst = dd_CopyGenerators(first);
    gsl_vector *vertex = gsl_vector_alloc(P1->H->size2);
    gsl_vector_set_zero(vertex);
    int is_included;
    bool is_subset = true;
    for(int i = 0; i<verticesFirst->rowsize;i++){
        double valueFirst0 = dd_get_d(verticesFirst->matrix[i][0]);
        if(valueFirst0 == 1){
            for(int j = 0; j<vertex->size; j++){
                double valueFirstj = dd_get_d(verticesFirst->matrix[i][j+1]);
                gsl_vector_set(vertex,(size_t)j, valueFirstj);
            }
            is_included = polytope_check_state(P2,vertex);
            if(!is_included){
                is_subset = false;
                break;
            }
        }
    }
    gsl_vector_free(vertex);
    dd_FreePolyhedra(first);
    dd_FreeMatrix(verticesFirst);
    return is_subset;
};

/**
 * Unite inequalities of P1 and P2 in new polytope and remove redundancies
 */
polytope * polytope_unite_inequalities(polytope *P1,
                                       polytope *P2)
{
    polytope *united = polytope_alloc(P1->H->size1+P2->H->size1,P1->H->size2);
    //Unite H
    gsl_matrix_set_zero(united->H);
    gsl_matrix_view H_P1 = gsl_matrix_submatrix(united->H, 0, 0, P1->H->size1, P1->H->size2);
    gsl_matrix_memcpy(&H_P1.matrix, P1->H);
    gsl_matrix_view H_P2 = gsl_matrix_submatrix(united->H, P1->H->size1, 0, P2->H->size1, P2->H->size2);
    gsl_matrix_memcpy(&H_P2.matrix, P2->H);

    //Unite G
    gsl_vector_set_zero(united->G);
    gsl_vector_view G_P1 = gsl_vector_subvector(united->G, 0, P1->G->size);
    gsl_vector_memcpy(&G_P1.vector, P1->G);
    gsl_vector_view G_P2 = gsl_vector_subvector(united->G, P1->G->size,P2->G->size);
    gsl_vector_memcpy(&G_P2.vector, P2->G);
    polytope * return_polytope = polytope_minimize(united);

    polytope_free(united);

    return return_polytope;

};

/**
 * Project original polytope (in cdd format) to the first n dimensions
 */
polytope * polytope_projection(polytope * original,
                               size_t n)
{

    dd_ErrorType err;
    dd_PolyhedraPtr orig_cdd = polytope_to_cdd(original, &err);
    dd_PolyhedraPtr new_cdd = NULL;

    cdd_projection(&orig_cdd, &new_cdd, n, &err);
    polytope * new = cdd_to_polytope(&new_cdd);
    dd_FreePolyhedra(orig_cdd);
    dd_FreePolyhedra(new_cdd);

    return new;

};

polytope * polytope_linear_transform(polytope *original,
                                     gsl_matrix *scale){

    dd_ErrorType err = dd_NoError;
    dd_PolyhedraPtr orig_cdd = polytope_to_cdd(original, &err);

    dd_MatrixPtr vertices = dd_CopyGenerators(orig_cdd);

    dd_MatrixPtr transformed_vertices = dd_CreateMatrix(1, scale->size1+1);
    dd_MatrixPtr transformed_vertex = dd_CreateMatrix(1, scale->size1+1);
    bool new_matrix_started = false;
    gsl_vector *vertex = gsl_vector_alloc((vertices->colsize - 1));
    gsl_vector *scaled_vertex = gsl_vector_alloc(scale->size1);
    gsl_vector_set_zero(vertex);
    gsl_vector_set_zero(scaled_vertex);

    for(int i = 0; i<vertices->rowsize; i++) {
        //Check whether row represents ray or vertex
        double is_vertex = dd_get_d(vertices->matrix[i][0]);
        if (is_vertex == 1) {
            for (size_t j = 1; j < vertices->colsize; j++) {
                double value = dd_get_d(vertices->matrix[i][j]);
                gsl_vector_set(vertex, j-1, value);
            }
            gsl_blas_dgemv(CblasNoTrans, 1.0, scale, vertex, 0.0, scaled_vertex);

            if (new_matrix_started) {
                dd_set_d(transformed_vertex->matrix[0][0], 1);
                for (size_t j = 0; j < scaled_vertex->size; j++) {
                    dd_set_d(transformed_vertex->matrix[0][j + 1], gsl_vector_get(scaled_vertex, j));
                }
                dd_MatrixAppendTo(&transformed_vertices, transformed_vertex);
            } else {
                dd_set_d(transformed_vertices->matrix[0][0], 1);
                for (size_t j = 0; j < scaled_vertex->size; j++) {
                    dd_set_d(transformed_vertices->matrix[0][j + 1], gsl_vector_get(scaled_vertex, j));
                }
                new_matrix_started = true;
            }

        }
    }

    transformed_vertices->representation = dd_Generator;
    dd_PolyhedraPtr transformed_cdd = dd_DDMatrix2Poly(transformed_vertices, &err);
    polytope *transformed = cdd_to_polytope(&transformed_cdd);

    //Clean up
    dd_FreeMatrix(transformed_vertex);
    dd_FreeMatrix(transformed_vertices);
    dd_FreePolyhedra(orig_cdd);
    dd_FreePolyhedra(transformed_cdd);
    dd_FreeMatrix(vertices);
    gsl_vector_free(vertex);
    gsl_vector_free(scaled_vertex);

    return transformed;

};
/**
 * Remove redundancies from gsl polytope inequalities
 */
polytope * polytope_minimize(polytope *original)
{

    dd_ErrorType err = dd_NoError;
    dd_MatrixPtr min_matrix;

    dd_PolyhedraPtr cdd_original = polytope_to_cdd(original, &err);

    dd_PolyhedraPtr cdd_minimized = cdd_minimize(&cdd_original, &err);
    min_matrix = dd_CopyInequalities(cdd_minimized);

    polytope * minimized = cdd_to_polytope(&cdd_minimized);
    dd_FreeMatrix(min_matrix);
    dd_FreePolyhedra(cdd_original);
    dd_FreePolyhedra(cdd_minimized);

    return minimized;

};

/**
 * Compute Minkowski sum of two polytopes
 */
polytope * polytope_minkowski(polytope *P1,
                              polytope *P2)
{
    dd_ErrorType err = dd_NoError;
    dd_PolyhedraPtr A = polytope_to_cdd(P1, &err);
    dd_PolyhedraPtr B = polytope_to_cdd(P2, &err);
    dd_PolyhedraPtr C = cdd_minkowski(A,B);
    polytope * returnPolytope = cdd_to_polytope(&C);
    dd_FreePolyhedra(A);
    dd_FreePolyhedra(B);
    dd_FreePolyhedra(C);
    return returnPolytope;
};

/**
 * Compute Pontryagin difference C = A-B s.t.:
 * A-B = {c \in A-B| c+b \in A, \forall b \in B}
 */
polytope * polytope_pontryagin(polytope* A,
                               polytope* B)
{

    dd_ErrorType err;
    dd_MatrixPtr verticesA, verticesB;
    //create cddPoly A,B
    dd_PolyhedraPtr cddA = polytope_to_cdd(A, &err);

    dd_PolyhedraPtr cddB = polytope_to_cdd(B, &err);

    polytope *C = NULL;

    verticesA = dd_CopyGenerators(cddA);
    verticesB = dd_CopyGenerators(cddB);
    for(int i = 0; i<verticesB->rowsize; i++){
        //Check whether row represents ray or vertex
        double value_B0 = dd_get_d(verticesB->matrix[i][0]);
        if(value_B0 == 1){
            dd_MatrixPtr tempA;
            //A-b (where b is the vertex)
            tempA = dd_CreateMatrix(verticesA->rowsize,verticesA->colsize);
            //each vertex of A displaced by b
            for(int j = 0; j<verticesA->rowsize; j++){
                double value_A0 = dd_get_d(verticesA->matrix[j][0]);
                if(value_A0 == 1){
                    dd_set_d(tempA->matrix[j][0], 1);
                    for(int k = 1; k<verticesA->colsize; k++){
                        double value_Ak = dd_get_d(verticesA->matrix[j][k]);
                        double value_Bk = dd_get_d(verticesB->matrix[i][k]);
                        dd_set_d(tempA->matrix[j][k],(value_Ak-value_Bk));
                    }
                }else{
                    dd_set_d(tempA->matrix[j][0], 0);
                    for(int k = 1; k<verticesA->colsize; k++){
                        double value_Ak = dd_get_d(verticesA->matrix[j][k]);
                        dd_set_d(tempA->matrix[j][k],(value_Ak));
                    }
                }
            }
            dd_PolyhedraPtr cdd_temp;
            tempA->representation = dd_Generator;
            cdd_temp = dd_DDMatrix2Poly(tempA, &err);
            polytope *tempC = cdd_to_polytope(&cdd_temp);
            dd_FreePolyhedra(cdd_temp);
            if(C == NULL){
                C = polytope_alloc(tempC->H->size1,tempC->H->size2);
                gsl_matrix_memcpy(C->H,tempC->H);
                gsl_vector_memcpy(C->G,tempC->G);
                polytope_free(tempC);
            }else{
                polytope *copyC = C;
                C = polytope_unite_inequalities(copyC, tempC);
                polytope_free(tempC);
                polytope_free(copyC);
            }
            dd_FreeMatrix(tempA);
        }
    }

    dd_FreeMatrix(verticesA);
    dd_FreeMatrix(verticesB);
    dd_FreePolyhedra(cddA);
    dd_FreePolyhedra(cddB);
    return C;
};

/**
 * Set up constraints in quadratic problem for GUROBI
 */
int polytope_to_constraints_gurobi(polytope *constraints,
                                   GRBmodel *model,
                                   size_t N)
{

    int error = 0;
    double constraint_val[N];
    int ind[N];
    for(size_t i = 0; i < constraints->H->size1;i++){
        char constraint_name[5];
        sprintf(constraint_name, "c_%d", (int)i);
        for(size_t j = 0; j < N;j++){
            ind[j] = (int)j;
            constraint_val[j] = gsl_matrix_get(constraints->H,i,j);
        }

        error = GRBaddconstr(model, (int)N, ind, constraint_val, GRB_LESS_EQUAL, gsl_vector_get(constraints->G,i), constraint_name);

        if(error){
            return error;
        }
    }
    return error;
};

/**
 * Generate a polytope representing a scaled unit cube
 */
dd_PolyhedraPtr cdd_scaled_unit_cube(double scale,
                                     int dimensions)
{

    dd_MatrixPtr constraints;
    dd_PolyhedraPtr cube = NULL;
    dd_ErrorType err = dd_NoError;
    constraints = dd_CreateMatrix(dimensions*2,dimensions+1);
    for(int i = 0; i<(dimensions); i++){

            dd_set_d(constraints->matrix[2*i][0],(scale*0.5));
            dd_set_d(constraints->matrix[2*i][i+1],-1);
            dd_set_d(constraints->matrix[2*i+1][0],(scale*0.5));
            dd_set_d(constraints->matrix[2*i+1][i+1],1);
    }
    constraints->representation=dd_Inequality;
    cube = dd_DDMatrix2Poly(constraints, &err);
    dd_FreeMatrix(constraints);

    return cube;
};

/**
 * Project original polytope (in cdd format) to the first n dimensions
 */
void cdd_projection(dd_PolyhedraPtr *original,
                    dd_PolyhedraPtr *new,
                    size_t n,
                    dd_ErrorType *err)
{
    dd_MatrixPtr full=NULL,projected=NULL;
    full = dd_CopyInequalities(*original);
    dd_colrange j,d;
    dd_rowset redset,impl_linset;
    dd_colset delset;
    dd_rowindex newpos;

    d=full->colsize;
    set_initialize(&delset, d);
    for (j=n+1; j<d; j++){
        set_addelem(delset, j+1);
    }

    projected=dd_BlockElimination(full, delset, err);

    dd_MatrixCanonicalize(&projected,&impl_linset,&redset,&newpos,err);

    dd_FreeMatrix(full);
    projected->representation = dd_Inequality;
    *new = dd_DDMatrix2Poly(projected, err);
    dd_FreeMatrix(projected);
    set_free(delset);
    set_free(redset);
    set_free(impl_linset);
    free(newpos);

};

/**
 * Remove redundancies from cdd polytope inequalities
 */
dd_PolyhedraPtr cdd_minimize(dd_PolyhedraPtr *original,
                             dd_ErrorType *err)
{

    dd_rowset redset,impl_linset;
    dd_rowindex newpos;
    dd_MatrixPtr full=NULL;
    full = dd_CopyInequalities(*original);
    dd_MatrixCanonicalize(&full,&impl_linset,&redset,&newpos,err);

    full->representation = dd_Inequality;
    dd_PolyhedraPtr new = dd_DDMatrix2Poly(full, err);
    dd_FreeMatrix(full);
    set_free(redset);
    set_free(impl_linset);
    free(newpos);
    return new;

};


#include <assert.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include <asf.h>
#include "asf_tiff.h"

#include <gsl/gsl_math.h>
#include <proj_api.h>

#include "asf_jpeg.h"
#include <png.h>
#include "envi.h"

#include "dateUtil.h"
#include <time.h>
#include "matrix.h"
#include <asf_nan.h>
#include <asf_endian.h>
#include <asf_meta.h>
#include <asf_export.h>
#include <asf_raster.h>
#include <float_image.h>
#include <spheroids.h>
#include <typlim.h>
#include <hdf5.h>
#include <netcdf.h>

#define RES 16
#define MAX_PTS 256

void nc_meta_double(int group_id, char *name, char *desc, char *units,
		    double *value)
{
  int var_id;
  char *str = (char *) MALLOC(sizeof(char)*1024);
  nc_def_var(group_id, name, NC_DOUBLE, 0, 0, &var_id);
  nc_put_att_text(group_id, var_id, "long_name", strlen(desc), desc);
  if (units && strlen(units) > 0) {
    strcpy(str, units);
    nc_put_att_text(group_id, var_id, "units", strlen(str), str);
  }
  nc_put_var_double(group_id, var_id, value);
}

void nc_meta_float(int group_id, char *name, char *desc, char *units,
		   float *value)
{
  int var_id;
  char *str = (char *) MALLOC(sizeof(char)*1024);
  nc_def_var(group_id, name, NC_FLOAT, 0, 0, &var_id);
  nc_put_att_text(group_id, var_id, "long_name", strlen(desc), desc);
  if (units && strlen(units) > 0) {
    strcpy(str, units);
    nc_put_att_text(group_id, var_id, "units", strlen(str), str);
  }
  nc_put_var_float(group_id, var_id, value);
}

void nc_meta_int(int group_id, char *name, char *desc, char *units,
		 int *value)
{
  int var_id;
  char *str = (char *) MALLOC(sizeof(char)*1024);
  nc_def_var(group_id, name, NC_INT, 0, 0, &var_id);
  nc_put_att_text(group_id, var_id, "long_name", strlen(desc), desc);
  if (units && strlen(units) > 0) {
    strcpy(str, units);
    nc_put_att_text(group_id, var_id, "units", strlen(str), str);
  }
  nc_put_var_int(group_id, var_id, value);
}

void nc_meta_str(int group_id, char *name, char *desc, char *units,
		 char *value)
{
  int var_id;
  const char *str_value = (char *) MALLOC(sizeof(char)*strlen(value));
  strcpy(str_value, value);
  char *str = (char *) MALLOC(sizeof(char)*1024);
  nc_def_var(group_id, name, NC_STRING, 0, 0, &var_id);
  nc_put_att_text(group_id, var_id, "long_name", strlen(desc), desc);
  if (units && strlen(units) > 0) {
    strcpy(str, units);
    nc_put_att_text(group_id, var_id, "units", strlen(str), str);
  }
  nc_put_var_string(group_id, var_id, &str_value);
}

netcdf_t *initialize_netcdf_file(const char *output_file,
				 meta_parameters *meta)
{
  int ii, status, ncid, var_id;
  int dim_xgrid_id, dim_ygrid_id, dim_lat_id, dim_lon_id, dim_time_id;
  char *spatial_ref=NULL, *datum=NULL, *spheroid=NULL;

  // Convenience variables
  meta_general *mg = meta->general;
  meta_sar *ms = meta->sar;
  meta_state_vectors *mo = meta->state_vectors;
  meta_projection *mp = meta->projection;

  // Assign parameters
  int projected = FALSE;
  int band_count = mg->band_count;
  int variable_count = band_count + 3;
  if (mp && mp->type != SCANSAR_PROJECTION) {
    projected = TRUE;
    variable_count += 2;
  }
  size_t line_count = mg->line_count;
  size_t sample_count = mg->sample_count;

  // Assign data type
  nc_type datatype;
  if (mg->data_type == BYTE)
    datatype = NC_CHAR;
  else if (mg->data_type == REAL32)
    datatype = NC_FLOAT;

  // Initialize the netCDF pointer structure
  netcdf_t *netcdf = (netcdf_t *) MALLOC(sizeof(netcdf_t));
  netcdf->var_count = variable_count;
  netcdf->var_id = (int *) MALLOC(sizeof(int)*variable_count);

  // Create the actual file
  status = nc_create(output_file, NC_CLOBBER|NC_NETCDF4, &ncid);
  netcdf->ncid = ncid;
  if (status != NC_NOERR)
    asfPrintError("Could not open netCDF file (%s).\n", nc_strerror(status));

  // Define dimensions
  if (projected) {
    nc_def_dim(ncid, "xgrid", sample_count, &dim_xgrid_id);
    nc_def_dim(ncid, "ygrid", line_count, &dim_ygrid_id);
  }
  else {
    status = nc_def_dim(ncid, "longitude", sample_count, &dim_lon_id);
    if (status != NC_NOERR)
      asfPrintError("Problem with longitude definition\n");
    status = nc_def_dim(ncid, "latitude", line_count, &dim_lat_id);
    if (status != NC_NOERR)
      asfPrintError("Problem with latitude definition\n");
  }
  status = nc_def_dim(ncid, "time", 1, &dim_time_id);
  if (status != NC_NOERR)
    asfPrintError("Problem with time definition\n");

  // Define projection
  char *str = (char *) MALLOC(sizeof(char)*1024);
  double lfValue;
  if (projected) {
    nc_def_var(ncid, "projection", NC_CHAR, 0, 0, &var_id);
    if (mp->type == UNIVERSAL_TRANSVERSE_MERCATOR) {

      strcpy(str, "transverse_mercator");
      nc_put_att_text(ncid, var_id, "grid_mapping_name", strlen(str), str);
      nc_put_att_double(ncid, var_id, "scale_factor_at_central_meridian",
			NC_DOUBLE, 1, &mp->param.utm.scale_factor);
      nc_put_att_double(ncid, var_id, "longitude_of_central_meridian",
			NC_DOUBLE, 1, &mp->param.utm.lon0);
      nc_put_att_double(ncid, var_id, "latitude_of_projection_origin",
			NC_DOUBLE, 1, &mp->param.utm.lat0);
      nc_put_att_double(ncid, var_id, "false_easting", NC_DOUBLE, 1,
			&mp->param.utm.false_easting);
      nc_put_att_double(ncid, var_id, "false_northing", NC_DOUBLE, 1,
			&mp->param.utm.false_northing);
      strcpy(str, "xgrid");
      nc_put_att_text(ncid, var_id, "projection_x_coordinate", strlen(str),
		      str);
      strcpy(str, "ygrid");
      nc_put_att_text(ncid, var_id, "projection_y_coordinate", strlen(str),
		      str);
      strcpy(str, "m");
      nc_put_att_text(ncid, var_id, "units", strlen(str), str);
      nc_put_att_double(ncid, var_id, "grid_boundary_top_projected_y",
			NC_DOUBLE, 1, &mp->startY);
      lfValue = mp->startY + mg->line_count * mp->perY;
      nc_put_att_double(ncid, var_id, "grid_boundary_bottom_projected_y",
			NC_DOUBLE, 1, &lfValue);
      lfValue = mp->startX + mg->sample_count * mp->perX;
      nc_put_att_double(ncid, var_id, "grid_boundary_right_projected_x",
			NC_DOUBLE, 1, &lfValue);
      nc_put_att_double(ncid, var_id, "grid_boundary_left_projected_x",
			NC_DOUBLE, 1, &mp->startX);
      spatial_ref = (char *) MALLOC(sizeof(char)*1024);
      datum = (char *) datum_toString(mp->datum);
      spheroid = (char *) spheroid_toString(mp->spheroid);
      double flat = mp->re_major/(mp->re_major - mp->re_minor);
      sprintf(spatial_ref, "PROJCS[\"%s_UTM_Zone_%d%c\",GEOGCS[\"GCS_%s\",DATUM[\"D_%s\",SPHEROID[\"%s\",%.1lf,%-16.11g]],PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.017453292519943295]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"False_Easting\",%.1lf],PARAMETER[\"False_Northing\",%.1lf],PARAMETER[\"Central_Meridian\",%.1lf],PARAMETER[\"Scale_Factor\",%.4lf],PARAMETER[\"Latitude_Of_Origin\",%.1lf],UNIT[\"Meter\",1]]",
	      spheroid, mp->param.utm.zone, mp->hem, spheroid, datum,
	      spheroid, mp->re_major, flat, mp->param.utm.false_easting,
	      mp->param.utm.false_northing, mp->param.utm.lon0,
	      mp->param.utm.scale_factor, mp->param.utm.lat0);
      nc_put_att_text(ncid, var_id, "spatial_ref", strlen(spatial_ref),
		      spatial_ref);
      sprintf(str, "+proj=utm +zone=%d", mp->param.utm.zone);
      if (meta->general->center_latitude < 0)
	strcat(str, " +south");
      nc_put_att_text(ncid, var_id, "proj4text", strlen(str), str);
      nc_put_att_int(ncid, var_id, "zone", NC_INT, 1, &mp->param.utm.zone);
      nc_put_att_double(ncid, var_id, "semimajor_radius", NC_DOUBLE, 1,
			&mp->re_major);
      nc_put_att_double(ncid, var_id, "semiminor_radius", NC_DOUBLE, 1,
			&mp->re_minor);
      sprintf(str, "%.6lf %.6lf 0 %.6lf 0 %.6lf", mp->startX, mp->perX,
	      mp->startY, mp->perY);
      nc_put_att_text(ncid, var_id, "GeoTransform", strlen(str), str);
    }
    else if (mp->type == POLAR_STEREOGRAPHIC) {

      strcpy(str, "polar_stereographic");
      nc_put_att_text(ncid, var_id, "grid_mapping_name", strlen(str), str);
      lfValue = 90.0;
      nc_put_att_double(ncid, var_id, "straight_vertical_longitude_from_pole",
			NC_DOUBLE, 1, &mp->param.ps.slon);
      nc_put_att_double(ncid, var_id, "longitude_of_central_meridian",
			NC_DOUBLE, 1, &lfValue);
      nc_put_att_double(ncid, var_id, "standard_parallel",
			NC_DOUBLE, 1, &mp->param.ps.slat);
      nc_put_att_double(ncid, var_id, "false_easting", NC_DOUBLE, 1,
			&mp->param.ps.false_easting);
      nc_put_att_double(ncid, var_id, "false_northing", NC_DOUBLE, 1,
			&mp->param.ps.false_northing);
      strcpy(str, "xgrid");
      nc_put_att_text(ncid, var_id, "projection_x_coordinate", strlen(str),
		      str);
      strcpy(str, "ygrid");
      nc_put_att_text(ncid, var_id, "projection_y_coordinate", strlen(str),
		      str);
      strcpy(str, "m");
      nc_put_att_text(ncid, var_id, "units", strlen(str), str);
      nc_put_att_double(ncid, var_id, "grid_boundary_top_projected_y",
			NC_DOUBLE, 1, &mp->startY);
      lfValue = mp->startY + mg->line_count * mp->perY;
      nc_put_att_double(ncid, var_id, "grid_boundary_bottom_projected_y",
			NC_DOUBLE, 1, &lfValue);
      lfValue = mp->startX + mg->sample_count * mp->perX;
      nc_put_att_double(ncid, var_id, "grid_boundary_right_projected_x",
			NC_DOUBLE, 1, &lfValue);
      nc_put_att_double(ncid, var_id, "grid_boundary_left_projected_x",
			NC_DOUBLE, 1, &mp->startX);
      spatial_ref = (char *) MALLOC(sizeof(char)*1024);
      datum = (char *) datum_toString(mp->datum);
      spheroid = (char *) spheroid_toString(mp->spheroid);
      double flat = mp->re_major/(mp->re_major - mp->re_minor);
      sprintf(spatial_ref, "PROJCS[\"Stereographic_North_Pole\",GEOGCS[\"unnamed ellipse\",DATUM[\"D_unknown\",SPHEROID[\"Unknown\",%.3lf,%-16.11g]],PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.0002247191011236]],PROJECTION[\"Stereographic_North_Pole\"],PARAMETER[\"standard_parallel_1\",%.4lf],PARAMETER[\"central_meridian\",%.4lf],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_easting\",%.3lf],PARAMETER[\"false_northing\",%.3lf],UNIT[\"Meter\",1,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"3411\"]]",
	      mp->re_major, flat, mp->param.ps.slat, mp->param.ps.slon,
	      mp->param.ps.false_easting, mp->param.ps.false_northing);
      nc_put_att_text(ncid, var_id, "spatial_ref", strlen(spatial_ref),
		      spatial_ref);
      if (mp->param.ps.is_north_pole)
	sprintf(str, "+proj=stere +lat_0=90.0000 +lat_ts=%.4lf "
		"+lon_0=%.4lf +k=1 +x_0=%.3lf +y_0=%.3lf +a=%.3lf +b=%.3lf "
		"+units=m +no_defs", mp->param.ps.slat, mp->param.ps.slon,
		mp->param.ps.false_easting, mp->param.ps.false_northing,
		mp->re_major, mp->re_minor);
      else
	sprintf(str, "+proj=stere +lat_0=-90.0000 +lat_ts=%.4lf "
		"+lon_0=%.4lf +k=1 +x_0=%.3lf +y_0=%.3lf +a=%.3lf +b=%.3lf "
		"+units=m +no_defs", mp->param.ps.slat, mp->param.ps.slon,
		mp->param.ps.false_easting, mp->param.ps.false_northing,
		mp->re_major, mp->re_minor);
      nc_put_att_text(ncid, var_id, "proj4text", strlen(str), str);
      nc_put_att_double(ncid, var_id, "latitude_of_true_scale", NC_DOUBLE, 1,
			&mp->param.ps.slat);
      nc_put_att_double(ncid, var_id, "longitude_of_projection_origin",
			NC_DOUBLE, 1, &mp->param.ps.slon);
      nc_put_att_double(ncid, var_id, "semimajor_radius", NC_DOUBLE, 1,
			&mp->re_major);
      nc_put_att_double(ncid, var_id, "semiminor_radius", NC_DOUBLE, 1,
			&mp->re_minor);
      sprintf(str, "%.6lf %.6lf 0 %.6lf 0 %.6lf", mp->startX, mp->perX,
	      mp->startY, mp->perY);
      nc_put_att_text(ncid, var_id, "GeoTransform", strlen(str), str);
    }
    else if (mp->type == ALBERS_EQUAL_AREA) {

      strcpy(str, "albers_conical_equal_area");
      nc_put_att_text(ncid, var_id, "grid_mapping_name", strlen(str), str);
      lfValue = 90.0;
      nc_put_att_double(ncid, var_id, "standard_parallel_1",
			NC_DOUBLE, 1, &mp->param.albers.std_parallel1);
      nc_put_att_double(ncid, var_id, "standard_parallel_2",
			NC_DOUBLE, 1, &mp->param.albers.std_parallel2);
      nc_put_att_double(ncid, var_id, "longitude_of_central_meridian",
			NC_DOUBLE, 1, &mp->param.albers.center_meridian);
      nc_put_att_double(ncid, var_id, "latitude_of_projection_origin",
			NC_DOUBLE, 1, &mp->param.albers.orig_latitude);
      nc_put_att_double(ncid, var_id, "false_easting", NC_DOUBLE, 1,
			&mp->param.albers.false_easting);
      nc_put_att_double(ncid, var_id, "false_northing", NC_DOUBLE, 1,
			&mp->param.albers.false_northing);
      strcpy(str, "xgrid");
      nc_put_att_text(ncid, var_id, "projection_x_coordinate", strlen(str),
		      str);
      strcpy(str, "ygrid");
      nc_put_att_text(ncid, var_id, "projection_y_coordinate", strlen(str),
		      str);
      strcpy(str, "m");
      nc_put_att_text(ncid, var_id, "units", strlen(str), str);
      nc_put_att_double(ncid, var_id, "grid_boundary_top_projected_y",
			NC_DOUBLE, 1, &mp->startY);
      lfValue = mp->startY + mg->line_count * mp->perY;
      nc_put_att_double(ncid, var_id, "grid_boundary_bottom_projected_y",
			NC_DOUBLE, 1, &lfValue);
      lfValue = mp->startX + mg->sample_count * mp->perX;
      nc_put_att_double(ncid, var_id, "grid_boundary_right_projected_x",
			NC_DOUBLE, 1, &lfValue);
      nc_put_att_double(ncid, var_id, "grid_boundary_left_projected_x",
			NC_DOUBLE, 1, &mp->startX);
      spatial_ref = (char *) MALLOC(sizeof(char)*1024);
      datum = (char *) datum_toString(mp->datum);
      spheroid = (char *) spheroid_toString(mp->spheroid);
      double flat = mp->re_major/(mp->re_major - mp->re_minor);
      sprintf(spatial_ref, "PROJCS[\"Albers_Equal_Area_Conic\",GEOGCS[\"GCS_%s\",DATUM[\"D_%s\",SPHEROID[\"%s\",%.3lf,%-16.11g]],PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.0174532925199432955]],PROJECTION[\"Albers\"],PARAMETER[\"False_Easting\",%.3lf],PARAMETER[\"False_Northing\",%.3lf],PARAMETER[\"Central_Meridian\",%.4lf],PARAMETER[\"Standard_Parallel_1\",%.4lf],PARAMETER[\"Standard_Parallel_2\",%.4lf],PARAMETER[\"Latitude_Of_Origin\",%.4lf],UNIT[\"Meter\",1]]",
	      datum, datum, spheroid, mp->re_major, flat,
	      mp->param.albers.false_easting, mp->param.albers.false_northing,
	      mp->param.albers.center_meridian, mp->param.albers.std_parallel1,
	      mp->param.albers.std_parallel2, mp->param.albers.orig_latitude);
      nc_put_att_text(ncid, var_id, "spatial_ref", strlen(spatial_ref),
		      spatial_ref);
      sprintf(str, "+proj=aea +lat_1=%.4lf +lat_2=%.4lf +lat_0=%.4lf "
	      "+lon_0=%.4lf +x_0=%.3lf +y_0=%.3lf",
	      mp->param.albers.std_parallel1, mp->param.albers.std_parallel2,
	      mp->param.albers.orig_latitude, mp->param.albers.center_meridian,
	      mp->param.albers.false_easting, mp->param.albers.false_northing);
      nc_put_att_text(ncid, var_id, "proj4text", strlen(str), str);
      nc_put_att_double(ncid, var_id, "latitude_of_true_scale", NC_DOUBLE, 1,
			&mp->param.ps.slat);
      nc_put_att_double(ncid, var_id, "longitude_of_projection_origin",
			NC_DOUBLE, 1, &mp->param.ps.slon);
      nc_put_att_double(ncid, var_id, "semimajor_radius", NC_DOUBLE, 1,
			&mp->re_major);
      nc_put_att_double(ncid, var_id, "semiminor_radius", NC_DOUBLE, 1,
			&mp->re_minor);
      sprintf(str, "%.6lf %.6lf 0 %.6lf 0 %.6lf", mp->startX, mp->perX,
	      mp->startY, mp->perY);
      nc_put_att_text(ncid, var_id, "GeoTransform", strlen(str), str);
    }
    else if (mp->type == LAMBERT_CONFORMAL_CONIC) {

      strcpy(str, "lambert_conformal_conic");
      nc_put_att_text(ncid, var_id, "grid_mapping_name", strlen(str), str);
      lfValue = 90.0;
      nc_put_att_double(ncid, var_id, "standard_parallel_1",
			NC_DOUBLE, 1, &mp->param.lamcc.plat1);
      nc_put_att_double(ncid, var_id, "standard_parallel_2",
			NC_DOUBLE, 1, &mp->param.lamcc.plat2);
      nc_put_att_double(ncid, var_id, "longitude_of_central_meridian",
			NC_DOUBLE, 1, &mp->param.lamcc.lon0);
      nc_put_att_double(ncid, var_id, "latitude_of_projection_origin",
			NC_DOUBLE, 1, &mp->param.lamcc.lat0);
      nc_put_att_double(ncid, var_id, "false_easting", NC_DOUBLE, 1,
			&mp->param.lamcc.false_easting);
      nc_put_att_double(ncid, var_id, "false_northing", NC_DOUBLE, 1,
			&mp->param.lamcc.false_northing);
      strcpy(str, "xgrid");
      nc_put_att_text(ncid, var_id, "projection_x_coordinate", strlen(str),
		      str);
      strcpy(str, "ygrid");
      nc_put_att_text(ncid, var_id, "projection_y_coordinate", strlen(str),
		      str);
      strcpy(str, "m");
      nc_put_att_text(ncid, var_id, "units", strlen(str), str);
      nc_put_att_double(ncid, var_id, "grid_boundary_top_projected_y",
			NC_DOUBLE, 1, &mp->startY);
      lfValue = mp->startY + mg->line_count * mp->perY;
      nc_put_att_double(ncid, var_id, "grid_boundary_bottom_projected_y",
			NC_DOUBLE, 1, &lfValue);
      lfValue = mp->startX + mg->sample_count * mp->perX;
      nc_put_att_double(ncid, var_id, "grid_boundary_right_projected_x",
			NC_DOUBLE, 1, &lfValue);
      nc_put_att_double(ncid, var_id, "grid_boundary_left_projected_x",
			NC_DOUBLE, 1, &mp->startX);
      spatial_ref = (char *) MALLOC(sizeof(char)*1024);
      datum = (char *) datum_toString(mp->datum);
      spheroid = (char *) spheroid_toString(mp->spheroid);
      double flat = mp->re_major/(mp->re_major - mp->re_minor);
      sprintf(spatial_ref, "PROJCS[\"Lambert_Conformal_Conic\",GEOGCS[\"GCS_%s\",DATUM[\"D_%s\",SPHEROID[\"%s\",%.3lf,%-16.11g]],PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.0174532925199432955]],PROJECTION[\"Lambert_Conformal_Conic\"],PARAMETER[\"False_Easting\",%.3lf],PARAMETER[\"False_Northing\",%.3lf],PARAMETER[\"Central_Meridian\",%.4lf],PARAMETER[\"Standard_Parallel_1\",%.4lf],PARAMETER[\"Standard_Parallel_2\",%.4lf],PARAMETER[\"Latitude_Of_Origin\",%.4lf],UNIT[\"Meter\",1]]",
	      datum, datum, spheroid, mp->re_major, flat,
	      mp->param.lamcc.false_easting, mp->param.lamcc.false_northing,
	      mp->param.lamcc.lon0, mp->param.lamcc.plat1,
	      mp->param.lamcc.plat2, mp->param.lamcc.lat0);
      nc_put_att_text(ncid, var_id, "spatial_ref", strlen(spatial_ref),
		      spatial_ref);
      sprintf(str, "+proj=lcc +lat_1=%.4lf +lat_2=%.4lf +lat_0=%.4lf "
	      "+lon_0=%.4lf +x_0=%.3lf +y_0=%.3lf",
	      mp->param.lamcc.plat1, mp->param.lamcc.plat2,
	      mp->param.lamcc.lat0, mp->param.lamcc.lon0,
	      mp->param.lamcc.false_easting, mp->param.lamcc.false_northing);
      nc_put_att_text(ncid, var_id, "proj4text", strlen(str), str);
      nc_put_att_double(ncid, var_id, "semimajor_radius", NC_DOUBLE, 1,
			&mp->re_major);
      nc_put_att_double(ncid, var_id, "semiminor_radius", NC_DOUBLE, 1,
			&mp->re_minor);
      sprintf(str, "%.6lf %.6lf 0 %.6lf 0 %.6lf", mp->startX, mp->perX,
	      mp->startY, mp->perY);
      nc_put_att_text(ncid, var_id, "GeoTransform", strlen(str), str);
    }
    else if (mp->type == LAMBERT_AZIMUTHAL_EQUAL_AREA) {

      strcpy(str, "lambert_azimuthal_equal_area");
      nc_put_att_text(ncid, var_id, "grid_mapping_name", strlen(str), str);
      nc_put_att_double(ncid, var_id, "longitude_of_projection_origin",
			NC_DOUBLE, 1, &mp->param.lamaz.center_lon);
      nc_put_att_double(ncid, var_id, "latitude_of_projection_origin",
			NC_DOUBLE, 1, &mp->param.lamaz.center_lat);
      nc_put_att_double(ncid, var_id, "false_easting", NC_DOUBLE, 1,
			&mp->param.lamaz.false_easting);
      nc_put_att_double(ncid, var_id, "false_northing", NC_DOUBLE, 1,
			&mp->param.lamaz.false_northing);
      strcpy(str, "xgrid");
      nc_put_att_text(ncid, var_id, "projection_x_coordinate", strlen(str),
		      str);
      strcpy(str, "ygrid");
      nc_put_att_text(ncid, var_id, "projection_y_coordinate", strlen(str),
		      str);
      strcpy(str, "m");
      nc_put_att_text(ncid, var_id, "units", strlen(str), str);
      nc_put_att_double(ncid, var_id, "grid_boundary_top_projected_y",
			NC_DOUBLE, 1, &mp->startY);
      lfValue = mp->startY + mg->line_count * mp->perY;
      nc_put_att_double(ncid, var_id, "grid_boundary_bottom_projected_y",
			NC_DOUBLE, 1, &lfValue);
      lfValue = mp->startX + mg->sample_count * mp->perX;
      nc_put_att_double(ncid, var_id, "grid_boundary_right_projected_x",
			NC_DOUBLE, 1, &lfValue);
      nc_put_att_double(ncid, var_id, "grid_boundary_left_projected_x",
			NC_DOUBLE, 1, &mp->startX);
      spatial_ref = (char *) MALLOC(sizeof(char)*1024);
      datum = (char *) datum_toString(mp->datum);
      spheroid = (char *) spheroid_toString(mp->spheroid);
      double flat = mp->re_major/(mp->re_major - mp->re_minor);
      sprintf(spatial_ref, "PROJCS[\"Lambert_Azimuthal_Equal_Area\",GEOGCS[\"GCS_%s\",DATUM[\"D_%s\",SPHEROID[\"%s\",%.3lf,%-16.11g]],PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.0174532925199432955]],PROJECTION[\"Lambert_Conformal_Conic\"],PARAMETER[\"False_Easting\",%.3lf],PARAMETER[\"False_Northing\",%.3lf],PARAMETER[\"Central_Meridian\",%.4lf],PARAMETER[\"Latitude_Of_Origin\",%.4lf],UNIT[\"Meter\",1]]",
	      datum, datum, spheroid, mp->re_major, flat,
	      mp->param.lamaz.false_easting, mp->param.lamaz.false_northing,
	      mp->param.lamaz.center_lon, mp->param.lamaz.center_lat);
      nc_put_att_text(ncid, var_id, "spatial_ref", strlen(spatial_ref),
		      spatial_ref);
      sprintf(str, "+proj=laea +lat_0=%.4lf +lon_0=%.4lf +x_0=%.3lf "
	      "+y_0=%.3lf",
	      mp->param.lamaz.center_lat, mp->param.lamaz.center_lon,
	      mp->param.lamaz.false_easting, mp->param.lamaz.false_northing);
      nc_put_att_text(ncid, var_id, "proj4text", strlen(str), str);
      nc_put_att_double(ncid, var_id, "semimajor_radius", NC_DOUBLE, 1,
			&mp->re_major);
      nc_put_att_double(ncid, var_id, "semiminor_radius", NC_DOUBLE, 1,
			&mp->re_minor);
      sprintf(str, "%.6lf %.6lf 0 %.6lf 0 %.6lf", mp->startX, mp->perX,
	      mp->startY, mp->perY);
      nc_put_att_text(ncid, var_id, "GeoTransform", strlen(str), str);
    }
  }

  // Define variables and data attributes
  char **band_name = extract_band_names(meta->general->bands, band_count);
  int dims_bands[3];
  dims_bands[0] = dim_time_id;
  if (projected) {
    dims_bands[1] = dim_ygrid_id;
    dims_bands[2] = dim_xgrid_id;
  }
  else {
    dims_bands[1] = dim_lon_id;
    dims_bands[2] = dim_lat_id;
  }

  for (ii=0; ii<band_count; ii++) {

    sprintf(str, "%s_AMPLITUDE_IMAGE", band_name[ii]);
    nc_def_var(ncid, str, datatype, 3, dims_bands, &var_id);
    netcdf->var_id[ii] = var_id;
    nc_def_var_deflate(ncid, var_id, 0, 1, 6);
    lfValue = -999.0;
    nc_put_att_double(ncid, var_id, "FillValue", NC_DOUBLE, 1, &lfValue);
    sprintf(str, "%s", mg->sensor);
    if (mg->image_data_type < 9)
      strcat(str, " radar backscatter");
    if (mg->radiometry >= r_SIGMA_DB && mg->radiometry <= r_GAMMA_DB)
      strcat(str, " in dB");
    nc_put_att_text(ncid, var_id, "long_name", strlen(str), str);
    strcpy(str, "area: backcatter value");
    nc_put_att_text(ncid, var_id, "cell_methods", strlen(str), str);
    strcpy(str, "1");
    nc_put_att_text(ncid, var_id, "units", strlen(str), str);
    strcpy(str, "unitless normalized radar cross-section");
    if (mg->radiometry >= r_SIGMA && mg->radiometry <= r_GAMMA)
      strcat(str, " stored as powerscale");
    else if (mg->radiometry >= r_SIGMA_DB && mg->radiometry <= r_GAMMA_DB)
      strcat(str, " stored as dB=10*log10(*)");
    nc_put_att_text(ncid, var_id, "units_description", strlen(str), str);
    strcpy(str, "longitude latitude");
    nc_put_att_text(ncid, var_id, "coordinates", strlen(str), str);
    if (projected) {
      strcpy(str, "projection");
      nc_put_att_text(ncid, var_id, "grid_mapping", strlen(str), str);
    }
  }

  // Define other attributes
  ymd_date ymd;
  hms_time hms;
  parse_date(mg->acquisition_date, &ymd, &hms);

  // Time
  ii = band_count;
  int dims_time[1] = { dim_time_id };
  nc_def_var(ncid, "time", NC_FLOAT, 1, dims_time, &var_id);
  netcdf->var_id[ii] = var_id;
  strcpy(str, "seconds since 1900-01-01T00:00:00Z");
  nc_put_att_text(ncid, var_id, "units", strlen(str), str);
  strcpy(str, "scene center time");
  nc_put_att_text(ncid, var_id, "references", strlen(str), str);
  strcpy(str, "time");
  nc_put_att_text(ncid, var_id, "standard_name", strlen(str), str);
  strcpy(str, "T");
  nc_put_att_text(ncid, var_id, "axis", strlen(str), str);
  strcpy(str, "serial date");
  nc_put_att_text(ncid, var_id, "long_name", strlen(str), str);

  // Longitude
  ii++;
  if (projected) {
    int dims_lon[2] = { dim_ygrid_id, dim_xgrid_id };
    nc_def_var(ncid, "longitude", NC_FLOAT, 2, dims_lon, &var_id);
  }
  else {
    int dims_lon[2] = { dim_lon_id, dim_lat_id };
    nc_def_var(ncid, "longitude", NC_FLOAT, 2, dims_lon, &var_id);
  }
  netcdf->var_id[ii] = var_id;
  nc_def_var_deflate(ncid, var_id, 0, 1, 6);
  strcpy(str, "longitude");
  nc_put_att_text(ncid, var_id, "standard_name", strlen(str), str);
  strcpy(str, "longitude");
  nc_put_att_text(ncid, var_id, "long_name", strlen(str), str);
  strcpy(str, "degrees_east");
  nc_put_att_text(ncid, var_id, "units", strlen(str), str);
  double *valid_range = (double *) MALLOC(sizeof(double)*2);
  valid_range[0] = -180.0;
  valid_range[1] = 180.0;
  nc_put_att_double(ncid, var_id, "valid_range", NC_DOUBLE, 2, valid_range);
  FREE(valid_range);
  lfValue = -999.0;
  nc_put_att_double(ncid, var_id, "FillValue", NC_DOUBLE, 1, &lfValue);

  // Latitude
  ii++;
  if (projected) {
    int dims_lat[2] = { dim_ygrid_id, dim_xgrid_id };
    nc_def_var(ncid, "latitude", NC_FLOAT, 2, dims_lat, &var_id);
  }
  else {
    int dims_lat[2] = { dim_lat_id, dim_lon_id };
    nc_def_var(ncid, "latitude", NC_FLOAT, 2, dims_lat, &var_id);
  }
  netcdf->var_id[ii] = var_id;
  nc_def_var_deflate(ncid, var_id, 0, 1, 6);
  strcpy(str, "latitude");
  nc_put_att_text(ncid, var_id, "standard_name", strlen(str), str);
  strcpy(str, "latitude");
  nc_put_att_text(ncid, var_id, "long_name", strlen(str), str);
  strcpy(str, "degrees_north");
  nc_put_att_text(ncid, var_id, "units", strlen(str), str);
  valid_range = (double *) MALLOC(sizeof(double)*2);
  valid_range[0] = -90.0;
  valid_range[1] = 90.0;
  nc_put_att_double(ncid, var_id, "valid_range", NC_DOUBLE, 2, valid_range);
  FREE(valid_range);
  lfValue = -999.0;
  nc_put_att_double(ncid, var_id, "FillValue", NC_DOUBLE, 1, &lfValue);

  if (projected) {

    // ygrid
    ii++;
    int dims_ygrid[1] = { dim_ygrid_id };
    nc_def_var(ncid, "ygrid", NC_FLOAT, 1, dims_ygrid, &var_id);
    netcdf->var_id[ii] = var_id;
    nc_def_var_deflate(ncid, var_id, 0, 1, 6);
    strcpy(str, "projection_y_coordinates");
    nc_put_att_text(ncid, var_id, "standard_name", strlen(str), str);
    strcpy(str, "projection_grid_y_centers");
    nc_put_att_text(ncid, var_id, "long_name", strlen(str), str);
    strcpy(str, "meters");
    nc_put_att_text(ncid, var_id, "units", strlen(str), str);
    strcpy(str, "Y");
    nc_put_att_text(ncid, var_id, "axis", strlen(str), str);

    // xgrid
    ii++;
    int dims_xgrid[1] = { dim_xgrid_id };
    nc_def_var(ncid, "xgrid", NC_FLOAT, 1, dims_xgrid, &var_id);
    netcdf->var_id[ii] = var_id;
    nc_def_var_deflate(ncid, var_id, 0, 1, 6);
    strcpy(str, "projection_x_coordinates");
    nc_put_att_text(ncid, var_id, "standard_name", strlen(str), str);
    strcpy(str, "projection_grid_x_centers");
    nc_put_att_text(ncid, var_id, "long_name", strlen(str), str);
    strcpy(str, "meters");
    nc_put_att_text(ncid, var_id, "units", strlen(str), str);
    strcpy(str, "X");
    nc_put_att_text(ncid, var_id, "axis", strlen(str), str);
  }

  // Define global attributes
  strcpy(str, "CF-1.4");
  nc_put_att_text(ncid, NC_GLOBAL, "Conventions", strlen(str), str);
  strcpy(str, "Alaska Satellite Facility");
  nc_put_att_text(ncid, NC_GLOBAL, "institution", strlen(str), str);
  sprintf(str, "%s %s %s image", mg->sensor, mg->sensor_name, mg->mode);
  nc_put_att_text(ncid, NC_GLOBAL, "title", strlen(str), str);
  if (mg->image_data_type == AMPLITUDE_IMAGE)
    strcpy(str, "SAR backcatter image");
  nc_put_att_text(ncid, NC_GLOBAL, "source", strlen(str), str);
  sprintf(str, "%s", mg->basename);
  nc_put_att_text(ncid, NC_GLOBAL, "original_file", strlen(str), str);
  if (strcmp_case(mg->sensor, "RSAT-1") == 0)
    sprintf(str, "Copyright Canadian Space Agency, %d", ymd.year);
  else if (strncmp_case(mg->sensor, "ERS", 3) == 0)
    sprintf(str, "Copyright European Space Agency, %d", ymd.year);
  else if (strcmp_case(mg->sensor, "JERS-1") == 0 ||
	   strcmp_case(mg->sensor, "ALOS") == 0)
    sprintf(str, "Copyright Japan Aerospace Exploration Agency , %d",
	    ymd.year);
  nc_put_att_text(ncid, NC_GLOBAL, "comment", strlen(str), str);
  strcpy(str, "Documentation available at: www.asf.alaska.edu");
  nc_put_att_text(ncid, NC_GLOBAL, "references", strlen(str), str);
  time_t t;
  struct tm *timeinfo;
  time(&t);
  timeinfo = gmtime(&t);
  sprintf(str, "%s", asctime(timeinfo));
  chomp(str);
  strcat(str, ", UTC: netCDF File created.");
  nc_put_att_text(ncid, NC_GLOBAL, "history", strlen(str), str);

  // Metadata
  int meta_id;
  nc_def_grp(ncid, "metadata", &meta_id);

  // Metadata - general block
  nc_meta_str(meta_id, "general_name", "file name", NULL, mg->basename);
  nc_meta_str(meta_id, "general_sensor", "imaging satellite", NULL, mg->sensor);
  nc_meta_str(meta_id, "general_sensor_name", "imaging sensor", NULL,
	      mg->sensor_name);
  nc_meta_str(meta_id, "general_mode", "imaging mode", NULL, mg->mode);
  nc_meta_str(meta_id, "general_processor", "name and version of processor",
	      NULL, mg->processor);
  nc_meta_str(meta_id, "general_data_type", "type of samples (e.g. REAL64)",
	      NULL, data_type2str(mg->data_type));
  nc_meta_str(meta_id, "general_image_data_type",
	      "image data type (e.g. AMPLITUDE_IMAGE)", NULL,
	      image_data_type2str(mg->image_data_type));
  nc_meta_str(meta_id, "general_radiometry", "radiometry (e.g. SIGMA)", NULL,
	      radiometry2str(mg->radiometry));
  nc_meta_str(meta_id, "general_acquisition_date",
	      "acquisition date of the image", NULL, mg->acquisition_date);
  nc_meta_int(meta_id, "general_orbit", "orbit number of image", NULL,
	      &mg->orbit);
  if (mg->orbit_direction == 'A')
    strcpy(str, "Ascending");
  else
    strcpy(str, "Descending");
  nc_meta_str(meta_id, "general_orbit_direction", "orbit direction", NULL, str);
  nc_meta_int(meta_id, "general_frame", "frame number of image", NULL,
	      &mg->frame);
  nc_meta_int(meta_id, "general_band_count", "number of bands in image", NULL,
	      &mg->band_count);
  nc_meta_str(meta_id, "general_bands", "bands of the sensor", NULL,
	      &mg->bands);
  nc_meta_int(meta_id, "general_line_count", "number of lines in image", NULL,
	      &mg->line_count);
  nc_meta_int(meta_id, "general_sample_count", "number of samples in image",
	      NULL, &mg->sample_count);
  nc_meta_int(meta_id, "general_start_line",
	      "first line relative to original image", NULL, &mg->start_line);
  nc_meta_int(meta_id, "general_start_sample",
	      "first sample relative to original image", NULL,
	      &mg->start_sample);
  nc_meta_double(meta_id, "general_x_pixel_size", "range pixel size", "m",
		 &mg->x_pixel_size);
  nc_meta_double(meta_id, "general_y_pixel_size", "azimuth pixel size", "m",
		 &mg->y_pixel_size);
  nc_meta_double(meta_id, "general_center_latitude",
		 "approximate image center latitude", "degrees_north",
		 &mg->center_latitude);
  nc_meta_double(meta_id, "general_center_longitude",
		 "approximate image center longitude", "degrees_east",
		 &mg->center_longitude);
  nc_meta_double(meta_id,  "general_re_major", "major (equator) axis of earth",
		 "m", &mg->re_major);
  nc_meta_double(meta_id, "general_re_minor", "minor (polar) axis of earth",
		 "m", &mg->re_minor);
  nc_meta_double(meta_id, "general_bit_error_rate",
		 "fraction of bits which are in error", NULL,
		 &mg->bit_error_rate);
  nc_meta_int(meta_id, "general_missing_lines",
	      "number of missing lines in data take", NULL, &mg->missing_lines);
  nc_meta_float(meta_id, "general_no_data",
		"value indicating no data for a pixel",	NULL, &mg->no_data);

  if (ms) {
    // Metadata - SAR block
    if (ms->image_type == 'S')
      sprintf(str, "slant range");
    else if (ms->image_type == 'G')
      sprintf(str, "ground range");
    else if (ms->image_type == 'P')
      sprintf(str, "projected");
    else if (ms->image_type == 'R')
      sprintf(str, "georeferenced");
    nc_meta_str(meta_id, "sar_image_type", "image type", NULL, str);
    if (ms->look_direction == 'R')
      sprintf(str, "right");
    else if (ms->look_direction == 'L')
      sprintf(str, "left");
    nc_meta_str(meta_id, "sar_look_direction", "SAR satellite look direction",
		NULL, str);
    nc_meta_int(meta_id, "sar_look_count", "number of looks to take from SLC",
		NULL, &ms->look_count);
    nc_meta_int(meta_id, "sar_multilook", "multilooking flag", NULL,
		&ms->multilook);
    nc_meta_int(meta_id, "sar_deskewed", "zero doppler deskew flag", NULL,
		&ms->deskewed);
    nc_meta_int(meta_id, "sar_original_line_count",
		"number of lines in original image", NULL,
		&ms->original_line_count);
    nc_meta_int(meta_id, "sar_original_sample_count",
		"number of samples in original image", NULL,
		&ms->original_sample_count);
    nc_meta_double(meta_id, "sar_line_increment",
		   "line increment for sampling", NULL, &ms->line_increment);
    nc_meta_double(meta_id, "sar_sample_increment",
		   "sample increment for sampling", NULL,
		   &ms->sample_increment);
    nc_meta_double(meta_id, "sar_range_time_per_pixel",
		   "time per pixel in range", "s",
		   &ms->range_time_per_pixel);
    nc_meta_double(meta_id, "sar_azimuth_time_per_pixel",
		   "time per pixel in azimuth", "s",
		   &ms->azimuth_time_per_pixel);
    nc_meta_double(meta_id, "sar_slant_range_first_pixel",
		   "slant range to first pixel", "m",
		   &ms->slant_range_first_pixel);
    nc_meta_double(meta_id, "sar_slant_shift",
		   "error correction factor in slant range", "m",
		   &ms->slant_shift);
    nc_meta_double(meta_id, "sar_time_shift", "error correction factor in time",
		   "s", &ms->time_shift);
    nc_meta_double(meta_id, "sar_wavelength", "SAR carrier wavelength", "m",
		   &ms->wavelength);
    nc_meta_double(meta_id, "sar_pulse_repetition_frequency",
		   "pulse repetition frequency", "Hz", &ms->prf);
    nc_meta_double(meta_id, "sar_earth_radius", "earth radius at scene center",
		   "m", &ms->earth_radius);
    nc_meta_double(meta_id, "sar_satellite_height",
		   "satellite height from earth's center", "m",
		   &ms->satellite_height);
    nc_meta_double(meta_id, "sar_range_doppler_centroid",
		   "range doppler centroid", "Hz",
		   &ms->range_doppler_coefficients[0]);
    nc_meta_double(meta_id, "sar_range_doppler_linear",
		   "range doppler per range pixel", "Hz/pixel",
		   &ms->range_doppler_coefficients[1]);
    nc_meta_double(meta_id, "sar_range_doppler_quadratic",
		   "range doppler per range pixel square", "Hz/pixel^2",
		   &ms->range_doppler_coefficients[2]);
    nc_meta_double(meta_id, "sar_azimuth_doppler_centroid",
		   "azimuth doppler centroid", "Hz",
		   &ms->azimuth_doppler_coefficients[0]);
    nc_meta_double(meta_id, "sar_azimuth_doppler_linear",
		   "azimuth doppler per azimuth pixel", "Hz/pixel",
		   &ms->azimuth_doppler_coefficients[1]);
    nc_meta_double(meta_id, "sar_azimuth_doppler_quadratic",
		   "azimuth doppler per azimuth per azimuth pixel square",
		   "Hz/pixel^2", &ms->azimuth_doppler_coefficients[2]);
  }

  char tmp[50];
  if (mo) {
    // Metadata - state vector block
    nc_meta_int(meta_id, "orbit_year", "year of image start", NULL, &mo->year);
    nc_meta_int(meta_id, "orbit_day_of_year", "day of the year at image start",
		NULL, &mo->julDay);
    nc_meta_double(meta_id, "orbit_second_of_day",
		   "second of the day at image start", "seconds", &mo->second);
    int vector_count = mo->vector_count;
    nc_meta_int(meta_id, "orbit_vector_count", "number of state vectors", NULL,
		&vector_count);
    for (ii=0; ii<vector_count; ii++) {
      sprintf(tmp, "orbit_vector[%d]_time", ii+1);
      nc_meta_double(meta_id, tmp, "time relative to image start", "s",
		     &mo->vecs[ii].time);
      sprintf(tmp, "orbit_vector[%d]_position_x", ii+1);
      nc_meta_double(meta_id, tmp, "x coordinate, earth-fixed", "m",
		     &mo->vecs[ii].vec.pos.x);
      sprintf(tmp, "orbit_vector[%d]_position_y", ii+1);
      nc_meta_double(meta_id, tmp, "y coordinate, earth-fixed", "m",
		     &mo->vecs[ii].vec.pos.y);
      sprintf(tmp, "orbit_vector[%d]_position_z", ii+1);
      nc_meta_double(meta_id, tmp, "z coordinate, earth-fixed", "m",
		     &mo->vecs[ii].vec.pos.z);
      sprintf(tmp, "orbit_vector[%d]_velocity_x", ii+1);
      nc_meta_double(meta_id, tmp, "x velocity, earth-fixed", "m/s",
		     &mo->vecs[ii].vec.vel.x);
      sprintf(tmp, "orbit_vector[%d]_velocity_y", ii+1);
      nc_meta_double(meta_id, tmp, "y velocity, earth-fixed", "m/s",
		     &mo->vecs[ii].vec.vel.y);
      sprintf(tmp, "orbit_vector[%d]_velocity_z", ii+1);
      nc_meta_double(meta_id, tmp, "z velocity, earth-fixed", "m/s",
		     &mo->vecs[ii].vec.vel.z);
    }
  }

  // Finish off definition block
  nc_enddef(ncid);

  // Write ASF metadata to XML file
  char *output_file_name =
    (char *) MALLOC(sizeof(char)*(strlen(output_file)+5));
  sprintf(output_file_name, "%s.xml", output_file);
  meta_write_xml(meta, output_file_name);
  FREE(output_file_name);

  // Clean up
  FREE(str);
  if (spatial_ref)
    FREE(spatial_ref);

  return netcdf;
}

void finalize_netcdf_file(netcdf_t *netcdf, meta_parameters *md)
{
  int ncid = netcdf->ncid;
  int n = md->general->band_count;
  int nl = md->general->line_count;
  int ns = md->general->sample_count;
  long pixel_count = md->general->line_count * md->general->sample_count;
  int projected = FALSE;
  if (md->projection && md->projection->type != SCANSAR_PROJECTION)
    projected = TRUE;

  // Extra bands - Time
  float time = (float) seconds_from_str(md->general->acquisition_date);
  asfPrintStatus("Storing band 'time' ...\n");
  nc_put_var_float(ncid, netcdf->var_id[n], &time);

  // Extra bands - longitude
  n++;
  int ii, kk;
  double *value = (double *) MALLOC(sizeof(double)*MAX_PTS);
  double *l = (double *) MALLOC(sizeof(double)*MAX_PTS);
  double *s = (double *) MALLOC(sizeof(double)*MAX_PTS);
  double line, sample, lat, lon, first_value;
  float *lons = (float *) MALLOC(sizeof(float)*pixel_count);
  asfPrintStatus("Generating band 'longitude' ...\n");
  meta_get_latLon(md, 0, 0, 0.0, &lat, &lon);
  if (lon < 0.0)
    first_value = lon + 360.0;
  else
    first_value = lon;
  asfPrintStatus("Calculating grid for quadratic fit ...\n");
  for (ii=0; ii<RES; ii++) {
    for (kk=0; kk<RES; kk++) {
      line = ii * nl / RES;
      sample = kk * ns / RES;
      meta_get_latLon(md, line, sample, 0.0, &lat, &lon);
      l[ii*RES+kk] = line;
      s[ii*RES+kk] = sample;
      if (lon < 0.0)
	value[ii*RES+kk] = lon + 360.0;
      else
	value[ii*RES+kk] = lon;
    }
    asfLineMeter(ii, nl);
  }
  quadratic_2d q = find_quadratic(value, l, s, MAX_PTS);
  q.A = first_value;
  for (ii=0; ii<nl; ii++) {
    for (kk=0; kk<ns; kk++) {
      lons[ii*ns+kk] = (float)
	(q.A + q.B*ii + q.C*kk + q.D*ii*ii + q.E*ii*kk + q.F*kk*kk +
	 q.G*ii*ii*kk + q.H*ii*kk*kk + q.I*ii*ii*kk*kk + q.J*ii*ii*ii +
	 q.K*kk*kk*kk) - 360.0;
      if (lons[ii*ns+kk] < -180.0)
	lons[ii*ns+kk] += 360.0;
    }
    asfLineMeter(ii, nl);
  }
  asfPrintStatus("Storing band 'longitude' ...\n");
  nc_put_var_float(ncid, netcdf->var_id[n], &lons[0]);
  FREE(lons);

  // Extra bands - Latitude
  n++;
  float *lats = (float *) MALLOC(sizeof(float)*pixel_count);
  asfPrintStatus("Generating band 'latitude' ...\n");
  meta_get_latLon(md, 0, 0, 0.0, &lat, &lon);
  first_value = lat + 180.0;
  asfPrintStatus("Calculating grid for quadratic fit ...\n");
  for (ii=0; ii<RES; ii++) {
    for (kk=0; kk<RES; kk++) {
      line = ii * nl / RES;
      sample = kk * ns / RES;
      meta_get_latLon(md, line, sample, 0.0, &lat, &lon);
      l[ii*RES+kk] = line;
      s[ii*RES+kk] = sample;
      value[ii*RES+kk] = lat + 180.0;
    }
    asfLineMeter(ii, nl);
  }
  q = find_quadratic(value, l, s, MAX_PTS);
  q.A = first_value;
  for (ii=0; ii<nl; ii++) {
    for (kk=0; kk<ns; kk++) {
      if (md->general->orbit_direction == 'A')
	lats[(nl-ii-1)*ns+kk] = (float)
	  (q.A + q.B*ii + q.C*kk + q.D*ii*ii + q.E*ii*kk + q.F*kk*kk +
	   q.G*ii*ii*kk + q.H*ii*kk*kk + q.I*ii*ii*kk*kk + q.J*ii*ii*ii +
	   q.K*kk*kk*kk) - 180.0;
      else
	lats[ii*ns+kk] = (float)
	  (q.A + q.B*ii + q.C*kk + q.D*ii*ii + q.E*ii*kk + q.F*kk*kk +
	   q.G*ii*ii*kk + q.H*ii*kk*kk + q.I*ii*ii*kk*kk + q.J*ii*ii*ii +
	   q.K*kk*kk*kk) - 180.0;
    }
    asfLineMeter(ii, nl);
  }
  asfPrintStatus("Storing band 'latitude' ...\n");
  nc_put_var_float(ncid, netcdf->var_id[n], &lats[0]);
  FREE(lats);

  if (projected) {
    // Extra bands - ygrid
    n++;
    float *ygrids = (float *) MALLOC(sizeof(float)*pixel_count);
    for (ii=0; ii<nl; ii++) {
      for (kk=0; kk<ns; kk++)
	ygrids[ii*ns+kk] =
	  md->projection->startY + kk*md->projection->perY;
      asfLineMeter(ii, nl);
    }
    asfPrintStatus("Storing band 'ygrid' ...\n");
    nc_put_var_float(ncid, netcdf->var_id[n], &ygrids[0]);
    FREE(ygrids);

    // Extra bands - xgrid
    n++;
    float *xgrids = (float *) MALLOC(sizeof(float)*pixel_count);
    for (ii=0; ii<nl; ii++) {
      for (kk=0; kk<ns; kk++)
	xgrids[ii*ns+kk] =
	  md->projection->startX + kk*md->projection->perX;
      asfLineMeter(ii, nl);
    }
    asfPrintStatus("Storing band 'xgrid' ...\n");
    nc_put_var_float(ncid, netcdf->var_id[n], &xgrids[0]);
    FREE(xgrids);
  }

  // Close file and clean up
  int status = nc_close(ncid);
  if (status != NC_NOERR)
    asfPrintError("Could not close netCDF file (%s).\n", nc_strerror(status));
  FREE(netcdf->var_id);
  FREE(netcdf);
}