All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ucar.nc2.iosp.misc.NmcObsLegacy Maven / Gradle / Ivy

Go to download

The NetCDF-Java Library is a Java interface to NetCDF files, as well as to many other types of scientific data formats.

The newest version!
/*
 * Copyright 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.iosp.misc;

import ucar.nc2.constants.AxisType;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.constants._Coordinate;
import ucar.unidata.io.RandomAccessFile;
import ucar.nc2.units.DateFormatter;
import ucar.nc2.iosp.AbstractIOServiceProvider;
import ucar.nc2.*;
import ucar.nc2.util.CancelTask;
import ucar.ma2.*;

import java.io.IOException;
import java.io.EOFException;
import java.util.*;
import java.nio.ByteBuffer;

/**
 * NMC Office Note 29
 *
 * @author caron
 * @since Feb 22, 2008
 */
public class NmcObsLegacy extends AbstractIOServiceProvider {
  static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NmcObsLegacy.class);

  private NetcdfFile ncfile;
  //private Structure reportVar;

  private List stations = new ArrayList<>();
  private List reports = new ArrayList<>();
  //private Map> map = new HashMap>();
  //private List stations;

  // private int nobs = 0, nstations = 0;
  private Calendar cal = null;
  private DateFormatter dateFormatter = new DateFormatter();
  private Date refDate; // from the header

  private List catStructures = new ArrayList<>(10);

  private boolean showObs = false, showSkip = false, showOverflow = false, showData = false,
          showHeader = false, showTime = false;
  private boolean checkType = false, checkPositions = false;

  public boolean isValidFile(RandomAccessFile raf) throws IOException {
    raf.seek(0);
    if (raf.length() < 60) return false;
    byte[] h = raf.readBytes(60);

    // 32 - 56 are X's
    for (int i = 32; i < 56; i++)
      if (h[i] != (byte) 'X') return false;

    try {
      short hour = Short.parseShort(new String(h, 0, 2, CDM.utf8Charset));
      short minute = Short.parseShort(new String(h, 2, 2, CDM.utf8Charset));
      short year = Short.parseShort(new String(h, 4, 2, CDM.utf8Charset));
      short month = Short.parseShort(new String(h, 6, 2, CDM.utf8Charset));
      short day = Short.parseShort(new String(h, 8, 2, CDM.utf8Charset));

      if ((hour < 0) || (hour > 24)) return false;
      if ((minute < 0) || (minute > 60)) return false;
      if ((year < 0) || (year > 100)) return false;
      if ((month < 0) || (month > 12)) return false;
      if ((day < 0) || (day > 31)) return false;

    } catch (Exception e) {
      return false;
    }

    return true;
  }

  public String getFileTypeId() {
    return "NMCon29";
  }

  public String getFileTypeDescription() {
    return "NMC Office Note 29";
  }

  public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask) throws IOException {
    this.raf = raf;
    this.ncfile = ncfile;

    init();

    ncfile.addAttribute(null, new Attribute(CDM.HISTORY, "Direct read of NMC ON29 by CDM"));
    ncfile.addAttribute(null, new Attribute(CDM.CONVENTIONS, "Unidata"));
    ncfile.addAttribute(null, new Attribute(CF.FEATURE_TYPE, CF.FeatureType.timeSeriesProfile.toString()));

    try {
      ncfile.addDimension(null, new Dimension("station", stations.size()));
      Structure station = makeStationStructure();
      ncfile.addVariable(null, station);

      ncfile.addDimension(null, new Dimension("report", reports.size()));
      Structure reportIndexVar = makeReportIndexStructure();
      ncfile.addVariable(null, reportIndexVar);

      Structure reportVar = makeReportStructure();
      ncfile.addVariable(null, reportVar);

    } catch (InvalidRangeException e) {
      log.error("open ON29 File", e);
      throw new IllegalStateException(e.getMessage());
    }
  }

  public Array readData(Variable v, Section section) throws IOException, InvalidRangeException {
    if (v.getShortName().equals("station"))
      return readStation(v, section);

    else if (v.getShortName().equals("report"))
      return readReport(v, section);

    else if (v.getShortName().equals("reportIndex"))
      return readReportIndex(v, section);

    throw new IllegalArgumentException("Unknown variable name= "+v.getShortName());
  }

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

  private Structure makeStationStructure() throws IOException, InvalidRangeException {
    Structure station = new Structure(ncfile, null, null, "station");
    station.setDimensions("station");
    station.addAttribute(new Attribute(CDM.LONG_NAME, "unique stations within this file"));

    int pos = 0;
    Variable v = station.addMemberVariable(new Variable(ncfile, null, station, "stationName", DataType.CHAR, ""));
    v.setDimensionsAnonymous(new int[]{6});
    v.addAttribute(new Attribute(CDM.LONG_NAME, "name of station"));
    v.addAttribute(new Attribute("standard_name", "station_name"));
    v.setSPobject(new Vinfo(pos));
    pos += 6;

    v = station.addMemberVariable(new Variable(ncfile, null, station, "lat", DataType.FLOAT, ""));
    v.addAttribute(new Attribute(CDM.UNITS, CDM.LAT_UNITS));
    v.addAttribute(new Attribute(CDM.LONG_NAME, "geographic latitude"));
    v.addAttribute(new Attribute("accuracy", "degree/100"));
    v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lat.toString()));
    v.setSPobject(new Vinfo(pos));
    pos += 4;

    v = station.addMemberVariable(new Variable(ncfile, null, station, "lon", DataType.FLOAT, ""));
    v.addAttribute(new Attribute(CDM.UNITS, CDM.LON_UNITS));
    v.addAttribute(new Attribute(CDM.LONG_NAME, "geographic longitude"));
    v.addAttribute(new Attribute("accuracy", "degree/100"));
    v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lon.toString()));
    v.setSPobject(new Vinfo(pos));
    pos += 4;

    v = station.addMemberVariable(new Variable(ncfile, null, station, "elev", DataType.FLOAT, ""));
    v.addAttribute(new Attribute(CDM.UNITS, "meters"));
    v.addAttribute(new Attribute(CDM.LONG_NAME, "station elevation above MSL"));
    v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Height.toString()));
    v.setSPobject(new Vinfo(pos));
    pos += 4;

    v = station.addMemberVariable(new Variable(ncfile, null, station, "nrecords", DataType.INT, ""));
    v.addAttribute(new Attribute(CDM.LONG_NAME, "number of records"));
    v.addAttribute(new Attribute("standard_name", "npts"));
    v.setSPobject(new Vinfo(pos));

    return station;
  }

  private Structure makeReportIndexStructure() throws InvalidRangeException, IOException {
    Structure reportIndex = new Structure(ncfile, null, null, "reportIndex");
    reportIndex.setDimensions("report");

    reportIndex.addAttribute(new Attribute(CDM.LONG_NAME, "index on report - in memory"));
    int pos = 0;

    Variable v = reportIndex.addMemberVariable(new Variable(ncfile, null, reportIndex, "stationName", DataType.CHAR, ""));
    v.setDimensionsAnonymous(new int[]{6});
    v.addAttribute(new Attribute(CDM.LONG_NAME, "name of station"));
    v.addAttribute(new Attribute("standard_name", "station_name"));
    v.setSPobject(new Vinfo(pos));
    pos += 6;

    v = reportIndex.addMemberVariable(new Variable(ncfile, null, reportIndex, "time", DataType.INT, ""));
    v.addAttribute(new Attribute(CDM.UNITS, "secs since 1970-01-01 00:00"));
    v.addAttribute(new Attribute(CDM.LONG_NAME, "observation time"));
    v.setSPobject(new Vinfo(pos));

    return reportIndex;
  }


  private Structure makeReportStructure() throws InvalidRangeException, IOException {
    Structure report = new Structure(ncfile, null, null, "report");
    report.setDimensions("report");
    report.addAttribute(new Attribute(CDM.LONG_NAME, "ON29 observation report"));
    int pos = 0;

    Variable v = report.addMemberVariable(new Variable(ncfile, null, report, "time", DataType.INT, ""));
    v.addAttribute(new Attribute(CDM.UNITS, "secs since 1970-01-01 00:00"));
    v.addAttribute(new Attribute(CDM.LONG_NAME, "observation time"));
    v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Time.toString()));
    v.setSPobject(new Vinfo(pos));
    pos += 4;

    v = report.addMemberVariable(new Variable(ncfile, null, report, "timeISO", DataType.CHAR, ""));
    v.setDimensionsAnonymous(new int[]{20});
    v.addAttribute(new Attribute(CDM.LONG_NAME, "ISO formatted date/time"));
    v.setSPobject(new Vinfo(pos));
    pos += 20;

    v = report.addMemberVariable(new Variable(ncfile, null, report, "reportType", DataType.SHORT, ""));
    v.addAttribute(new Attribute(CDM.LONG_NAME, "report type from Table R.1"));
    v.setSPobject(new Vinfo(pos));
    pos += 2;

    // only for ON29
    v = report.addMemberVariable(new Variable(ncfile, null, report, "instType", DataType.SHORT, ""));
    v.addAttribute(new Attribute(CDM.LONG_NAME, "instrument type from Table R.2"));
    v.setSPobject(new Vinfo(pos));
    pos += 2;

    v = report.addMemberVariable(new Variable(ncfile, null, report, "reserved", DataType.CHAR, ""));
    v.setDimensionsAnonymous(new int[]{7});
    v.addAttribute(new Attribute(CDM.LONG_NAME, "reserved characters"));
    v.setSPobject(new Vinfo(pos));
    pos += 7;

    List records = firstReport.readData(); // for the moment, we will use the first report as the exemplar
    pos = makeInnerSequence(report, records, 1, pos);
    pos = makeInnerSequence(report, records, 2, pos);
    pos = makeInnerSequence(report, records, 3, pos);
    pos = makeInnerSequence(report, records, 4, pos);
    pos = makeInnerSequence(report, records, 5, pos);
    pos = makeInnerSequence(report, records, 7, pos);
    pos = makeInnerSequence(report, records, 8, pos);
    pos = makeInnerSequence(report, records, 51, pos);
    makeInnerSequence(report, records, 52, pos);
    report.calcElementSize(); // recalc since we added new members

    return report;
  }

  private int makeInnerSequence(Structure reportVar, List records, int code, int obs_pos) throws InvalidRangeException {

    for (Record record : records) {
      if (record.code == code) {
        Entry first = record.entries[0];
        Structure s = first.makeStructure(reportVar);
        s.setSPobject(new Vinfo(obs_pos));
        obs_pos += 4;
        reportVar.addMemberVariable(s);
        catStructures.add(new StructureCode(s, code));
        break;
      }
    }
    return obs_pos;
  }

  private static class Vinfo {
    int offset;

    Vinfo(int offset) {
      this.offset = offset;
    }
  }

  private static class StructureCode {
    Structure s;
    int code;

    StructureCode(Structure s, int code) {
      this.s = s;
      this.code = code;
    }
  }

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

  private Array readStation(Variable v, Section section) throws IOException, InvalidRangeException {
    Structure s = (Structure) v;
    StructureMembers members = s.makeStructureMembers();
    for (Variable v2 : s.getVariables()) {
      Vinfo vinfo = (Vinfo) v2.getSPobject();
      StructureMembers.Member m = members.findMember(v2.getShortName());
      if (vinfo != null) {
        m.setDataParam(vinfo.offset);
        //m.setVariableInfo( vinfo.size);
      }
    }

    int size = (int) section.computeSize();
    ArrayStructureBB abb = new ArrayStructureBB(members, new int[]{size});
    ByteBuffer bb = abb.getByteBuffer();

    Range r = section.getRange(0);
    for (int i = r.first(); i <= r.last(); i += r.stride()) {
      Station station = stations.get(i);
      bb.put(station.r.stationId.getBytes(CDM.utf8Charset));
      bb.putFloat(station.r.lat);
      bb.putFloat(station.r.lon);
      bb.putFloat(station.r.elevMeters);
      bb.putInt(station.nreports);
    }

    return abb;
  }

  public Array readReportIndex(Variable v, Section section) throws IOException, InvalidRangeException {
      //coverity[FB.BC_UNCONFIRMED_CAST]
    Structure s = (Structure) v;
    StructureMembers members = s.makeStructureMembers();
    for (Variable v2 : s.getVariables()) {
      Vinfo vinfo = (Vinfo) v2.getSPobject();
      StructureMembers.Member m = members.findMember(v2.getShortName());
      m.setDataParam(vinfo.offset);
    }

    int size = (int) section.computeSize();
    ArrayStructureBB abb = new ArrayStructureBB(members, new int[]{size});
    ByteBuffer bb = abb.getByteBuffer();

    Range r = section.getRange(0);
    for (int i = r.first(); i <= r.last(); i += r.stride()) {
      Report report = reports.get(i);
      report.loadIndexData(bb);
    }

    return abb;
  }

  public Array readReport(Variable v, Section section) throws IOException, InvalidRangeException {
    //coverity[FB.BC_UNCONFIRMED_CAST]
    Structure s = (Structure) v;
    StructureMembers members = s.makeStructureMembers();
    for (Variable v2 : s.getVariables()) {
      Vinfo vinfo = (Vinfo) v2.getSPobject();
      StructureMembers.Member m = members.findMember(v2.getShortName());
      m.setDataParam(vinfo.offset);
    }

    int size = (int) section.computeSize();
    ArrayStructureBB abb = new ArrayStructureBB(members, new int[]{size});
    ByteBuffer bb = abb.getByteBuffer();

    Range r = section.getRange(0);
    for (int i = r.first(); i <= r.last(); i += r.stride()) {
      Report report = reports.get(i);
      report.loadStructureData(abb, bb);
    }

    return abb;
  }

  /* private class ReportIterator implements StructureDataIterator {
    List reports;
    Iterator iter;
    StructureMembers members;

    ReportIterator(List reports) {
      this.reports = reports;
      iter = reports.iterator();

      members = reportVar.makeStructureMembers();
      for (Variable v2 : reportVar.getVariables()) {
        Vinfo vinfo = (Vinfo) v2.getSPobject();
        StructureMembers.Member m = members.findMember(v2.getShortName());
        m.setDataParam(vinfo.offset);
      }
    }

    public boolean hasNext() throws IOException {
      return iter.hasNext();
    }

    public StructureData next() throws IOException {
      Report r = iter.next();

      // LOOK should optimize - read 10 at a time or something ???
      ArrayStructureBB abb = new ArrayStructureBB(members, new int[]{1});
      ByteBuffer bb = abb.getByteBuffer();
      bb.position(0);
      r.loadStructureData(abb, bb);
      return abb.getStructureData(0);
    }

    public void setBufferSize(int bytes) {
    }

    public StructureDataIterator reset() {
      iter = reports.iterator();
      return this;
    }
  } */


  private Report firstReport = null;

  private void init() throws IOException {
    int badPos = 0;
    int badType = 0;
    short firstType = -1;

    raf.seek(0);
    readHeader(raf);

    // read through all the reports, construct unique stations
    Map map = new HashMap<>();
    while (true) {
      Report report = new Report();
      if (!report.readId(raf)) break;

      if (firstReport == null) {
        firstReport = report;
        firstType = firstReport.reportType;
      }

      if (checkType && (report.reportType != firstType)) {
        System.out.println(report.stationId + " type: " + report.reportType + " not " + firstType);
        badType++;
      }

      Station stn = map.get(report.stationId);
      if (stn == null) {
        stn = new Station(report);
        map.put(report.stationId, stn);
        stations.add(stn);

      } else {
        stn.nreports++;

        if (checkPositions) {
          Report first = reports.get(0);
          if (first.lat != report.lat) {
            System.out.println(report.stationId + " lat: " + first.lat + " !=" + report.lat);
            badPos++;
          }
          if (first.lon != report.lon)
            System.out.println(report.stationId + " lon: " + first.lon + " !=" + report.lon);
          if (first.elevMeters != report.elevMeters)
            System.out.println(report.stationId + " elev: " + first.elevMeters + " !=" + report.elevMeters);
        }
      }

      reports.add(report);
    }

    Collections.sort(stations);


    if (checkPositions)
      System.out.println("\nnon matching lats= " + badPos);
    if (checkType)
      System.out.println("\nnon matching reportTypes= " + badType);

    //System.out.println(firstReport);
    //firstReport.show();
    //firstReport.readData();

    /* Set keys = map.keySet();
    if (showTimes || readData || checkSort) {
      int unsorted = 0;

      for (String key : keys) {
        List reports = map.get(key);
        if (showTimes) System.out.print("Station " + key + ": ");
        if (summarizeData) System.out.println("Station " + key + " :");
        Report last = null;
        for (Report r : reports) {
          if ((last != null) && last.date.after(r.date)) {
            System.out.println("***NOT ORDERED " + key +
                " last=" + dateFormatter.toDateTimeStringISO(last.date) + "(" + last.filePos + ")" +
                " next =" + dateFormatter.toDateTimeStringISO(r.date) + "(" + r.filePos + ")");
            unsorted++;
          }
          last = r;

          if (showTimes) System.out.print(dateFormatter.toDateTimeStringISO(r.date) + " ");
          if (readData || summarizeData) {
            List cats = r.readData();
            if (summarizeData) {
              System.out.print("  " + r.obsTime + ": (");
              for (Record cat : cats)
                System.out.print(cat.code + "/" + cat.nlevels + " ");
              System.out.println(")");
            }
          }

        }
        if (showTimes) System.out.println();
      }
      if (checkSort)
        System.out.println("\nunsorted= " + unsorted);

    }
    nstations = keys.size(); */

    // System.out.println("\nnreports= " + reports.size() + " nstations= " + stations.size());
  }

  private static class Station implements Comparable {
    String name;
    Report r;
    int nreports;

    Station(Report r) {
      this.name = r.stationId;
      this.r = r;
      this.nreports = 1;
    }

    public int compareTo(Station o) {
      return name.compareTo(o.name);
    }
  }

  private class Report {
    float lat, lon, elevMeters;
    String stationId;
    byte[] reserved = new byte[7];
    short reportType, instType, obsTime;
    int reportLen;
    long filePos;
    Date date;

    boolean readId(RandomAccessFile raf) throws IOException {

      filePos = raf.getFilePointer();
      byte[] reportId = raf.readBytes(40);
      String latS = new String(reportId, 0, 5, CDM.utf8Charset);

      if (latS.equals("END R")) {
        raf.skipBytes(-40);
        endRecord(raf);

        filePos = raf.getFilePointer();
        reportId = raf.readBytes(40);
        latS = new String(reportId, 0, 5, CDM.utf8Charset);
      }
      if (latS.equals("ENDOF")) {
        raf.skipBytes(-40);
        if (!endFile(raf)) return false;

        filePos = raf.getFilePointer();
        reportId = raf.readBytes(40);
        latS = new String(reportId, 0, 5, CDM.utf8Charset);
      }

      //System.out.println("ReportId start at " + start);
      try {
        lat = (float) (.01 * Float.parseFloat(latS));
        lon = (float) (360.0 - .01 * Float.parseFloat(new String(reportId, 5, 5, CDM.utf8Charset)));

        stationId = new String(reportId, 10, 6, CDM.utf8Charset);
        obsTime = Short.parseShort(new String(reportId, 16, 4, CDM.utf8Charset));
        System.arraycopy(reportId, 20, reserved, 0, 7);
        reportType = Short.parseShort(new String(reportId, 27, 3, CDM.utf8Charset));
        elevMeters = Float.parseFloat(new String(reportId, 30, 5, CDM.utf8Charset));
        instType = Short.parseShort(new String(reportId, 35, 2, CDM.utf8Charset));
        reportLen = 10 * Integer.parseInt(new String(reportId, 37, 3, CDM.utf8Charset));

        cal.setTime(refDate);
        int hour = cal.get(Calendar.HOUR_OF_DAY);
        if (obsTime / 100 > hour + 4) // if greater than 4 hours from reference time
          cal.add(Calendar.DAY_OF_MONTH, -1); // subtract a day LOOK
        cal.set(Calendar.HOUR_OF_DAY, obsTime / 100);
        cal.set(Calendar.MINUTE, 6 * (obsTime % 100));
        date = cal.getTime();

        if (showObs) System.out.println(this);
        else if (showTime) System.out.print("  time=" + obsTime + " date= " + dateFormatter.toDateTimeString(date));

        //nobs++;
        raf.skipBytes(reportLen - 40);
        return reportLen < 30000;

      } catch (IOException e) {
        throw new IOException("BAD reportId=" + new String(reportId, CDM.utf8Charset) + " starts at " + filePos);
      }
    }

    public String toString() {
      return "Report " + " stationId=" + stationId + " lat=" + lat + " lon=" + lon +
              " obsTime=" + obsTime + " date= " + dateFormatter.toDateTimeStringISO(date) +
              " reportType=" + reportType + " elevMeters=" + elevMeters + " instType=" + instType + " reserved=" + new String(reserved, CDM.utf8Charset) +
              " start=" + filePos + " reportLen=" + reportLen;
    }

    // heres where the data for this Report is read into memory

    List readData() throws IOException {
      List records = new ArrayList<>();

      raf.seek(filePos + 40);
      byte[] b = raf.readBytes(reportLen - 40);
      if (showData) System.out.println("\n" + new String(b, CDM.utf8Charset));
      if (showData) System.out.println(this);

      int offset = 0;
      while (true) {
        Record record = new Record();
        offset = record.read(b, offset);
        records.add(record);
        if (record.next >= reportLen / 10) break;
      }

      return records;
    }

    void show(RandomAccessFile raf) throws IOException {
      raf.seek(filePos);
      byte[] b = raf.readBytes(40);
      System.out.println(new String(b, CDM.utf8Charset));
    }

    void loadIndexData(ByteBuffer bb) throws IOException {
      bb.put(stationId.getBytes(CDM.utf8Charset));
      bb.putInt((int) (date.getTime() / 1000));
    }

    void loadStructureData(ArrayStructureBB abb, ByteBuffer bb) throws IOException {
      bb.putInt((int) (date.getTime() / 1000));
      bb.put(dateFormatter.toDateTimeStringISO(date).getBytes(CDM.utf8Charset));
      bb.putShort(reportType);
      bb.putShort(instType);
      bb.put(reserved);

      List records = readData();
      for (StructureCode sc : catStructures)
        loadInnerSequence(abb, bb, records, sc.s, sc.code);
    }

    private void loadInnerSequence(ArrayStructureBB abb, ByteBuffer bb, List records, Structure useStructure, int code) {

      for (Record record : records) {
        if (record.code == code) {
          CatIterator iter = new CatIterator(record.entries, useStructure);
          ArraySequence seq = new ArraySequence(iter.members, iter, record.entries.length);
          int index = abb.addObjectToHeap(seq);
          bb.putInt(index);
          return;
        }
      }

      // need an empty one
      CatIterator iter = new CatIterator(new Entry[0], useStructure);
      ArraySequence seq = new ArraySequence(iter.members, iter, -1); // ??
      int index = abb.addObjectToHeap(seq);
      bb.putInt(index);
    }

    private class CatIterator implements StructureDataIterator {
      Entry[] entries;
      int count = 0;
      StructureMembers members;

      CatIterator(Entry[] entries, Structure useStructure) {
        this.entries = entries;

        members = useStructure.makeStructureMembers();
        for (Variable v2 : useStructure.getVariables()) {
          Vinfo vinfo = (Vinfo) v2.getSPobject();
          StructureMembers.Member m = members.findMember(v2.getShortName());
          m.setDataParam(vinfo.offset);
        }
      }

      @Override
      public boolean hasNext() throws IOException {
        return count < entries.length;
      }

      @Override
      public StructureData next() throws IOException {
        Entry entry = entries[count++];

        // LOOK should read 10 at a time or something ???
        ArrayStructureBB abb = new ArrayStructureBB(members, new int[]{1});
        ByteBuffer bb = abb.getByteBuffer();
        bb.position(0);
        entry.loadStructureData(bb);
        return abb.getStructureData(0);
      }

      @Override
      public void setBufferSize(int bytes) {
      }

      @Override
      public StructureDataIterator reset() {
        count = 0;
        return this;
      }

      @Override
      public int getCurrentRecno() {
        return count - 1;
      }

      @Override
      public void finish() {
        // ignored
      }
    }

  }

  // a record has a variable number of entries, which are all of one "category" type

  private class Record {
    int code, next, nlevels, nbytes;
    Entry[] entries;

    int read(byte[] b, int offset) throws IOException {

      code = Integer.parseInt(new String(b, offset, 2, CDM.utf8Charset));
      next = Integer.parseInt(new String(b, offset + 2, 3, CDM.utf8Charset));
      nlevels = Integer.parseInt(new String(b, offset + 5, 2, CDM.utf8Charset));
      nbytes = readIntWithOverflow(b, offset + 7, 3);
      if (showData) System.out.println("\n" + this);

      offset += 10;

      if (code == 1) {
        if (showData) System.out.println(catNames[1] + ":");
        entries = new Cat01[nlevels];
        for (int i = 0; i < nlevels; i++) {
          entries[i] = new Cat01(b, offset, i);
          if (showData) System.out.println(" " + i + ": " + entries[i]);
          offset += 22;
        }
      } else if (code == 2) {
        if (showData) System.out.println(catNames[2] + ":");
        entries = new Cat02[nlevels];
        for (int i = 0; i < nlevels; i++) {
          entries[i] = new Cat02(b, offset);
          if (showData) System.out.println(" " + i + ": " + entries[i]);
          offset += 15;
        }
      } else if (code == 3) {
        if (showData) System.out.println(catNames[3] + ":");
        entries = new Cat03[nlevels];
        for (int i = 0; i < nlevels; i++) {
          entries[i] = new Cat03(b, offset);
          if (showData) System.out.println(" " + i + ": " + entries[i]);
          offset += 13;
        }
      } else if (code == 4) {
        if (showData) System.out.println(catNames[4] + ":");
        entries = new Cat04[nlevels];
        for (int i = 0; i < nlevels; i++) {
          entries[i] = new Cat04(b, offset);
          if (showData) System.out.println(" " + i + ": " + entries[i]);
          offset += 13;
        }
      } else if (code == 5) {
        if (showData) System.out.println(catNames[5] + ":");
        entries = new Cat05[nlevels];
        for (int i = 0; i < nlevels; i++) {
          entries[i] = new Cat05(b, offset);
          if (showData) System.out.println(" " + i + ": " + entries[i]);
          offset += 22;
        }
      } else if (code == 7) {
        if (showData) System.out.println(catNames[7] + ":");
        entries = new Cat07[nlevels];
        for (int i = 0; i < nlevels; i++) {
          entries[i] = new Cat07(b, offset);
          if (showData) System.out.println(" " + i + ": " + entries[i]);
          offset += 10;
        }
      } else if (code == 8) {
        if (showData) System.out.println(catNames[8] + ":");
        entries = new Cat08[nlevels];
        for (int i = 0; i < nlevels; i++) {
          entries[i] = new Cat08(b, offset);
          if (showData) System.out.println(" " + i + ": " + entries[i]);
          offset += 10;
        }
      } else if (code == 51) {
        if (showData) System.out.println(catNames[10] + ":");
        entries = new Cat51[nlevels];
        for (int i = 0; i < nlevels; i++) {
          entries[i] = new Cat51(b, offset);
          if (showData) System.out.println(" " + i + ": " + entries[i]);
          offset += 60;
        }
      } else if (code == 52) {
        if (showData) System.out.println(catNames[10] + ":");
        entries = new Cat52[nlevels];
        for (int i = 0; i < nlevels; i++) {
          entries[i] = new Cat52(b, offset);
          if (showData) System.out.println(" " + i + ": " + entries[i]);
          offset += 40;
        }
      } else {
        throw new UnsupportedOperationException("code= " + code);
      }

      // must be multiple of 10
      int skip = offset % 10;
      if (skip > 0)
        offset += (10 - skip);
      return offset;
    }

    public String toString() {
      return "Category/Group " + " code=" + code + " next= " + next + " nlevels=" + nlevels + " nbytes=" + nbytes;
    }
  }

  private int readIntWithOverflow(byte[] b, int offset, int len) {

    String s = new String(b, offset, len, CDM.utf8Charset);
    try {
      return Integer.parseInt(s);
    } catch (Exception e) {
      if (showOverflow) System.out.println("OVERFLOW=" + s);
      return 0;
    }
  }

  private String[] catNames = new String[]{"",
          "Category 01: mandatory constant-pressure data",
          "Category 02: temperature/dewpoint at variable pressure-levels ",
          "Category 03: wind at variable pressure-levels ",
          "Category 04: wind at variable height-levels ",
          "Category 05: tropopause data", "",
          "Category 07: cloud cover",
          "Category 08: additional data", "", "",
          "Category 51: surface Data",
          "Category 52: ship surface Data"};


  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  private static float[] mandPressureLevel = new float[]{1000, 850, 700, 500, 400, 300, 250, 200, 150, 100,
          70, 50, 30, 20, 10, 7, 5, 3, 2, 1};

  private abstract class Entry {
    abstract Structure makeStructure(Structure parent) throws InvalidRangeException;

    abstract void loadStructureData(ByteBuffer bb);
  }

  private class Cat01 extends Entry {
    short windDir, windSpeed;
    float geopot, press, temp, dewp;
    byte[] quality = new byte[4];

    Cat01(byte[] b, int offset, int level) throws IOException {
      press = mandPressureLevel[level];
      geopot = Float.parseFloat(new String(b, offset, 5, CDM.utf8Charset));
      temp = .1f * Float.parseFloat(new String(b, offset + 5, 4, CDM.utf8Charset));
      dewp = .1f * Float.parseFloat(new String(b, offset + 9, 3, CDM.utf8Charset));
      windDir = Short.parseShort(new String(b, offset + 12, 3, CDM.utf8Charset));
      windSpeed = Short.parseShort(new String(b, offset + 15, 3, CDM.utf8Charset));
      System.arraycopy(b, offset + 18, quality, 0, 4);
    }

    public String toString() {
      return "Cat01: press= " + press + " geopot=" + geopot + " temp= " + temp + " dewp=" + dewp + " windDir=" + windDir +
              " windSpeed=" + windSpeed + " qs=" + new String(quality, CDM.utf8Charset);
    }

    Structure makeStructure(Structure parent) throws InvalidRangeException {
      Sequence seq = new Sequence(ncfile, null, parent, "mandatoryLevels");
      seq.addAttribute(new Attribute(CDM.LONG_NAME, catNames[1]));

      int pos = 0;

      Variable v = seq.addMemberVariable(new Variable(ncfile, null, parent, "pressure", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "mbars"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "pressure level"));
      v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Pressure.toString()));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "geopotential", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "meter"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "geopotential"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 99999.0f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "temperature", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "celsius"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "temperature"));
      v.addAttribute(new Attribute("accuracy", "celsius/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 999.9f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "dewpoint", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "celsius"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "dewpoint depression"));
      v.addAttribute(new Attribute("accuracy", "celsius/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 99.9f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "windDir", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "degrees"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "wind direction"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 999));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "windSpeed", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "knots"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "wind speed"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 999));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "qualityFlags", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{4});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "quality marks: 0=geopot, 1=temp, 2=dewpoint, 3=wind"));
      v.setSPobject(new Vinfo(pos));

      return seq;
    }

    void loadStructureData(ByteBuffer bb) {
      bb.putFloat(press);
      bb.putFloat(geopot);
      bb.putFloat(temp);
      bb.putFloat(dewp);
      bb.putShort(windDir);
      bb.putShort(windSpeed);
      bb.put(quality);
    }
  }

  private class Cat02 extends Entry {
    float press, temp, dewp;
    byte[] quality = new byte[3];
    String qs;

    Cat02(byte[] b, int offset) throws IOException {
      press = .1f * Float.parseFloat(new String(b, offset, 5, CDM.utf8Charset));
      temp = .1f * Float.parseFloat(new String(b, offset + 5, 4, CDM.utf8Charset));
      dewp = .1f * Float.parseFloat(new String(b, offset + 9, 3, CDM.utf8Charset));
      System.arraycopy(b, offset + 12, quality, 0, 3);
      qs = new String(quality, CDM.utf8Charset);
    }

    public String toString() {
      return "Cat02: press=" + press + " temp= " + temp + " dewp=" + dewp + " qs=" + qs;
    }

    Structure makeStructure(Structure parent) throws InvalidRangeException {
      Sequence seq = new Sequence(ncfile, null, parent, "tempPressureLevels");
      seq.addAttribute(new Attribute(CDM.LONG_NAME, catNames[2]));

      int pos = 0;

      Variable v = seq.addMemberVariable(new Variable(ncfile, null, parent, "pressure", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "mbars"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "pressure level"));
      v.addAttribute(new Attribute("accuracy", "mbar/10"));
      v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Pressure.toString()));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "temperature", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "celsius"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "temperature"));
      v.addAttribute(new Attribute("accuracy", "celsius/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 999.9f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "dewpoint", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "celsius"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "dewpoint depression"));
      v.addAttribute(new Attribute("accuracy", "celsius/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 99.9f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "qualityFlags", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{3});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "quality marks: 0=pressure, 1=temp, 2=dewpoint"));
      v.setSPobject(new Vinfo(pos));

      return seq;
    }

    void loadStructureData(ByteBuffer bb) {
      bb.putFloat(press);
      bb.putFloat(temp);
      bb.putFloat(dewp);
      bb.put(quality);
    }
  }

  private class Cat03 extends Entry {
    float press;
    short windDir, windSpeed;
    byte[] quality;
    String qs;

    Cat03(byte[] b, int offset) throws IOException {
      press = .1f * Float.parseFloat(new String(b, offset, 5, CDM.utf8Charset));
      windDir = Short.parseShort(new String(b, offset + 5, 3, CDM.utf8Charset));
      windSpeed = Short.parseShort(new String(b, offset + 8, 3, CDM.utf8Charset));
      quality = new byte[2];
      System.arraycopy(b, offset + 11, quality, 0, 2);
      qs = new String(quality, CDM.utf8Charset);
    }

    public String toString() {
      return "Cat03: press=" + press + " windDir=" + windDir + " windSpeed=" + windSpeed + " qs=" + qs;
    }

    Structure makeStructure(Structure parent) throws InvalidRangeException {
      Sequence seq = new Sequence(ncfile, null, parent, "windPressureLevels");
      seq.addAttribute(new Attribute(CDM.LONG_NAME, catNames[3]));

      int pos = 0;

      Variable v = seq.addMemberVariable(new Variable(ncfile, null, parent, "pressure", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "mbars"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "pressure level"));
      v.addAttribute(new Attribute("accuracy", "mbar/10"));
      v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Pressure.toString()));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "windDir", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "degrees"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "wind direction"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 999));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "windSpeed", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "knots"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "wind speed"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 999));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "qualityFlags", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{2});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "quality marks: 0=pressure, 1=wind"));
      v.setSPobject(new Vinfo(pos));

      return seq;
    }

    void loadStructureData(ByteBuffer bb) {
      bb.putFloat(press);
      bb.putShort(windDir);
      bb.putShort(windSpeed);
      bb.put(quality);
    }
  }

  private class Cat04 extends Entry {
    float geopot;
    short windDir, windSpeed;
    byte[] quality;
    String qs;

    Cat04(byte[] b, int offset) throws IOException {
      geopot = Float.parseFloat(new String(b, offset, 5, CDM.utf8Charset));
      windDir = Short.parseShort(new String(b, offset + 5, 3, CDM.utf8Charset));
      windSpeed = Short.parseShort(new String(b, offset + 8, 3, CDM.utf8Charset));
      quality = new byte[2];
      System.arraycopy(b, offset + 11, quality, 0, 2);
      qs = new String(quality, CDM.utf8Charset);
    }

    public String toString() {
      return "Cat04: geopot=" + geopot + " windDir=" + windDir + " windSpeed=" + windSpeed + " qs=" + qs;
    }

    Structure makeStructure(Structure parent) throws InvalidRangeException {
      Sequence seq = new Sequence(ncfile, null, parent, "windHeightLevels");
      seq.addAttribute(new Attribute(CDM.LONG_NAME, catNames[4]));

      int pos = 0;

      Variable v = seq.addMemberVariable(new Variable(ncfile, null, parent, "geopotential", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "meter"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "geopotential"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 99999.0f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "windDir", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "degrees"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "wind direction"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 999));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "windSpeed", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "knots"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "wind speed"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 999));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "qualityFlags", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{2});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "quality marks: 0=geopot, 1=wind"));
      v.setSPobject(new Vinfo(pos));

      return seq;
    }

    void loadStructureData(ByteBuffer bb) {
      bb.putFloat(geopot);
      bb.putShort(windDir);
      bb.putShort(windSpeed);
      bb.put(quality);
    }
  }

  private class Cat05 extends Entry {
    float press, temp, dewp;
    short windDir, windSpeed;
    byte[] quality;
    String qs;

    Cat05(byte[] b, int offset) throws IOException {
      press = .1f * Float.parseFloat(new String(b, offset, 5, CDM.utf8Charset));
      temp = .1f * Float.parseFloat(new String(b, offset + 5, 4, CDM.utf8Charset));
      dewp = .1f * Float.parseFloat(new String(b, offset + 9, 3, CDM.utf8Charset));
      windDir = Short.parseShort(new String(b, offset + 12, 3, CDM.utf8Charset));
      windSpeed = Short.parseShort(new String(b, offset + 15, 3, CDM.utf8Charset));
      quality = new byte[4];
      System.arraycopy(b, offset + 18, quality, 0, 4);
      qs = new String(quality, CDM.utf8Charset);
    }

    public String toString() {
      return "Cat05: press= " + press + " temp= " + temp + " dewp=" + dewp + " windDir=" + windDir +
              " windSpeed=" + windSpeed + " qs=" + qs;
    }

    Structure makeStructure(Structure parent) throws InvalidRangeException {
      Sequence seq = new Sequence(ncfile, null, parent, "tropopause");
      seq.addAttribute(new Attribute(CDM.LONG_NAME, catNames[5]));

      int pos = 0;

      Variable v = seq.addMemberVariable(new Variable(ncfile, null, parent, "pressure", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "mbars"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "pressure level"));
      v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Pressure.toString()));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "temperature", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "celsius"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "temperature"));
      v.addAttribute(new Attribute("accuracy", "celsius/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 999.9f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "dewpoint", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "celsius"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "dewpoint depression"));
      v.addAttribute(new Attribute("accuracy", "celsius/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 99.9f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "windDir", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "degrees"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "wind direction"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 999));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "windSpeed", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "knots"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "wind speed"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 999));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "qualityFlags", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{4});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "quality marks: 0=pressure, 1=temp, 2=dewpoint, 3=wind"));
      v.setSPobject(new Vinfo(pos));

      return seq;
    }

    void loadStructureData(ByteBuffer bb) {
      bb.putFloat(press);
      bb.putFloat(temp);
      bb.putFloat(dewp);
      bb.putShort(windDir);
      bb.putShort(windSpeed);
      bb.put(quality);
    }
  }

  private class Cat07 extends Entry {
    float press;
    short percentClouds;
    byte[] quality;
    String qs;

    Cat07(byte[] b, int offset) throws IOException {
      press = .1f * Float.parseFloat(new String(b, offset, 5, CDM.utf8Charset));
      percentClouds = Short.parseShort(new String(b, offset + 5, 3, CDM.utf8Charset));
      quality = new byte[2];
      System.arraycopy(b, offset + 8, quality, 0, 2);
      qs = new String(quality, CDM.utf8Charset);
    }

    public String toString() {
      return "Cat07: press=" + press + " percentClouds=" + percentClouds + " qs=" + qs;
    }

    Structure makeStructure(Structure parent) throws InvalidRangeException {
      Sequence seq = new Sequence(ncfile, null, parent, "clouds");
      seq.addAttribute(new Attribute(CDM.LONG_NAME, catNames[7]));

      int pos = 0;

      Variable v = seq.addMemberVariable(new Variable(ncfile, null, parent, "pressure", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "mbars"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "pressure level"));
      v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Pressure.toString()));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "percentClouds", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, ""));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "amount of cloudiness (%)"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 999));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "qualityFlags", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{2});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "quality marks: 0=pressure, 1=percentClouds"));
      v.setSPobject(new Vinfo(pos));

      return seq;
    }

    void loadStructureData(ByteBuffer bb) {
      bb.putFloat(press);
      bb.putShort(percentClouds);
      bb.put(quality);
    }
  }

  private class Cat08 extends Entry {
    int data;
    short table101code;
    byte[] quality;
    String qs;

    Cat08(byte[] b, int offset) throws IOException {
      data = Integer.parseInt(new String(b, offset, 5, CDM.utf8Charset));
      table101code = Short.parseShort(new String(b, offset + 5, 3, CDM.utf8Charset));
      quality = new byte[2];
      System.arraycopy(b, offset + 8, quality, 0, 2);
      qs = new String(quality, CDM.utf8Charset);
    }

    public String toString() {
      return "Cat08: data=" + data + " table101code=" + table101code + " qs=" + qs;
    }

    Structure makeStructure(Structure parent) throws InvalidRangeException {
      Sequence seq = new Sequence(ncfile, null, parent, "otherData");
      seq.addAttribute(new Attribute(CDM.LONG_NAME, catNames[8]));

      int pos = 0;

      Variable v = seq.addMemberVariable(new Variable(ncfile, null, parent, "data", DataType.INT, ""));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "additional data specified in table 101.1"));
      v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Pressure.toString()));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "table101code", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "code figure from table 101"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 999));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "indicatorFlags", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{2});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "quality marks: 0=data, 1=form"));
      v.setSPobject(new Vinfo(pos));

      return seq;
    }

    void loadStructureData(ByteBuffer bb) {
      bb.putInt(data);
      bb.putShort(table101code);
      bb.put(quality);
    }
  }

  private class Cat51 extends Entry {
    short windDir, windSpeed;
    float pressSeaLevel, pressStation, geopot, press, temp, dewp, maxTemp, minTemp, pressureTendency;
    byte[] quality = new byte[4];
    byte pastWeatherW2, pressureTendencyChar;
    byte[] horizVis = new byte[3];
    byte[] presentWeather = new byte[3];
    byte[] pastWeatherW1 = new byte[2];
    byte[] fracCloudN = new byte[2];
    byte[] fracCloudNh = new byte[2];
    byte[] cloudCl = new byte[2];
    byte[] cloudBaseHeight = new byte[2];
    byte[] cloudCm = new byte[2];
    byte[] cloudCh = new byte[2];

    Cat51(byte[] b, int offset) throws IOException {
      pressSeaLevel = Float.parseFloat(new String(b, offset, 5, CDM.utf8Charset));
      pressStation = Float.parseFloat(new String(b, offset + 5, 5, CDM.utf8Charset));
      windDir = Short.parseShort(new String(b, offset + 10, 3, CDM.utf8Charset));
      windSpeed = Short.parseShort(new String(b, offset + 13, 3, CDM.utf8Charset));
      temp = .1f * Float.parseFloat(new String(b, offset + 16, 4, CDM.utf8Charset));
      dewp = .1f * Float.parseFloat(new String(b, offset + 20, 3, CDM.utf8Charset));
      maxTemp = .1f * Float.parseFloat(new String(b, offset + 23, 4, CDM.utf8Charset));
      minTemp = .1f * Float.parseFloat(new String(b, offset + 27, 4, CDM.utf8Charset));
      System.arraycopy(b, offset + 31, quality, 0, 4);

      pastWeatherW2 = b[offset + 35];
      System.arraycopy(b, offset + 36, horizVis, 0, 3);
      System.arraycopy(b, offset + 39, presentWeather, 0, 3);
      System.arraycopy(b, offset + 42, pastWeatherW1, 0, 2);
      System.arraycopy(b, offset + 44, fracCloudN, 0, 2);
      System.arraycopy(b, offset + 46, fracCloudNh, 0, 2);
      System.arraycopy(b, offset + 48, cloudCl, 0, 2);
      System.arraycopy(b, offset + 50, cloudBaseHeight, 0, 2);
      System.arraycopy(b, offset + 52, cloudCm, 0, 2);
      System.arraycopy(b, offset + 54, cloudCh, 0, 2);
      pressureTendencyChar = b[offset + 56];
      pressureTendency = .1f * Float.parseFloat(new String(b, offset + 57, 3, CDM.utf8Charset));
    }

    public String toString() {
      return "Cat51: press= " + press + " geopot=" + geopot + " temp= " + temp + " dewp=" + dewp + " windDir=" + windDir +
              " windSpeed=" + windSpeed + " qs=" + new String(quality, CDM.utf8Charset) + " pressureTendency=" + pressureTendency;
    }

    Structure makeStructure(Structure parent) throws InvalidRangeException {
      Sequence seq = new Sequence(ncfile, null, parent, "surfaceData");
      seq.addAttribute(new Attribute(CDM.LONG_NAME, catNames[11]));

      int pos = 0;

      Variable v = seq.addMemberVariable(new Variable(ncfile, null, parent, "pressureSeaLevel", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "mbars"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "sea level pressure"));
      v.addAttribute(new Attribute("accuracy", "mbars/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 9999.9f));
      v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Pressure.toString()));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "pressure", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "mbars"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "station pressure"));
      v.addAttribute(new Attribute("accuracy", "mbars/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 9999.9f));
      v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Pressure.toString()));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "windDir", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "degrees"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "wind direction"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 999));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "windSpeed", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "knots"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "wind speed"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 999));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "temperature", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "celsius"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "air temperature"));
      v.addAttribute(new Attribute("accuracy", "celsius/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 999.9f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "dewpoint", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "celsius"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "dewpoint depression"));
      v.addAttribute(new Attribute("accuracy", "celsius/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 99.9f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "temperatureMax", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "celsius"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "maximum temperature"));
      v.addAttribute(new Attribute("accuracy", "celsius/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 999.9f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "temperatureMin", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "celsius"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "minimum temperature"));
      v.addAttribute(new Attribute("accuracy", "celsius/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 999.9f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "qualityFlags", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{4});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "quality marks: 0=pressureSeaLevel, 1=pressure, 2=wind, 3=temperature"));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "pastWeatherW2", DataType.CHAR, ""));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "past weather (W2): WMO table 4561"));
      v.setSPobject(new Vinfo(pos));
      pos += 1;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "horizViz", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{3});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "horizontal visibility: WMO table 4300"));
      v.setSPobject(new Vinfo(pos));
      pos += 3;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "presentWeatherWW", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{3});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "present weather (WW): WMO table 4677"));
      v.setSPobject(new Vinfo(pos));
      pos += 3;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "pastWeatherW1", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{2});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "past weather (WW): WMO table 4561"));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "cloudFractionN", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{2});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "cloud fraction (N): WMO table 2700"));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "cloudFractionNh", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{2});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "cloud fraction (Nh): WMO table 2700"));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "cloudFractionCL", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{2});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "cloud fraction (CL): WMO table 0513"));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "cloudHeightCL", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{2});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "cloud base height above ground (h): WMO table 1600"));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "cloudFractionCM", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{2});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "cloud fraction (CM): WMO table 0515"));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "cloudFractionCH", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{2});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "cloud fraction (CH): WMO table 0509"));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "pressureTendencyCharacteristic", DataType.CHAR, ""));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "pressure tendency characteristic for 3 hours previous to obs time: WMO table 0200"));
      v.setSPobject(new Vinfo(pos));
      pos += 1;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "pressureTendency", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "mbars"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "pressure tendency magnitude"));
      v.addAttribute(new Attribute("accuracy", "mbars/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 99.9f));
      v.setSPobject(new Vinfo(pos));

      return seq;
    }

    void loadStructureData(ByteBuffer bb) {
      bb.putFloat(pressSeaLevel);
      bb.putFloat(pressStation);
      bb.putShort(windDir);
      bb.putShort(windSpeed);
      bb.putFloat(temp);
      bb.putFloat(dewp);
      bb.putFloat(maxTemp);
      bb.putFloat(minTemp);
      bb.put(quality);
      bb.put(pastWeatherW2);
      bb.put(horizVis);
      bb.put(presentWeather);
      bb.put(pastWeatherW1);
      bb.put(fracCloudN);
      bb.put(fracCloudNh);
      bb.put(cloudCl);
      bb.put(cloudBaseHeight);
      bb.put(cloudCm);
      bb.put(cloudCh);
      bb.put(pressureTendencyChar);
      bb.putFloat(pressureTendency);
    }

  }

  private class Cat52 extends Entry {
    short snowDepth, wavePeriod, waveHeight, waveSwellPeriod, waveSwellHeight;
    float precip6hours, precip24hours, sst, waterEquiv;
    byte precipDuration, shipCourse;
    byte[] waveDirection = new byte[2];
    byte[] special = new byte[2];
    byte[] special2 = new byte[2];
    byte[] shipSpeed = new byte[2];

    Cat52(byte[] b, int offset) throws IOException {
      precip6hours = .01f * Float.parseFloat(new String(b, offset, 4, CDM.utf8Charset));
      snowDepth = Short.parseShort(new String(b, offset + 4, 3, CDM.utf8Charset));
      precip24hours = .01f * Float.parseFloat(new String(b, offset + 7, 4, CDM.utf8Charset));
      precipDuration = b[offset + 11];
      wavePeriod = Short.parseShort(new String(b, offset + 12, 2, CDM.utf8Charset));
      waveHeight = Short.parseShort(new String(b, offset + 14, 2, CDM.utf8Charset));
      System.arraycopy(b, offset + 16, waveDirection, 0, 2);
      waveSwellPeriod = Short.parseShort(new String(b, offset + 18, 2, CDM.utf8Charset));
      waveSwellHeight = Short.parseShort(new String(b, offset + 20, 2, CDM.utf8Charset));
      sst = .1f * Float.parseFloat(new String(b, offset + 22, 4, CDM.utf8Charset));
      System.arraycopy(b, offset + 26, special, 0, 2);
      System.arraycopy(b, offset + 28, special2, 0, 2);
      shipCourse = b[offset + 30];
      System.arraycopy(b, offset + 31, shipSpeed, 0, 2);
      waterEquiv = .001f * Float.parseFloat(new String(b, offset + 33, 7, CDM.utf8Charset));
    }

    void loadStructureData(ByteBuffer bb) {
      bb.putFloat(precip6hours);
      bb.putShort(snowDepth);
      bb.putFloat(precip24hours);
      bb.put(precipDuration);
      bb.putShort(wavePeriod);
      bb.putShort(waveHeight);
      bb.put(waveDirection);
      bb.putShort(waveSwellPeriod);
      bb.putShort(waveSwellHeight);
      bb.putFloat(sst);
      bb.put(special);
      bb.put(special2);
      bb.put(shipCourse);
      bb.put(shipSpeed);
      bb.putFloat(waterEquiv);
    }

    public String toString() {
      return "Cat52: precip6hours= " + precip6hours + " precip24hours=" + precip24hours + " sst= " + sst + " waterEquiv=" + waterEquiv +
              " snowDepth=" + snowDepth + " wavePeriod=" + wavePeriod + " waveHeight=" + waveHeight +
              " waveSwellPeriod=" + waveSwellPeriod + " waveSwellHeight=" + waveSwellHeight;
    }

    Structure makeStructure(Structure parent) throws InvalidRangeException {
      Sequence seq = new Sequence(ncfile, null, parent, "surfaceData2");
      seq.addAttribute(new Attribute(CDM.LONG_NAME, catNames[12]));

      int pos = 0;

      Variable v = seq.addMemberVariable(new Variable(ncfile, null, parent, "precip6hours", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "inch"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "precipitation past 6 hours"));
      v.addAttribute(new Attribute("accuracy", "inch/100"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 99.99f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "snowDepth", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "inch"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "total depth of snow on ground"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 999));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "precip24hours", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "inch"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "precipitation past 24 hours"));
      v.addAttribute(new Attribute("accuracy", "inch/100"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 99.99f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "precipDuration", DataType.BYTE, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "6 hours"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "duration of precipitation observation"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 9));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "wavePeriod", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "second"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "period of waves"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 99));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "waveHeight", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "meter/2"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "height of waves"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 99));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "swellWaveDir", DataType.CHAR, ""));
      v.setDimensionsAnonymous(new int[]{2});
      v.addAttribute(new Attribute(CDM.LONG_NAME, "direction from which swell waves are moving: WMO table 0877"));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "swellWavePeriod", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "second"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "period of swell waves"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 99));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "swellWaveHeight", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "meter/2"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "height of waves"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) 99));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "sst", DataType.FLOAT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "celsius"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "sea surface temperature"));
      v.addAttribute(new Attribute("accuracy", "celsius/10"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 999.9f));
      v.setSPobject(new Vinfo(pos));
      pos += 4;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "special", DataType.CHAR, ""));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "special phenomena - general"));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "specialDetail", DataType.CHAR, ""));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "special phenomena - detailed"));
      v.setSPobject(new Vinfo(pos));
      pos += 2;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "shipCourse", DataType.CHAR, ""));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "ships course: WMO table 0700"));
      v.setSPobject(new Vinfo(pos));
      pos += 1;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "shipSpeed", DataType.CHAR, ""));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "ships average speed: WMO table 4451"));
      v.setSPobject(new Vinfo(pos));
      pos += 1;

      v = seq.addMemberVariable(new Variable(ncfile, null, parent, "waterEquiv", DataType.SHORT, ""));
      v.addAttribute(new Attribute(CDM.UNITS, "inch"));
      v.addAttribute(new Attribute(CDM.LONG_NAME, "water equivalent of snow and/or ice"));
      v.addAttribute(new Attribute("accuracy", "inch/100"));
      v.addAttribute(new Attribute(CDM.MISSING_VALUE, 99999.99f));
      v.setSPobject(new Vinfo(pos));

      return seq;
    }

  }

  private boolean endRecord(RandomAccessFile raf) throws IOException {
    if (showSkip) System.out.print(" endRecord start at " + raf.getFilePointer());

    int skipped = 0;
    String endRecord = raf.readString(10); // new String(raf.readBytes(10), CDM.utf8Charset);
    while (endRecord.equals("END RECORD")) {
      endRecord = raf.readString(10); // new String(raf.readBytes(10));
      skipped++;
    }
    if (showSkip) System.out.println(" last 10 chars= " + endRecord + " skipped= " + skipped);
    return true;
  }

  private boolean endFile(RandomAccessFile raf) throws IOException {
    if (showSkip) System.out.println(" endFile start at " + raf.getFilePointer());

    String endRecord = raf.readString(10); // new String(raf.readBytes(10));
    while (endRecord.equals("ENDOF FILE")) {
      endRecord = raf.readString(10); // new String(raf.readBytes(10));
    }

    try {
      while (raf.read() != (int) 'X') ; //find where X's start
      while (raf.read() == (int) 'X') ; //skip X's till you run out
      raf.skipBytes(-1); // go back one
      readHeader(raf);
      return true;

    } catch (EOFException e) {
      return false;
    }
  }

  private void readHeader(RandomAccessFile raf) throws IOException {
    byte[] h = raf.readBytes(60);

    // 12 00 070101
    short hour = Short.parseShort( new String(h, 0, 2, CDM.utf8Charset));
    short minute = Short.parseShort(new String(h, 2, 2, CDM.utf8Charset));
    short year = Short.parseShort(new String(h, 4, 2, CDM.utf8Charset));
    short month = Short.parseShort(new String(h, 6, 2, CDM.utf8Charset));
    short day = Short.parseShort(new String(h, 8, 2, CDM.utf8Charset));

    int fullyear = (year > 30) ? 1900 + year : 2000 + year;

    if (cal == null) {
      cal = Calendar.getInstance();
      cal.setTimeZone(TimeZone.getTimeZone("UTC"));
    }
    cal.clear();
    cal.set(fullyear, month - 1, day, hour, minute);
    refDate = cal.getTime();

    if (showHeader) System.out.println("\nhead=" + new String(h, CDM.utf8Charset) +
            " date= " + dateFormatter.toDateTimeString(refDate));

    int b, count = 0;
    while ((b = raf.read()) == (int) 'X') count++;
    char c = (char) b;
    if (showSkip) System.out.println(" b=" + b + " c=" + c + " at " + raf.getFilePointer() + " skipped= " + count);
    raf.skipBytes(-1); // go back one
  }

  static public void main(String args[]) throws IOException, InvalidRangeException {
    String filename = "C:/data/cadis/tempting";
    //String filename = "C:/data/cadis/Y94179";
    //String filename = "C:/data/cadis/Y94132";
    NmcObsLegacy iosp = new NmcObsLegacy();
    RandomAccessFile raf = new RandomAccessFile(filename, "r");
    NetcdfFile ncfile = new NetcdfFileSubclass(iosp, filename);
    iosp.open(raf, ncfile, null);
    System.out.println("\n" + ncfile);

    Variable v = ncfile.findVariable("station");
    Array data = v.read(new Section().appendRange(0, 1));
    System.out.println(NCdumpW.toString(data, "station", null));

    v = ncfile.findVariable("report");
    data = v.read(new Section().appendRange(0, 0));
    System.out.println(NCdumpW.toString(data, "report", null));

    v = ncfile.findVariable("reportIndex");
    data = v.read();
    System.out.println(NCdumpW.toString(data, "reportIndex", null));

    iosp.close();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy