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

ucar.nc2.internal.dataset.conv.AWIPSConvention Maven / Gradle / Ivy

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

package ucar.nc2.internal.dataset.conv;

import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import javax.annotation.Nullable;
import ucar.ma2.Array;
import ucar.ma2.ArrayChar;
import ucar.ma2.DataType;
import ucar.ma2.IndexIterator;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.Section;
import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.nc2.NetcdfFile;
import ucar.nc2.Variable;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants._Coordinate;
import ucar.nc2.dataset.CoordinateAxis;
import ucar.nc2.dataset.CoordinateAxis1D;
import ucar.nc2.dataset.CoordinateTransform;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dataset.ProjectionCT;
import ucar.nc2.dataset.VariableDS;
import ucar.nc2.dataset.spi.CoordSystemBuilderFactory;
import ucar.nc2.internal.dataset.CoordSystemBuilder;
import ucar.nc2.iosp.netcdf3.N3iosp;
import ucar.nc2.units.SimpleUnit;
import ucar.nc2.util.CancelTask;
import ucar.unidata.geoloc.LatLonPoint;
import ucar.unidata.geoloc.ProjectionPoint;
import ucar.unidata.geoloc.projection.LambertConformal;
import ucar.unidata.geoloc.projection.Stereographic;
import ucar.unidata.util.StringUtil2;

/** AWIPS netcdf output. */
public class AWIPSConvention extends CoordSystemBuilder {
  private static final String CONVENTION_NAME = "AWIPS";

  private static final boolean debugProj = false;
  private static final boolean debugBreakup = false;

  AWIPSConvention(NetcdfDataset.Builder datasetBuilder) {
    super(datasetBuilder);
    this.conventionName = CONVENTION_NAME;
  }

  public static class Factory implements CoordSystemBuilderFactory {
    @Override
    public String getConventionName() {
      return CONVENTION_NAME;
    }

    @Override
    public boolean isMine(NetcdfFile ncfile) {
      return (null != ncfile.findGlobalAttribute("projName")) && (null != ncfile.findDimension("charsPerLevel"))
          && (null != ncfile.findDimension("x")) && (null != ncfile.findDimension("y"));
    }

    @Override
    public CoordSystemBuilder open(NetcdfDataset.Builder datasetBuilder) {
      return new AWIPSConvention(datasetBuilder);
    }
  }

  ProjectionCT projCT;
  double startx, starty, dx, dy;

  @Override
  public void augmentDataset(CancelTask cancelTask) throws IOException {
    if (rootGroup.findVariableLocal("x").isPresent()) {
      return; // check if its already been done - aggregating enhanced datasets.
    }

    int nx = rootGroup.findDimension("x").map(Dimension::getLength)
        .orElseThrow(() -> new RuntimeException("missing dimension x"));
    int ny = rootGroup.findDimension("y").map(Dimension::getLength)
        .orElseThrow(() -> new RuntimeException("missing dimension y"));

    String projName = rootGroup.getAttributeContainer().findAttributeString("projName", "none");
    if (projName.equalsIgnoreCase("LATLON")) {
      datasetBuilder.replaceCoordinateAxis(rootGroup, makeLonCoordAxis(nx, "x"));
      datasetBuilder.replaceCoordinateAxis(rootGroup, makeLatCoordAxis(ny, "y"));
    } else if (projName.equalsIgnoreCase("LAMBERT_CONFORMAL")) {
      projCT = makeLCProjection(projName);
      datasetBuilder.replaceCoordinateAxis(rootGroup, makeXCoordAxis("x"));
      datasetBuilder.replaceCoordinateAxis(rootGroup, makeYCoordAxis("y"));
    } else if (projName.equalsIgnoreCase("STEREOGRAPHIC")) {
      projCT = makeStereoProjection(projName);
      datasetBuilder.replaceCoordinateAxis(rootGroup, makeXCoordAxis("x"));
      datasetBuilder.replaceCoordinateAxis(rootGroup, makeYCoordAxis("y"));
    }

    CoordinateAxis.Builder timeCoord = makeTimeCoordAxis();
    if (timeCoord != null) {
      datasetBuilder.replaceCoordinateAxis(rootGroup, timeCoord);
      String dimName = timeCoord.getFirstDimensionName();
      if (!timeCoord.shortName.equals(dimName)) {
        timeCoord.addAttribute(new Attribute(_Coordinate.AliasForDimension, dimName));
      }
    }

    // AWIPS cleverly combines multiple z levels into a single variable (!!)
    for (Variable.Builder ncvar : ImmutableList.copyOf(rootGroup.vbuilders)) {
      String levelName = ncvar.shortName + "Levels";
      if (rootGroup.findVariableLocal(levelName).isPresent()) {
        VariableDS.Builder levelVar = (VariableDS.Builder) rootGroup.findVariableLocal(levelName).get();
        if (levelVar.getRank() != 2)
          continue;
        if (levelVar.dataType != DataType.CHAR)
          continue;

        try {
          List levels = breakupLevels(levelVar);
          createNewVariables((VariableDS.Builder) ncvar, levels, levelVar.orgVar.getDimension(0));
        } catch (IOException | InvalidRangeException ioe) {
          parseInfo.format("createNewVariables IOException%n");
        }
      }
    }

    if (projCT != null) {
      VariableDS.Builder v = makeCoordinateTransformVariable(projCT);
      v.addAttribute(new Attribute(_Coordinate.Axes, "x y"));
      rootGroup.addVariable(v);
    }

    // kludge in fixing the units
    for (Variable.Builder v : rootGroup.vbuilders) {
      String units = v.getAttributeContainer().findAttributeString(CDM.UNITS, null);
      if (units != null) {
        ((VariableDS.Builder) v).setUnits(normalize(units)); // removes the old
      }
    }
  }

