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

ucar.nc2.ft.point.standard.plug.CFpointObs 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.

There is a newer version: 4.3.22
Show 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.ft.point.standard.plug;

import ucar.nc2.dataset.CoordinateAxis;
import ucar.nc2.ft.point.standard.*;
import ucar.nc2.ft.point.standard.CoordSysEvaluator;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dataset.VariableDS;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.constants.CF;
import ucar.nc2.constants.AxisType;
import ucar.nc2.Dimension;
import ucar.nc2.Variable;
import ucar.nc2.Structure;
import ucar.ma2.DataType;

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

/**
 * CF "point obs" Convention.
 *
 * @author caron
 * @see "http://www.unidata.ucar.edu/software/netcdf-java/reference/FeatureDatasets/CFencodingTable.html"
 * @since Nov 3, 2008
 */
public class CFpointObs extends TableConfigurerImpl {

  private enum Encoding {
    single, multidim, raggedContiguous, raggedIndex, flat
  }

  public boolean isMine(FeatureType wantFeatureType, NetcdfDataset ds) {
    String conv = ds.findAttValueIgnoreCase(null, "Conventions", null);
    if (conv == null) return false;

    StringTokenizer stoke = new StringTokenizer(conv, ",");
    while (stoke.hasMoreTokens()) {
      String toke = stoke.nextToken().trim();
      if (toke.startsWith("CF"))
        return true;
    }
    return false;
  }

  public TableConfig getConfig(FeatureType wantFeatureType, NetcdfDataset ds, Formatter errlog) throws IOException {

    // figure out the actual feature type of the dataset
    String ftypeS = ds.findAttValueIgnoreCase(null, CF.featureTypeAtt, null);
    if (ftypeS == null)
      ftypeS = ds.findAttValueIgnoreCase(null, CF.featureTypeAtt2, null);
    if (ftypeS == null)
      ftypeS = ds.findAttValueIgnoreCase(null, CF.featureTypeAtt3, null);
    if (ftypeS == null)
      ftypeS = ds.findAttValueIgnoreCase(null, CF.featureTypeAtt4, null);

    CF.FeatureType ftype;
    if (ftypeS == null)
      ftype = CF.FeatureType.point;
    else {
      ftype = CF.FeatureType.getFeatureType(ftypeS);
      if (ftypeS == null)
        ftype = CF.FeatureType.point;
    }

    // make sure lat, lon, time coordinates exist
    if (!checkCoordinates(ds, errlog)) return null;

    switch (ftype) {
      case point:
        return getPointConfig(ds, errlog);
      case timeSeries:
        return getStationConfig(ds, errlog);
      case trajectory:
        return getTrajectoryConfig(ds, errlog);
      case profile:
        return getProfileConfig(ds, errlog);
      case timeSeriesProfile:
        return getTimeSeriesProfileConfig(ds, errlog);
      case trajectoryProfile:
        return getSectionConfig(ds, errlog);
    }

    return null;
  }


  private boolean checkCoordinates(NetcdfDataset ds, Formatter errlog) {
    boolean ok = true;
    Variable time = CoordSysEvaluator.findCoordByType(ds, AxisType.Time);
    if (time == null) {
      errlog.format("CFpointObs cant find a Time coordinate %n");
      ok = false;
    }

    // find lat coord
    Variable lat = CoordSysEvaluator.findCoordByType(ds, AxisType.Lat);
    if (lat == null) {
      errlog.format("CFpointObs cant find a Latitude coordinate %n");
      ok = false;
    }

    // find lon coord
    Variable lon = CoordSysEvaluator.findCoordByType(ds, AxisType.Lon);
    if (lon == null) {
      errlog.format("CFpointObs cant find a Longitude coordinate %n");
      ok = false;
    }

    if (!ok) return false;

    // dimensions must match
    List dimLat = lat.getDimensions();
    List dimLon = lon.getDimensions();
    if (!dimLat.equals(dimLon)) {
      errlog.format("Lat and Lon coordinate dimensions must match lat=%s lon=%s %n", lat.getNameAndDimensions(), lon.getNameAndDimensions());
      ok = false;
    }

    return ok;
  }

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


  private TableConfig getPointConfig(NetcdfDataset ds, Formatter errlog) throws IOException {
    Variable time = CoordSysEvaluator.findCoordByType(ds, AxisType.Time);
    if (time.getRank() != 1) {
      errlog.format("CFpointObs type=point: coord time must have rank 1, coord var= %s %n", time.getNameAndDimensions());
      return null;
    }
    Dimension obsDim = time.getDimension(0);

    TableConfig obsTable = makeSingle(ds, obsDim, errlog);
    obsTable.featureType = FeatureType.POINT;
    return obsTable;
  }

  ////

  private TableConfig getStationConfig(NetcdfDataset ds, Formatter errlog) throws IOException {
    EncodingInfo info = identifyEncodingStation(ds, CF.FeatureType.timeSeries, errlog);
    if (info == null) return null;

    // obs dimension
    VariableDS time = CoordSysEvaluator.findCoordByType(ds, AxisType.Time);
    Variable parentId = identifyParent(ds, CF.FeatureType.timeSeries);
    Dimension obsDim = info.childDim;

    // make station table
    TableConfig stnTable = makeStationTable(ds, FeatureType.STATION, info, errlog);
    if (stnTable == null) return null;

    TableConfig obsTable = null;
    switch (info.encoding) {
      case single:
        obsTable = makeSingle(ds, obsDim, errlog);
        break;

      case multidim:
        obsTable = makeMultidimInner(ds, stnTable, obsDim, errlog);
        if (time.getRank() == 1) { // time(time)
          obsTable.addJoin(new JoinArray(time, JoinArray.Type.raw, 0));
          obsTable.time = time.getShortName();
        }
        break;

      case raggedContiguous:
        obsTable = makeRaggedContiguous(ds, info.parentDim, info.childDim, errlog);
        break;

      case raggedIndex:
        obsTable = makeRaggedIndex(ds, info.parentDim, info.childDim, errlog);
        break;

      case flat:
        obsTable = makeStructTable(ds, FeatureType.STATION, new EncodingInfo(Encoding.flat, obsDim), errlog);
        obsTable.parentIndex = (parentId == null) ? null : parentId.getName();
        obsTable.stnId = findNameVariableWithStandardNameAndDimension(ds, CF.STATION_ID, obsDim, errlog);
        obsTable.stnDesc = findNameVariableWithStandardNameAndDimension(ds, CF.STATION_DESC, obsDim, errlog);
        obsTable.stnWmoId = findNameVariableWithStandardNameAndDimension(ds, CF.STATION_WMOID, obsDim, errlog);
        obsTable.stnAlt = findNameVariableWithStandardNameAndDimension(ds, CF.STATION_ALTITUDE, obsDim, errlog);
        break;
    }
    if (obsTable == null) return null;

    stnTable.addChild(obsTable);
    return stnTable;
  }

  ////

