/*
 * $Id: GeoGridAdapter.java,v 1.49 2004/08/19 21:35:30 jeffmc Exp $
 *
 * Copyright © 1997-2004 Unidata Program Center/University Corporation for
 * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
 * support@unidata.ucar.edu.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library 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 Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

package ucar.nc2.visad;

import ucar.ma2.*;
import ucar.nc2.*;

import ucar.unidata.geoloc.*;
import ucar.unidata.geoloc.projection.*;
import ucar.unidata.geoloc.vertical.*;

import ucar.visad.ProjectionCoordinateSystem;
import ucar.visad.RadarGridCoordinateSystem;
import ucar.visad.quantities.AirPressure;
import ucar.visad.quantities.CommonUnits;

import ucar.nc2.dataset.*;
import ucar.nc2.dataset.grid.*;

import visad.*;
import visad.CartesianProductCoordinateSystem;
import visad.CommonUnit;
import visad.CoordinateSystem;
import visad.DateTime;
import visad.EmpiricalCoordinateSystem;
import visad.ErrorEstimate;
import visad.FieldImpl;
import visad.FlatField;
import visad.FunctionType;
import visad.Gridded2DSet;
import visad.Gridded3DSet;
import visad.GriddedSet;
import visad.IdentityCoordinateSystem;
import visad.Linear1DSet;
import visad.Linear2DSet;
import visad.Linear3DSet;
import visad.LinearLatLonSet;
import visad.Real;
import visad.RealTuple;
import visad.RealTupleType;
import visad.RealType;
import visad.Set;
import visad.SetException;
import visad.SampledSet;
import visad.SetException;
import visad.SetType;
import visad.SingletonSet;
import visad.Unit;
import visad.VisADException;
import visad.data.in.ArithProg;
import visad.data.in.LonArithProg;

import java.util.Iterator;
import java.util.TreeMap;

import java.io.IOException;

/**
 * Adapt a ucar.unidata.GeoGrid into VisAD Data object(s).
 *
 * GeoGridAdapter gets a Geogrid and has methods to create a
 * a VisAD 2D or 3D FlatField, or sequence of same, from the GeoGrid
 * for parameter names and time(s) desired.
 *
 * @author Don Murray
 * @version $Revision: 1.49 $ $Date: 2004/08/19 21:35:30 $
 */
public class GeoGridAdapter {

    /** the associated data source (for caching) */
    //private DataSourceImpl dataSource;

    /** the geogrid to adapt */
    private GeoGrid geoGrid = null;

    /** fixed height above ground level */
    private double fhgLevel = -999.9;

    /** the count for this instance */
    private static int count = 0;

    /** the associated netCDF file */
    private NetcdfFile ncFile;

    /** the parameter name */
    private String paramName;

    /** the parameter type */
    RealType paramType;

    /**
     * Construct a GeoGridAdapter that will take a GeoGrid and return
     * VisAD Data objects from it.
     *
     * @param geoGrid     the GeoGrid in question
     * @param paramName   name of the parameter
     * @param ncFile      file that the GeoGrid data is coming from
     *
     * @throws VisADException  unable to create one of the VisAD objects
     */
    public GeoGridAdapter(GeoGrid geoGrid, String paramName, NetcdfFile ncFile)
            throws VisADException {
        //this.dataSource = dataSource;
        if (geoGrid == null) {
            throw new IllegalArgumentException(
                "GeoGridAdapter: geogrid cannot be null");
        }
        this.ncFile    = ncFile;
        this.paramName = paramName;
        this.geoGrid   = geoGrid;

        paramType = makeRealType(paramName, getUnit(geoGrid.getUnitsString()));
    }

    /**
     * ugly, long method to create VisAD Set from a geogrid. The
     * Set can take on a variety of forms (2D or 3D, linear or gridded)
     * and has a geolocation feature either through a lat/lon
     * SetType or through a CoordinateSystem transform.
     *
     * @param geogrid  object that contains the metadata used to create
     *                 the set.
     * @param timeIndex  time index for time dependent grids
     *
     * @return GriddedSet that represents the geolocation of the data.
     *         Set is cached so all GeoGrids with the same transforms
     *         use the identical, immutable set.
     *
     * @throws VisADException  a problem creating the domain set
     */
    private GriddedSet getSpatialDomainSet(GeoGrid geogrid, int timeIndex)
            throws VisADException {

        /* get coordinates for each axis */
        GridCoordSys      gcs = geogrid.getCoordinateSystem();
        VerticalTransform vt  = gcs.getVerticalTransform();
        GriddedSet dset = null;

        // LOOK! this assumes a product set
        CoordinateAxis   xAxis = gcs.getXHorizAxis();
        CoordinateAxis   yAxis = gcs.getYHorizAxis();
        CoordinateAxis1D zAxis = gcs.getVerticalAxis();
        // Check to see if they are linear or not
        boolean isLinear = false;
        isLinear = checkLinearity(yAxis, false);
        int sizeZ = (zAxis == null)
                    ? 0
                    : (int) zAxis.getSize();
        // check ZAxis (check if x is linear and sizeZ > 0)
        if (isLinear && (sizeZ > 0)) {
            isLinear = checkLinearity(zAxis, false);
        }

        //  The possibilities that are handled are:
        //  xAxis = x[km], yAxis = y[km], zAxis = altitude[unit of length]
        //  xAxis = x[km], yAxis = y[km], zAxis = level[pressure]
        //  xAxis = lon[deg], yAxis = lat[deg], zAxis = altitude[unit of length]
        //  xAxis = lon[deg], yAxis = lat[deg], zAxis = level[pressure]
        //  NB:  if ZAxis is null or there is only one level, a 2D domain is made
        Unit xUnit = getUnit(xAxis.getUnitsString());
        Unit yUnit = getUnit(yAxis.getUnitsString());
        Unit zUnit = null;
        if (zAxis != null) {
            zUnit = getUnit(zAxis.getUnitsString());
        }

        boolean isLatLon = gcs.isLatLon();


        RealTupleType    domainTemplate = null;
        CoordinateSystem domainCS       = null;
        RealType         xType          = null;
        RealType         yType          = null;
        RealType         zType          = null;

        if (isLatLon) {

            if (isLinear) {            // check lons if x and z were linear
                isLinear = checkLinearity(xAxis, true);
            }
            //if (sizeZ <= 1) {    // 2D Domain
            if (sizeZ < 1) {           // 2D Domain
                xType          = RealType.Longitude;
                yType          = RealType.Latitude;
                domainTemplate = RealTupleType.SpatialEarth2DTuple;
            } else {                   // 3D domain

                if (Unit.canConvert(zUnit, CommonUnit.meter)) {
                    // check to see if positive is up
                    if (CoordinateAxis.POSITIVE_DOWN.equals(
                            ((CoordinateAxis1D) zAxis).getPositive())) {
                        // negate units if depth
                        zUnit = zUnit.scale(-1);
                    }
                    xType          = RealType.Longitude;
                    yType          = RealType.Latitude;
                    zType          = RealType.Altitude;
                    domainTemplate = RealTupleType.SpatialEarth3DTuple;
                } else {               //not plain old lat/lon/alt
                    xType = makeRealType((xAxis.getName().equals("Longitude"))
                                         ? "longi"
                                         : xAxis.getName(), xUnit);
                    yType = makeRealType((yAxis.getName().equals("Latitude"))
                                         ? "lati"
                                         : yAxis.getName(), yUnit);
                    zType = makeRealType(zAxis.getName(), zUnit);
                    CoordinateSystem compCS = null;

                    if (vt != null) {  // vertical transform handled later
                        compCS = new CartesianProductCoordinateSystem(
                            new IdentityCoordinateSystem(
                                RealTupleType
                                    .SpatialEarth2DTuple), new IdentityCoordinateSystem(
                                        new RealTupleType(zType)));

                        // if have z as "level" with pressure mb units
                    } else if (Unit.canConvert(zUnit, CommonUnits.MILLIBAR)) {
                        compCS = new CartesianProductCoordinateSystem(
                            new IdentityCoordinateSystem(
                                RealTupleType
                                    .SpatialEarth2DTuple), new CachingCoordinateSystem(
                                        AirPressure
                                            .getStandardAtmosphereCS()));
                    } else if
                    // elevation angle
                    (Unit.canConvert(zUnit, CommonUnit.degree) &&
                    // make sure it's not dimensionless
                    !zUnit.getIdentifier().equals("")) {
                        zType  = makeRealType("elev_angle", zUnit);
                        compCS = makeElevationCS();

                    } else {           // unimplemented coversion
                        throw new VisADException(
                            "Unable to handle Z axis with Unit " + zUnit);
                    }
                    domainTemplate =
                        new RealTupleType(xType, yType, zType,
                                          new CachingCoordinateSystem(compCS),
                                          null);
                }
            }

        } else {                       // not lat/lon

            if (isLinear) {            // check y if x and z were linear
                isLinear = checkLinearity(xAxis, false);
            }
            /* get the Projection for this GeoGrid's GridCoordSys  */
            ProjectionImpl project = gcs.getProjection();
            CoordinateSystem pCS = new CachingCoordinateSystem(
                                       new ProjectionCoordinateSystem(
                                           project));
            // make the proper RealTypes
            xType = makeRealType(xAxis.getName(), xUnit);
            yType = makeRealType(yAxis.getName(), yUnit);
            //if (sizeZ <= 1) {    // 2D Domain
            if (sizeZ < 1) {           // 2D Domain
                domainTemplate = new RealTupleType(xType, yType, pCS, null);
            } else {
                //System.out.println("non-linear 3D grid");
                CoordinateSystem compCS = null;
                zType = makeRealType(zAxis.getName(), zUnit);
                //System.out.println("zType="+zType+","+zUnit);

                if (vt != null) {      // vertical transform handled later
                    compCS = new CartesianProductCoordinateSystem(
                        pCS,
                        new IdentityCoordinateSystem(
                            new RealTupleType(zType)));

                    // only if z units convertible with meters
                } else if (Unit.canConvert(zUnit, CommonUnit.meter)) {
                    zType = makeRealType("alti", zUnit);
                    compCS = new CartesianProductCoordinateSystem(
                        pCS,
                        new IdentityCoordinateSystem(
                            new RealTupleType(RealType.Altitude)));
                    // if z units is pressure units
                } else if (Unit.canConvert(zUnit, CommonUnits.MILLIBAR)) {
                    compCS = new CartesianProductCoordinateSystem(
                        pCS,
                        new CachingCoordinateSystem(
                            AirPressure.getStandardAtmosphereCS()));
                } else {
                    throw new VisADException(
                        "Unable to handle Z axis with Unit " + zUnit);
                }
                domainTemplate =
                    new RealTupleType(xType, yType, zType,
                                      new CachingCoordinateSystem(compCS),
                                      null);
            }
        }                              // end non-lat/lon domain

        if (isLinear) {
            Linear1DSet xSet = makeLinear1DSet((CoordinateAxis1D) xAxis,
                                               xType, xUnit);
            Linear1DSet ySet = makeLinear1DSet((CoordinateAxis1D) yAxis,
                                               yType, yUnit);

            //if (sizeZ <= 1) {                      // 2D Domain
            if (sizeZ < 1) {                     // 2D Domain
                if (isLatLon == true) {
                    try {
                        dset = new LinearLatLonSet(domainTemplate,
                                                   new Linear1DSet[]{ xSet,
                                                                      ySet }, (CoordinateSystem) null,
                                                                      new Unit[]{
                                                                          xUnit,
                                                                          yUnit }, (ErrorEstimate[]) null,
                                                                          true);
                    } catch (SetException se) {  // bad values for lon
                        dset = new Linear2DSet(domainTemplate,
                                               new Linear1DSet[]{ xSet,
                                                                  ySet }, (CoordinateSystem) null,
                                                                  new Unit[]{
                                                                      xUnit,
                                                                      yUnit }, (ErrorEstimate[]) null,
                                                                      true);
                    }
                } else {
                    dset = new Linear2DSet(domainTemplate,
                                           new Linear1DSet[]{ xSet,
                                                              ySet }, (CoordinateSystem) null,
                                                              new Unit[]{
                                                                  xUnit,
                                                                  yUnit }, (ErrorEstimate[]) null,
                                                                  true);
                }
            } else {
                Linear1DSet zSet = makeLinear1DSet(zAxis, zType, zUnit);
                dset = new Linear3DSet(domainTemplate,
                                       new Linear1DSet[]{ xSet,
                                                          ySet,
                                                          zSet }, (CoordinateSystem) null,
                                                          new Unit[]{ xUnit,
                                                                      yUnit,
                                                                      zUnit }, (ErrorEstimate[]) null,
                                                                      true);
            }
        } else {                                 // not linear or only one level
            // make the gridded 3D set of coordinates
            float[][] coordData;
            int       sizeX = (int) xAxis.getSize();
            int sizeY = (int) yAxis.getSize();
            boolean is1D = xAxis.getRank() == 1;
            int[]  lengths;
            Unit[] units;
            //if (sizeZ <= 1) {                      // 2D Domain
            if (sizeZ < 1) {                     // 2D Domain
                coordData = new float[2][(is1D)
                                         ? sizeX * sizeY
                                         : sizeX];
                int idx = 0;
                if (is1D) {
                    for (int j = 0; j < sizeY; j++) {
                        for (int i = 0; i < sizeX; i++) {

                            // set or load coordinate values
                            coordData[0][idx] =
                                (float) ((CoordinateAxis1D) xAxis)
                                    .getCoordValue(i);
                            coordData[1][idx] =
                                (float) ((CoordinateAxis1D) yAxis)
                                    .getCoordValue(j);
                            idx++;
                        }
                    }
                    lengths = new int[]{ sizeX, sizeY };
                } else {                         // CoordinateAxis2D
                    int[]      shape = xAxis.getShape();
                    double[][] vals  = {
                        ((CoordinateAxis2D) xAxis).getCoordValues(),
                        ((CoordinateAxis2D) yAxis).getCoordValues()
                    };
                    coordData = Set.doubleToFloat(vals);
                    lengths   = new int[]{ shape[1], shape[0] };
                }
                units = new Unit[]{ xUnit, yUnit };
            } else {                             // 3D set
                coordData = (is1D)
                            ? new float[3][sizeX * sizeY * sizeZ]
                            : new float[3][sizeX * sizeZ];
                int idx = 0;
                if (is1D) {
                    for (int k = 0; k < sizeZ; k++) {
                        for (int j = 0; j < sizeY; j++) {
                            for (int i = 0; i < sizeX; i++) {

                                // set or load coordinate values
                                coordData[0][idx] =
                                    (float) ((CoordinateAxis1D) xAxis)
                                        .getCoordValue(i);
                                coordData[1][idx] =
                                    (float) ((CoordinateAxis1D) yAxis)
                                        .getCoordValue(j);
                                coordData[2][idx] =
                                    (float) zAxis.getCoordValue(k);
                                idx++;
                            }
                        }
                    }
                    lengths = (sizeZ > 1)
                              ? new int[]{ sizeX, sizeY, sizeZ }
                              : new int[]{ sizeX, sizeY };
                } else {
                    int[] shape = xAxis.getShape();
                    for (int k = 0; k < sizeZ; k++) {
                        for (int i = 0; i < shape[0]; i++) {
                            for (int j = 0; j < shape[1]; j++) {

                                coordData[0][idx] =
                                    (float) ((CoordinateAxis2D) xAxis)
                                        .getCoordValue(i, j);
                                coordData[1][idx] =
                                    (float) ((CoordinateAxis2D) yAxis)
                                        .getCoordValue(i, j);
                                coordData[2][idx] =
                                    (float) zAxis.getCoordValue(k);
                                idx++;
                            }
                        }
                    }
                    lengths = (sizeZ > 1)
                              ? new int[]{ shape[1], shape[0], sizeZ }
                              : new int[]{ shape[1], shape[0] };
                }
                units = new Unit[]{ xUnit, yUnit, zUnit };
            }                                    // end 3D gridded set
            dset = GriddedSet.create(domainTemplate, coordData, lengths,
                                     (CoordinateSystem) null, units,
                                     (ErrorEstimate[]) null, false);
        }                                        // end non-linear
        if (vt != null) {
            dset = makeDomainWithVerticalTransform(vt, dset, timeIndex);
        }

        return dset;
    }

    /**
     * Make a domain set that supports a vertical transform.
     *
     * @param vt    VerticalTransform
     * @param dset  3D domain set from data
     * @param timeIndex time index
     *
     * @return Gridded3DSet with EmpiricalCoordianteSystem to transform
     *         vertical coordinate to Altitude
     *
     * @throws VisADException Problem creating the CoordinateSystem
     */
    private GriddedSet makeDomainWithVerticalTransform(VerticalTransform vt, GriddedSet dset, int timeIndex)
            throws VisADException {

        Gridded3DSet newDSet = (Gridded3DSet) dset;
        try {
            CoordinateSystem cs = dset.getCoordinateSystem();
            if (cs == null) {
                return newDSet;
            }
            RealTupleType   setType = ((SetType) dset.getType()).getDomain();
            RealTupleType   refType   = cs.getReference();
            ErrorEstimate[] oldErrors = dset.getSetErrors();
            ErrorEstimate[] newErrors = new ErrorEstimate[oldErrors.length];
            float[][]       setVals   = dset.getSamples();
            // transform to the reference
            float[][] refVals = CoordinateSystem.transformCoordinates(refType,
                                    refType.getCoordinateSystem(),
                                    refType.getDefaultUnits(), newErrors,
                                    setType, cs, dset.getSetUnits(),
                                    oldErrors, setVals);

            // now create a new domain type based on the vertical transform
            Unit       vtu        = getUnit(vt.getUnitString());
            RealType[] types      = refType.getRealComponents();
            boolean    isPressure = false;
            if ( !Unit.canConvert(vtu, CommonUnit.meter)) {  // other than height
                if (Unit.canConvert(vtu, CommonUnits.MILLIBAR)) {
                    isPressure = true;
                } else {
                    throw new VisADException("unknown vertical coordinate");
                }
            }
            RealTupleType newDomainType = new RealTupleType(types[0],
                                              types[1], RealType.Altitude);

            // now, let's substitute the altitude values in
            refVals[2] = (float[]) vt.getCoordinateArray(timeIndex).get1DJavaArray(float.class);

            if (isPressure) {  // convert to altitude using standard atmos
                refVals[2] =
                    AirPressure.getStandardAtmosphereCS().toReference(
                        new float[][] {
                    refVals[2]
                }, new Unit[]{ vtu })[0];
                vtu = AirPressure.getStandardAtmosphereCS()
                    .getReferenceUnits()[0];
            }
            int[]  lengths        = dset.getLengths();
            Unit[] newDomainUnits = newDomainType.getDefaultUnits();
            newDomainUnits[2] = vtu;

            Gridded3DSet newDomain = new Gridded3DSet(newDomainType, refVals,
                                                      lengths[0], lengths[1],
                                                      lengths[2], null,
                                                      newDomainUnits,
                                                      newErrors, false, false);
            CoordinateSystem gcs = new CachingCoordinateSystem(
                                       new EmpiricalCoordinateSystem(
                                           dset, newDomain));
            RealTupleType newSetType =
                new RealTupleType(setType.getRealComponents(), gcs, null);

            newDSet = new Gridded3DSet(newSetType, setVals, lengths[0],
                                       lengths[1], lengths[2], null,
                                       dset.getSetUnits(), oldErrors, false, true);


        } catch (VisADException ve) {
            throw ve;
        } catch (Exception re) {
            throw new VisADException(re.getMessage());
        }
        return newDSet;
    }

    /**
     * Get a time ordered sequence of 2D flat fields
     *
     * @return data for all the times for a particular 2D field
     */
    public FieldImpl getSequence() {
        return makeSequence(null);
    }

    /**
     * Get a time ordered sequence of 2D flat fields
     *
     * @param timeIndices  indices of times in the data
     *
     * @return data for all the times for a particular 2D field
     */
    public FieldImpl getSequence(int[] timeIndices) {
        return makeSequence(timeIndices);
    }

    /**
     * Get all the data from this GeoGrid.
     *
     * @return  VisAD representation of this GeoGrid
     *
     * @throws VisADException  problem creating the grid
     */
    public FieldImpl getData() throws VisADException {
        return (geoGrid.getCoordinateSystem().getTimeAxis() != null)
               ? getSequence()
               : (getBaseTime() == null)
                 ? getFlatField(0)
                 : makeSequence(null);
    }

    /**
     * Create a FlatField for the particular time index.  Retrieve from
     * cache if possible.
     *
     * @param timeIndex  index into set of times
     * @return  the data at that time
     *
     * @throws VisADException  problem creating the FlatField
     */
    private FlatField getFlatField(int timeIndex) throws VisADException {
        FlatField retField = null;

        GridCoordSys gcs = geoGrid.getCoordinateSystem();

        /* get the multiarray from the GeoGrid, and make an Array
         * with the data from the appropriate time */

        GriddedSet domainSet = getSpatialDomainSet(geoGrid, timeIndex);

        Array      arr;
        try {
            arr = geoGrid.readVolumeData(timeIndex);

            // 3D grid with one level - slice to 2D grid
            if ((arr.getRank() > 2) && (domainSet.getDimension() == 2)) {
                int[] lengths    = domainSet.getLengths();
                int   sizeX      = lengths[0];
                int   sizeY      = lengths[1];
                int   levelIndex = 0;  // get the first by default
                int[] shape      = arr.getShape();
                for (int i = 0; i <= arr.getRank(); i++) {
                    // find the index whose dimension is not x or y
                    if ((shape[i] != sizeX) && (shape[i] != sizeY)) {
                        // extract the correct "z" level data:
                        arr = arr.slice(i, levelIndex);
                        break;
                    }
                }
            }

            return null;
        } catch (IOException e) {
            LogUtil.printException(log_, "getFlatField read got IOException",
                                   e);
            return null;
        }


        /* Simple Java arrays are used to make FlatFields:
         *   to hold coordinates:
         *   there are x,y,z values for every point in the 3 D array;
         *   they are ALL stored here (allows for non regular grid, possibly)
         */

        float[][] fieldarray = new float[1][];
        fieldarray[0] = (float[]) arr.get1DJavaArray(float.class);

        /*  GeoGrid was changed to use NaN by default
        float[] values = get1DValues(arr);
        Trace.call1 ("setMissingToNaN");
        fieldarray[0] = geoGrid.setMissingToNaN (values);
        Trace.call2 ("setMissingToNaN");
        */

        FunctionType ffType =
            new FunctionType(((SetType) domainSet.getType()).getDomain(),
                             paramType);
        retField = new FlatField(ffType, domainSet);
        try {
            retField.setSamples(fieldarray, false);
        }  // can't happen here
        return retField;
    }

    /**
     * Get a time ordered sequence of FlatFields
     *
     * @param timeIndices  indices of requested times
     *
     * @return all grid data for all the times requested, in proper increasing
     * time order, for the particular parameter loaded in this GeoGridAdapter.
     */
    private FieldImpl makeSequence(int[] timeIndices) {

        FieldImpl data = null;

        try {
            TreeMap          gridMap  = new TreeMap();
            GridCoordSys     geoSys   = geoGrid.getCoordinateSystem();
            CoordinateAxis1D timeAxis = geoSys.getTimeAxis();
            int[]            times;

            if (timeAxis == null) {
                times = new int[]{ 0 };
            } else if (timeIndices == null) {
                int numTimes = (int) timeAxis.getSize();
                times = new int[numTimes];
                for (int i = 0; i < numTimes; i++) {
                    times[i] = i;
                }
            } else {
                times = timeIndices;
            }

            for (int i = 0; i < times.length; i++) {
                DateTime time;
                if (times[i] >= 0) {
                    if (timeAxis != null) {
                        time = new DateTime(
                            new Real(
                                RealType.Time,
                                timeAxis.getCoordValue(times[i]),
                                ucar.visad.Util.parseUnit(
                                    timeAxis.getUnitString())));
                    } else {
                        time = getBaseTime();  // will be null if not found
                        if (time == null) {
                            if (timeAxis == null) {
                                return getFlatField(0);
                            } else {
                                // return current time.
                                // probably not good, but what the hey.
                                time = new DateTime();
                            }
                        }
                    }

                    try {
                        FlatField sample = getFlatField(times[i]);
                        if ((sample != null) && !sample.isMissing()) {
                            Range range = GridUtil.fieldMinMax(sample)[0];
                            // For now, min and max are flipped if all values
                            // were NaN
                            if ( !((range.getMin() == Float.MAX_VALUE)
                                    && (range.getMax() == Float.MIN_VALUE))) {
                                gridMap.put(time, sample);
                            }
                        }
                    } catch (VisADException ve) {
                        throw ve;
                    } catch (Exception excp) {
                        throw new Exception("Problem getting data "
                                            + excp.getMessage());
                    }
                }
            }

            //System.out.println("    found " + gridMap.size() + " times");

            java.util.Set keySet = gridMap.keySet();
            if (gridMap.size() > 0) {
                SampledSet domain = (gridMap.size() == 1)
                                    ? (SampledSet) new SingletonSet(
                                        new RealTuple(
                                            new Real[]{
                                                (DateTime) gridMap
                                                    .firstKey() }))
                                    : (SampledSet) DateTime.makeTimeSet(
                                        (DateTime[]) keySet.toArray(
                                            new DateTime[keySet.size()]));
                int i = 0;

                for (Iterator iter = keySet.iterator(); iter.hasNext(); ) {
                    FlatField field = (FlatField) gridMap.get(iter.next());

                    if (i == 0) {
                        FunctionType fType =
                            new FunctionType(RealType.Time, field.getType());

                        data = new FieldImpl(fType, domain);
                    }

                    data.setSample(i, field, false);

                    i++;
                }
            }
        } catch (Exception e) {
            LogUtil.logException("Couldn't get data ", e);
        }
        return data;
    }


    /**
     * make proper RealType from name and unit name.
     *
     * @param name    name for the RealType
     * @param unit    RealType Unit
     * @return  RealType or null
     *
     * @throws VisADException  problem creating the RealType
     */
    private RealType makeRealType(String name, Unit unit)
            throws VisADException {

        String newname = name.replace(' ', '_');
        newname = newname.replace('.', '_');

        RealType rt = RealType.getRealType(newname, unit);
        if (rt == null) {                // try again with new name;
            rt = RealType.getRealType(newname + "_" + count, unit);
            if (rt /*still*/ == null) {  // try one more time
                rt = RealType.getRealType(newname + "_" + count++, unit);
            }
        }
        return rt;
    }


    /**
     * Get the unit from the string
     *
     * @param uString    unit specification
     * @return  corresponding Unit
     */
    private Unit getUnit(String uString) {
        Unit r = null;
        try {
          ucar.nc2.units.SimpleUnit su = ucar.nc2.units.SimpleUnit.factory(uString);
          return su.getUnit();
        } catch (Exception excp) {
          System.out.println("Unknown unit " + uString);
        }
        return r;
    }

    /**
     * Make a Linear1DSet from a 1D axis.  Use the RealType for the
     * MathType of the set.  Must ensure that axis is linear
     * (use {@link #checkLinearity(CoordinateAxis, boolean}) before
     * calling this.
     *
     * @param  axis  1D CoordinateAxis
     * @param  type  MathType for LinearSet.
     *
     * @return  Linear1DSet representing the axis
     *
     * @throws VisADException  problem making set
     */
    private Linear1DSet makeLinear1DSet(CoordinateAxis1D axis, RealType type)
            throws VisADException {
        return makeLinear1DSet(axis, type, null);
    }

    /**
     * Make a Linear1DSet from a 1D axis.  Use the RealType for the
     * MathType of the set.  Must ensure that axis is linear
     * (use {@link #checkLinearity(CoordinateAxis, boolean}) before
     * calling this.
     *
     * @param  axis  1D CoordinateAxis
     * @param  type  MathType for LinearSet.
     * @param  u     unit for data
     *
     * @return  Linear1DSet representing the axis
     *
     * @throws VisADException  problem making set
     */
    private Linear1DSet makeLinear1DSet(CoordinateAxis1D axis, RealType type, Unit u)
            throws VisADException {

        Linear1DSet result =
            new Linear1DSet(type, axis.getCoordValue(0),
                            axis.getCoordValue((int) axis.getSize() - 1),
                            (int) axis.getSize(), (CoordinateSystem) null,
                            new Unit[]{ u }, (ErrorEstimate[]) null, true);  // cache the results
        return result;
    }

    /**
     * Check to see if the axis is a linear progression or not.  If
     * it's longitude, use a different algorithm.
     *
     * @param  axis  CoordinateAxis to check.
     * @param isLon  true if this is a set of longitudes
     *
     * @return  true if a linear progression, false if not or 2D axis
     *
     * @throws VisADException  unable to check
     */
    private boolean checkLinearity(CoordinateAxis axis, boolean isLon)
            throws VisADException {
        if (axis.getRank() > 1) {
            return false;
        }
        ArithProg progChecker = (isLon == true)
                                ? new LonArithProg()
                                : new ArithProg();
        int       i           = 0;
        boolean   linear      = true;
        while ((i < axis.getSize()) && linear) {
            linear = progChecker.accumulate(
                ((CoordinateAxis1D) axis).getCoordValue(i));
            i++;
        }
        return linear;
    }


    /**
     * HACK to make a RadarCoordinateSystem for a radar dataset.
     * @return  RadarCoordinateSystem
     *
     * @throws VisADException  problem making CS
     */
    private CoordinateSystem makeElevationCS() throws VisADException {
        if (ncFile == null) {
            throw new VisADException("Unable to determine center point ");
        }
        Variable lat = ncFile.findVariable("sensor_latitude");
        Variable lon = ncFile.findVariable("sensor_longitude");
        Variable alt = ncFile.findVariable("sensor_altitude");
        if ((lat == null) || (lon == null) || (alt == null)) {
            throw new VisADException(
                "Unable to find center point variables ");
        }
        Real latitude  = makeReal(lat, RealType.Latitude);
        Real longitude = makeReal(lon, RealType.Longitude);
        Real altitude  = makeReal(alt, RealType.Altitude);
        return new RadarGridCoordinateSystem(
            latitude.getValue(CommonUnit.degree),
            longitude.getValue(CommonUnit.degree),
            altitude.getValue(CommonUnit.meter));
    }

    /**
     * Make a Real from a <code>Variable</code> and a <code>RealType</code>
     *
     * @param v  variable
     * @param rt  RealType
     * @return   the Real
     *
     * @throws VisADException
     */
    private Real makeReal(Variable v, RealType rt) throws VisADException {
        Unit      unit;
        double    value;
        Attribute a = v.findAttribute("units");
        try {
            unit = (a != null)
                   ? ucar.visad.Util.parseUnit(a.getStringValue())
                   : rt.getDefaultUnit();
            Array array = v.read();
            value = array.getDouble(array.getIndex());

        } catch (IOException ioe) {
            throw new VisADException("couldn't read varaible " + v);
        } catch (Exception pe) {
            throw new VisADException("couldn't parse unit "
                                     + a.getStringValue());
        }
        return new Real(rt, value, unit);
    }

}