  // pretty much WRF specific
  private String normalize(String units) {
    if (units.equals("/second"))
      units = "1/sec";
    if (units.equals("degrees K"))
      units = "K";
    else {
      units = StringUtil2.substitute(units, "**", "^");
      units = StringUtil2.remove(units, ')');
      units = StringUtil2.remove(units, '(');
    }
    return units;
  }

  // LOOK not dealing with "FHAG 0 10 ", "FHAG 0 30 "
  // take a combined level variable and create multiple levels out of it
  // return the list of Dimensions that were created
  private List breakupLevels(VariableDS.Builder levelVar) throws IOException {
    if (debugBreakup)
      parseInfo.format("breakupLevels = %s%n", levelVar.shortName);

    List dimList = new ArrayList<>();
    ArrayChar levelVarData;
    try {
      levelVarData = (ArrayChar) levelVar.orgVar.read();
    } catch (IOException ioe) {
      return dimList;
    }

    List values = null;
    String currentUnits = null;
    ArrayChar.StringIterator iter = levelVarData.getStringIterator();
    while (iter.hasNext()) {
      String s = iter.next();
      if (debugBreakup)
        parseInfo.format("   %s%n", s);
      StringTokenizer stoke = new StringTokenizer(s);

      /*
       * problem with blank string:
       * char pvvLevels(levels_35=35, charsPerLevel=10);
       * "MB 1000   ", "MB 975    ", "MB 950    ", "MB 925    ", "MB 900    ", "MB 875    ", "MB 850    ", "MB 825    ",
       * "MB 800    ", "MB 775    ", "MB 750    ",
       * "MB 725    ", "MB 700    ", "MB 675    ", "MB 650    ", "MB 625    ", "MB 600    ", "MB 575    ", "MB 550    ",
       * "MB 525    ", "MB 500    ", "MB 450    ",
       * "MB 400    ", "MB 350    ", "MB 300    ", "MB 250    ", "MB 200    ", "MB 150    ", "MB 100    ", "BL 0 30   ",
       * "BL 60 90  ", "BL 90 120 ", "BL 120 150",
       * "BL 150 180", ""
       */
      if (!stoke.hasMoreTokens())
        continue; // skip it

      // first token is the unit
      String units = stoke.nextToken().trim();
      if (!units.equals(currentUnits)) {
        if (values != null)
          dimList.add(makeZCoordAxis(values, currentUnits));
        values = new ArrayList<>();
        currentUnits = units;
      }

      // next token is the value
      if (stoke.hasMoreTokens())
        values.add(stoke.nextToken());
      else
        values.add("0");
    }
    if (values != null)
      dimList.add(makeZCoordAxis(values, currentUnits));

    if (debugBreakup)
      parseInfo.format("  done breakup%n");

    return dimList;
  }