  private TableConfig getTrajectoryConfig(NetcdfDataset ds, Formatter errlog) throws IOException {
    EncodingInfo info = identifyEncodingTraj(ds, CF.FeatureType.trajectory, errlog);
    if (info == null) return null;

    TableConfig parentTable = makeStructTable(ds, FeatureType.TRAJECTORY, info, errlog);
    if (parentTable == null) return null;
    parentTable.feature_id = identifyParentId(ds, CF.FeatureType.trajectory);
    if (parentTable.feature_id == null) {
      errlog.format("getTrajectoryConfig cant find a trajectoy id %n");
    }

    // obs table
    //Variable time = CoordSysEvaluator.findCoordByType(ds, AxisType.Time);
    //Dimension obsDim = time.getDimension(time.getRank() - 1); // may be time(time) or time(traj, obs)

    TableConfig obsConfig = null;
    switch (info.encoding) {
      case single:
        obsConfig = makeSingle(ds, info.childDim, errlog);
        break;
      case multidim:
        obsConfig = makeMultidimInner(ds, parentTable, info.childDim, errlog);
        break;
      case raggedContiguous:
        obsConfig = makeRaggedContiguous(ds, info.parentDim, info.childDim, errlog);
        break;
      case raggedIndex:
        obsConfig = makeRaggedIndex(ds, info.parentDim, info.childDim, errlog);
        break;
      case flat:
        throw new UnsupportedOperationException("CFpointObs: trajectory flat encoding");
    }
    if (obsConfig == null) return null;

    parentTable.addChild(obsConfig);
    return parentTable;
  }

  ////

  private TableConfig getProfileConfig(NetcdfDataset ds, Formatter errlog) throws IOException {
    EncodingInfo info = identifyEncodingProfile(ds, CF.FeatureType.profile, errlog);
    if (info == null) return null;

    TableConfig parentTable = makeStructTable(ds, FeatureType.PROFILE, info, errlog);
    if (parentTable == null) return null;
    parentTable.feature_id = identifyParentId(ds, CF.FeatureType.profile);
    if (parentTable.feature_id == null) {
      errlog.format("getProfileConfig cant find a profile id %n");
    }

    // obs table
    VariableDS z = CoordSysEvaluator.findCoordByType(ds, AxisType.Height);
    if (z == null) {
      errlog.format("getProfileConfig cant find a Height coordinate %n");
      return null;
    }
    //Dimension obsDim = z.getDimension(z.getRank() - 1); // may be z(z) or z(profile, z)

    TableConfig obsTable = null;
    switch (info.encoding) {
      case single:
        obsTable = makeSingle(ds, info.childDim, errlog);
        break;
      case multidim:
        obsTable = makeMultidimInner(ds, parentTable, info.childDim, errlog);
        if (z.getRank() == 1) // z(z)
          obsTable.addJoin(new JoinArray(z, JoinArray.Type.raw, 0));
        break;
      case raggedContiguous:
        obsTable = makeRaggedContiguous(ds, info.parentDim, info.childDim, errlog);
        break;
      case raggedIndex:
        obsTable = makeRaggedIndex(ds, info.parentDim, info.childDim, errlog);
        break;
      case flat:
        throw new UnsupportedOperationException("CFpointObs: profile flat encoding");
    }
    if (obsTable == null) return null;

    parentTable.addChild(obsTable);
    return parentTable;
  }

  ////

  private TableConfig getTimeSeriesProfileConfig(NetcdfDataset ds, Formatter errlog) throws IOException {
    EncodingInfo info = identifyEncodingTimeSeriesProfile(ds, CF.FeatureType.timeSeriesProfile, errlog);
    if (info == null) return null;

    VariableDS time = CoordSysEvaluator.findCoordByType(ds, AxisType.Time);
    if (time.getRank() == 0) {
      errlog.format("timeSeriesProfile cannot have a scalar time coordinate%n");
      return null;
    }

    // find the non-station altitude
    VariableDS z = findZAxisNotStationAlt(ds);
    if (z == null) {
      errlog.format("timeSeriesProfile must have a z coordinate%n");
      return null;
    }
    if (z.getRank() == 0) {
      errlog.format("timeSeriesProfile cannot have a scalar z coordinate%n");
      return null;
    }

    /* distinguish multidim from flat
    if ((info.encoding == Encoding.multidim) && (time.getRank() < 3) && (z.getRank() < 3)) {
      Variable parentId = identifyParent(ds, CF.FeatureType.timeSeriesProfile);
      if ((parentId != null) && (parentId.getRank() == 1) && (parentId.getDimension(0).equals(time.getDimension(0)))) {
        if (time.getRank() == 1) // multidim time must be 2 or 3 dim
          info = new EncodingInfo(Encoding.flat, parentId);
        else if (time.getRank() == 2) {
          Dimension zDim = z.getDimension(z.getRank() - 1); // may be z(z) or z(profile, z)
          if (zDim.equals(time.getDimension(1))) // flat 2D time will have time as inner dim 
            info = new EncodingInfo(Encoding.flat, parentId);
        }
      }
    } */

    TableConfig stationTable = makeStationTable(ds, FeatureType.STATION_PROFILE, info, errlog);
    if (stationTable == null) return null;

    //Dimension stationDim = ds.findDimension(stationTable.dimName);
    //Dimension profileDim = null;
    //Dimension zDim = null;

    switch (info.encoding) {
      case single: {
        assert ((time.getRank() >= 1) && (time.getRank() <= 2)) : "time must be rank 1 or 2";
        assert ((z.getRank() >= 1) && (z.getRank() <= 2)) : "z must be rank 1 or 2";

        if (time.getRank() == 2) {
          if (z.getRank() == 2)  // 2d time, 2d z
            assert time.getDimensions().equals(z.getDimensions()) : "rank-2 time and z dimensions must be the same";
          else  // 2d time, 1d z
            assert time.getDimension(1).equals(z.getDimension(0)) : "rank-2 time must have z inner dimension";
          //profileDim = time.getDimension(0);
          //zDim = time.getDimension(1);

        } else { // 1d time
          if (z.getRank() == 2) { // 1d time, 2d z
            assert z.getDimension(0).equals(time.getDimension(0)) : "rank-2 z must have time outer dimension";
            //profileDim = z.getDimension(0);
            //zDim = z.getDimension(1);
          } else { // 1d time, 1d z
            assert !time.getDimension(0).equals(z.getDimension(0)) : "time and z dimensions must be different";
            //profileDim = time.getDimension(0);
            //zDim = z.getDimension(0);
          }
        }
        // make profile table
        TableConfig profileTable = makeStructTable(ds, FeatureType.PROFILE, new EncodingInfo(Encoding.multidim, info.childDim), errlog);
        if (profileTable == null) return null;
        if (time.getRank() == 1) // join time(time)
          profileTable.addJoin(new JoinArray(time, JoinArray.Type.raw, 0));
        stationTable.addChild(profileTable);

        // make the inner (z) table
        TableConfig zTable = makeMultidimInner(ds, profileTable, info.grandChildDim, errlog);
        if (z.getRank() == 1) // join z(z)
          zTable.addJoin(new JoinArray(z, JoinArray.Type.raw, 0));
        profileTable.addChild(zTable);

        break;
      }

      case multidim: {
        assert ((time.getRank() >= 2) && (time.getRank() <= 3)) : "time must be rank 2 or 3";
        assert ((z.getRank() == 1) || (z.getRank() == 3)) : "z must be rank 1 or 3";

        if (time.getRank() == 3) {
          if (z.getRank() == 3)  // 3d time, 3d z
            assert time.getDimensions().equals(z.getDimensions()) : "rank-3 time and z dimensions must be the same";
          else  // 3d time, 1d z
            assert time.getDimension(2).equals(z.getDimension(0)) : "rank-3 time must have z inner dimension";
          //profileDim = time.getDimension(1);
          //zDim = time.getDimension(2);

        } else { // 2d time
          if (z.getRank() == 3) { // 2d time, 3d z
            assert z.getDimension(1).equals(time.getDimension(1)) : "rank-2 time must have time inner dimension";
            //profileDim = z.getDimension(1);
            //zDim = z.getDimension(2);
          } else { // 2d time, 1d z
            assert !time.getDimension(0).equals(z.getDimension(0)) : "time and z dimensions must be different";
            assert !time.getDimension(1).equals(z.getDimension(0)) : "time and z dimensions must be different";
            //profileDim = time.getDimension(1);
            //zDim = z.getDimension(0);
          }
        }

        // make profile table
        //   private TableConfig makeMultidimInner(NetcdfDataset ds, TableConfig parentTable, Dimension obsDim, Formatter errlog) throws IOException {

        TableConfig profileTable = makeMultidimInner(ds, stationTable, info.childDim, errlog);
        if (profileTable == null) return null;
        stationTable.addChild(profileTable);

        // make the inner (z) table
        TableConfig zTable = makeMultidimInner3D(ds, stationTable, profileTable, info.grandChildDim, errlog);
        if (z.getRank() == 1) // join z(z)
          zTable.addJoin(new JoinArray(z, JoinArray.Type.raw, 0));
        profileTable.addChild(zTable);
        break;
      }

      case raggedIndex: {
        //zDim = z.getDimension(0);

        Variable stationIndex = Evaluator.getVariableWithAttributeValue(ds, CF.RAGGED_PARENTINDEX, info.parentDim.getName());
        if (stationIndex == null) {
          errlog.format("timeSeriesProfile stationIndex: must have a ragged_parentIndex variable with profile dimension%n");
          return null;
        }
        if (stationIndex.getRank() != 1) {
          errlog.format("timeSeriesProfile stationIndex: %s variable must be rank 1%n", stationIndex.getName());
          return null;
        }
        //profileDim = stationIndex.getDimension(0);

        Variable ragged_rowSize = Evaluator.getVariableWithAttributeValue(ds, CF.RAGGED_ROWSIZE, info.grandChildDim.getName());
        if (ragged_rowSize == null) {
          errlog.format("timeSeriesProfile numObs: must have a ragged_rowSize variable with profile dimension %s%n", info.childDim);
          return null;
        }
        if (ragged_rowSize.getRank() != 1) {
          errlog.format("timeSeriesProfile numObs: %s variable for observations must be rank 1%n", ragged_rowSize.getName());
          return null;
        }
        if (info.childDim.equals(info.grandChildDim)) {
          errlog.format("timeSeriesProfile profile dimension %s cannot be obs dimension %s%n", info.childDim, info.grandChildDim);
          return null;
        }

        TableConfig profileTable = makeRaggedIndex(ds, info.parentDim, info.childDim, errlog);
        stationTable.addChild(profileTable);
        TableConfig zTable = makeRaggedContiguous(ds, info.childDim, info.grandChildDim, errlog);
        profileTable.addChild(zTable);
        break;
      }

      case raggedContiguous:   // NOT USED
          throw new UnsupportedOperationException("CFpointObs: profile raggedContiguous encoding");

        /*
      case flat:
        //profileDim = time.getDimension(0); // may be time(profile) or time(profile, z)
        Variable parentId = identifyParent(ds, CF.FeatureType.timeSeriesProfile);

        TableConfig profileTable = makeStructTable(ds, FeatureType.PROFILE, info, errlog);
        profileTable.parentIndex = parentId.getName();
        profileTable.stnId = findNameVariableWithStandardNameAndDimension(ds, CF.STATION_ID, info.childDim, errlog);
        profileTable.stnDesc = findNameVariableWithStandardNameAndDimension(ds, CF.STATION_DESC, info.childDim, errlog);
        profileTable.stnWmoId = findNameVariableWithStandardNameAndDimension(ds, CF.STATION_WMOID, info.childDim, errlog);
        profileTable.stnAlt = findNameVariableWithStandardNameAndDimension(ds, CF.STATION_ALTITUDE, info.childDim, errlog);
        stationTable.addChild(profileTable);

        //zDim = z.getDimension(z.getRank() - 1); // may be z(z) or z(profile, z)
        TableConfig zTable = makeMultidimInner(ds, profileTable, info.grandChildDim, errlog);
        if (z.getRank() == 1) // z(z)
          zTable.addJoin(new JoinArray(z, JoinArray.Type.raw, 0));
        profileTable.addChild(zTable);

        break; */
    }

    return stationTable;
  }

  private TableConfig getSectionConfig(NetcdfDataset ds, Formatter errlog) throws IOException {
    EncodingInfo info = identifyEncodingSection(ds, CF.FeatureType.trajectoryProfile, errlog);
    if (info == null) return null;

    VariableDS time = CoordSysEvaluator.findCoordByType(ds, AxisType.Time);
    if (time.getRank() == 0) {
      errlog.format("section cannot have a scalar time coordinate%n");
      return null;
    }

    /* if (info.encoding == Encoding.single) {
      Dimension profileDim = time.getDimension(0); // may be time(profile) or time(profile, z)
      Variable parentId = identifyParent(ds, CF.FeatureType.trajectoryProfile);
      if ((parentId != null) && (parentId.getRank() == 1) && (parentId.getDimension(0).equals(profileDim))) {
        info = new EncodingInfo(Encoding.flat, parentId);
      }
    } */

    TableConfig parentTable = makeStructTable(ds, FeatureType.SECTION, info, errlog);
    if (parentTable == null) return null;
    parentTable.feature_id = identifyParentId(ds, CF.FeatureType.trajectoryProfile);
    if (parentTable.feature_id == null) {
      errlog.format("getSectionConfig cant find a section id %n");
    }

    //Dimension sectionDim = ds.findDimension(parentTable.dimName);
    //Dimension profileDim = null;
    //Dimension zDim = null;

    // find the non-station altitude
    VariableDS z = findZAxisNotStationAlt(ds);
    if (z == null) {
      errlog.format("section must have a z coordinate%n");
      return null;
    }
    if (z.getRank() == 0) {
      errlog.format("section cannot have a scalar z coordinate%n");
      return null;
    }

    switch (info.encoding) {
      case single: {
        assert ((time.getRank() >= 1) && (time.getRank() <= 2)) : "time must be rank 1 or 2";
        assert ((z.getRank() >= 1) && (z.getRank() <= 2)) : "z must be rank 1 or 2";

        if (time.getRank() == 2) {
          if (z.getRank() == 2)  // 2d time, 2d z
            assert time.getDimensions().equals(z.getDimensions()) : "rank-2 time and z dimensions must be the same";
          else  // 2d time, 1d z
            assert time.getDimension(1).equals(z.getDimension(0)) : "rank-2 time must have z inner dimension";
          //profileDim = time.getDimension(0);
          //zDim = time.getDimension(1);

        } else { // 1d time
          if (z.getRank() == 2) { // 1d time, 2d z
            assert z.getDimension(0).equals(time.getDimension(0)) : "rank-2 z must have time outer dimension";
            //profileDim = z.getDimension(0);
            //zDim = z.getDimension(1);
          } else { // 1d time, 1d z
            assert !time.getDimension(0).equals(z.getDimension(0)) : "time and z dimensions must be different";
            //profileDim = time.getDimension(0);
            //zDim = z.getDimension(0);
          }
        }
        // make profile table
        TableConfig profileTable = makeStructTable(ds, FeatureType.PROFILE, new EncodingInfo(Encoding.multidim, info.childDim), errlog);
        if (profileTable == null) return null;
        if (time.getRank() == 1) // join time(time)
          profileTable.addJoin(new JoinArray(time, JoinArray.Type.raw, 0));
        parentTable.addChild(profileTable);

        // make the inner (z) table
        TableConfig zTable = makeMultidimInner(ds, profileTable, info.grandChildDim, errlog);
        if (z.getRank() == 1) // join z(z)
          zTable.addJoin(new JoinArray(z, JoinArray.Type.raw, 0));
        profileTable.addChild(zTable);

        break;
      }

      case multidim: {
        assert ((time.getRank() >= 2) && (time.getRank() <= 3)) : "time must be rank 2 or 3";
        assert ((z.getRank() == 1) || (z.getRank() == 3)) : "z must be rank 1 or 3";

        if (time.getRank() == 3) {
          if (z.getRank() == 3)  // 3d time, 3d z
            assert time.getDimensions().equals(z.getDimensions()) : "rank-3 time and z dimensions must be the same";
          else  // 3d time, 1d z
            assert time.getDimension(2).equals(z.getDimension(0)) : "rank-3 time must have z inner dimension";
          //profileDim = time.getDimension(1);
          //zDim = time.getDimension(2);

        } else { // 2d time
          if (z.getRank() == 3) { // 2d time, 3d z
            assert z.getDimension(1).equals(time.getDimension(1)) : "rank-2 time must have time inner dimension";
           // profileDim = z.getDimension(1);
            //zDim = z.getDimension(2);
          } else { // 2d time, 1d z
            assert !time.getDimension(0).equals(z.getDimension(0)) : "time and z dimensions must be different";
            assert !time.getDimension(1).equals(z.getDimension(0)) : "time and z dimensions must be different";
            //profileDim = time.getDimension(1);
            //zDim = z.getDimension(0);
          }
        }

        // make profile table
        //   private TableConfig makeMultidimInner(NetcdfDataset ds, TableConfig parentTable, Dimension obsDim, Formatter errlog) throws IOException {

        TableConfig profileTable = makeMultidimInner(ds, parentTable, info.childDim, errlog);
        if (profileTable == null) return null;
        profileTable.feature_id = identifyParentId(ds, CF.FeatureType.profile);
        parentTable.addChild(profileTable);

        // make the inner (z) table
        TableConfig zTable = makeMultidimInner3D(ds, parentTable, profileTable, info.grandChildDim, errlog);
        if (z.getRank() == 1) // join z(z)
          zTable.addJoin(new JoinArray(z, JoinArray.Type.raw, 0));
        profileTable.addChild(zTable);
        break;
      }

      case raggedIndex: {
        //zDim = z.getDimension(0);

        Variable sectionIndex = Evaluator.getVariableWithAttributeValue(ds, CF.RAGGED_PARENTINDEX, info.parentDim.getName());
        //Variable sectionIndex = findVariableWithStandardNameAndNotDimension(ds, CF.RAGGED_PARENTINDEX, info.parentDim, errlog);
        if (sectionIndex == null) {
          errlog.format("section sectionIndex: must have a ragged_parentIndex variable with profile dimension%n");
          return null;
        }
        if (sectionIndex.getRank() != 1) {
          errlog.format("section sectionIndex: %s variable must be rank 1%n", sectionIndex.getName());
          return null;
        }
        //profileDim = sectionIndex.getDimension(0);

        Variable numObs = Evaluator.getVariableWithAttributeValue(ds, CF.RAGGED_ROWSIZE, info.grandChildDim.getName());
        //Variable numObs = findVariableWithStandardNameAndDimension(ds, CF.RAGGED_ROWSIZE, info.childDim, errlog);
        if (numObs == null) {
          errlog.format("section numObs: must have a ragged_rowSize variable with profile dimension %s%n", info.childDim);
          return null;
        }
        if (numObs.getRank() != 1) {
          errlog.format("section numObs: %s variable for observations must be rank 1%n", numObs.getName());
          return null;
        }
        if (info.childDim.equals(info.grandChildDim)) {
          errlog.format("section profile dimension %s cannot be obs dimension %s%n", info.childDim, info.grandChildDim);
          return null;
        }

        TableConfig profileTable = makeRaggedIndex(ds, info.parentDim, info.childDim, errlog);
        parentTable.addChild(profileTable);
        TableConfig zTable = makeRaggedContiguous(ds, info.childDim, info.grandChildDim, errlog);
        profileTable.addChild(zTable);
        break;
      }

      case raggedContiguous: {
        throw new UnsupportedOperationException("CFpointObs: section raggedContiguous encoding%n");
      }

      /*
      case flat:
        parentTable.type = Table.Type.Construct; // override default
        profileDim = time.getDimension(0); // may be time(profile) or time(profile, z)
        Variable parentId = identifyParent(ds, CF.FeatureType.trajectoryProfile);

        TableConfig profileTable = makeStructTable(ds, FeatureType.SECTION, info, errlog);
        profileTable.parentIndex = parentId.getName();
        profileTable.feature_id = identifyParentId(ds, CF.FeatureType.profile);
        parentTable.addChild(profileTable);

        zDim = z.getDimension(z.getRank() - 1); // may be z(z) or z(profile, z)
        TableConfig zTable = makeMultidimInner(ds, profileTable, zDim, errlog);
        if (z.getRank() == 1) // z(z)
          zTable.addJoin(new JoinArray(z, JoinArray.Type.raw, 0));
        profileTable.addChild(zTable);

        break;  */
    }

    return parentTable;
  }

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

  private class EncodingInfo {
    Encoding encoding;
    Dimension parentDim, childDim, grandChildDim;

    EncodingInfo(Encoding encoding, Dimension parentDim) {
      this.encoding = encoding;
      this.parentDim = parentDim;
    }

    EncodingInfo(Encoding encoding, Dimension parentDim, Dimension childDim) {
      this.encoding = encoding;
      this.parentDim = parentDim;
      this.childDim = childDim;
    }

    EncodingInfo(Encoding encoding, Dimension parentDim, Dimension childDim, Dimension grandChildDim) {
      this.encoding = encoding;
      this.parentDim = parentDim;
      this.childDim = childDim;
      this.grandChildDim = grandChildDim;
    }

    EncodingInfo(Encoding encoding, Variable v) {
      this.encoding = encoding;
      this.parentDim = (v == null) ? null : v.getDimension(0);
    }
  }

  // given the feature type, figure out the encoding

  private EncodingInfo identifyEncoding(NetcdfDataset ds, CF.FeatureType ftype, Formatter errlog) {
    Variable ragged_rowSize = Evaluator.getVariableWithAttribute(ds, CF.RAGGED_ROWSIZE);
    if (ragged_rowSize != null) {
      if (ftype == CF.FeatureType.trajectoryProfile) {
        Variable parentId = identifyParent(ds, ftype);
        if (parentId == null) {
          errlog.format("Section ragged must have section_id variable%n");
          return null;
        }
        return new EncodingInfo(Encoding.raggedContiguous, parentId);
      }
      return new EncodingInfo(Encoding.raggedContiguous, ragged_rowSize);
    }

    Variable ragged_parentIndex = Evaluator.getVariableWithAttribute(ds, CF.RAGGED_PARENTINDEX);
    if (ragged_parentIndex != null) {
      Variable ragged_parentId = identifyParent(ds, ftype);
      return new EncodingInfo(Encoding.raggedIndex, ragged_parentId);
    }

    Variable lat = CoordSysEvaluator.findCoordByType(ds, AxisType.Lat);
    if (lat == null) {
      errlog.format("Must have a Latitude coordinate%n");
      return null;
    }

    switch (ftype) {
      case point:
        return new EncodingInfo(Encoding.multidim, (Dimension) null);

      case timeSeries:
      case profile:
      case timeSeriesProfile:
        if (lat.getRank() == 0)
          return new EncodingInfo(Encoding.single, (Dimension) null);
        else if (lat.getRank() == 1)
          return new EncodingInfo(Encoding.multidim, lat);

        errlog.format("CFpointObs %s Must have Lat/Lon coordinates of rank 0 or 1%n", ftype);
        return null;

      case trajectory:
      case trajectoryProfile:
        if (lat.getRank() == 1)
          return new EncodingInfo(Encoding.single, (Dimension) null);
        else if (lat.getRank() == 2)
          return new EncodingInfo(Encoding.multidim, lat);

        errlog.format("CFpointObs %s Must have Lat/Lon coordinates of rank 1 or 2%n", ftype);
        return null;
    }

    return null;
  }

