// $Id: GridCoordSys.java,v 1.9 2004/12/08 18:08:31 caron Exp $
/*
 * Copyright 1997-2000 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.dataset.grid;

import ucar.nc2.*;
import ucar.nc2.dataset.*;
import ucar.nc2.util.NamedObject;
import ucar.nc2.units.*;

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

import java.util.*;

/**
 * A georeferencing "gridded" CoordinateSystem. This describes a "grid" of coordinates, which
 *  implies a connected topology such that values next to each other in index space are next to
 *  each other in coordinate space.
 *  Note: these classes should be considered experimental and will likely be refactored in the next release.
 * <p>
 * This currently assumes that the CoordinateSystem
 *  <ol>
 *  <li> is georeferencing (has Lat, Lon or GeoX, GeoY axes)
 *  <li> x, y are 1 or 2-dimensional axes.
 *  <li> z, t are 1-dimensional axes.
 *  </ol>
 *
 * This is the common case for georeferencing coordinate systems. Mathematically it is a product set:
 *  {X,Y} x {Z} x {T}. The x and y axes may be 1 or 2 dimensional.
 *
 * <p>
 * A CoordinateSystem may have multiple horizontal and vertical axes. GridCoordSys chooses one
 *  axis corresponding to X, Y, Z, and T. It gives preference to one dimensional axes (CoordinateAxis1D).
 *
 * @author caron
 * @version $Revision: 1.9 $ $Date: 2004/12/08 18:08:31 $
 */

public class GridCoordSys extends CoordinateSystem {

  /**
   * Determine if this CoordinateSystem can be made into a GridCoordSys.
   * This currently assumes that the CoordinateSystem:
   *  <ol>
   *  <li> is georeferencing (cs.isGeoReferencing())
   *  <li> x, y are 1 or 2-dimensional axes.
   *  <li> z, t, if they exist, are 1-dimensional axes.
   *  </ol>
   * @param cs the CoordinateSystem to test
   * @return true if it can be made into a GridCoordSys.
   * @see CoordinateSystem#isGeoReferencing
   */
  public static boolean isGridCoordSys( StringBuffer sbuff, CoordinateSystem cs) {

    if (!cs.isLatLon()) {
      // do check for GeoXY ourself
      if ((cs.getXaxis() == null) || (cs.getYaxis() == null)) {
        if (sbuff != null) sbuff.append(" NO Lat,Lon or X,Y axis\n");
        return false;
      }
      if (null == cs.getProjection()) {
        if (sbuff != null) sbuff.append(" NO projection found\n");
        return false;
      }
    }

    // check ranks
    if (cs.isProductSet()) return true;

    CoordinateAxis xaxis = null, yaxis = null;
    if (cs.isGeoXY()) {
      xaxis = cs.getXaxis();
      yaxis = cs.getYaxis();
    } else if (cs.isLatLon())  {
      xaxis = cs.getLonAxis();
      yaxis = cs.getLatAxis();
    }

    if ((xaxis.getRank() > 2) || (yaxis.getRank() > 2)) {
      if (sbuff != null) sbuff.append(" X or Y rank > 2\n");
      return false;
    }
    return true;
  }

  /**
   * Determine if the CoordinateSystem cs can be made into a GridCoordSys for the Variable v.
   * @param sbuff put debug information into this StringBuffer; may be null.
   * @param cs CoordinateSystem to check.
   * @param v Variable to check.
   * @return the GridCoordSys made from cs, else null.
   */
  public static GridCoordSys makeGridCoordSys( StringBuffer sbuff, CoordinateSystem cs, VariableEnhanced v) {
    if (sbuff != null) {
      sbuff.append(" ");
      v.getNameAndDimensions(sbuff, true, false);
      sbuff.append(" check CS " + cs.getName());
    }
    if (isGridCoordSys( sbuff, cs)) {
      GridCoordSys gcs = new GridCoordSys( cs);
      if (gcs.isComplete( v)) {
        if (sbuff != null) sbuff.append(" OK\n");
        return gcs;
      } else {
        if (sbuff != null) sbuff.append(" NOT complete\n");
      }
    }

    return null;
  }


  /////////////////////////////////////////////////////////////////////////////
  private ProjectionImpl proj;
  private CoordinateAxis horizXaxis, horizYaxis;
  private CoordinateAxis1D vertZaxis, timeTaxis;
  private VerticalCT vCT;
  private Dimension timeDim;

  private boolean isDate = false;
  private boolean isLatLon = false;
  private ArrayList levels = null;
  private ArrayList times = null;
  private Date[] timeDates = null;

  /** Create a GridCoordSys from an existing Coordinate System.
   *  This will choose which axes are the XHoriz, YHoriz, Vertical, and Time.
   *  If theres a Projection, it will set its map area
   */
  public GridCoordSys( CoordinateSystem cs) {
    super();
    //super( cs.getCoordinateAxes(), cs.getCoordinateTransforms());

    if (cs.isGeoXY()) {
      horizXaxis = xAxis = cs.getXaxis();
      horizYaxis = yAxis = cs.getYaxis();
    } else if (cs.isLatLon())  {
      horizXaxis = latAxis = cs.getLonAxis();
      horizYaxis = lonAxis = cs.getLatAxis();
      isLatLon = true;
    } else
      throw new IllegalArgumentException("CoordinateSystem is not geoReferencing");
    coordAxes.add( horizXaxis);
    coordAxes.add( horizYaxis);
    proj = cs.getProjection();

    // set canonical area
    if (proj != null)
      proj.setDefaultMapArea( getBoundingBox());

    // need to generalize to non 1D vertical.
    CoordinateAxis z = hAxis = cs.getHeightAxis();
    if ((z == null) || !(z instanceof CoordinateAxis1D)) z = pAxis = cs.getPressureAxis();
    if ((z == null) || !(z instanceof CoordinateAxis1D)) z = zAxis = cs.getZaxis();
    if ((z != null) && !(z instanceof CoordinateAxis1D)) z = null;
    if (z != null) {
      vertZaxis = (CoordinateAxis1D) z;
      coordAxes.add( vertZaxis);
    } else {
      hAxis = pAxis = zAxis = null;
    }

    // look for VerticalCT
    List list = cs.getCoordinateTransforms();
    for (int i=0; i<list.size(); i++) {
      CoordinateTransform ct = (CoordinateTransform) list.get(i);
      if (ct instanceof VerticalCT) {
        vCT = (VerticalCT) ct;
        break;
      }
    }

    // need to generalize to non 1D time?.
    CoordinateAxis t = cs.getTaxis();
    if ((t != null) && (t instanceof CoordinateAxis1D)) {
      tAxis = t;
      timeTaxis = (CoordinateAxis1D) t;
      coordAxes.add( timeTaxis);
      timeDim = t.getDimension(0);
    }

    // make name based on coordinate
    Collections.sort( coordAxes, new CoordinateAxis.AxisComparator()); // canonical ordering of axes
    this.name = makeName( coordAxes);

    if (coordTrans != null)
      this.coordTrans = new ArrayList( coordTrans);

      // collect dimensions
    for (int i=0; i<coordAxes.size(); i++) {
      CoordinateAxis axis = (CoordinateAxis) coordAxes.get(i);
      List dims = axis.getDimensions();
      for (int j=0; j<dims.size(); j++) {
        Dimension dim = (Dimension) dims.get(j);
        if (!domain.contains(dim))
          domain.add(dim);
      }
    }

    makeLevels();
    makeTimes();
  }

  private VerticalTransform vt = null;
  /** Get the vertical transform, if any. */
  public VerticalTransform  getVerticalTransform() { return vt; }

  /* we have to delay making these, since we dont identify the dimensions specifically until now */
  void makeVerticalTransform( GridDataset gds) { // delay this till we know we need it
    if (vCT == null) return;

    try {
      VerticalCT.Type type = vCT.getVerticalTransformType();
      if (type == VerticalCT.Type.OceanS) {
        vt = new OceanS( gds.getNetcdfDataset(), timeDim, vCT);

      } else if (type == VerticalCT.Type.HybridSigmaPressure) {
        vt = new HybridSigmaPressure( gds.getNetcdfDataset(), timeDim, vCT);

      } else if (type == VerticalCT.Type.WRFEta) {
        vt = new WRFEta(gds.getNetcdfDataset(), timeDim, vCT);
      }

    } catch (Exception ee) {
      ee.printStackTrace();
      return;
    }


    // test
    try {
      vt.getCoordinateArray(0);
    } catch (Exception e) {
      System.out.println("VerticalTransform getCoordinateArray BAD "+vCT.getVerticalTransformType());
      //e.printStackTrace();
      return; // barf outta here
    }

    vCT.setVerticalTransform(vt);

  }


  /** Create a GridCoordSys from an existing Coordinate System and explcitly set
   *  which axes are the x, y, z, and time axes.
   *
  public GridCoordSys( CoordinateSystem cs, CoordinateAxis xaxis, CoordinateAxis yaxis,
     CoordinateAxis1D zaxis, CoordinateAxis1D taxis) {
    super( cs.getCoordinateAxes(), cs.getCoordinateTransforms());

    if (!isGeoReferencing())
      throw new IllegalArgumentException("CoordinateSystem is not geoReferencing");

    this.xaxis = xaxis;
    this.yaxis = yaxis;
    this.zaxis = zaxis;
    this.taxis = taxis;

    makeLevels();
    makeTimes();
  } */

  /** get the X Horizontal axis (either GeoX or Lon) */
  public CoordinateAxis getXHorizAxis() { return horizXaxis; }
  /** get the Y Horizontal axis (either GeoY or Lat) */
  public CoordinateAxis getYHorizAxis() { return horizYaxis; }
  /** get the Vertical axis (either Geoz, Height, or Pressure) */
  public CoordinateAxis1D getVerticalAxis() { return vertZaxis; }
  /** get the Time axis (same as getTaxis()) */
  public CoordinateAxis1D getTimeAxis() { return timeTaxis; }

  /** get the projection */
  public ProjectionImpl getProjection() { return proj; }

  /** Get the list of level names, to be used for user selection.
   *  The ith one refers to the ith level coordinate.
   * @return ArrayList of ucar.nc2.util.NamedObject, or empty list. */
  public ArrayList getLevels() { return levels; }

  /** Get the list of time names, to be used for user selection.
   *  The ith one refers to the ith time coordinate.
   * @return ArrayList of ucar.nc2.util.NamedObject, or empty list. */
  public ArrayList getTimes() { return times; }

  /** Get the list of times as Dates. Only valid if isDate() is true;
   * @return array of java.util.Date, or null. */
  public java.util.Date[] getTimeDates() { return timeDates; }

  /** is this a Lat/Lon coordinate system? */
  public boolean isLatLon() { return isLatLon; }

  /** is there a time coordinate, and can it be expressed as a Date? */
  public boolean isDate() { return isDate; }

  /** true if increasing z coordinate values means "up" in altitude */
  public boolean isZPositive() {
    if (vertZaxis == null) return false;
    if (vertZaxis.getPositive() != null) {
      return vertZaxis.getPositive().equalsIgnoreCase(CoordinateAxis.POSITIVE_UP);
    }
    if (vertZaxis.getAxisType() == AxisType.Height) return true;
    if (vertZaxis.getAxisType() == AxisType.Pressure) return false;
    return true; // default
  }

  /**
   * Given a point in x,y coordinate space, find the x,y index in the coordinate system.
   * Not implemented yet for 2D.
   * @param xpos position in x coordinate space.
   * @param ypos position in y coordinate space.
   * @param result put result in here, may be null
   * @return int[2], 0=x,1=y indices in the coordinate system of the point. These will be -1 if out of range.
   */
  public int[] findXYCoordElement(double xpos, double ypos, int[] result) {
    if (result == null)
      result = new int[2];

    if ((horizXaxis instanceof CoordinateAxis1D) && (horizYaxis instanceof CoordinateAxis1D)) {
      result[0] = ((CoordinateAxis1D) horizXaxis).findCoordElement( xpos);
      result[1] = ((CoordinateAxis1D) horizYaxis).findCoordElement( ypos);
      return result;
    } else if ((horizXaxis instanceof CoordinateAxis2D) && (horizYaxis instanceof CoordinateAxis2D)){
      result[0] = -1;
      result[1] = -1;
      return result;
      //return ((CoordinateAxis2D) xaxis).findXYCoordElement( xpos, ypos, result);
    }

    // cant happen
    throw new IllegalStateException("GridCoordSystem.findXYCoordElement");
  }


  /**
   * Given a Date, find the corresponding time index on the time coordinate axis.
   * Can only call this is hasDate() is true.
   * This will return
   * <ul>
   *  <li> i, if time(i) <= d < time(i+1).
   *  <li> -1, if d < time(0)
   *  <li> n-1, if d > time(n-1),  where n is length of time coordinates
   * </ul>
   *
   * @param d date to look for
   * @return corresponding time index on the time coordinate axis
   * @throws UnsupportedOperationException is no time axis or isDate() false
   */
  public int findTimeCoordElement(java.util.Date d) {
    if (timeTaxis == null || !isDate())
      throw new UnsupportedOperationException("GridCoordSys: ti");

    int n = (int) timeTaxis.getSize();
    long m = d.getTime();
    int index = 0;
    while(index<n) {
      if (m < timeDates[index].getTime())
        break;
      index++;
    }
    return index-1;
  }


  /* public void setName(String name) { this.name = name; }
  public void setDescription(String desc) { this.desc = desc; }
  public void setAxes(int xdim, int ydim, int zdim, int tdim,
        CoordAxisImpl xaxis, CoordAxisImpl yaxis, CoordAxisImpl zaxis, CoordAxisImpl taxis) {
    this.xdim = xdim;
    this.ydim = ydim;
    this.zdim = zdim;
    this.tdim = tdim;
    this.xaxis = xaxis;
    this.yaxis = yaxis;
    this.zaxis = zaxis;
    this.taxis = taxis;

    axes = new CoordAxisImpl[4];
    if (xdim >= 0)
      axes[xdim] = xaxis;
    if (ydim >= 0)
      axes[ydim] = yaxis;
    if (zdim >= 0)
      axes[zdim] = zaxis;
    if (tdim >= 0)
      axes[tdim] = taxis;
  }
  public void setProjection(Projection proj) {
    if (proj instanceof ProjectionImpl)
      this.proj = (ProjectionImpl) proj;
    else
      this.proj = new ProjectionAdapter( proj);

    ProjectionRect ma = getCanonicalMapArea();
    if (ma != null)
      this.proj.setDefaultMapArea( ma);
  }

    // standard contructor
  /* public GeoCoordSysImpl(String name, String desc, CoordAxisImpl[] axes, int xdim,
      int ydim, int zdim, int tdim, Projection proj) {
    this.name = name;
    this.desc = desc;
    this.xdim = xdim;
    this.ydim = ydim;
    this.zdim = zdim;
    this.tdim = tdim;

    this.axes = axes;
    xaxis = (xdim < 0) ? null : axes[xdim];
    yaxis = (ydim < 0) ? null : axes[ydim];
    zaxis = (zdim < 0) ? null : axes[zdim];
    taxis = (tdim < 0) ? null : axes[tdim];

    makeLevels();
    makeTimes();

    setProjection( proj);
  }

    // GeoCoordSys routines
  public String getDescription() { return desc; }

  public int getXdim() { return xdim; }
  public int getYdim() { return ydim; }
  public int getZdim() { return zdim; }
  public int getTdim() { return tdim; }

  public CoordAxis getXaxis() { return xaxis; }
  public CoordAxis getYaxis() { return yaxis; }
  public CoordAxisVert getZaxis() { return zaxis; }
  public CoordAxis getTaxis() { return taxis; }

  public thredds.datamodel.geoloc.Projection getProjection() { return proj; }
  public LatLonPoint getLatLonCoord( int xindex, int yindex) {
    if ((xaxis == null) || (yaxis == null))
       throw new UnsupportedOperationException("GeoCoordSysImpl.getLatLonCoord on non-horiz grid");
    return (LatLonPoint) proj.projToLatLon(xaxis.getCoordValue(xindex), yaxis.getCoordValue(yindex));
  }

    // convenience, "value-added" routines
  public CoordAxisImpl getAxis(int d) {
    if ((d < 0) || (d > axes.length))
      return null;
    else
      return axes[d];
  }
  public thredds.datamodel.geoloc.ProjectionImpl getProjectionImpl() { return proj; }
  */

  /**
   * Get the String name for the ith level(z) coordinate.
   * @param index which level coordinate
   * @return level name
   */
  public String getLevelName(int index) {
    if ((vertZaxis == null) || (index < 0) || (index >= vertZaxis.getSize()))
      throw new IllegalArgumentException("getLevelName = "+index);

    NamedAnything name = (NamedAnything) levels.get(index);
    return name.getName();
  }

  /**
   * Get the string name for the ith time coordinate.
   * @param index which time coordinate
   * @return time name.
   */
  public String getTimeName(int index) {
    if ((timeTaxis == null) || (index < 0) || (index >= timeTaxis.getSize()))
      throw new IllegalArgumentException("getTimeName = "+index);

    NamedAnything name = (NamedAnything) times.get(index);
    return name.getName();
  }

  private ProjectionRect mapArea = null;
  /**
   * Get the x,y bounding box in projection coordinates.
   */
  public ProjectionRect getBoundingBox() {
    if (mapArea == null) {

      if ((horizXaxis == null) || !horizXaxis.isNumeric() || (horizYaxis == null) || !horizYaxis.isNumeric())
        return null; // impossible

      // x,y may be 2D
      if ( !(horizXaxis instanceof CoordinateAxis1D) || !(horizYaxis instanceof CoordinateAxis1D)) {
        mapArea = new ProjectionRect( horizXaxis.getMinValue(), horizYaxis.getMinValue(),
            horizXaxis.getMaxValue(), horizYaxis.getMaxValue());

      } else {

        CoordinateAxis1D xaxis1 = (CoordinateAxis1D) horizXaxis;
        CoordinateAxis1D yaxis1 = (CoordinateAxis1D) horizYaxis;

          // add one percent on each side if its a projection. WHY?
        double dx = 0.0, dy = 0.0;
        if (!isLatLon()) {
          dx = .01 * (xaxis1.getCoordEdge((int)xaxis1.getSize())- xaxis1.getCoordEdge(0));
          dy = .01 * (yaxis1.getCoordEdge((int)yaxis1.getSize()) - yaxis1.getCoordEdge(0));
        }

        mapArea = new ProjectionRect( xaxis1.getCoordEdge(0)-dx, yaxis1.getCoordEdge(0)-dy,
          xaxis1.getCoordEdge((int)xaxis1.getSize())+dx,
          yaxis1.getCoordEdge((int)yaxis1.getSize())+dy);
      }
    }

    return mapArea;
  }

  private LatLonRect llbb = null;
  /**
   * Get x,y bounding box in lat, lon coordinates.
   * @return lat, lon bounding box.
   */
  public LatLonRect getLatLonBoundingBox() {

    if (llbb == null) {
      ProjectionRect bb = getBoundingBox();

      if (isLatLon()) {
        ProjectionPoint minPt = bb.getMinPoint();
        ProjectionPoint maxPt = bb.getMaxPoint();
        double deltaLat = maxPt.getY() - minPt.getY();
        double deltaLon = maxPt.getX() - minPt.getX();
        LatLonPoint llpt = new LatLonPointImpl(  minPt.getY(),  minPt.getX());
        llbb = new LatLonRect(llpt, deltaLat, deltaLon);

      } else {
        Projection dataProjection = getProjection();
        LatLonPoint pt1 = dataProjection.projToLatLon( bb.getMinPoint(), new LatLonPointImpl());
        LatLonPoint pt2 = dataProjection.projToLatLon( bb.getMaxPoint(), new LatLonPointImpl());
        llbb = new LatLonRect(pt1, pt2);
        // System.out.println("GridCoordSys: bb= "+bb+" llbb= "+llbb);
      }
    }

    return llbb;
  }

  /* private GeneralPath bbShape = null;
  public Shape getLatLonBoundingShape() {
    if (isLatLon())
      return getBoundingBox();

    if (bbShape == null) {
      ProjectionRect bb = getBoundingBox();
      Projection displayProjection = displayMap.getProjection();
      Projection dataProjection = getProjection();

      bbShape = new GeneralPath();

      LatLonPoint llpt = dataProjection.projToLatLon( bb.getX(), bb.getY());
      ProjectionPoint pt = displayProjection.latLonToProj( llpt);
      bbShape.lineTo(pt.getX(), pt.getY());

      llpt = dataProjection.projToLatLon( bb.getX(), bb.getY()+bb.getHeight());
      pt = displayProjection.latLonToProj( llpt);
      bbShape.lineTo(pt.getX(), pt.getY());

      llpt = dataProjection.projToLatLon( bb.getX()+bb.getWidth(), bb.getY()+bb.getHeight());
      pt = displayProjection.latLonToProj( llpt);
      bbShape.lineTo(pt.getX(), pt.getY());

      llpt = dataProjection.projToLatLon( bb.getX()+bb.getWidth(), bb.getY());
      pt = displayProjection.latLonToProj( llpt);
      bbShape.lineTo(pt.getX(), pt.getY());


      bbShape.closePath();
    }

    return bbShape;
  } */

  private StringBuffer buff = new StringBuffer(200);
  /** String representation. */
  public String toString() {
    buff.setLength(0);
    buff.append("("+getName()+") ");

    /* if (xdim >= 0) buff.append("x="+xaxis.getName()+",");
    if (ydim >= 0) buff.append("y="+yaxis.getName()+",");
    if (zdim >= 0) buff.append("z="+zaxis.getName()+",");
    if (tdim >= 0) buff.append("t="+taxis.getName()); */

    if (proj != null)
      buff.append("  Projection:"+ proj.getName()+ " "+proj.getClassName());
    return buff.toString();
  }

  /////////////////////////////////////////////////////////////////

  private void makeLevels() {
    levels = new ArrayList();
    if (vertZaxis == null)
      return;

    int n = (int)vertZaxis.getSize();
    for (int i=0; i< n; i++)
      levels.add( new NamedAnything( vertZaxis.getCoordName(i), vertZaxis.getUnitsString()));
  }

  private void makeTimes() {
    times = new ArrayList();
    if ((timeTaxis == null) || (timeTaxis.getSize() == 0))
      return;
    int n = (int)timeTaxis.getSize();

    SimpleUnit su = SimpleUnit.factory(timeTaxis.getUnitsString());
    isDate = (su != null) && (su instanceof DateUnit);
    if (isDate)
      timeDates = new Date[n];

    for (int i=0; i< n; i++) {
      if (isDate) {
        DateUnit du = (DateUnit) su;
        Date d = du.getStandardDate( timeTaxis.getCoordValue(i));
        String name = DateUnit.getStandardDateString(d);
        if (name == null)  // bug in udunits
          name = Double.toString(timeTaxis.getCoordValue(i));
        times.add( new NamedAnything( name, "date/time"));
        timeDates[i] = d;
      } else
        times.add( new NamedAnything( timeTaxis.getCoordName(i), timeTaxis.getUnitsString()));
    }
  }

  private static class NamedAnything implements NamedObject {
    private String name, desc;
    NamedAnything( String name, String desc) {
      this.name = name;
      this.desc = desc;
    }
    public String getName() { return name; }
    public String getDescription() { return desc; }
    public String toString() { return name; }
  }

}


/**
 * $Log: GridCoordSys.java,v $
 * Revision 1.9  2004/12/08 18:08:31  caron
 * implement _CoordinateAliasForDimension
 *
 * Revision 1.8  2004/12/07 01:29:29  caron
 * redo convention parsing, use _Coordinate encoding.
 *
 * Revision 1.7  2004/11/10 17:00:28  caron
 * no message
 *
 * Revision 1.6  2004/11/10 16:47:14  caron
 * no message
 *
 * Revision 1.5  2004/10/29 00:14:09  caron
 * no message
 *
 * Revision 1.4  2004/10/13 19:45:11  caron
 * add strict NCDump
 *
 * Revision 1.3  2004/10/06 19:03:41  caron
 * clean up javadoc
 * change useV3 -> useRecordsAsStructure
 * remove id, title, from NetcdfFile constructors
 * add "in memory" NetcdfFile
 *
 * Revision 1.2  2004/09/09 22:47:40  caron
 * station updates
 *
 * Revision 1.1  2004/08/16 20:53:50  caron
 * 2.2 alpha (2)
 *
 */