  // make a new variable out of the list in "values"
  private Dimension makeZCoordAxis(List values, String units) {
    int len = values.size();
    String name = makeZCoordName(units);
    if (len > 1)
      name = name + len;
    else
      name = name + values.get(0);
    StringUtil2.replace(name, ' ', "-");

    if (rootGroup.findDimension(name).isPresent()) {
      Dimension dim = rootGroup.findDimension(name).get();
      if (dim.getLength() == len) {
        if (rootGroup.findVariableLocal(name).isPresent()) {
          return dim;
        }
      }
    }

    String orgName = name;
    int count = 1;
    while (rootGroup.findDimension(name).isPresent()) {
      name = orgName + "-" + count;
      count++;
    }

    // create new one
    Dimension dim = new Dimension(name, len);
    rootGroup.addDimension(dim);
    if (debugBreakup) {
      parseInfo.format("  make Dimension = %s length = %d%n", name, len);
      parseInfo.format("  make ZCoordAxis = = %s length = %d%n", name, len);
    }

    CoordinateAxis1D.Builder v =
        CoordinateAxis1D.builder().setName(name).setDataType(DataType.DOUBLE).setParentGroupBuilder(rootGroup)
            .setDimensionsByName(name).setUnits(makeUnitsName(units)).setDesc(makeLongName(name));
    String positive = getZisPositive(v);
    if (null != positive) {
      v.addAttribute(new Attribute(_Coordinate.ZisPositive, positive));
    }

    Array data = Array.makeArray(DataType.DOUBLE, values);
    v.setCachedData(data, true);
    datasetBuilder.replaceCoordinateAxis(rootGroup, v);

    parseInfo.format("Created Z Coordinate Axis = %s%n", name);
    return dim;
  }

  private String makeZCoordName(String units) {
    if (units.equalsIgnoreCase("MB"))
      return "PressureLevels";
    if (units.equalsIgnoreCase("K"))
      return "PotTempLevels";
    if (units.equalsIgnoreCase("BL"))
      return "BoundaryLayers";

    if (units.equalsIgnoreCase("FHAG"))
      return "FixedHeightAboveGround";
    if (units.equalsIgnoreCase("FH"))
      return "FixedHeight";
    if (units.equalsIgnoreCase("SFC"))
      return "Surface";
    if (units.equalsIgnoreCase("MSL"))
      return "MeanSeaLevel";
    if (units.equalsIgnoreCase("FRZ"))
      return "FreezingLevel";
    if (units.equalsIgnoreCase("TROP"))
      return "Tropopause";
    if (units.equalsIgnoreCase("MAXW"))
      return "MaxWindLevel";
    return units;
  }

  private String makeUnitsName(String units) {
    if (units.equalsIgnoreCase("MB"))
      return "hPa";
    if (units.equalsIgnoreCase("BL"))
      return "hPa";
    if (units.equalsIgnoreCase("FHAG"))
      return "m";
    if (units.equalsIgnoreCase("FH"))
      return "m";
    return "";
  }

  private String makeLongName(String name) {
    if (name.equalsIgnoreCase("PotTempLevels"))
      return "Potential Temperature Level";
    if (name.equalsIgnoreCase("BoundaryLayers"))
      return "BoundaryLayer hectoPascals above ground";
    else
      return name;
  }

  // create new variables as sections of ncVar
  private void createNewVariables(VariableDS.Builder ncVar, List newDims, Dimension levelDim)
      throws InvalidRangeException {

    ArrayList dims = new ArrayList<>(ncVar.orgVar.getDimensions());
    int newDimIndex = dims.indexOf(levelDim);

    int[] origin = new int[ncVar.getRank()];
    int[] shape = ncVar.orgVar.getShape();
    int count = 0;
    for (Dimension dim : newDims) {
      origin[newDimIndex] = count;
      shape[newDimIndex] = dim.getLength();
      Variable varSection = ncVar.orgVar.section(new Section(origin, shape));

      String name = ncVar.shortName + "-" + dim.getShortName();
      VariableDS.Builder varNew =
          VariableDS.builder().setName(name).setOriginalVariable(varSection).setDataType(ncVar.dataType);
      dims.set(newDimIndex, dim);
      varNew.addDimensions(dims);
      varNew.addAttributes(ncVar.getAttributeContainer());

      // synthesize long name
      String long_name = ncVar.getAttributeContainer().findAttributeString(CDM.LONG_NAME, ncVar.shortName);
      long_name = long_name + "-" + dim.getShortName();
      varNew.getAttributeContainer().addAttribute(new Attribute(CDM.LONG_NAME, long_name));

      rootGroup.addVariable(varNew);
      parseInfo.format("Created New Variable as section = %s%n", name);
      count += dim.getLength();
    }
  }

  @Override
  @Nullable
  protected AxisType getAxisType(VariableDS.Builder v) {
    String vname = v.shortName;

    if (vname.equalsIgnoreCase("x"))
      return AxisType.GeoX;
    if (vname.equalsIgnoreCase("lon"))
      return AxisType.Lon;
    if (vname.equalsIgnoreCase("y"))
      return AxisType.GeoY;
    if (vname.equalsIgnoreCase("lat"))
      return AxisType.Lat;
    if (vname.equalsIgnoreCase("record"))
      return AxisType.Time;

    String dimName = v.getFirstDimensionName();
    if ((dimName != null) && dimName.equalsIgnoreCase("record"))
      return AxisType.Time;

    String unit = v.getUnits();
    if (unit != null) {
      if (SimpleUnit.isCompatible("millibar", unit))
        return AxisType.Pressure;
      if (SimpleUnit.isCompatible("m", unit))
        return AxisType.Height;
    }
    // otherwise guess
    return AxisType.GeoZ;
  }

  @Override
  protected void makeCoordinateTransforms() {
    if (projCT != null) {
      VarProcess vp = findVarProcess(projCT.getName(), null);
      vp.isCoordinateTransform = true;
      vp.ct = CoordinateTransform.builder().setPreBuilt(projCT);
    }
    super.makeCoordinateTransforms();
  }

  private String getZisPositive(CoordinateAxis.Builder v) {
    String attValue = v.getAttributeContainer().findAttributeString("positive", null);
    if (null != attValue) {
      return attValue.equalsIgnoreCase("up") ? "up" : "down";
    }

    String unit = v.getUnits();
    if ((unit != null) && SimpleUnit.isCompatible("millibar", unit))
      return "down";
    if ((unit != null) && SimpleUnit.isCompatible("m", unit))
      return "up";

    // dunno
    return null;
  }

  private ProjectionCT makeLCProjection(String name) throws NoSuchElementException {
    double centralLat = findAttributeDouble("centralLat");
    double centralLon = findAttributeDouble("centralLon");
    double rotation = findAttributeDouble("rotation");

    // we have to project in order to find the origin
    LambertConformal lc = new LambertConformal(rotation, centralLon, centralLat, centralLat);
    double lat0 = findAttributeDouble("lat00");
    double lon0 = findAttributeDouble("lon00");
    ProjectionPoint start = lc.latLonToProj(LatLonPoint.create(lat0, lon0));
    if (debugProj)
      parseInfo.format("getLCProjection start at proj coord %s%n", start);
    startx = start.getX();
    starty = start.getY();
    dx = findAttributeDouble("dxKm");
    dy = findAttributeDouble("dyKm");

    return new ProjectionCT(name, "FGDC", lc);
  }

  private ProjectionCT makeStereoProjection(String name) throws NoSuchElementException {
    double centralLat = findAttributeDouble("centralLat");
    double centralLon = findAttributeDouble("centralLon");

    // scale factor at lat = k = 2*k0/(1+sin(lat)) [Snyder,Working Manual p157]
    // then to make scale = 1 at lat, k0 = (1+sin(lat))/2
    double latDxDy = findAttributeDouble("latDxDy");
    double latR = Math.toRadians(latDxDy);
    double scale = (1.0 + Math.abs(Math.sin(latR))) / 2; // thanks to R Schmunk

    // Stereographic(double latt, double lont, double scale)

    Stereographic proj = new Stereographic(centralLat, centralLon, scale);
    // we have to project in order to find the origin
    double lat0 = findAttributeDouble("lat00");
    double lon0 = findAttributeDouble("lon00");
    ProjectionPoint start = proj.latLonToProj(LatLonPoint.create(lat0, lon0));
    startx = start.getX();
    starty = start.getY();
    dx = findAttributeDouble("dxKm");
    dy = findAttributeDouble("dyKm");

    // projection info
    parseInfo.format("---makeStereoProjection start at proj coord %s%n", start);

    double latN = findAttributeDouble("latNxNy");
    double lonN = findAttributeDouble("lonNxNy");
    ProjectionPoint pt = proj.latLonToProj(LatLonPoint.create(latN, lonN));
    parseInfo.format("                        end at proj coord %s%n", pt);
    parseInfo.format("                        scale= %f%n", scale);

    return new ProjectionCT(name, "FGDC", proj);
  }

  CoordinateAxis.Builder makeXCoordAxis(String xname) {
    CoordinateAxis1D.Builder v = CoordinateAxis1D.builder().setName(xname).setDataType(DataType.DOUBLE)
        .setParentGroupBuilder(rootGroup).setDimensionsByName(xname).setUnits("km").setDesc("x on projection");
    v.setAutoGen(startx, dx);

    parseInfo.format("Created X Coordinate Axis = %s%n", xname);
    return v;
  }

  CoordinateAxis.Builder makeYCoordAxis(String yname) {
    CoordinateAxis1D.Builder v = CoordinateAxis1D.builder().setName(yname).setDataType(DataType.DOUBLE)
        .setParentGroupBuilder(rootGroup).setDimensionsByName(yname).setUnits("km").setDesc("y on projection");
    v.setAutoGen(starty, dy);

    parseInfo.format("Created Y Coordinate Axis = %s%n", yname);
    return v;
  }