  // given the feature type, figure out the encoding

  private EncodingInfo identifyEncodingStation(NetcdfDataset ds, CF.FeatureType ftype, Formatter errlog) {
    // find the obs dimension
    VariableDS time = CoordSysEvaluator.findCoordByType(ds, AxisType.Time);
    if (time == null) {
      errlog.format("Must have a Time coordinate%n");
      return null;
    }
    Dimension obsDim = null;
    if (time.getRank() > 0)
      obsDim = time.getDimension(time.getRank() - 1); // may be time(time) or time(stn, obs)
    else if (time.getParentStructure() != null) {
      Structure parent = time.getParentStructure(); // if time axis is a structure member, try pulling dimension out of parent structure
      obsDim = parent.getDimension(parent.getRank() - 1);
    }
    if (obsDim == null) {
      errlog.format("Must have a non-scalar Time coordinate%n");
      return null;
    }

    // station dimension
    Variable lat = CoordSysEvaluator.findCoordByType(ds, AxisType.Lat);
    if (lat == null) {
      errlog.format("Must have a Latitude coordinate%n");
      return null;
    }
    if (lat.getRank() == 0)
      return new EncodingInfo(Encoding.single, (Dimension) null, obsDim);
    Dimension stnDim = lat.getDimension(0);

    if (obsDim == stnDim)
      return new EncodingInfo(Encoding.flat, (Dimension) null, obsDim);

    // the raggeds
    Variable ragged_rowSize = Evaluator.getVariableWithAttributeValue(ds, CF.RAGGED_ROWSIZE, obsDim.getName());
    if (ragged_rowSize != null) {
      return new EncodingInfo(Encoding.raggedContiguous, stnDim, obsDim);
    }
    Variable ragged_parentIndex = Evaluator.getVariableWithAttributeValue(ds, CF.RAGGED_PARENTINDEX, stnDim.getName());
    if (ragged_parentIndex != null) {
      return new EncodingInfo(Encoding.raggedIndex, stnDim, obsDim);
    }

    if (lat.getRank() == 1)
      return new EncodingInfo(Encoding.multidim, stnDim, obsDim);

    errlog.format("CFpointObs %s Must have Lat/Lon coordinates of rank 0 or 1%n", ftype);
    return null;
  }

  private EncodingInfo identifyEncodingTraj(NetcdfDataset ds, CF.FeatureType ftype, Formatter errlog) {
    // find the obs dimension
    VariableDS time = CoordSysEvaluator.findCoordByType(ds, AxisType.Time);
    if (time == null) {
      errlog.format("Must have a Time coordinate%n");
      return null;
    }
    Dimension obsDim = null;
    if (time.getRank() > 0)
      obsDim = time.getDimension(time.getRank() - 1); // may be time(time) or time(traj, obs)
    else if (time.getParentStructure() != null) {
      Structure parent = time.getParentStructure(); // if time axis is a structure member, try pulling dimension out of parent structure
      obsDim = parent.getDimension(parent.getRank() - 1);
    }
    if (obsDim == null) {
      errlog.format("Must have a non-scalar Time coordinate%n");
      return null;
    }

    // parent dimension
    Dimension parentDim = null;
    if (time.getRank() > 1) {
      parentDim = time.getDimension(0);
      return new EncodingInfo(Encoding.multidim, parentDim, obsDim);
    }

    // the raggeds
    String dimName = Evaluator.getVariableAttributeValue(ds, CF.RAGGED_PARENTINDEX);
    if (dimName != null) {
      parentDim = ds.findDimension(dimName);
      if (parentDim != null)
        return new EncodingInfo(Encoding.raggedIndex, parentDim, obsDim);
      else {
        errlog.format("CFpointObs %s ragged_parent_index must name parent dimension%n", ftype);
        return null;
      }
    }

    Variable ragged_rowSize = Evaluator.getVariableWithAttributeValue(ds, CF.RAGGED_ROWSIZE, obsDim.getName());
    if (ragged_rowSize != null) {
      parentDim = ragged_rowSize.getDimension(0);
      return new EncodingInfo(Encoding.raggedContiguous, parentDim, obsDim);
    }

    VariableDS lat = CoordSysEvaluator.findCoordByType(ds, AxisType.Lat);
    if (lat == null) {
      errlog.format("Must have a Lat coordinate%n");
      return null;
    }
    if (lat.getRank() > 0) { // multidim case
      for (Dimension d : lat.getDimensions()) {
         if (!d.equals(obsDim)) {
            return new EncodingInfo(Encoding.multidim, d, obsDim);
         }
      }
    }

    //othewise its a single traj in the file
    return new EncodingInfo(Encoding.single, null, obsDim);
  }

  private EncodingInfo identifyEncodingProfile(NetcdfDataset ds, CF.FeatureType ftype, Formatter errlog) {
    // find the obs dimension
    VariableDS z = CoordSysEvaluator.findCoordByType(ds, AxisType.Height);
    if (z == null) {
      errlog.format("Must have a Height coordinate%n");
      return null;
    }
    Dimension obsDim = null;
    if (z.getRank() > 0)
      obsDim = z.getDimension(z.getRank() - 1); // may be z(z) or alt(profile, z)
    else if (z.getParentStructure() != null) {
      Structure parent = z.getParentStructure(); // if time axis is a structure member, try pulling dimension out of parent structure
      obsDim = parent.getDimension(parent.getRank() - 1);
    }
    if (obsDim == null) {
      errlog.format("Must have a non-scalar Height coordinate%n");
      return null;
    }

    // parent dimension
    Dimension parentDim = null;
    if (z.getRank() > 1) {
      parentDim = z.getDimension(0);
      return new EncodingInfo(Encoding.multidim, parentDim, obsDim);
    }

    // the raggeds
    String dimName = Evaluator.getVariableAttributeValue(ds, CF.RAGGED_PARENTINDEX);
    if (dimName != null) {
      parentDim = ds.findDimension(dimName);
      if (parentDim != null)
        return new EncodingInfo(Encoding.raggedIndex, parentDim, obsDim);
      else {
        errlog.format("CFpointObs %s ragged_parent_index must name parent dimension%n", ftype);
        return null;
      }
    }

    Variable ragged_rowSize = Evaluator.getVariableWithAttributeValue(ds, CF.RAGGED_ROWSIZE, obsDim.getName());
    if (ragged_rowSize != null) {
      parentDim = ragged_rowSize.getDimension(0);
      return new EncodingInfo(Encoding.raggedContiguous, parentDim, obsDim);
    }

    VariableDS time = CoordSysEvaluator.findCoordByType(ds, AxisType.Time);
    if (time == null) {
      errlog.format("Must have a Time coordinate%n");
      return null;
    }
    if ((time.getRank() == 0) || (time.getDimension(0) == obsDim))
      return new EncodingInfo(Encoding.single, null, obsDim);

    parentDim = time.getDimension(0);
    return new EncodingInfo(Encoding.multidim, parentDim, obsDim);
  }

  private EncodingInfo identifyEncodingSection(NetcdfDataset ds, CF.FeatureType ftype, Formatter errlog) {
    // find the z dimension
    VariableDS z = CoordSysEvaluator.findCoordByType(ds, AxisType.Height);
    if (z == null) {
      errlog.format("Must have a Height coordinate%n");
      return null;
    }
    Dimension obsDim = null;
    if (z.getRank() > 0)
      obsDim = z.getDimension(z.getRank() - 1); // may be z(z) or alt(profile, z)
    else if (z.getParentStructure() != null) {
      Structure parent = z.getParentStructure(); // if time axis is a structure member, try pulling dimension out of parent structure
      obsDim = parent.getDimension(parent.getRank() - 1);
    }
    if (obsDim == null) {
      errlog.format("Must have a non-scalar Height coordinate%n");
      return null;
    }

    // parent dimension
    Dimension trajDim = null;
    Dimension profileDim = null;
    if (z.getRank() > 2) {
      trajDim = z.getDimension(0);
      profileDim = z.getDimension(1);
      return new EncodingInfo(Encoding.multidim, trajDim, profileDim, obsDim);
    }

    // ragged
    String dimName = Evaluator.getVariableAttributeValue(ds, CF.RAGGED_PARENTINDEX);
    if (dimName != null) {
      trajDim = ds.findDimension(dimName);
      if (trajDim != null) {
        Variable ragged_rowSize = Evaluator.getVariableWithAttributeValue(ds, CF.RAGGED_ROWSIZE, obsDim.getName());
        if (ragged_rowSize != null) {
          profileDim = ragged_rowSize.getDimension(0);
          return new EncodingInfo(Encoding.raggedIndex, trajDim, profileDim, obsDim);
        } else {
          errlog.format("CFpointObs %s ragged_row_count must name obs dimension%n", ftype);
          return null;
        }
      } else {
        errlog.format("CFpointObs %s ragged_parent_index must name station dimension%n", ftype);
        return null;
      }
    }

    VariableDS lat = CoordSysEvaluator.findCoordByType(ds, AxisType.Lat);
    if (lat == null) {
      errlog.format("Must have a Lat coordinate%n");
      return null;
    }
    VariableDS time = CoordSysEvaluator.findCoordByType(ds, AxisType.Time);
    if (time == null) {
      errlog.format("Must have a Time coordinate%n");
      return null;
    }
    if (lat.getRank() == 0) {
      errlog.format("CFpointObs %s may not have scalar lat/lon%n", ftype);
      return null;
    }

    if (time.getRank() > 2) {
      trajDim = time.getDimension(0);
      profileDim = time.getDimension(1);
      return new EncodingInfo(Encoding.multidim, trajDim, profileDim, obsDim);
    }

    if (lat.getRank() == 1) {
      profileDim = lat.getDimension(0);
      return new EncodingInfo(Encoding.single, null, profileDim, obsDim);
    }

    if (lat.getRank() == 2) {
      trajDim = lat.getDimension(0);
      profileDim = lat.getDimension(0);
      return new EncodingInfo(Encoding.multidim, trajDim, profileDim, obsDim);
    }

    // forget flat for now
    errlog.format("CFpointObs %s unrecognized form%n", ftype);
    return null;
  }

  private EncodingInfo identifyEncodingTimeSeriesProfile(NetcdfDataset ds, CF.FeatureType ftype, Formatter errlog) {
    // find the non-station altitude
    VariableDS z = findZAxisNotStationAlt(ds);
    if (z == null) {
      errlog.format("timeSeriesProfile must have a z coordinate, not the station altitude%n");
      return null;
    }
    if (z.getRank() == 0) {
      errlog.format("timeSeriesProfile cannot have a scalar z coordinate%n");
      return null;
    }

    Dimension obsDim = z.getDimension(z.getRank() - 1); // may be z(z) or alt(profile, z)

    // parent dimension
    Dimension stnDim = null;
    Dimension profileDim = null;
    if (z.getRank() > 2) {
      stnDim = z.getDimension(0);
      profileDim = z.getDimension(1);
      return new EncodingInfo(Encoding.multidim, stnDim, profileDim, obsDim);
    }

    // ragged
    String dimName = Evaluator.getVariableAttributeValue(ds, CF.RAGGED_PARENTINDEX);
    if (dimName != null) {
      stnDim = ds.findDimension(dimName);
      if (stnDim != null) {
        Variable ragged_rowSize = Evaluator.getVariableWithAttributeValue(ds, CF.RAGGED_ROWSIZE, obsDim.getName());
        if (ragged_rowSize != null) {
          profileDim = ragged_rowSize.getDimension(0);
          return new EncodingInfo(Encoding.raggedIndex, stnDim, profileDim, obsDim);
        } else {
          errlog.format("CFpointObs %s ragged_row_count must name obs dimension%n", ftype);
          return null;
        }
      } else {
        errlog.format("CFpointObs %s ragged_parent_index must name station dimension%n", ftype);
        return null;
      }
    }

    VariableDS lat = CoordSysEvaluator.findCoordByType(ds, AxisType.Lat);
    if (lat == null) {
      errlog.format("Must have a Lat coordinate%n");
      return null;
    }
    VariableDS time = CoordSysEvaluator.findCoordByType(ds, AxisType.Time);
    if (time == null) {
      errlog.format("Must have a Time coordinate%n");
      return null;
    }
    if (lat.getRank() == 0) {
      profileDim = time.getDimension(0); // may be time(profile) or time(profile, z)
      return new EncodingInfo(Encoding.single, null, profileDim, obsDim);
    }

    if ((time.getRank() == 1) || (time.getRank() == 2 && time.getDimension(1) == obsDim)) {
      profileDim = time.getDimension(0); // may be time(profile) or time(profile, z)
      return new EncodingInfo(Encoding.flat, null, profileDim, obsDim);
    }

    if (time.getRank() > 1) {
      stnDim = time.getDimension(0); // may be time(station, profile) or time(station, profile, z)
      profileDim = time.getDimension(1);
      return new EncodingInfo(Encoding.multidim, stnDim, profileDim, obsDim);
    }

    errlog.format("CFpointObs %s unrecognized form%n", ftype);
    return null;
  }

  private String identifyParentId(NetcdfDataset ds, CF.FeatureType ftype) {
    Variable v = identifyParent(ds, ftype);
    return (v == null) ? null : v.getShortName();
  }

  private Variable identifyParent(NetcdfDataset ds, CF.FeatureType ftype) {
    switch (ftype) {
      case timeSeriesProfile:
      case timeSeries:
        return Evaluator.getVariableWithAttributeValue(ds, CF.STANDARD_NAME, CF.STATION_ID);
      case trajectory:
        return Evaluator.getVariableWithAttributeValue(ds, CF.STANDARD_NAME, CF.TRAJ_ID);
      case profile:
        return Evaluator.getVariableWithAttributeValue(ds, CF.STANDARD_NAME, CF.PROFILE_ID);
      case trajectoryProfile:
        return Evaluator.getVariableWithAttributeValue(ds, CF.STANDARD_NAME, CF.TRAJ_ID);
    }
    return null;
  }

  // for station and stationProfile, not flat

  private TableConfig makeStationTable(NetcdfDataset ds, FeatureType ftype, EncodingInfo info, Formatter errlog) throws IOException {
    Variable lat = CoordSysEvaluator.findCoordByType(ds, AxisType.Lat);
    Variable lon = CoordSysEvaluator.findCoordByType(ds, AxisType.Lon);

    //Dimension stationDim = (info.encoding == Encoding.single) ? null : lat.getDimension(0); // assumes outer dim of lat is parent dimension, single = scalar

    Table.Type stationTableType = Table.Type.Structure;
    if (info.encoding == Encoding.single) stationTableType = Table.Type.Top;
    if (info.encoding == Encoding.flat) stationTableType = Table.Type.Construct;

    Dimension stationDim = (info.encoding == Encoding.flat) ? info.childDim : info.parentDim;
    String name = (stationDim == null) ? " single" : stationDim.getName();
    TableConfig stnTable = new TableConfig(stationTableType, name);
    stnTable.featureType = ftype;
    stnTable.stnId = findNameVariableWithStandardNameAndDimension(ds, CF.STATION_ID, stationDim, errlog);
    stnTable.stnDesc = findNameVariableWithStandardNameAndDimension(ds, CF.STATION_DESC, stationDim, errlog);
    stnTable.stnWmoId = findNameVariableWithStandardNameAndDimension(ds, CF.STATION_WMOID, stationDim, errlog);
    stnTable.stnAlt = findNameVariableWithStandardNameAndDimension(ds, CF.STATION_ALTITUDE, stationDim, errlog);
    stnTable.lat = lat.getName();
    stnTable.lon = lon.getName();

    // station id
    if (stnTable.stnId == null) {
      errlog.format("Must have a Station id variable with standard name station_id%n");
      return null;
    }

    if (info.encoding != Encoding.single) {
      // set up structure
      boolean hasStruct = Evaluator.hasRecordStructure(ds) && stationDim.isUnlimited();
      stnTable.structureType = hasStruct ? TableConfig.StructureType.Structure : TableConfig.StructureType.PsuedoStructure;
      stnTable.dimName = stationDim.getName();
      stnTable.structName = hasStruct ? "record" : stationDim.getName();
    }

    // LOOK probably need a standard name here
    // optional alt coord - detect if its a station height or actually associated with the obs, eg for a profile
    if (stnTable.stnAlt == null) {
      Variable alt = CoordSysEvaluator.findCoordByType(ds, AxisType.Height);
      if (alt != null) {
        if ((info.encoding == Encoding.single) && alt.getRank() == 0)
          stnTable.stnAlt = alt.getName();

        if ((info.encoding != Encoding.single) && (lat.getRank() == alt.getRank()) && alt.getDimension(0).equals(stationDim))
          stnTable.stnAlt = alt.getName();
      }
    }

    return stnTable;
  }

  private TableConfig makeStructTable(NetcdfDataset ds, FeatureType ftype, EncodingInfo info, Formatter errlog) throws IOException {
    Table.Type tableType = Table.Type.Structure;
    if (info.encoding == Encoding.single) tableType = Table.Type.Top;
    if (info.encoding == Encoding.flat) tableType = Table.Type.ParentId;

    String name = (info.parentDim == null) ? " single" : info.parentDim.getName();
    TableConfig tableConfig = new TableConfig(tableType, name);
    tableConfig.lat = matchAxisTypeAndDimension(ds, AxisType.Lat, info.parentDim);
    tableConfig.lon = matchAxisTypeAndDimension(ds, AxisType.Lon, info.parentDim);
    tableConfig.elev = matchAxisTypeAndDimension(ds, AxisType.Height, info.parentDim);
    tableConfig.time = matchAxisTypeAndDimension(ds, AxisType.Time, info.parentDim);
    tableConfig.featureType = ftype;

    if (info.encoding != Encoding.single) {
      // set up structure
      boolean stnIsStruct = Evaluator.hasRecordStructure(ds) && info.parentDim.isUnlimited();
      tableConfig.structureType = stnIsStruct ? TableConfig.StructureType.Structure : TableConfig.StructureType.PsuedoStructure;
      tableConfig.dimName = info.parentDim.getName();
      tableConfig.structName = stnIsStruct ? "record" : tableConfig.dimName;
    }

    return tableConfig;
  }

  // test E:/work/signell/traj2D.ncml
  private TableConfig makeStructTableTestTraj(NetcdfDataset ds, FeatureType ftype, EncodingInfo info, Formatter errlog) throws IOException {
    Table.Type tableType = Table.Type.Structure;
    if (info.encoding == Encoding.single) tableType = Table.Type.Top;
    if (info.encoding == Encoding.flat) tableType = Table.Type.ParentId;

    String name = (info.parentDim == null) ? " single" : info.parentDim.getName();
    TableConfig tableConfig = new TableConfig(tableType, name);
    tableConfig.lat = CoordSysEvaluator.findCoordNameByType(ds, AxisType.Lat);
    tableConfig.lon = CoordSysEvaluator.findCoordNameByType(ds, AxisType.Lon);
    tableConfig.elev = CoordSysEvaluator.findCoordNameByType(ds, AxisType.Height);
    tableConfig.time = CoordSysEvaluator.findCoordNameByType(ds, AxisType.Time);
    tableConfig.featureType = ftype;

    if (info.encoding != Encoding.single) {
      // set up structure
      boolean stnIsStruct = Evaluator.hasRecordStructure(ds) && info.parentDim.isUnlimited();
      tableConfig.structureType = stnIsStruct ? TableConfig.StructureType.Structure : TableConfig.StructureType.PsuedoStructure;
      tableConfig.dimName = info.parentDim.getName();
      tableConfig.structName = stnIsStruct ? "record" : tableConfig.dimName;
    }

    return tableConfig;
  }

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

  private TableConfig makeRaggedContiguous(NetcdfDataset ds, Dimension parentDim, Dimension childDim, Formatter errlog) throws IOException {
    TableConfig obsTable = new TableConfig(Table.Type.Contiguous, childDim.getName());
    obsTable.dimName = childDim.getName();

    obsTable.lat = matchAxisTypeAndDimension(ds, AxisType.Lat, childDim);
    obsTable.lon = matchAxisTypeAndDimension(ds, AxisType.Lon, childDim);
    obsTable.elev = matchAxisTypeAndDimension(ds, AxisType.Height, childDim);
    obsTable.time = matchAxisTypeAndDimension(ds, AxisType.Time, childDim);

    boolean obsIsStruct = Evaluator.hasRecordStructure(ds) && childDim.isUnlimited();
    obsTable.structName = obsIsStruct ? "record" : childDim.getName();
    obsTable.structureType = obsIsStruct ? TableConfig.StructureType.Structure : TableConfig.StructureType.PsuedoStructure;

    Variable ragged_rowSize = Evaluator.getVariableWithAttributeValue(ds, CF.RAGGED_ROWSIZE, childDim.getName());
    if ((null == ragged_rowSize) || (ragged_rowSize.getRank() == 0) || (ragged_rowSize.getDimension(0).getName() != parentDim.getName())) {
      errlog.format("there must be a ragged_row_count variable with outer dimension that matches latitude/longitude dimension %s%n", parentDim.getName());
      return null;
    }
    obsTable.numRecords = ragged_rowSize.getName();

    return obsTable;
  }

  private TableConfig makeRaggedIndex(NetcdfDataset ds,  Dimension parentDim, Dimension childDim, Formatter errlog) throws IOException {
    TableConfig obsTable = new TableConfig(Table.Type.ParentIndex, childDim.getName());
    obsTable.dimName = childDim.getName();

    obsTable.lat = matchAxisTypeAndDimension(ds, AxisType.Lat, childDim);
    obsTable.lon = matchAxisTypeAndDimension(ds, AxisType.Lon, childDim);
    obsTable.elev = matchAxisTypeAndDimension(ds, AxisType.Height, childDim);
    obsTable.time = matchAxisTypeAndDimension(ds, AxisType.Time, childDim);

    boolean obsIsStruct = Evaluator.hasRecordStructure(ds) && childDim.isUnlimited();
    obsTable.structName = obsIsStruct ? "record" : childDim.getName();
    obsTable.structureType = obsIsStruct ? TableConfig.StructureType.Structure : TableConfig.StructureType.PsuedoStructure;

    Variable ragged_parentIndex = Evaluator.getVariableWithAttributeValue(ds, CF.RAGGED_PARENTINDEX, parentDim.getName());
    if ((null == ragged_parentIndex) || (ragged_parentIndex.getRank() == 0) || (ragged_parentIndex.getDimension(0).getName() != childDim.getName())) {
      errlog.format("there must be a ragged_parent_index variable with outer dimension that matches obs dimension %s%n", childDim.getName());
      return null;
    }
    obsTable.parentIndex = ragged_parentIndex.getName();

    return obsTable;
  }

  // the inner table of Structure(outer, inner) and middle table of Structure(outer, middle, inner)

  private TableConfig makeMultidimInner(NetcdfDataset ds, TableConfig parentTable, Dimension obsDim, Formatter errlog) throws IOException {
    Dimension parentDim = ds.findDimension(parentTable.dimName);

    Table.Type obsTableType = (parentTable.structureType == TableConfig.StructureType.PsuedoStructure) ? Table.Type.MultidimInnerPsuedo : Table.Type.MultidimInner;
    TableConfig obsTable = new TableConfig(obsTableType, obsDim.getName());

    obsTable.lat = matchAxisTypeAndDimension(ds, AxisType.Lat, parentDim, obsDim);
    obsTable.lon = matchAxisTypeAndDimension(ds, AxisType.Lon, parentDim, obsDim);
    obsTable.elev = matchAxisTypeAndDimension(ds, AxisType.Height, parentDim, obsDim);
    obsTable.time = matchAxisTypeAndDimension(ds, AxisType.Time, parentDim, obsDim);

    // divide up the variables between the parent and the obs
    List obsVars = null;
    List vars = ds.getVariables();
    List parentVars = new ArrayList(vars.size());
    obsVars = new ArrayList(vars.size());
    for (Variable orgV : vars) {
      if (orgV instanceof Structure) continue;

      Dimension dim0 = orgV.getDimension(0);
      if ((dim0 != null) && dim0.equals(parentDim)) {
        if ((orgV.getRank() == 1) || ((orgV.getRank() == 2) && orgV.getDataType() == DataType.CHAR)) {
          parentVars.add(orgV.getShortName());
        } else {
          Dimension dim1 = orgV.getDimension(1);
          if ((dim1 != null) && dim1.equals(obsDim))
            obsVars.add(orgV.getShortName());
        }
      }
    }
    parentTable.vars = parentVars;
    // parentTable.vars = parentTable.isPsuedoStructure ? parentVars : null; // restrict to these if psuedoStruct

    obsTable.structureType = parentTable.structureType;
    obsTable.outerName = parentDim.getName();
    obsTable.innerName = obsDim.getName();
    obsTable.dimName = (parentTable.structureType == TableConfig.StructureType.PsuedoStructure) ? obsTable.outerName : obsTable.innerName;
    obsTable.structName = obsDim.getName();
    obsTable.vars = obsVars;

    return obsTable;
  }

  // the inner table of Structure(outer, middle, inner)

  private TableConfig makeMultidimInner3D(NetcdfDataset ds, TableConfig outerTable, TableConfig middleTable, Dimension innerDim, Formatter errlog) throws IOException {
    Dimension outerDim = ds.findDimension(outerTable.dimName);
    Dimension middleDim = ds.findDimension(middleTable.innerName);

    Table.Type obsTableType = (outerTable.structureType == TableConfig.StructureType.PsuedoStructure) ? Table.Type.MultidimInnerPsuedo3D : Table.Type.MultidimInner3D;
    TableConfig obsTable = new TableConfig(obsTableType, innerDim.getName());
    obsTable.structureType = TableConfig.StructureType.PsuedoStructure2D;
    obsTable.dimName = outerTable.dimName;
    obsTable.outerName = middleTable.innerName;
    obsTable.innerName = innerDim.getName();
    obsTable.structName = innerDim.getName();

    obsTable.lat = matchAxisTypeAndDimension(ds, AxisType.Lat, outerDim, middleDim, innerDim);
    obsTable.lon = matchAxisTypeAndDimension(ds, AxisType.Lon, outerDim, middleDim, innerDim);
    obsTable.elev = matchAxisTypeAndDimension(ds, AxisType.Height, outerDim, middleDim, innerDim);
    obsTable.time = matchAxisTypeAndDimension(ds, AxisType.Time, outerDim, middleDim, innerDim);

    // divide up the variables between the 3 tables
    List vars = ds.getVariables();
    List outerVars = new ArrayList(vars.size());
    List middleVars = new ArrayList(vars.size());
    List innerVars = new ArrayList(vars.size());
    for (Variable orgV : vars) {
      if (orgV instanceof Structure) continue;

      if ((orgV.getRank() == 1) || ((orgV.getRank() == 2) && orgV.getDataType() == DataType.CHAR)) {
        if (outerDim.equals(orgV.getDimension(0)))
          outerVars.add(orgV.getShortName());

      } else if (orgV.getRank() == 2) {
        if (outerDim.equals(orgV.getDimension(0)) && middleDim.equals(orgV.getDimension(1)))
          middleVars.add(orgV.getShortName());

      } else if (orgV.getRank() == 3) {
        if (outerDim.equals(orgV.getDimension(0)) && middleDim.equals(orgV.getDimension(1)) && innerDim.equals(orgV.getDimension(2)))
          innerVars.add(orgV.getShortName());
      }
    }
    outerTable.vars = outerVars;
    middleTable.vars = middleVars;
    obsTable.vars = innerVars;

    return obsTable;
  }


  private TableConfig makeSingle(NetcdfDataset ds, Dimension obsDim, Formatter errlog) throws IOException {

    Table.Type obsTableType = Table.Type.Structure;
    TableConfig obsTable = new TableConfig(obsTableType, "single");
    obsTable.dimName = obsDim.getName();

    obsTable.lat = matchAxisTypeAndDimension(ds, AxisType.Lat, obsDim);
    obsTable.lon = matchAxisTypeAndDimension(ds, AxisType.Lon, obsDim);
    obsTable.elev = matchAxisTypeAndDimension(ds, AxisType.Height, obsDim);
    obsTable.time = matchAxisTypeAndDimension(ds, AxisType.Time, obsDim);

    boolean obsIsStruct = Evaluator.hasRecordStructure(ds) && obsDim.isUnlimited();
    obsTable.structName = obsIsStruct ? "record" : obsDim.getName();
    obsTable.structureType = obsIsStruct ? TableConfig.StructureType.Structure : TableConfig.StructureType.PsuedoStructure;

    return obsTable;
  }

  private TableConfig makeMiddleTable(NetcdfDataset ds, TableConfig parentTable, Dimension obsDim, Formatter errlog) throws IOException {
    throw new UnsupportedOperationException("CFpointObs: middleTable encoding");
  }

  // Adds check for dimensions against parent structure if applicable...
  //
  // Note to John.  It may be that this implementation can be pushed into the super
  // class, I don't unserstand enough of the code base to anticipate implementation artifacts.

  @Override
  protected String matchAxisTypeAndDimension(NetcdfDataset ds, AxisType type, final Dimension outer) {
    Variable var = CoordSysEvaluator.findCoordByType(ds, type, new CoordSysEvaluator.Predicate() {
      public boolean match(CoordinateAxis axis) {
        if ((outer == null) && (axis.getRank() == 0))
          return true;
        if ((outer != null) && (axis.getRank() == 1) && (outer.equals(axis.getDimension(0))))
          return true;

        // if axis is structure member, try pulling dimension out of parent structure
        if (axis.getParentStructure() != null) {
          Structure parent = axis.getParentStructure();
          if ((outer != null) && (parent.getRank() == 1) && (outer.equals(parent.getDimension(0))))
            return true;
        }
        return false;
      }
    });
    if (var == null) return null;
    return var.getShortName();
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy