/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.iosp.gempak;

import ucar.nc2.constants.CDM;

import java.io.*;

import java.net.URL;
import java.net.URLConnection;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Class to hold a lookup of gempak parameters
 *
 * @author IDV Development Team
 */
public class GempakParameterTable {

  /**
   * table to hold the  values
   */
  private HashMap<String, GempakParameter> paramMap = new HashMap<>(256);

  /**
   * table to hold the template values
   */
  private HashMap<String, GempakParameter> templateParamMap = new HashMap<>(20);

  /**
   * indices of breakpoints in the table
   */
  private static int[] indices = {
          0, 4, 38, 59, 72, 79, 90, 97
  };

  /**
   * lengths
   */
  private static int[] lengths = {
          4, 33, 21, 13, 7, 11, 6, 6
  };

  /**
   * Create a new table.
   */
  public GempakParameterTable() {
  }

    /*
ID# NAME                             UNITS                GNAM         SCALE   MISSING  HZREMAP DIRECTION
    */

  /**
   * Add parameters from the table
   *
   * @param tbl table location
   * @throws IOException problem reading table.
   */
  public void addParameters(String tbl) throws IOException {
    try (InputStream is = getInputStream(tbl)) {
      if (is == null) {
        throw new IOException("Unable to open " + tbl);
      }
      String content = readContents(is);   // LOOK this is silly - should just read one line at a time
      // List           lines   = StringUtil.split(content, "\n", false);
      String[] lines = content.split("\n");
      List<String[]> result = new ArrayList<>();
      for (String line : lines) {
        //String line  = (String) lines.get(i);
        String tline = line.trim();
        if (tline.length() == 0) {
          continue;
        }
        if (tline.startsWith("!")) {
          continue;
        }
        String[] words = new String[indices.length];
        for (int idx = 0; idx < indices.length; idx++) {
          if (indices[idx] >= tline.length()) {
            continue;
          }
          if (indices[idx] + lengths[idx] > tline.length()) {
            words[idx] = line.substring(indices[idx]);
          } else {
            words[idx] = line.substring(indices[idx],
                    indices[idx] + lengths[idx]);
          }
          //if (trimWords) {
          words[idx] = words[idx].trim();
          //}
        }
        result.add(words);
      }
      for (String[] aResult : result) {
        GempakParameter p = makeParameter(aResult);
        if (p != null) {
          if (p.getName().contains("(")) {
            templateParamMap.put(p.getName(), p);
          } else {
            paramMap.put(p.getName(), p);
          }
        }
      }
    }

  }

  /**
   * Make a parameter from the tokens
   *
   * @param words the tokens
   * @return a grid parameter (may be null)
   */
  private GempakParameter makeParameter(String[] words) {
    int num = 0;
    String description;
    if (words[0] != null) {
      num = (int) Double.parseDouble(words[0]);
    }
    if ((words[3] == null) || words[3].equals("")) {  // no param name
      return null;
    }
    String name = words[3];
    if (name.contains("-")) {
      int first = name.indexOf("-");
      int last = name.lastIndexOf("-");
      StringBuilder buf = new StringBuilder(name.substring(0, first));
      buf.append("(");
      for (int i = first; i <= last; i++) {
        buf.append("\\d");
      }
      buf.append(")");
      buf.append(name.substring(last + 1));
      name = buf.toString();
    }

    if ((words[1] == null) || words[1].equals("")) {
      description = words[3];
    } else {
      description = words[1];
    }
    String unit = words[2];
    if (unit != null) {
      unit = unit.replaceAll("\\*\\*", "");
      if (unit.equals("-")) {
        unit = "";
      }
    }
    int decimalScale;
    try {
      decimalScale = Integer.parseInt(words[4].trim());
    } catch (NumberFormatException ne) {
      decimalScale = 0;
    }

    return new GempakParameter(num, name, description, unit,
            decimalScale);
  }

  /**
   * Get the parameter for the given name
   *
   * @param name name of the parameter (eg:, TMPK);
   * @return corresponding parameter or null if not found in table
   */
  public GempakParameter getParameter(String name) {
    GempakParameter param = paramMap.get(name);
    if (param == null) {  // try the regex list
      Set<String> keys = templateParamMap.keySet();
      if (!keys.isEmpty()) {
        for (String key : keys) {
          Pattern p = Pattern.compile(key);
          Matcher m = p.matcher(name);
          if (m.matches()) {
            //System.out.println("found match " + key + " for " + name);
            String value = m.group(1);
            GempakParameter match = templateParamMap.get(key);
            param = new GempakParameter(match.getNumber(), name,
                    match.getDescription() + " (" + value
                            + " hour)", match.getUnit(),
                    match.getDecimalScale());
            paramMap.put(name, param);
            break;
          }
        }
      }
    }
    return param;
  }

  /**
   * Test
   *
   * @param args ignored
   * @throws IOException problem reading the table.
   */
  public static void main(String[] args) throws IOException {
    GempakParameterTable pt = new GempakParameterTable();
    //pt.addParameters("resources/nj22/tables/gempak/wmogrib3.tbl");
    pt.addParameters("resources/nj22/tables/gempak/params.tbl");
    if (args.length > 0) {
      String param = args[0];
      GempakParameter parm = pt.getParameter(param);
      if (parm != null) {
        System.out.println("Found " + param + ": " + parm);
      }
    }
  }

  /**
   * Read in the bytes from the given InputStream
   * and construct and return a String.
   * Closes the InputStream argument.
   *
   * @param is InputStream to read from
   * @return contents as a String
   * @throws IOException problem reading contents
   */
  private String readContents(InputStream is) throws IOException {
    return new String(readBytes(is), CDM.utf8Charset);
  }

  /**
   * Read the bytes in the given input stream.
   *
   * @param is The input stream
   * @return The bytes
   * @throws IOException On badness
   */
  private byte[] readBytes(InputStream is) throws IOException {
    int totalRead = 0;
    byte[] content = new byte[1000000];
    while (true) {
      int howMany = is.read(content, totalRead,
              content.length - totalRead);
      if (howMany < 0) {
        break;
      }
      if (howMany == 0) {
        continue;
      }
      totalRead += howMany;
      if (totalRead >= content.length) {
        byte[] tmp = content;
        int newLength = ((content.length < 25000000)
                ? content.length * 2
                : content.length + 5000000);
        content = new byte[newLength];
        System.arraycopy(tmp, 0, content, 0, totalRead);
      }
    }
    is.close();
    byte[] results = new byte[totalRead];
    System.arraycopy(content, 0, results, 0, totalRead);
    return results;
  }

  /**
   * Get the input stream to the given resource
   *
   * @param resourceName The resource name. May be a file, url,
   *                     java resource, etc.
   * @return The input stream to the resource
   */
  private InputStream getInputStream(String resourceName) throws IOException {

    // Try class loader to get resource
    ClassLoader cl = GempakParameterTable.class.getClassLoader();
    InputStream s = cl.getResourceAsStream(resourceName);
    if (s != null) {
      return s;
    }

    //Try the file system
    File f = new File(resourceName);
    if (f.exists()) {
        s = new FileInputStream(f);
    }
    if (s != null) {
      return s;
    }

    //Try it as a url
    Matcher m = Pattern.compile(" ").matcher(resourceName);
    String encodedUrl = m.replaceAll("%20");
    URL dataUrl = new URL(encodedUrl);
    URLConnection connection = dataUrl.openConnection();
    return connection.getInputStream();
  }

}


package ucar.nc2.ogc.swe;

import ucar.nc2.ogc.erddap.util.ErddapEDUnits;
import net.opengis.swe.x20.UnitReference;
import ucar.nc2.VariableSimpleIF;

/**
 * Created by cwardgar on 2014/03/06.
 */
public class NcUnitReference {
    // wml2:Collection/wml2:observationMember/om:OM_Observation/om:result/wml2:MeasurementTimeseries/
    //         wml2:defaultPointMetadata/wml2:DefaultTVPMeasurementMetadata/wml2:uom
    public static UnitReference initUom(UnitReference uom, VariableSimpleIF dataVar) {
        // @code
        String udunits = dataVar.getUnitsString();
        if (udunits == null) {  // Variable may not have a "units" attribute.
            return null;
        }

        String ucum = ErddapEDUnits.udunitsToUcum(udunits);
        uom.setCode(ucum);

        return uom;
    }

    private NcUnitReference() { }
}


/*
 * Copyright (c) 1998 - 2009. University Corporation for Atmospheric Research/Unidata
 * Portions of this software were developed by the Unidata Program at the
 * University Corporation for Atmospheric Research.
 *
 * Access and use of this software shall impose the following obligations
 * and understandings on the user. The user is granted the right, without
 * any fee or cost, to use, copy, modify, alter, enhance and distribute
 * this software, and any derivative works thereof, and its supporting
 * documentation for any purpose whatsoever, provided that this entire
 * notice appears in all copies of the software, derivative works and
 * supporting documentation.  Further, UCAR requests that the user credit
 * UCAR/Unidata in any publications that result from the use of this
 * software or in any product that includes this software. The names UCAR
 * and/or Unidata, however, may not be used in any advertising or publicity
 * to endorse or promote any products or commercial entity unless specific
 * written permission is obtained from UCAR/Unidata. The user also
 * understands that UCAR/Unidata is not obligated to provide the user with
 * any support, consulting, training or assistance of any kind with regard
 * to the use, operation and performance of this software nor to provide
 * the user with any updates, revisions, new versions or "bug fixes."
 *
 * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package ucar.nc2.dt.trajectory;

import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dataset.VariableDS;
import ucar.nc2.StructurePseudo;
import ucar.nc2.dt.*;
import ucar.nc2.units.DateUnit;
import ucar.nc2.units.SimpleUnit;
import ucar.unidata.geoloc.LatLonRect;

import java.io.IOException;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: yuanho
 * Date: Jul 14, 2009
 * Time: 1:30:40 PM
 * To change this template use File | Settings | File Templates.
 * @deprecated use ucar.nc2.ft.*
 */
public abstract class TrajectoryObsDatasetImpl extends TypedDatasetImpl implements TrajectoryObsDataset {

    /** _more_          */
    protected String trajectoryId;  // the Id String for the trajectory.

    /** _more_          */
    protected int trajectoryNumPoint;  // the num of points in the trajectory.

    /** _more_          */
    protected HashMap trajectoryVarsMap;  // a Map of all the TypedDataVariables in the trajectory keyed by their names.

    /** _more_          */
    protected Dimension trajectoryDim;

    /** _more_          */
    protected Variable dimVar;    // the variable associated with the dimension of the trajectory

    /** _more_          */
    protected Structure recordVar;

    /** _more_          */
    protected Variable latVar;

    /** _more_          */
    protected Variable lonVar;

    /** _more_          */
    protected Variable elevVar;

    /** _more_          */
    protected String dimVarUnitsString;

    /** _more_          */
    protected double elevVarUnitsConversionFactor;

    /** _more_          */
    protected TrajectoryObsDatatype trajectory;

    /**
     * _more_
     */
    public TrajectoryObsDatasetImpl() {}

    /**
     * _more_
     *
     * @param ncfile _more_
     */
    public TrajectoryObsDatasetImpl(NetcdfDataset ncfile) {
        super(ncfile);
    }

    /**
     * Setup needed for all SingleTrajectoryObsDatatypes. Can only be called once.
     *
     * Units of time varible must be udunits time units.
     * Units of latitude variable must be convertible to "degrees_north" by udunits.
     * Units of longitude variable must be convertible to "degrees_east" by udunits.
     * Units of altitude variable must be convertible to "meters" by udunits.
     *
     *
     * @param trajConfig _more_
     * @throws IllegalArgumentException if units of time, latitude, longitude, or altitude variables are not as required.
     * @throws IllegalStateException if this method has already been called.
     *
     * @throws IOException _more_
     */
    public void setTrajectoryInfo(Config trajConfig) throws IOException {

        if (trajectoryDim != null) {
            throw new IllegalStateException(
                "The setTrajectoryInfo() method can only be called once.");
        }

        this.trajectoryId  = trajConfig.getTrajectoryId();
        this.trajectoryDim = trajConfig.getTrajectoryDim();
        this.dimVar        = trajConfig.getDimensionVar();
        this.latVar        = trajConfig.getLatVar();
        this.lonVar        = trajConfig.getLonVar();
        this.elevVar       = trajConfig.getElevVar();

        trajectoryNumPoint = this.trajectoryDim.getLength();
        dimVarUnitsString =
            this.dimVar.findAttribute("units").getStringValue();

        // Check that time, lat, lon, elev units are acceptable.
     //   if (DateUnit.getStandardDate(dimVarUnitsString) == null) {
      //      throw new IllegalArgumentException("Units of time variable <"
     //               + dimVarUnitsString + "> not a date unit.");
     //   }
        String latVarUnitsString =
            this.latVar.findAttribute("units").getStringValue();
        if ( !SimpleUnit.isCompatible(latVarUnitsString, "deg")) {
            throw new IllegalArgumentException("Units of lat var <"
                    + latVarUnitsString
                    + "> not compatible with \"degrees_north\".");
        }
        String lonVarUnitsString =
            this.lonVar.findAttribute("units").getStringValue();
        if ( !SimpleUnit.isCompatible(lonVarUnitsString, "deg")) {
            throw new IllegalArgumentException("Units of lon var <"
                    + lonVarUnitsString
                    + "> not compatible with \"degrees_east\".");
        }
        String elevVarUnitsString =
            this.elevVar.findAttribute("units").getStringValue();
        if ( !SimpleUnit.isCompatible(elevVarUnitsString, "meters")) {
            throw new IllegalArgumentException("Units of elev var <"
                    + elevVarUnitsString
                    + "> not compatible with \"meters\".");
        }

        try {
            elevVarUnitsConversionFactor =
                getMetersConversionFactor(elevVarUnitsString);
        } catch (Exception e) {
            throw new IllegalArgumentException(
                "Exception on getMetersConversionFactor() for the units of elev var <"
                + elevVarUnitsString + ">.");
        }

        if (this.netcdfDataset.hasUnlimitedDimension()
                && this.netcdfDataset.getUnlimitedDimension().equals(trajectoryDim)) {
            Object result = this.netcdfDataset.sendIospMessage(
                                NetcdfFile.IOSP_MESSAGE_ADD_RECORD_STRUCTURE);
            if ((result != null) && (Boolean) result) {
                this.recordVar =
                    (Structure) this.netcdfDataset.getRootGroup().findVariable(
                        "record");
            } else {
                this.recordVar = new StructurePseudo(this.netcdfDataset, null,
                        "record", trajectoryDim);
            }
        } else {
            this.recordVar = new StructurePseudo(this.netcdfDataset, null, "record",
                    trajectoryDim);
        }

        // @todo HACK, HACK, HACK - remove once addRecordStructure() deals with ncd attribute changes.
        Variable elevVarInRecVar =
            this.recordVar.findVariable(this.elevVar.getFullNameEscaped());
        if ( !elevVarUnitsString.equals(
                elevVarInRecVar.findAttribute("units").getStringValue())) {
            elevVarInRecVar.addAttribute(new Attribute("units",
                    elevVarUnitsString));
        }

        trajectoryVarsMap = new HashMap();
        //for ( Iterator it = this.recordVar.getVariables().iterator(); it.hasNext(); )
        for (Iterator it =
                this.netcdfDataset.getRootGroup().getVariables().iterator();
                it.hasNext(); ) {
            Variable curVar = (Variable) it.next();
            if ((curVar.getRank() > 0) && !curVar.equals(this.dimVar)
                    && !curVar.equals(this.latVar)
                    && !curVar.equals(this.lonVar)
                    && !curVar.equals(this.elevVar)
                    && ((this.recordVar == null)
                        ? true
                        : !curVar.equals(this.recordVar))) {
                MyTypedDataVariable typedVar =
                    new MyTypedDataVariable(new VariableDS(null, curVar,
                        true));
                dataVariables.add(typedVar);
                trajectoryVarsMap.put(typedVar.getShortName(), typedVar);
            }
        }

        trajectory = new Trajectory(this.trajectoryId,
                                          trajectoryNumPoint, this.dimVar,
                                          dimVarUnitsString, this.latVar,
                                          this.lonVar, this.elevVar,
                                          dataVariables, trajectoryVarsMap);

        startDate = getStartDate();
        endDate   = getEndDate();

        ((Trajectory) trajectory).setStartDate(startDate);
        ((Trajectory) trajectory).setEndDate(endDate);

    }

    /**
     * _more_
     *
     * @param unitsString _more_
     *
     * @return _more_
     *
     * @throws Exception _more_
     */
    protected static double getMetersConversionFactor(
            String unitsString) throws Exception {
        SimpleUnit unit = SimpleUnit.factoryWithExceptions(unitsString);
        return unit.convertTo(1.0, SimpleUnit.meterUnit);
    }

    /**
     * _more_
     */
  //  Date getStartDate() {}

    /**
     * _more_
     */
  //  protected void setEndDate() {}

    /**
     * _more_
     */
    protected void setBoundingBox() {}

    /**
     * _more_
     *
     * @return _more_
     */
    public List getTrajectoryIds() {
        List l = new ArrayList();
        l.add(trajectoryId);
        return (l);
    }

    /**
     * _more_
     *
     * @return _more_
     */
    public List getTrajectories()  // throws IOException;
    {
        List l = new ArrayList();
        l.add(trajectory);
        return (l);
    }

    /**
     * _more_
     *
     * @param trajectoryId _more_
     *
     * @return _more_
     */
    public TrajectoryObsDatatype getTrajectory(String trajectoryId)  // throws IOException;
    {
        if ( !trajectoryId.equals(this.trajectoryId)) {
            return (null);
        }
        return (trajectory);
    }

    /**
     * _more_
     *
     * @return _more_
     */
    public String getDetailInfo() {
        StringBuffer sbuff = new StringBuffer();
        sbuff.append("TrajectoryObsDataset\n");
        sbuff.append("  adapter   = " + getClass().getName() + "\n");
        sbuff.append("  trajectories:" + "\n");
        for (Iterator it =
                this.getTrajectoryIds().iterator(); it.hasNext(); ) {
            sbuff.append("      " + (String) it.next() + "\n");
        }
        sbuff.append(super.getDetailInfo());

        return sbuff.toString();
    }

    /**
     * _more_
     *
     * @return _more_
     */
    public boolean syncExtend() {
        if ( !this.netcdfDataset.hasUnlimitedDimension()) {
            return false;
        }
        try {
            if ( !this.netcdfDataset.syncExtend()) {
                return false;
            }
        } catch (IOException e) {
            return false;
        }

        // Update number of points in this TrajectoryObsDataset and in the child TrajectoryObsDatatype.
        int newNumPoints = this.trajectoryDim.getLength();
        if (this.trajectoryNumPoint >= newNumPoints) {
            return false;
        }
        this.trajectoryNumPoint = newNumPoints;
        ((Trajectory) this.trajectory).setNumPoints(
            this.trajectoryNumPoint);

        // Update end date in this TrajectoryObsDataset and in the child TrajectoryObsDatatype.
        try {
            endDate = trajectory.getTime(trajectoryNumPoint - 1);
        } catch (IOException e) {
            return false;
        }
        ((Trajectory) trajectory).setEndDate(endDate);

        return true;
    }

    /**
     * Class Config _more_
     *
     *
     * @author IDV Development Team
     * @version $Revision: 1.1 $
     */
    public static class Config {

        /** _more_          */
        protected String trajectoryId;  // the Id String for the trajectory.

        /** _more_          */
        protected Dimension trajectoryDim;

        /** _more_          */
        protected Variable dimVar;

        /** _more_          */
        protected Variable latVar;

        /** _more_          */
        protected Variable lonVar;

        /** _more_          */
        protected Variable elevVar;

        /**
         * _more_
         */
        public Config() {}

        /**
         * _more_
         *
         * @param trajectoryId _more_
         * @param trajectoryDim _more_
         * @param dimVar _more_
         * @param latVar _more_
         * @param lonVar _more_
         * @param elevVar _more_
         */
        public Config(String trajectoryId, Dimension trajectoryDim,
                      Variable dimVar, Variable latVar, Variable lonVar,
                      Variable elevVar) {
            this.trajectoryId = trajectoryId;
            this.trajectoryDim       = trajectoryDim;
            this.dimVar       = dimVar;
            this.latVar       = latVar;
            this.lonVar       = lonVar;
            this.elevVar      = elevVar;
        }

        /**
         * _more_
         *
         * @param trajectoryId _more_
         */
        public void setTrajectoryId(String trajectoryId) {
            this.trajectoryId = trajectoryId;
        }

        /**
         * _more_
         *
         * @param trajectoryDim _more_
         */
        public void setTrajectoryDim(Dimension trajectoryDim) {
            this.trajectoryDim = trajectoryDim;
        }

        /**
         * _more_
         *
         * @param dimVar _more_
         */
        public void setDimensionVar(Variable dimVar) {
            this.dimVar = dimVar;
        }

        /**
         * _more_
         *
         * @param latVar _more_
         */
        public void setLatVar(Variable latVar) {
            this.latVar = latVar;
        }

        /**
         * _more_
         *
         * @param lonVar _more_
         */
        public void setLonVar(Variable lonVar) {
            this.lonVar = lonVar;
        }

        /**
         * _more_
         *
         * @param elevVar _more_
         */
        public void setElevVar(Variable elevVar) {
            this.elevVar = elevVar;
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public String getTrajectoryId() {
            return trajectoryId;
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public Dimension getTrajectoryDim() {
            return trajectoryDim;
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public Variable getDimensionVar() {
            return dimVar;
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public Variable getLatVar() {
            return latVar;
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public Variable getLonVar() {
            return lonVar;
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public Variable getElevVar() {
            return elevVar;
        }
    }

    /**
     * Class SingleTrajectory _more_
     *
     *
     * @author IDV Development Team
     * @version $Revision: 1.1 $
     */
    private class Trajectory implements TrajectoryObsDatatype {

        /** _more_          */
        private String id;

        /** _more_          */
        private String description;

        /** _more_          */
        private int numPoints;

        /** _more_          */
        private Date startDate;

        /** _more_          */
        private Date endDate;

        /** _more_          */
        private String dimVarUnitsString;

        /** _more_          */
        private Variable dimVar;

        /** _more_          */
        private Variable latVar;

        /** _more_          */
        private Variable lonVar;

        /** _more_          */
        private Variable elevVar;

        /** _more_          */
        private List variables;

        /** _more_          */
        private HashMap variablesMap;

        //private Structure struct;

        /**
         * _more_
         *
         * @param id _more_
         * @param numPoints _more_
         * @param dimVar _more_
         * @param dimVarUnitsString _more_
         * @param latVar _more_
         * @param lonVar _more_
         * @param elevVar _more_
         * @param variables _more_
         * @param variablesMap _more_
         */
        private Trajectory(String id, int numPoints, Variable dimVar,
                                 String dimVarUnitsString, Variable latVar,
                                 Variable lonVar, Variable elevVar,
                                 List variables, HashMap variablesMap) {
            this.description        = null;
            this.id                 = id;
            this.numPoints          = numPoints;
            this.dimVarUnitsString = dimVarUnitsString;
            this.dimVar            = dimVar;
            this.variables          = variables;
            this.variablesMap       = variablesMap;
            this.latVar             = latVar;
            this.lonVar             = lonVar;
            this.elevVar            = elevVar;

            //this.struct = new Structure( ncfile, ncfile.getRootGroup(), null, "struct for building StructureDatas");
        }

        /**
         * _more_
         *
         * @param numPoints _more_
         */
        protected void setNumPoints(int numPoints) {
            this.numPoints = numPoints;
        }

        /**
         * _more_
         *
         * @param startDate _more_
         */
        protected void setStartDate(Date startDate) {
            if (this.startDate != null) {
                throw new IllegalStateException(
                    "Can only call setStartDate() once.");
            }
            this.startDate = startDate;
        }

        /**
         * _more_
         *
         * @param endDate _more_
         */
        protected void setEndDate(Date endDate) {
            //if ( this.endDate != null ) throw new IllegalStateException( "Can only call setEndDate() once.");
            this.endDate = endDate;
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public String getId() {
            return (id);
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public String getDescription() {
            return (description);
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public int getNumberPoints() {
            return (numPoints);
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public List getDataVariables() {
            return (variables);
        }

        /**
         * _more_
         *
         * @param name _more_
         *
         * @return _more_
         */
        public VariableSimpleIF getDataVariable(String name) {
            return ((VariableSimpleIF) variablesMap.get(name));
        }

        /**
         * _more_
         *
         * @param point _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         */
        public PointObsDatatype getPointObsData(
                int point) throws IOException {
            return (new MyPointObsDatatype(point));
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public Date getStartDate() {
            return (startDate);
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public Date getEndDate() {
            return (endDate);
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public LatLonRect getBoundingBox() {
            return null;
        }

        /**
         * _more_
         *
         * @param point _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         */
        public Date getTime(int point) throws IOException {
            if(SimpleUnit.isDateUnit( dimVarUnitsString))
                return (DateUnit.getStandardDate(getTimeValue(point) + " "
                                             + dimVarUnitsString));
            else
                return startDate;
        }

        /**
         * _more_
         *
         * @param point _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         */
        public ucar.unidata.geoloc.EarthLocation getLocation(
                int point) throws IOException {
            return (new MyEarthLocation(point));
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public String getTimeUnitsIdentifier() {
            return (dimVarUnitsString);
        }

        /**
         * _more_
         *
         * @param point _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         */
        public double getTimeValue(int point) throws IOException {
            Array array = null;
            try {
                array = getTime(this.getPointRange(point));
            } catch (InvalidRangeException e) {
                IllegalArgumentException iae =
                    new IllegalArgumentException("Point <" + point
                        + "> not in valid range <0, "
                        + (this.getNumberPoints() - 1) + ">: "
                        + e.getMessage());
                iae.initCause(e);
                throw iae;
            }
            if (array instanceof ArrayDouble) {
                return (array.getDouble(array.getIndex()));
            } else if (array instanceof ArrayFloat) {
                return (array.getFloat(array.getIndex()));
            } else if (array instanceof ArrayInt) {
                return (array.getInt(array.getIndex()));
            } else {
                throw new IOException(
                    "Time variable not float, double, or integer <"
                    + array.getElementType().toString() + ">.");
            }
        }

        // @todo Make sure units are degrees_north

        /**
         * _more_
         *
         * @param point _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         */
        public double getLatitude(int point) throws IOException  // required, units degrees_north
        {
            Array array = null;
            try {
                array = getLatitude(this.getPointRange(point));
            } catch (InvalidRangeException e) {
                IllegalArgumentException iae =
                    new IllegalArgumentException("Point <" + point
                        + "> not in valid range <0, "
                        + (this.getNumberPoints() - 1) + ">: "
                        + e.getMessage());
                iae.initCause(e);
                throw iae;
            }
            if (array instanceof ArrayDouble) {
                return (array.getDouble(array.getIndex()));
            } else if (array instanceof ArrayFloat) {
                return (array.getFloat(array.getIndex()));
            } else {
                throw new IOException(
                    "Latitude variable not float or double <"
                    + array.getElementType().toString() + ">.");
            }
        }

        // @todo Make sure units are degrees_east

        /**
         * _more_
         *
         * @param point _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         */
        public double getLongitude(int point) throws IOException  // required, units degrees_east
        {
            Array array = null;
            try {
                array = getLongitude(this.getPointRange(point));
            } catch (InvalidRangeException e) {
                IllegalArgumentException iae =
                    new IllegalArgumentException("Point <" + point
                        + "> not in valid range <0, "
                        + (this.getNumberPoints() - 1) + ">: "
                        + e.getMessage());
                iae.initCause(e);
                throw iae;
            }
            if (array instanceof ArrayDouble) {
                return (array.getDouble(array.getIndex()));
            } else if (array instanceof ArrayFloat) {
                return (array.getFloat(array.getIndex()));
            } else {
                throw new IOException(
                    "Longitude variable not float or double <"
                    + array.getElementType().toString() + ">.");
            }
        }

        // @todo Make sure units are meters

        /**
         * _more_
         *
         * @param point _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         */
        public double getElevation(int point) throws IOException  // optional; units meters;  missing = NaN.
        {
            Array array = null;
            try {
                array = getElevation(this.getPointRange(point));
            } catch (InvalidRangeException e) {
                IllegalArgumentException iae =
                    new IllegalArgumentException("Point <" + point
                        + "> not in valid range <0, "
                        + (this.getNumberPoints() - 1) + ">: "
                        + e.getMessage());
                iae.initCause(e);
                throw iae;
            }
            if (array instanceof ArrayDouble) {
                return array.getDouble(array.getIndex());
            } else if (array instanceof ArrayFloat) {
                return array.getFloat(array.getIndex());
            } else {
                throw new IOException(
                    "Elevation variable not float or double <"
                    + array.getElementType().toString() + ">.");
            }
        }

        /**
         * _more_
         *
         * @param point _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         * @throws InvalidRangeException _more_
         */
        public StructureData getData(int point) throws IOException,
                InvalidRangeException {
            return TrajectoryObsDatasetImpl.this.recordVar.readStructure(point);
        }

        /**
         * _more_
         *
         * @param point _more_
         * @param parameterName _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         */
        public Array getData(int point,
                             String parameterName) throws IOException {
            try {
                return (getData(this.getPointRange(point), parameterName));
            } catch (InvalidRangeException e) {
                IllegalArgumentException iae =
                    new IllegalArgumentException("Point <" + point
                        + "> not in valid range <0, "
                        + (this.getNumberPoints() - 1) + ">: "
                        + e.getMessage());
                iae.initCause(e);
                throw iae;
            }
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public Range getFullRange() {
            Range range = null;
            try {
                range = new Range(0, this.getNumberPoints() - 1);
            } catch (InvalidRangeException e) {
                IllegalStateException ise =
                    new IllegalStateException(
                        "Full trajectory range invalid <0, "
                        + (this.getNumberPoints() - 1) + ">: "
                        + e.getMessage());
                ise.initCause(e);
                throw (ise);
            }
            return range;
        }

        /**
         * _more_
         *
         * @param point _more_
         *
         * @return _more_
         *
         * @throws InvalidRangeException _more_
         */
        public Range getPointRange(int point) throws InvalidRangeException {
            if (point >= this.getNumberPoints()) {
                throw new InvalidRangeException("Point <" + point
                        + "> not in acceptible range <0, "
                        + (this.getNumberPoints() - 1) + ">.");
            }
            return (new Range(point, point));
        }

        /**
         * _more_
         *
         * @param start _more_
         * @param end _more_
         * @param stride _more_
         *
         * @return _more_
         *
         * @throws InvalidRangeException _more_
         */
        public Range getRange(int start, int end,
                              int stride) throws InvalidRangeException {
            if (end >= this.getNumberPoints()) {
                throw new InvalidRangeException("End point <" + end
                        + "> not in acceptible range <0, "
                        + (this.getNumberPoints() - 1) + ">.");
            }
            return (new Range(start, end, stride));
        }

        /**
         * _more_
         *
         * @param range _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         * @throws InvalidRangeException _more_
         */
        public Array getTime(Range range) throws IOException,
                InvalidRangeException {
            Array array = null;
            List section = new ArrayList(1);
            section.add(range);
            array = dimVar.read(section);
            if(SimpleUnit.isDateUnit( dimVarUnitsString)){
                return array;
            }

            int size = (int) array.getSize();
            double [] pa = new double[size];
            for (int i = 0; i < size; i++) {
                 pa[i] = (double)startDate.getTime();
            }

            return Array.factory(DataType.DOUBLE, array.getShape(), pa);
        }

        // @todo Make sure units are degrees_north

        /**
         * _more_
         *
         * @param range _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         * @throws InvalidRangeException _more_
         */
        public Array getLatitude(Range range) throws IOException,
                InvalidRangeException {
            List section = new ArrayList(1);
            section.add(range);
            return (latVar.read(section));
        }

        // @todo Make sure units are degrees_east

        /**
         * _more_
         *
         * @param range _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         * @throws InvalidRangeException _more_
         */
        public Array getLongitude(Range range) throws IOException,
                InvalidRangeException {
            List section = new ArrayList(1);
            section.add(range);
            return (lonVar.read(section));
        }

        // @todo Make sure units are meters

        /**
         * _more_
         *
         * @param range _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         * @throws InvalidRangeException _more_
         */
        public Array getElevation(Range range) throws IOException,
                InvalidRangeException {
            List section = new ArrayList(1);
            section.add(range);
            Array a = elevVar.read(section);
            if (elevVarUnitsConversionFactor == 1.0) {
                return (a);
            }
            for (IndexIterator it = a.getIndexIterator(); it.hasNext(); ) {
                if (elevVar.getDataType() == DataType.DOUBLE) {
                    double val = it.getDoubleNext();
                    it.setDoubleCurrent(val * elevVarUnitsConversionFactor);
                } else if (elevVar.getDataType() == DataType.FLOAT) {
                    float val = it.getFloatNext();
                    it.setFloatCurrent((float) (val
                            * elevVarUnitsConversionFactor));
                } else if (elevVar.getDataType() == DataType.INT) {
                    int val = it.getIntNext();
                    it.setIntCurrent((int) (val
                                            * elevVarUnitsConversionFactor));
                } else if (elevVar.getDataType() == DataType.LONG) {
                    long val = it.getLongNext();
                    it.setLongCurrent((long) (val
                            * elevVarUnitsConversionFactor));
                } else {
                    throw new IllegalStateException(
                        "Elevation variable type <"
                        + elevVar.getDataType().toString()
                        + "> not double, float, int, or long.");
                }
            }
            return (a);
        }

        /**
         * _more_
         *
         * @param range _more_
         * @param parameterName _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         * @throws InvalidRangeException _more_
         */
        public Array getData(Range range,
                             String parameterName) throws IOException,
                                 InvalidRangeException {
            Variable variable =
                netcdfDataset.getRootGroup().findVariable(parameterName);
            int   varRank  = variable.getRank();
            int[] varShape = variable.getShape();
            List  section  = new ArrayList(varRank);
            section.add(range);
            for (int i = 1; i < varRank; i++) {
                section.add(new Range(0, varShape[i] - 1));
            }
            Array array = variable.read(section);
            if (array.getShape()[0] == 1) {
                return (array.reduce(0));
            } else {
                return (array);
            }
            //return( array.getShape()[0] == 1 ? array.reduce( 0 ) : array);
        }

        /**
         * _more_
         *
         * @param bufferSize _more_
         *
         * @return _more_
         *
         * @throws IOException _more_
         */
        public DataIterator getDataIterator(
                int bufferSize) throws IOException {
            return new PointDatatypeIterator(recordVar, bufferSize);
        }

        /**
         * Class PointDatatypeIterator _more_
         *
         *
         * @author IDV Development Team
         * @version $Revision: 1.1 $
         */
        private class PointDatatypeIterator extends DatatypeIterator {

            /**
             * _more_
             *
             * @param recnum _more_
             * @param sdata _more_
             *
             * @return _more_
             */
            protected Object makeDatatypeWithData(int recnum,
                    StructureData sdata) {
                return new MyPointObsDatatype(recnum, sdata);
            }

            /**
             * _more_
             *
             * @param struct _more_
             * @param bufferSize _more_
             */
            PointDatatypeIterator(Structure struct, int bufferSize) {
                super(struct, bufferSize);
            }
        }

        // PointObsDatatype implementation used by SingleTrajectory.

        /**
         * Class MyPointObsDatatype _more_
         *
         *
         * @author IDV Development Team
         * @version $Revision: 1.1 $
         */
        private class MyPointObsDatatype implements PointObsDatatype {

            /** _more_          */
            private int point;

            /** _more_          */
            private StructureData sdata;

            /** _more_          */
            private double time;

            /** _more_          */
            private ucar.unidata.geoloc.EarthLocation earthLoc;

            /**
             * _more_
             *
             * @param point _more_
             *
             * @throws IOException _more_
             */
            private MyPointObsDatatype(int point) throws IOException {
                this.point    = point;
                this.time     = Trajectory.this.getTimeValue(point);
                this.earthLoc = Trajectory.this.getLocation(point);
            }

            /**
             * _more_
             *
             * @param point _more_
             * @param sdata _more_
             */
            private MyPointObsDatatype(int point, StructureData sdata) {
                this.point = point;
                this.sdata = sdata;
                this.time = sdata.convertScalarDouble(
                    Trajectory.this.dimVar.getShortName());
                this.earthLoc = new MyEarthLocation(sdata);
            }

            /**
             * _more_
             *
             * @return _more_
             */
            public double getNominalTime() {
                return (this.time);
            }

            /**
             * _more_
             *
             * @return _more_
             */
            public double getObservationTime() {
                return (this.time);
            }

            /**
             * _more_
             *
             * @return _more_
             */
            public Date getNominalTimeAsDate() {
                String dateStr = getNominalTime() + " " + dimVarUnitsString;
                return DateUnit.getStandardDate(dateStr);
            }

            /**
             * _more_
             *
             * @return _more_
             */
            public Date getObservationTimeAsDate() {
                String dateStr = getObservationTime() + " "
                                 + dimVarUnitsString;
                return DateUnit.getStandardDate(dateStr);
            }

            /**
             * _more_
             *
             * @return _more_
             */
            public ucar.unidata.geoloc.EarthLocation getLocation() {
                return (this.earthLoc);
            }

            /**
             * _more_
             *
             * @return _more_
             *
             * @throws IOException _more_
             */
            public StructureData getData() throws IOException {
                if (sdata != null) {
                    return sdata;
                }
                try {
                    return (Trajectory.this.getData(point));
                } catch (InvalidRangeException e) {
                    throw new IllegalStateException(e.getMessage());
                }
            }
        }

        // EarthLocation implementation used by SingleTrajectory.

        /**
         * Class MyEarthLocation _more_
         *
         *
         * @author IDV Development Team
         * @version $Revision: 1.1 $
         */
        private class MyEarthLocation extends ucar.unidata.geoloc
            .EarthLocationImpl {

            /** _more_          */
            private double latitude;

            /** _more_          */
            private double longitude;

            /** _more_          */
            private double elevation;

            /**
             * _more_
             *
             * @param point _more_
             *
             * @throws IOException _more_
             */
            private MyEarthLocation(int point) throws IOException {
                this.latitude  = Trajectory.this.getLatitude(point);
                this.longitude = Trajectory.this.getLongitude(point);
                this.elevation = Trajectory.this.getElevation(point);
            }

            /**
             * _more_
             *
             * @param sdata _more_
             */
            private MyEarthLocation(StructureData sdata) {
                this.latitude = sdata.convertScalarDouble(
                    Trajectory.this.latVar.getShortName());
                this.longitude = sdata.convertScalarDouble(
                    Trajectory.this.lonVar.getShortName());
                this.elevation = sdata.convertScalarDouble(
                    Trajectory.this.elevVar.getShortName());
                if (elevVarUnitsConversionFactor != 1.0) {
                    this.elevation *= elevVarUnitsConversionFactor;
                }
            }

            /**
             * _more_
             *
             * @return _more_
             */
            public double getLatitude() {
                return (this.latitude);
            }

            /**
             * _more_
             *
             * @return _more_
             */
            public double getLongitude() {
                return (this.longitude);
            }

            /**
             * _more_
             *
             * @return _more_
             */
            public double getAltitude() {
                return (this.elevation);
            }
        }
    }

    /**
     * Class MyTypedDataVariable _more_
     *
     *
     * @author IDV Development Team
     * @version $Revision: 1.1 $
     */
    private class MyTypedDataVariable extends VariableSimpleSubclass {

        /** _more_          */
        private int rank;

        /** _more_          */
        private int[] shape;

        /**
         * _more_
         *
         * @param v _more_
         */
        private MyTypedDataVariable(VariableDS v) {
            super(v);

            // Calculate the rank and shape of the variable, removing trajectory dimesion.
            rank = super.getRank() - 1;
            int[] varShape = super.getShape();
            shape = new int[varShape.length - 1];
            int trajDimIndex =
                v.findDimensionIndex(
                    TrajectoryObsDatasetImpl.this.trajectoryDim.getShortName());
            for (int i = 0, j = 0; i < varShape.length; i++) {
                if (i == trajDimIndex) {
                    continue;
                }
                shape[j++] = varShape[i];
            }
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public int getRank() {
            return (rank);
        }

        /**
         * _more_
         *
         * @return _more_
         */
        public int[] getShape() {
            return (shape);
        }
    }

}


package ucar.nc2.ft.point;

import java.util.Iterator;
import java.util.NoSuchElementException;
import ucar.nc2.ft.PointFeature;
import ucar.nc2.ft.PointFeatureIterator;

/**
 * Adapts an {@link Iterator}<{@link PointFeature}> to a {@link PointFeatureIterator}. The original iterator gains
 * bounds calculation and compatibility with the rest of the Point Feature API.
 *
 * @author cwardgar
 * @since 2014/08/30
 */
public class PointIteratorAdapter extends PointIteratorAbstract {
    private final Iterator<? extends PointFeature> pointIter;

    /**
     * Creates a {@link PointFeatureIterator} from the supplied basic iterator.
     *
     * @param pointIter  a basic iterator over point features.
     */
    public PointIteratorAdapter(Iterator<? extends PointFeature> pointIter) {
        this.pointIter = pointIter;
    }

    /**
     * Returns {@code true} if the iteration has more elements. (In other words, returns {@code true} if {@link #next}
     * would return an element rather than throwing an exception.)
     *

     * This method is idempotent, meaning that when it is called repeatedly without an intervening
     * {@link #next}, calls after the first will have no effect.
     *
     * @return {@code true} if the iteration has more elements, {@code false} otherwise. A {@code false} return value
     *         {@link #close finishes} this iteration.
     */
    // PointFeatureIterator.hasNext() doesn't guarantee idempotency, but we do.
    @Override
    public boolean hasNext() {
        if (!pointIter.hasNext()) {
            close();
            return false;
        } else {
            return true;
        }
    }

    /**
     * Returns the next element in the iteration.
     *
     * @return the next element in the iteration
     * @throws NoSuchElementException if the iteration has no more elements.
     */
    // PointFeatureIterator.next() doesn't actually specify the behavior of next() when there are no more elements,
    // but we can define a stronger contract.
    @Override
    public PointFeature next() throws NoSuchElementException {
        if (!hasNext()) {
            throw new NoSuchElementException("The iteration has no more elements.");
        }

        PointFeature pointFeat = pointIter.next();
        assert pointFeat != null : "hasNext() should have been false. WTF?";

        calcBounds(pointFeat);
        return pointFeat;
    }

    /**
     * Finishes bounds calculation, {@link #setCalculateBounds if it has been enabled}. Do not attempt to retrieve
     * bounds information (via {@link #getBoundingBox}, {@link #getDateRange}, {@link #getCalendarDateRange}, or
     * {@link #getCount}) before this method has been invoked or {@link #hasNext} returns {@code false}.
     *

     * This method is idempotent, meaning that calls after the first have no effect.
     */
    @Override
    public void close() {
        finishCalcBounds();  // Method is idempotent.
    }
}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

/**
 *
 */
package ucar.nc2.iosp.grads;

import java.util.List;

/**
 * Extension of GradsDimension to handle the complexities of ensembles
 *
 * @author Don Murray, CU-CIRES
 */
public class GradsEnsembleDimension extends GradsDimension {

    /** ensemble names identifier */
    public static final String NAMES = "NAMES";

    /** ensemble filename template */
    public static final String ENS_TEMPLATE_ID = "%e";

    /**
     * Create a new ensemble dimension holder
     *
     * @param name  the dimension name
     * @param size  the dimension size
     * @param mapping  the dimension mapping type
     */
    public GradsEnsembleDimension(String name, int size, String mapping) {
        super(name, size, mapping);
    }

    /**
     * Get the ensemble member names
     *
     * @return the list of names
     */
    public List<String> getEnsembleNames() {
        return getLevels();
    }

    /**
     * Make the level values from the specifications
     *
     * @return the level values
     */
    protected double[] makeLevelValues() {
        double[] vals = new double[getSize()];
        for (int i = 0; i < getSize(); i++) {
            vals[i] = i;
        }
        return vals;
    }

    /**
     * Replace the ensemble template parameter in a filename
     *
     * @param filespec  the file template
     * @param ensIndex the ensemble index
     *
     * @return  the filled in template
     */
    public String replaceFileTemplate(String filespec, int ensIndex) {
        return filespec.replaceAll(ENS_TEMPLATE_ID,
                                   getEnsembleNames().get(ensIndex));
    }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.dt.point;

import ucar.nc2.*;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dataset.StructureDS;
import ucar.nc2.StructurePseudo;
import ucar.nc2.units.DateUnit;
import ucar.nc2.util.CancelTask;
import ucar.nc2.dt.*;
import ucar.unidata.geoloc.LatLonPointImpl;
import ucar.unidata.geoloc.LatLonRect;
import ucar.unidata.geoloc.LatLonPoint;
import ucar.ma2.StructureData;
import ucar.ma2.DataType;
import ucar.ma2.StructureMembers;
import ucar.ma2.StructureDataIterator;

import java.io.*;
import java.util.*;

/**
 * Helper class for using the netcdf-3 record dimension. Can be used for PointObs or StationObs.
 *
 * @author caron
 * @deprecated use ucar.nc2.ft.*
 */

public class RecordDatasetHelper {
  protected NetcdfDataset ncfile;
  protected String obsTimeVName, nomTimeVName;
  protected String stnIdVName, stnNameVName, stnDescVName;
  protected String latVName, lonVName, altVName;
  protected DataType stationIdType;

  protected Map<Object, ucar.unidata.geoloc.Station> stnHash;
  protected StructureDS recordVar;
  protected Dimension obsDim;

  protected LatLonRect boundingBox;
  protected double minDate, maxDate;
  protected DateUnit timeUnit;

  protected double altScaleFactor = 1.0;

  protected StringBuffer errs = null;
  protected boolean showErrors = true;

  /**
   * Constructor.
   *
   * @param ncfile             the netccdf file
   * @param typedDataVariables list of data variables; all record variables will be added to this list, except . You
   *                           can remove extra
   * @param obsTimeVName       observation time variable name (required)
   * @param nomTimeVName       nominal time variable name (may be null)
   * @throws IllegalArgumentException if ncfile has no unlimited dimension.
   */
  public RecordDatasetHelper(NetcdfDataset ncfile, String obsTimeVName, String nomTimeVName, List typedDataVariables) {
    this(ncfile, obsTimeVName, nomTimeVName, typedDataVariables, null, null);
  }

  public RecordDatasetHelper(NetcdfDataset ncfile, String obsTimeVName, String nomTimeVName, List typedDataVariables,
                             StringBuffer errBuffer) {
    this(ncfile, obsTimeVName, nomTimeVName, typedDataVariables, null, errBuffer);
  }

  /**
   * Constructor.
   *
   * @param ncfile             the netccdf file
   * @param typedDataVariables list of data variables; all record variables will be added to this list, except . You
   *                           can remove extra
   * @param obsTimeVName       observation time variable name (required)
   * @param nomTimeVName       nominal time variable name (may be null)
   * @throws IllegalArgumentException if ncfile has no unlimited dimension and recDimName is null.
   */
  public RecordDatasetHelper(NetcdfDataset ncfile, String obsTimeVName, String nomTimeVName, List typedDataVariables,
                             String recDimName, StringBuffer errBuffer) {
    this.ncfile = ncfile;
    this.obsTimeVName = obsTimeVName;
    this.nomTimeVName = nomTimeVName;
    this.errs = errBuffer;

    // check if we already have a structure vs if we have to add it.

    if (this.ncfile.hasUnlimitedDimension()) {
      this.ncfile.sendIospMessage(NetcdfFile.IOSP_MESSAGE_ADD_RECORD_STRUCTURE);
      this.recordVar = (StructureDS) this.ncfile.getRootGroup().findVariable("record");
      this.obsDim = ncfile.getUnlimitedDimension();

    } else {
      if (recDimName == null)
        throw new IllegalArgumentException("File <" + this.ncfile.getLocation() +
                "> has no unlimited dimension, specify psuedo record dimension with observationDimension global attribute.");
      this.obsDim = this.ncfile.getRootGroup().findDimension(recDimName);
      this.recordVar = new StructureDS(null, new StructurePseudo(this.ncfile, null, "record", obsDim)); // true
    }

    // create member variables
    List<Variable> recordMembers = ncfile.getVariables();
    for (Variable v : recordMembers) {
      if (v == recordVar) continue;
      if (v.isScalar()) continue;
      if (v.getDimension(0) == this.obsDim)
        typedDataVariables.add(v);
    }

    // need the time units
    Variable timeVar = ncfile.findVariable(obsTimeVName);
    String timeUnitString = ncfile.findAttValueIgnoreCase(timeVar, "units", "seconds since 1970-01-01");
    try {
      timeUnit = new DateUnit(timeUnitString);
    } catch (Exception e) {
      if (null != errs) errs.append("Error on string = " + timeUnitString + " == " + e.getMessage() + "\n");
      try {
        timeUnit = new DateUnit("seconds since 1970-01-01");
      } catch (Exception e1) {
        // cant happen
      }
    }
  }

  /**
   * Set extra information used by station obs datasets.
   *
   * @param stnIdVName   the obs variable that is used to find the station in the stnHash; may be type  int or a String (char).
   * @param stnDescVName optional station var containing station description
   */
  public void setStationInfo(String stnIdVName, String stnDescVName) {
    this.stnIdVName = stnIdVName;
    this.stnDescVName = stnDescVName;

    Variable stationVar = ncfile.findVariable(stnIdVName);
    stationIdType = stationVar.getDataType();
  }

  public void setLocationInfo(String latVName, String lonVName, String altVName) {
    this.latVName = latVName;
    this.lonVName = lonVName;
    this.altVName = altVName;

    // check for meter conversion
    if (altVName != null) {
      Variable v = ncfile.findVariable(altVName);
      String units = ncfile.findAttValueIgnoreCase(v, "units", null);
      if (units != null)
        try {
          altScaleFactor = PointObsDatasetImpl.getMetersConversionFactor(units);
        } catch (Exception e) {
          if (errs != null) errs.append(e.getMessage());
        }
    }
  }

  public Structure getRecordVar() {
    return (this.recordVar);
  }

  public int getRecordCount() {
    Dimension unlimitedDim = ncfile.getUnlimitedDimension();
    return unlimitedDim.getLength();
  }

  public void setTimeUnit(DateUnit timeUnit) {
    this.timeUnit = timeUnit;
  }

  public DateUnit getTimeUnit() {
    return (this.timeUnit);
  }

  /**
   * This reads through all the records in the dataset, and constructs a list of
   * RecordPointObs or RecordStationObs. It does not cache the data.
   * <p>If stnIdVName is not null, its a StationDataset, then construct a Station HashMap of StationImpl
   * objects. Add the RecordStationObs into the list of obs for that station.
   *
   * @param cancel allow user to cancel
   * @return List of RecordPointObs or RecordStationObs
   * @throws IOException
   */
  public ArrayList readAllCreateObs(CancelTask cancel) throws IOException {

    // see if its a station or point dataset
    boolean hasStations = stnIdVName != null;
    if (hasStations)
      stnHash = new HashMap<Object, ucar.unidata.geoloc.Station>();

    // get min and max date and lat,lon
    double minDate = Double.MAX_VALUE;
    double maxDate = -Double.MAX_VALUE;

    double minLat = Double.MAX_VALUE;
    double maxLat = -Double.MAX_VALUE;

    double minLon = Double.MAX_VALUE;
    double maxLon = -Double.MAX_VALUE;

    // read all the data, create a RecordObs
    ArrayList records = new ArrayList();
    int recno = 0;
    try (StructureDataIterator ii = recordVar.getStructureIterator()) {
      while (ii.hasNext()) {
        StructureData sdata = ii.next();
        StructureMembers members = sdata.getStructureMembers();

        Object stationId = null;
        if (hasStations) {
          if (stationIdType == DataType.INT) {
            int stationNum = sdata.getScalarInt(stnIdVName);
            stationId = new Integer(stationNum);
          } else
            stationId = sdata.getScalarString(stnIdVName).trim();
        }

        String desc = (stnDescVName == null) ? null : sdata.getScalarString(stnDescVName);
        double lat = sdata.convertScalarDouble(latVName);
        double lon = sdata.convertScalarDouble(lonVName);
        double alt = (altVName == null) ? Double.NaN : altScaleFactor * sdata.convertScalarDouble(altVName);
        double obsTime = sdata.convertScalarDouble(members.findMember(obsTimeVName));
        double nomTime = (nomTimeVName == null) ? obsTime : sdata.convertScalarDouble(members.findMember(nomTimeVName));

        //double obsTime = sdata.convertScalarDouble( members.findMember( obsTimeVName) );
        //double nomTime = (nomTimeVName == null) ? obsTime : sdata.convertScalarDouble( members.findMember( nomTimeVName));

        if (hasStations) {
          StationImpl stn = (StationImpl) stnHash.get(stationId);
          if (stn == null) {
            stn = new StationImpl(stationId.toString(), desc, lat, lon, alt);
            stnHash.put(stationId, stn);
          }
          RecordStationObs stnObs = new RecordStationObs(stn, obsTime, nomTime, recno);
          records.add(stnObs);
          stn.addObs(stnObs);

        } else {
          records.add(new RecordPointObs(new ucar.unidata.geoloc.EarthLocationImpl(lat, lon, alt), obsTime, nomTime, recno));
        }

        // track date range and bounding box
        minDate = Math.min(minDate, obsTime);
        maxDate = Math.max(maxDate, obsTime);

        minLat = Math.min(minLat, lat);
        maxLat = Math.max(maxLat, lat);
        minLon = Math.min(minLon, lon);
        maxLon = Math.max(maxLon, lon);

        recno++;
        if ((cancel != null) && cancel.isCancel()) return null;
      }
    }

    boundingBox = new LatLonRect(new LatLonPointImpl(minLat, minLon), new LatLonPointImpl(maxLat, maxLon));

    return records;
  }

  // make structure variable names to shortNames so StructureData sdata can
  // access it members

  public void setShortNames(String latVName, String lonVName, String altVName, String obsTimeVName, String nomTimeVName) {
    this.latVName = latVName;
    this.lonVName = lonVName;
    this.altVName = altVName;
    this.obsTimeVName = obsTimeVName;
    this.nomTimeVName = nomTimeVName;
  }

  private boolean debugBB = false;

  public List getData(ArrayList records, LatLonRect boundingBox, CancelTask cancel) throws IOException {
    if (debugBB) System.out.println("Want bb= " + boundingBox);
    ArrayList result = new ArrayList();
    for (int i = 0; i < records.size(); i++) {
      RecordDatasetHelper.RecordPointObs r = (RecordDatasetHelper.RecordPointObs) records.get(i);
      if (boundingBox.contains(r.getLatLon())) {
        if (debugBB) System.out.println(" ok latlon= " + r.getLatLon());
        result.add(r);
      }
      if ((cancel != null) && cancel.isCancel()) return null;
    }
    return result;
  }

  // return List<PointObsDatatype>

  public List getData(ArrayList records, LatLonRect boundingBox, double startTime, double endTime, CancelTask cancel) throws IOException {
    if (debugBB) System.out.println("Want bb= " + boundingBox);
    ArrayList result = new ArrayList();
    for (int i = 0; i < records.size(); i++) {
      RecordDatasetHelper.RecordPointObs r = (RecordDatasetHelper.RecordPointObs) records.get(i);
      if (boundingBox.contains(r.getLatLon())) {
        if (debugBB) System.out.println(" ok latlon= " + r.getLatLon());
        double timeValue = r.getObservationTime();
        if ((timeValue >= startTime) && (timeValue <= endTime))
          result.add(r);
      }
      if ((cancel != null) && cancel.isCancel()) return null;
    }
    return result;
  }

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

  public class RecordPointObs extends PointObsDatatypeImpl {
    protected int recno;
    protected LatLonPointImpl llpt = null;
    protected StructureData sdata;

    protected RecordPointObs() {
    }

    /**
     * Constructor for the case where you keep track of the location, time of each record, but not the data.
     */
    protected RecordPointObs(ucar.unidata.geoloc.EarthLocation location, double obsTime, double nomTime, int recno) {
      super(location, obsTime, nomTime);
      this.recno = recno;
    }

    /**
     * Constructor for when you already have the StructureData and want to wrap it in a StationObsDatatype
     *
     * @param recno record number LOOK why do we need ??
     * @param sdata the structure data
     */
    public RecordPointObs(int recno, StructureData sdata) {
      this.recno = recno;
      this.sdata = sdata;

      StructureMembers members = sdata.getStructureMembers();
      obsTime = sdata.convertScalarDouble(members.findMember(obsTimeVName));
      nomTime = (nomTimeVName == null) ? obsTime : sdata.convertScalarDouble(members.findMember(nomTimeVName));

      // obsTime = sdata.convertScalarDouble( members.findMember(obsTimeVName) );
      //nomTime = (nomTimeVName == null) ? obsTime : sdata.convertScalarDouble( members.findMember(nomTimeVName));

      double lat = sdata.convertScalarDouble(latVName);
      double lon = sdata.convertScalarDouble(lonVName);
      double alt = (altVName == null) ? Double.NaN : altScaleFactor * sdata.convertScalarDouble(altVName);
      location = new ucar.unidata.geoloc.EarthLocationImpl(lat, lon, alt);
    }

    public LatLonPoint getLatLon() {
      if (llpt == null)
        llpt = new LatLonPointImpl(location.getLatitude(), location.getLongitude());
      return llpt;
    }

    public Date getNominalTimeAsDate() {
      return timeUnit.makeDate(getNominalTime());
    }

    public Date getObservationTimeAsDate() {
      return timeUnit.makeDate(getObservationTime());
    }

    public StructureData getData() throws IOException {
      if (null != sdata) return sdata;

      try {
        return recordVar.readStructure(recno);
      } catch (ucar.ma2.InvalidRangeException e) {
        e.printStackTrace();
        throw new IOException(e.getMessage());
      }
    }
  }

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

  public class RecordStationObs extends RecordPointObs implements StationObsDatatype {
    private ucar.unidata.geoloc.Station station;

    /**
     * Constructor for the case where you keep track of the station, time of each record, but the data reading is deferred.
     *
     * @param station data is for this Station
     * @param obsTime observation time
     * @param nomTime nominal time (may be NaN)
     * @param recno   data is at this record number
     */
    protected RecordStationObs(ucar.unidata.geoloc.Station station, double obsTime, double nomTime, int recno) {
      super(station, obsTime, nomTime, recno);
      this.station = station;
    }

    /**
     * Constructor for when you already have the StructureData and want to wrap it in a StationObsDatatype
     *
     * @param recno record number LOOK why do we need ??
     * @param sdata the structure data
     */
    protected RecordStationObs(int recno, StructureData sdata) {
      this.recno = recno;
      this.sdata = sdata;

      StructureMembers members = sdata.getStructureMembers();

      obsTime = sdata.convertScalarDouble(members.findMember(obsTimeVName));
      nomTime = (nomTimeVName == null) ? obsTime : sdata.convertScalarDouble(members.findMember(nomTimeVName));

      //obsTime = sdata.convertScalarDouble( members.findMember(obsTimeVName) );
      //nomTime = (nomTimeVName == null) ? obsTime : sdata.convertScalarDouble( members.findMember(nomTimeVName));

      Object stationId;
      if (stationIdType == DataType.INT) {
        stationId = sdata.getScalarInt(stnIdVName);
      } else
        stationId = sdata.getScalarString(stnIdVName).trim();

      station = (ucar.unidata.geoloc.Station) stnHash.get(stationId);
      location = station;
      if (station == null) {
        if (null != errs)
          errs.append(" cant find station = <").append(stationId).append(">" + "when reading record ").append(recno).append("\n");
        if (showErrors)
          System.out.println(" cant find station = <" + stationId + ">" + "when reading record " + recno);
      }
    }

    /**
     * Constructor for when you already have the StructureData and want to wrap it in a StationObsDatatype
     *
     * @param station data is for this Station
     * @param obsTime observation time
     * @param nomTime nominal time (may be NaN)
     * @param sdata   the structure data
     */
    protected RecordStationObs(ucar.unidata.geoloc.Station station, double obsTime, double nomTime, StructureData sdata) {
      this.station = station;
      this.location = station;
      this.obsTime = obsTime;
      this.nomTime = nomTime;
      this.sdata = sdata;
    }

    /**
     * Constructor for when you already have the StructureData and want to wrap it in a StationObsDatatype
     *
     * @param station data is for this Station
     * @param sdata   the structure data
     */
    protected RecordStationObs(ucar.unidata.geoloc.Station station, StructureData sdata) {
      this.station = station;
      this.location = station;
      this.sdata = sdata;
      StructureMembers members = sdata.getStructureMembers();
      obsTime = sdata.convertScalarDouble(members.findMember(obsTimeVName));
      nomTime = (nomTimeVName == null) ? obsTime : sdata.convertScalarDouble(members.findMember(nomTimeVName));

      //obsTime = sdata.convertScalarDouble( members.findMember(obsTimeVName) );
      //nomTime = (nomTimeVName == null) ? obsTime : sdata.convertScalarDouble( members.findMember(nomTimeVName));
    }

    public ucar.unidata.geoloc.Station getStation() {
      return station;
    }

    public StructureData getData() throws IOException {
      if (null != sdata) return sdata;

      try {
        return recordVar.readStructure(recno); // note we dont cache sdata
      } catch (ucar.ma2.InvalidRangeException e) {
        e.printStackTrace();
        throw new IOException(e.getMessage());
      }
    }
  }

}