  @Nullable
  private CoordinateAxis.Builder makeLonCoordAxis(int n, String xname) {
    double min = findAttributeDouble("xMin");
    double max = findAttributeDouble("xMax");
    double d = findAttributeDouble("dx");
    if (Double.isNaN(min) || Double.isNaN(max) || Double.isNaN(d))
      return null;

    CoordinateAxis1D.Builder v = CoordinateAxis1D.builder().setName(xname).setDataType(DataType.DOUBLE)
        .setParentGroupBuilder(rootGroup).setDimensionsByName(xname).setUnits(CDM.LON_UNITS).setDesc("longitude");
    v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lon.toString()));
    v.setAutoGen(min, d);

    double maxCalc = min + d * n;
    parseInfo.format("Created Lon Coordinate Axis (max calc= %f should be = %f)%n", maxCalc, max);
    return v;
  }

  private CoordinateAxis.Builder makeLatCoordAxis(int n, String name) {
    double min = findAttributeDouble("yMin");
    double max = findAttributeDouble("yMax");
    double d = findAttributeDouble("dy");
    if (Double.isNaN(min) || Double.isNaN(max) || Double.isNaN(d))
      return null;

    CoordinateAxis1D.Builder v = CoordinateAxis1D.builder().setName(name).setDataType(DataType.DOUBLE)
        .setParentGroupBuilder(rootGroup).setDimensionsByName(name).setUnits(CDM.LAT_UNITS).setDesc("latitude");
    v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lat.toString()));
    v.setAutoGen(min, d);

    double maxCalc = min + d * n;
    parseInfo.format("Created Lat Coordinate Axis (max calc= %f should be = %f)%n", maxCalc, max);
    return v;
  }

  private CoordinateAxis.Builder makeTimeCoordAxis() {
    VariableDS.Builder timeVar = (VariableDS.Builder) rootGroup.findVariableLocal("valtimeMINUSreftime")
        .orElseThrow(() -> new RuntimeException("must have varible 'valtimeMINUSreftime'"));

    Dimension recordDim =
        rootGroup.findDimension("record").orElseThrow(() -> new RuntimeException("must have dimension 'record'"));

    Array vals;
    try {
      vals = timeVar.orgVar.read();
    } catch (IOException ioe) {
      return null;
    }

    // it seems that the record dimension does not always match valtimeMINUSreftime dimension!!
    // HAHAHAHAHAHAHAHA !
    int recLen = recordDim.getLength();
    int valLen = (int) vals.getSize();
    if (recLen != valLen) {
      try {
        vals = vals.sectionNoReduce(new int[] {0}, new int[] {recordDim.getLength()}, null);
        parseInfo.format(" corrected the TimeCoordAxis length%n");
      } catch (InvalidRangeException e) {
        parseInfo.format("makeTimeCoordAxis InvalidRangeException%n");
      }
    }

    // create the units out of the filename if possible
    String units = makeTimeUnitFromFilename(datasetBuilder.location);
    if (units == null) // ok that didnt work, try something else
      return makeTimeCoordAxisFromReference(vals);

    // create the coord axis
    String name = "timeCoord";
    String desc = "synthesized time coordinate from valtimeMINUSreftime and filename YYYYMMDD_HHMM";
    CoordinateAxis1D.Builder timeCoord =
        CoordinateAxis1D.builder().setName(name).setDataType(DataType.INT).setParentGroupBuilder(rootGroup)
            .setDimensionsByName("record").setUnits(units).setDesc(desc).setCachedData(vals, true);

    parseInfo.format("Created Time Coordinate Axis = %s%n", name);
    return timeCoord;
  }

  private String makeTimeUnitFromFilename(String dsName) {
    dsName = dsName.replace('\\', '/');

    // posFirst: last '/' if it exists
    int posFirst = dsName.lastIndexOf('/');
    if (posFirst < 0)
      posFirst = 0;

    // posLast: next '.' if it exists
    int posLast = dsName.indexOf('.', posFirst);
    if (posLast < 0)
      dsName = dsName.substring(posFirst + 1);
    else
      dsName = dsName.substring(posFirst + 1, posLast);

    // gotta be YYYYMMDD_HHMM
    if (dsName.length() != 13)
      return null;

    String year = dsName.substring(0, 4);
    String mon = dsName.substring(4, 6);
    String day = dsName.substring(6, 8);
    String hour = dsName.substring(9, 11);
    String min = dsName.substring(11, 13);

    return "seconds since " + year + "-" + mon + "-" + day + " " + hour + ":" + min + ":0";
  }

  // construct time coordinate from reftime variable
  @Nullable
  private CoordinateAxis.Builder makeTimeCoordAxisFromReference(Array vals) {
    if (!rootGroup.findVariableLocal("reftime").isPresent())
      return null;
    VariableDS.Builder refVar = (VariableDS.Builder) rootGroup.findVariableLocal("reftime").get();

    double refValue;
    try {
      Array refArray = refVar.orgVar.read();
      refValue = refArray.getDouble(refArray.getIndex()); // get the first value
    } catch (IOException ioe) {
      return null;
    }
    if (refValue == N3iosp.NC_FILL_DOUBLE) // why?
      return null;

    // construct the values array - make it a double to be safe
    Array dvals = Array.factory(DataType.DOUBLE, vals.getShape());
    IndexIterator diter = dvals.getIndexIterator();
    IndexIterator iiter = vals.getIndexIterator();
    while (iiter.hasNext())
      diter.setDoubleNext(iiter.getDoubleNext() + refValue); // add reftime to each of the values

    String name = "timeCoord";
    String units = refVar.getAttributeContainer().findAttributeString(CDM.UNITS, "seconds since 1970-1-1 00:00:00");
    units = normalize(units);
    String desc = "synthesized time coordinate from reftime, valtimeMINUSreftime";
    CoordinateAxis1D.Builder timeCoord =
        CoordinateAxis1D.builder().setName(name).setDataType(DataType.DOUBLE).setParentGroupBuilder(rootGroup)
            .setDimensionsByName("record").setUnits(units).setDesc(desc).setCachedData(dvals, true);

    parseInfo.format("Created Time Coordinate Axis From reftime Variable%n");
    return timeCoord;
  }

  double findAttributeDouble(String attname) {
    Attribute att = rootGroup.getAttributeContainer().findAttributeIgnoreCase(attname);
    if (att == null || att.isString()) {
      parseInfo.format("ERROR cant find numeric attribute= %s%n", attname);
      return Double.NaN;
    }
    return att.getNumericValue().doubleValue();
  }

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy