cdm/src/main/java/ucar/nc2/dataset/CoordinateAxis1D.java
public List<NamedObject> getNames() {
    int n = getDimension(0).getLength();
    List<NamedObject> names = new ArrayList<>(n);
    for (int i = 0; i < n; i++)
      names.add(new ucar.nc2.util.NamedAnything(getCoordName(i), getShortName() + " " + getUnitsString()));
    return names;
  }

  /**
   * The "name" of the ith coordinate. If nominal, this is all there is to a coordinate.
   * If numeric, this will return a String representation of the coordinate.
   *
   * @param index which one ?
   * @return the ith coordinate value as a String
   */
  public String getCoordName(int index) {
    if (!wasRead) doRead();
    if (isNumeric())
      return Format.d(getCoordValue(index), 5, 8);
    else
      return names[index];
  }

  /**
   * Get the ith coordinate value. This is the value of the coordinate axis at which
   * the data value is associated. These must be strictly monotonic.
   *
   * @param index which coordinate. Between 0 and getNumElements()-1 inclusive.
   * @return coordinate value.
   * @throws UnsupportedOperationException if !isNumeric()
   */
  public double getCoordValue(int index) {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordValue() on non-numeric");
    if (!wasRead) doRead();
    return coords[index];
  }

  @Override
  public double getMinValue() {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordValue() on non-numeric");
    if (!wasRead) doRead();

    return Math.min(coords[0], coords[coords.length - 1]);
  }

  @Override
  public double getMaxValue() {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordValue() on non-numeric");
    if (!wasRead) doRead();

    return Math.max(coords[0], coords[coords.length - 1]);
  }

  public double getMinEdgeValue() {
    if (edge == null) return getMinValue();
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordValue() on non-numeric");
    if (!wasRead) doRead();

    return Math.min(edge[0], edge[edge.length - 1]);
  }

  public double getMaxEdgeValue() {
    if (edge == null) return getMaxValue();
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordValue() on non-numeric");
    if (!wasRead) doRead();

    return Math.max(edge[0], edge[edge.length - 1]);
  }

  /**
   * Get the ith coordinate edge. Exact only if isContiguous() is true, otherwise use getBound1() and getBound2().
   * This is the value where the underlying grid element switches
   * from "belonging to" coordinate value i-1 to "belonging to" coordinate value i.
   * In some grids, this may not be well defined, and so should be considered an
   * approximation or a visualization hint.
   * <p><pre>
   *  Coordinate edges must be strictly monotonic:
   *    coordEdge(0) < coordValue(0) < coordEdge(1) < coordValue(1) ...
   *    ... coordEdge(i) < coordValue(i) < coordEdge(i+1) < coordValue(i+1) ...
   *    ... coordEdge(n-1) < coordValue(n-1) < coordEdge(n)
   *  </pre>
   *
   * @param index which coordinate. Between 0 and getNumElements() inclusive.
   * @return coordinate edge.
   * @throws UnsupportedOperationException if !isNumeric()
   */
  public double getCoordEdge(int index) {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordEdge() on non-numeric");
    if (!wasBoundsDone) makeBounds();
    return edge[index];
  }

  /**
   * Get the coordinate values as a double array.
   *
   * @return coordinate value.
   * @throws UnsupportedOperationException if !isNumeric()
   */
  public double[] getCoordValues() {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordValues() on non-numeric");
    if (!wasRead) doRead();
    return coords.clone();
  }

  /**
   * Get the coordinate edges as a double array.
   * Exact only if isContiguous() is true, otherwise use getBound1() and getBound2().
   *
   * @return coordinate edges.
   * @throws UnsupportedOperationException if !isNumeric()
   */
  public double[] getCoordEdges() {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordEdges() on non-numeric");
    if (!wasBoundsDone) makeBounds();
    return edge.clone();
  }

  @Override
  public boolean isContiguous() {
    if (!wasBoundsDone) makeBounds();  // this sets isContiguous
    return isContiguous;
  }

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

  /**
   * If this coordinate has interval values.
   * If so, then one should use getBound1, getBound2, and not getCoordEdges()
   *
   * @return true if coordinate has interval values
   */
  public boolean isInterval() {
    if (!wasBoundsDone) makeBounds();      // this sets isInterval
    return isInterval;
  }

  /**
   * Get the coordinate bound1 as a double array.
   * bound1[i] # coordValue[i] # bound2[i], where # is < if increasing (bound1[i] < bound1[i+1])
   * else < if decreasing.
   *
   * @return coordinate bound1.
   * @throws UnsupportedOperationException if !isNumeric()
   */
  public double[] getBound1() {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getBound1() on non-numeric");
    if (!wasBoundsDone) makeBounds();
    if (bound1 == null) makeBoundsFromEdges();
    assert bound1 != null;
    return bound1.clone();
  }

  /**
   * Get the coordinate bound1 as a double array.
   * bound1[i] # coordValue[i] # bound2[i],  where # is < if increasing (bound1[i] < bound1[i+1])
   * else < if decreasing.
   *
   * @return coordinate bound2.
   * @throws UnsupportedOperationException if !isNumeric()
   */
  public double[] getBound2() {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getBound2() on non-numeric");
    if (!wasBoundsDone) makeBounds();
    if (bound2 == null) makeBoundsFromEdges();
    assert bound2 != null;
    return bound2.clone();
  }

  /**
   * Get the coordinate bounds for the ith coordinate.
   * Can use this for isContiguous() true or false.
   *
   * @param i coordinate index
   * @return double[2] edges for ith coordinate
   */
  public double[] getCoordBounds(int i) {
    if (!wasBoundsDone) makeBounds();

    double[] e = new double[2];
    if (isContiguous()) {
      e[0] = getCoordEdge(i);
      e[1] = getCoordEdge(i + 1);
    } else {
      e[0] = bound1[i];
      e[1] = bound2[i];
    }
    return e;
  }

  public double getCoordBoundsMidpoint(int i) {
    double[] bounds = getCoordBounds(i);
    return (bounds[0]+bounds[1])/2;
  }

  /**
   * Given a coordinate value, find what grid element contains it.
   * This means that
   * <pre>
   * edge[i] <= value < edge[i+1] (if values are ascending)
   * edge[i] > value >= edge[i+1] (if values are descending)
   * </pre>
   *
   * @param coordVal position in this coordinate system
   * @return index of grid point containing it, or -1 if outside grid area
   */
  public int findCoordElement(double coordVal) {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis.findCoordElement() on non-numeric");

    if (isRegular())
      return findCoordElementRegular(coordVal, false);
    if (isContiguous())
      return findCoordElementIrregular(coordVal, false);
    else
      return findCoordElementNonContiguous(coordVal, false);
  }

  /**
   * Given a coordinate position, find what grid element contains it, or is closest to it.
   *
   * @param coordVal position in this coordinate system
   * @return index of grid point containing it, or best estimate of closest grid interval.
   */
  public int findCoordElementBounded(double coordVal) {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis.findCoordElementBounded() on non-numeric");

    // the scalar or len-1 case:
    if (this.getSize() == 1) return 0;

    if (isRegular())
      return findCoordElementRegular(coordVal, true);
    if (isContiguous())
      return findCoordElementIrregular(coordVal, true);
    else
      return findCoordElementNonContiguous(coordVal, true);
  }

  /**
   * @deprecated use findCoordElement(coordVal)
   */
  public int findCoordElement(double coordVal, int lastIndex) {
    return findCoordElement(coordVal);
  }

  //////////////////////////////////////////////////////////////////
  // following is from Jon Blower's ncWMS
  // faster routines for coordValue -> index search
  // significantly modified

  /**
   * Optimize the regular case
   * Gets the index of the given point. Uses index = (value - start) / stride,
   * hence this is faster than an exhaustive search.
   * from jon blower's ncWMS.
   *
   * @param coordValue The value along this coordinate axis
   * @param bounded    if false and not in range, return -1, else nearest index
   * @return the index that is nearest to this point, or -1 if the point is
   * out of range for the axis
   */
  private int findCoordElementRegular(double coordValue, boolean bounded) {
    int n = (int) this.getSize();

    // the scalar or len-1 case:
    if (this.getSize() == 1) {
      return 0;
    }

  /*  if (axisType == AxisType.Lon) {
      double maxValue = this.start + this.increment * n;
      if (betweenLon(coordValue, this.start, maxValue)) {
        double distance = LatLonPointImpl.getClockwiseDistanceTo(this.start, coordValue);
        double exactNumSteps = distance / this.increment;
        // This axis might wrap, so we make sure that the returned index is within range
        return ((int) Math.round(exactNumSteps)) % (int) this.getSize();

      } else if (coordValue < this.start) {
        return bounded ? 0 : -1;
      } else {
        return bounded ? n - 1 : -1;
      }
    } */

    double distance = coordValue - this.start;
    double exactNumSteps = distance / this.increment;
    int index = (int) Math.round(exactNumSteps);
    if (index < 0)
      return bounded ? 0 : -1;
    else if (index >= n)
      return bounded ? n - 1 : -1;
    return index;
  }

  private boolean betweenLon(double lon, double lonBeg, double lonEnd) {
    while (lon < lonBeg) lon += 360;
    return (lon >= lonBeg) && (lon <= lonEnd);
  }

  /**
   * Performs a binary search to find the index of the element of the array
   * whose value is contained in the interval, so must be contiguous.
   *
   * @param target  The value to search for
   * @param bounded if false, and not in range, return -1, else nearest index
   * @return the index of the element in values whose value is closest to target,
   * or -1 if the target is out of range
   */
  private int findCoordElementIrregular(double target, boolean bounded) {
    int n = (int) this.getSize();
    int low = 0;
    int high = n;

    if (isAscending) {
      // Check that the point is within range
      if (target < this.edge[low])
        return bounded ? 0 : -1;
      else if (target > this.edge[high])
        return bounded ? n - 1 : -1;

      // do a binary search to find the nearest index
      int mid = low;
      while (high > low + 1) {
        mid = (low + high) / 2;
        double midVal = this.edge[mid];
        if (midVal == target) return mid;
        else if (midVal < target) low = mid;
        else high = mid;
      }

      return low;

    } else {

      // Check that the point is within range
      if (target > this.edge[low])
        return bounded ? 0 : -1;
      else if (target < this.edge[high])
        return bounded ? n - 1 : -1;

      // do a binary search to find the nearest index
      int mid = low;
      while (high > low + 1) {
        mid = (low + high) / 2;
        double midVal = this.edge[mid];
        if (midVal == target) return mid;
        else if (midVal < target) high = mid;
        else low = mid;
      }

      return high - 1;
    }
  }

  /**
   * Given a coordinate position, find what grid element contains it.
   * Only use if isContiguous() == false
   * This algorithm does a linear search in the bound1[] amd bound2[] array.
   * <p>
   * This means that
   * <pre>
   * edge[i] <= pos < edge[i+1] (if values are ascending)
   * edge[i] > pos >= edge[i+1] (if values are descending)
   * </pre>
   *
   * @param target  The value to search for
   * @param bounded if false, and not in range, return -1, else nearest index
   * @return the index of the element in values whose value is closest to target,
   * or -1 if the target is out of range
   */
  private int findCoordElementNonContiguous(double target, boolean bounded) {

    double[] bounds1 = getBound1();
    double[] bounds2 = getBound2();
    int n = bounds1.length;

    if (isAscending) {
      // Check that the point is within range
      if (target < bounds1[0])
        return bounded ? 0 : -1;
      else if (target > bounds2[n - 1])
        return bounded ? n - 1 : -1;

      int[] idx = findSingleHit(bounds1, bounds2, target);
      if (idx[0] == 0 && !bounded) return -1;  // no hits
      if (idx[0] == 1) return idx[1];          // one hit

      // multiple hits = choose closest to the midpoint i guess
      return findClosest(coords, target);

    } else {

      // Check that the point is within range
      if (target > bounds1[0])
        return bounded ? 0 : -1;
      else if (target < bounds2[n - 1])
        return bounded ? n - 1 : -1;

      int[] idx = findSingleHit(bounds2, bounds1, target);
      if (idx[0] == 0 && !bounded) return -1;  // no hits
      if (idx[0] == 1) return idx[1];

      // multiple hits = choose closest to the midpoint i guess
      return findClosest(getCoordValues(), target);
    }
  }

  // return index if only one match, else -1
  private int[] findSingleHit(double[] low, double[] high, double target) {
    int hits = 0;
    int idxFound = -1;
    int n = low.length;
    for (int i = 0; i < n; i++) {
      if ((low[i] <= target) && (target <= high[i])) {
        hits++;
        idxFound = i;
      }
    }
    return new int[] {hits, idxFound};
  }

  // return index of closest value to target
  private int findClosest(double[] values, double target) {
    double minDiff =  Double.MAX_VALUE;
    int idxFound = -1;
    int n = values.length;
    for (int i = 0; i < n; i++) {
      double diff =  Math.abs(values[i]-target);
      if (diff < minDiff) {
        minDiff = diff;
        idxFound = i;
      }
    }
    return idxFound;
  }

  ///////////////////////////////////////////////////////////////////////////////
  // check if Regular

  /**
   * Get starting value if isRegular()
   *
   * @return starting value if isRegular()
   */
  public double getStart() {
    calcIsRegular();
    return start;
  }

  /**
   * Get increment value if isRegular()
   *
   * @return increment value if isRegular()
   */
  public double getIncrement() {
    calcIsRegular();
    return increment;
  }

  /**
   * If true, then value(i) = <i>getStart()</i> + i * <i>getIncrement()</i>.
   *
   * @return if evenly spaced.
   */
  public boolean isRegular() {
    calcIsRegular();
    return isRegular;
  }

  private void calcIsRegular() {
    if (wasCalcRegular) return;
    if (!wasRead) doRead();

    if (!isNumeric())
      isRegular = false;
    else if (getSize() < 2)
      isRegular = true;
    else {
      start = getCoordValue(0);
      int n = (int) getSize();
      increment = (getCoordValue(n - 1) - getCoordValue(0)) / (n - 1);
      isRegular = true;
      for (int i = 1; i < getSize(); i++)
        if (!ucar.nc2.util.Misc.nearlyEquals(getCoordValue(i) - getCoordValue(i - 1), increment, 5.0e-3)) {
          isRegular = false;
          break;
        }
    }
    wasCalcRegular = true;
  }

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


  private void doRead() {
    if (isNumeric()) {
      readValues();
      wasRead = true;

      if (getSize() < 2)
        isAscending = true;
      else
        isAscending = getCoordValue(0) < getCoordValue(1);
      //  calcIsRegular(); */
    } else if (getDataType() == DataType.STRING) {
      readStringValues();
      wasRead = true;
    } else {
      readCharValues();
      wasRead = true;
    }
  }

  // turns longitude coordinate into monotonic, dealing with possible wrap.
  public void correctLongitudeWrap() {
    // correct non-monotonic longitude coords
    if (axisType != AxisType.Lon) {
      return;
    }

    if (!wasRead) doRead();
    if (!wasBoundsDone) makeBounds();

    boolean monotonic = true;
    for (int i = 0; i < coords.length - 1; i++)
      monotonic &= isAscending ? coords[i] < coords[i + 1] : coords[i] > coords[i + 1];

    if (!monotonic) {
      boolean cross = false;
      if (isAscending) {
        for (int i = 0; i < coords.length; i++) {
          if (cross) coords[i] += 360;
          if (!cross && (i < coords.length - 1) && (coords[i] > coords[i + 1]))
            cross = true;
        }
      } else {
        for (int i = 0; i < coords.length; i++) {
          if (cross) coords[i] -= 360;
          if (!cross && (i < coords.length - 1) && (coords[i] < coords[i + 1]))
            cross = true;
        }
      }

      // LOOK - need to make sure we get stuff from the cache
      Array cachedData = Array.factory(DataType.DOUBLE, getShape(), coords);
      if (getDataType() != DataType.DOUBLE)
        cachedData = MAMath.convert(cachedData, getDataType());
      setCachedData(cachedData);

      if (!isInterval) {
        makeEdges();
      }
    }

  }

  // only used if String

  private void readStringValues() {
    int count = 0;
    Array data;
    try {
      data = read();
    } catch (IOException ioe) {
      log.error("Error reading string coordinate values ", ioe);
      throw new IllegalStateException(ioe);
    }

    names = new String[(int) data.getSize()];
    IndexIterator ii = data.getIndexIterator();
    while (ii.hasNext())
      names[count++] = (String) ii.getObjectNext();
  }

  private void readCharValues() {
    int count = 0;
    ArrayChar data;
    try {
      data = (ArrayChar) read();
    } catch (IOException ioe) {
      log.error("Error reading char coordinate values ", ioe);
      throw new IllegalStateException(ioe);
    }
    ArrayChar.StringIterator iter = data.getStringIterator();
    names = new String[iter.getNumElems()];
    while (iter.hasNext())
      names[count++] = iter.next();
  }

  private void readValues() {
    Array data;
    try {
      // setUseNaNs(false); // missing values not allowed LOOK not true for point data !!
      data = read();
      // if (!hasCachedData()) setCachedData(data, false); //cache data for subsequent reading
    } catch (IOException ioe) {
      log.error("Error reading coordinate values ", ioe);
      throw new IllegalStateException(ioe);
    }

    coords = (double []) data.get1DJavaArray(DataType.DOUBLE);
    //IndexIterator iter = data.getIndexIterator();
    //while (iter.hasNext())
    //  coords[count++] = iter.getDoubleNext();
  }

  /**
   * Calculate bounds, set isInterval, isContiguous
   */
  private void makeBounds() {
    if (!wasRead) doRead();
    if (isNumeric()) {
      if (!makeBoundsFromAux()) {
        makeEdges();
      }
    }
    wasBoundsDone = true;
  }

  private boolean makeBoundsFromAux() {
    Attribute boundsAtt = findAttributeIgnoreCase(CF.BOUNDS);
    if ((null == boundsAtt) || !boundsAtt.isString()) return false;
    String boundsVarName = boundsAtt.getStringValue();
    VariableDS boundsVar = (VariableDS) ncd.findVariable(getParentGroup(), boundsVarName);
    if (null == boundsVar) return false;
    if (2 != boundsVar.getRank()) return false;

    if (getDimension(0) != boundsVar.getDimension(0)) return false;
    if (2 != boundsVar.getDimension(1).getLength()) return false;

    Array data;
    try {
      boundsVar.removeEnhancement(NetcdfDataset.Enhance.ConvertMissing);  // Don't convert missing values to NaN.
      data = boundsVar.read();
    } catch (IOException e) {
      log.warn("CoordinateAxis1D.hasBounds read failed ", e);
      return false;
    }

    assert (data.getRank() == 2) && (data.getShape()[1] == 2) : "incorrect shape data for variable " + boundsVar;

    // extract the bounds
    int n = shape[0];
    double[] value1 = new double[n];
    double[] value2 = new double[n];
    Index ima = data.getIndex();
    for (int i = 0; i < n; i++) {
      ima.set0(i);
      value1[i] = data.getDouble(ima.set1(0));
      value2[i] = data.getDouble(ima.set1(1));
    }

    /* flip if needed
    boolean firstLower = true; // in the first interval, is lower < upper ?
    for (int i = 0; i < value1.length; i++) {
      if (Misc.nearlyEquals(value1[i], value2[i])) continue; // skip when lower == upper
      firstLower = value1[i] < value2[i];
      break;
    }
    // check first against last : lower, unless all lower equal then upper
    boolean goesUp = (n < 2) || value1[n - 1] > value1[0] || (Misc.nearlyEquals(value1[n - 1], value2[0]) && value2[n - 1] > value2[0]);
    if (goesUp != firstLower) {
      double[] temp = value1;
      value1 = value2;
      value2 = temp;
    } */

    // decide if they are contiguous
    boolean contig = true;
    for (int i = 0; i < n - 1; i++) {
      if (!ucar.nc2.util.Misc.nearlyEquals(value1[i + 1], value2[i]))
        contig = false;
    }

    if (contig) {
      edge = new double[n + 1];
      edge[0] = value1[0];
      for (int i = 1; i < n + 1; i++)
        edge[i] = value2[i - 1];
    } else {                           // what does edge mean when not contiguous ??
      edge = new double[n + 1];
      edge[0] = value1[0];
      for (int i = 1; i < n; i++)
        edge[i] = (value1[i] + value2[i - 1]) / 2;
      edge[n] = value2[n - 1];
      isContiguous = false;
    }

    bound1 = value1;
    bound2 = value2;
    isInterval = true;

    return true;
  }

  private void makeEdges() {
    int size = (int) getSize();
    edge = new double[size + 1];
    if (size < 1) return;
    for (int i = 1; i < size; i++)
      edge[i] = (coords[i - 1] + coords[i]) / 2;
    edge[0] = coords[0] - (edge[1] - coords[0]);
    edge[size] = coords[size - 1] + (coords[size - 1] - edge[size - 1]);
    isContiguous = true;
  }

  private void makeBoundsFromEdges() {
    int size = (int) getSize();
    if (size == 0) return;

    bound1 = new double[size];
    bound2 = new double[size];
    for (int i = 0; i < size; i++) {
      bound1[i] = edge[i];
      bound2[i] = edge[i + 1];
    }

    // flip if needed
    if (bound1[0] > bound2[0]) {
      double[] temp = bound1;
      bound1 = bound2;
      bound2 = temp;
    }
  }

}
public List<NamedObject> getNames() {
    int n = getDimension(0).getLength();
    List<NamedObject> names = new ArrayList<>(n);
    for (int i = 0; i < n; i++)
      names.add(new ucar.nc2.util.NamedAnything(getCoordName(i), getShortName() + " " + getUnitsString()));
    return names;
  }

  /**
   * The "name" of the ith coordinate. If nominal, this is all there is to a coordinate.
   * If numeric, this will return a String representation of the coordinate.
   *
   * @param index which one ?
   * @return the ith coordinate value as a String
   */
  public String getCoordName(int index) {
    if (!wasRead) doRead();
    if (isNumeric())
      return Format.d(getCoordValue(index), 5, 8);
    else
      return names[index];
  }

  /**
   * Get the ith coordinate value. This is the value of the coordinate axis at which
   * the data value is associated. These must be strictly monotonic.
   *
   * @param index which coordinate. Between 0 and getNumElements()-1 inclusive.
   * @return coordinate value.
   * @throws UnsupportedOperationException if !isNumeric()
   */
  public double getCoordValue(int index) {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordValue() on non-numeric");
    if (!wasRead) doRead();
    return coords[index];
  }

  @Override
  public double getMinValue() {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordValue() on non-numeric");
    if (!wasRead) doRead();

    return Math.min(coords[0], coords[coords.length - 1]);
  }

  @Override
  public double getMaxValue() {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordValue() on non-numeric");
    if (!wasRead) doRead();

    return Math.max(coords[0], coords[coords.length - 1]);
  }

  public double getMinEdgeValue() {
    if (edge == null) return getMinValue();
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordValue() on non-numeric");
    if (!wasRead) doRead();

    return Math.min(edge[0], edge[edge.length - 1]);
  }

  public double getMaxEdgeValue() {
    if (edge == null) return getMaxValue();
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordValue() on non-numeric");
    if (!wasRead) doRead();

    return Math.max(edge[0], edge[edge.length - 1]);
  }

  /**
   * Get the ith coordinate edge. Exact only if isContiguous() is true, otherwise use getBound1() and getBound2().
   * This is the value where the underlying grid element switches
   * from "belonging to" coordinate value i-1 to "belonging to" coordinate value i.
   * In some grids, this may not be well defined, and so should be considered an
   * approximation or a visualization hint.
   * <p><pre>
   *  Coordinate edges must be strictly monotonic:
   *    coordEdge(0) < coordValue(0) < coordEdge(1) < coordValue(1) ...
   *    ... coordEdge(i) < coordValue(i) < coordEdge(i+1) < coordValue(i+1) ...
   *    ... coordEdge(n-1) < coordValue(n-1) < coordEdge(n)
   *  </pre>
   *
   * @param index which coordinate. Between 0 and getNumElements() inclusive.
   * @return coordinate edge.
   * @throws UnsupportedOperationException if !isNumeric()
   */
  public double getCoordEdge(int index) {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordEdge() on non-numeric");
    if (!wasBoundsDone) makeBounds();
    return edge[index];
  }

  /**
   * Get the coordinate values as a double array.
   *
   * @return coordinate value.
   * @throws UnsupportedOperationException if !isNumeric()
   */
  public double[] getCoordValues() {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordValues() on non-numeric");
    if (!wasRead) doRead();
    return coords.clone();
  }

  /**
   * Get the coordinate edges as a double array.
   * Exact only if isContiguous() is true, otherwise use getBound1() and getBound2().
   *
   * @return coordinate edges.
   * @throws UnsupportedOperationException if !isNumeric()
   */
  public double[] getCoordEdges() {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getCoordEdges() on non-numeric");
    if (!wasBoundsDone) makeBounds();
    return edge.clone();
  }

  @Override
  public boolean isContiguous() {
    if (!wasBoundsDone) makeBounds();  // this sets isContiguous
    return isContiguous;
  }

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

  /**
   * If this coordinate has interval values.
   * If so, then one should use getBound1, getBound2, and not getCoordEdges()
   *
   * @return true if coordinate has interval values
   */
  public boolean isInterval() {
    if (!wasBoundsDone) makeBounds();      // this sets isInterval
    return isInterval;
  }

  /**
   * Get the coordinate bound1 as a double array.
   * bound1[i] # coordValue[i] # bound2[i], where # is < if increasing (bound1[i] < bound1[i+1])
   * else < if decreasing.
   *
   * @return coordinate bound1.
   * @throws UnsupportedOperationException if !isNumeric()
   */
  public double[] getBound1() {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getBound1() on non-numeric");
    if (!wasBoundsDone) makeBounds();
    if (bound1 == null) makeBoundsFromEdges();
    assert bound1 != null;
    return bound1.clone();
  }

  /**
   * Get the coordinate bound1 as a double array.
   * bound1[i] # coordValue[i] # bound2[i],  where # is < if increasing (bound1[i] < bound1[i+1])
   * else < if decreasing.
   *
   * @return coordinate bound2.
   * @throws UnsupportedOperationException if !isNumeric()
   */
  public double[] getBound2() {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis1D.getBound2() on non-numeric");
    if (!wasBoundsDone) makeBounds();
    if (bound2 == null) makeBoundsFromEdges();
    assert bound2 != null;
    return bound2.clone();
  }

  /**
   * Get the coordinate bounds for the ith coordinate.
   * Can use this for isContiguous() true or false.
   *
   * @param i coordinate index
   * @return double[2] edges for ith coordinate
   */
  public double[] getCoordBounds(int i) {
    if (!wasBoundsDone) makeBounds();

    double[] e = new double[2];
    if (isContiguous()) {
      e[0] = getCoordEdge(i);
      e[1] = getCoordEdge(i + 1);
    } else {
      e[0] = bound1[i];
      e[1] = bound2[i];
    }
    return e;
  }

  public double getCoordBoundsMidpoint(int i) {
    double[] bounds = getCoordBounds(i);
    return (bounds[0]+bounds[1])/2;
  }

  /**
   * Given a coordinate value, find what grid element contains it.
   * This means that
   * <pre>
   * edge[i] <= value < edge[i+1] (if values are ascending)
   * edge[i] > value >= edge[i+1] (if values are descending)
   * </pre>
   *
   * @param coordVal position in this coordinate system
   * @return index of grid point containing it, or -1 if outside grid area
   */
  public int findCoordElement(double coordVal) {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis.findCoordElement() on non-numeric");

    if (isRegular())
      return findCoordElementRegular(coordVal, false);
    if (isContiguous())
      return findCoordElementIrregular(coordVal, false);
    else
      return findCoordElementNonContiguous(coordVal, false);
  }

  /**
   * Given a coordinate position, find what grid element contains it, or is closest to it.
   *
   * @param coordVal position in this coordinate system
   * @return index of grid point containing it, or best estimate of closest grid interval.
   */
  public int findCoordElementBounded(double coordVal) {
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis.findCoordElementBounded() on non-numeric");

    // the scalar or len-1 case:
    if (this.getSize() == 1) return 0;

    if (isRegular())
      return findCoordElementRegular(coordVal, true);
    if (isContiguous())
      return findCoordElementIrregular(coordVal, true);
    else
      return findCoordElementNonContiguous(coordVal, true);
  }

  /**
   * @deprecated use findCoordElement(coordVal)
   */
  public int findCoordElement(double coordVal, int lastIndex) {
    return findCoordElement(coordVal);
  }

  //////////////////////////////////////////////////////////////////
  // following is from Jon Blower's ncWMS
  // faster routines for coordValue -> index search
  // significantly modified

  /**
   * Optimize the regular case
   * Gets the index of the given point. Uses index = (value - start) / stride,
   * hence this is faster than an exhaustive search.
   * from jon blower's ncWMS.
   *
   * @param coordValue The value along this coordinate axis
   * @param bounded    if false and not in range, return -1, else nearest index
   * @return the index that is nearest to this point, or -1 if the point is
   * out of range for the axis
   */
  private int findCoordElementRegular(double coordValue, boolean bounded) {
    int n = (int) this.getSize();

    // the scalar or len-1 case:
    if (this.getSize() == 1) {
      return 0;
    }

  /*  if (axisType == AxisType.Lon) {
      double maxValue = this.start + this.increment * n;
      if (betweenLon(coordValue, this.start, maxValue)) {
        double distance = LatLonPointImpl.getClockwiseDistanceTo(this.start, coordValue);
        double exactNumSteps = distance / this.increment;
        // This axis might wrap, so we make sure that the returned index is within range
        return ((int) Math.round(exactNumSteps)) % (int) this.getSize();

      } else if (coordValue < this.start) {
        return bounded ? 0 : -1;
      } else {
        return bounded ? n - 1 : -1;
      }
    } */

    double distance = coordValue - this.start;
    double exactNumSteps = distance / this.increment;
    int index = (int) Math.round(exactNumSteps);
    if (index < 0)
      return bounded ? 0 : -1;
    else if (index >= n)
      return bounded ? n - 1 : -1;
    return index;
  }

  private boolean betweenLon(double lon, double lonBeg, double lonEnd) {
    while (lon < lonBeg) lon += 360;
    return (lon >= lonBeg) && (lon <= lonEnd);
  }

  /**
   * Performs a binary search to find the index of the element of the array
   * whose value is contained in the interval, so must be contiguous.
   *
   * @param target  The value to search for
   * @param bounded if false, and not in range, return -1, else nearest index
   * @return the index of the element in values whose value is closest to target,
   * or -1 if the target is out of range
   */
  private int findCoordElementIrregular(double target, boolean bounded) {
    int n = (int) this.getSize();
    int low = 0;
    int high = n;

    if (isAscending) {
      // Check that the point is within range
      if (target < this.edge[low])
        return bounded ? 0 : -1;
      else if (target > this.edge[high])
        return bounded ? n - 1 : -1;

      // do a binary search to find the nearest index
      int mid = low;
      while (high > low + 1) {
        mid = (low + high) / 2;
        double midVal = this.edge[mid];
        if (midVal == target) return mid;
        else if (midVal < target) low = mid;
        else high = mid;
      }

      return low;

    } else {

      // Check that the point is within range
      if (target > this.edge[low])
        return bounded ? 0 : -1;
      else if (target < this.edge[high])
        return bounded ? n - 1 : -1;

      // do a binary search to find the nearest index
      int mid = low;
      while (high > low + 1) {
        mid = (low + high) / 2;
        double midVal = this.edge[mid];
        if (midVal == target) return mid;
        else if (midVal < target) high = mid;
        else low = mid;
      }

      return high - 1;
    }
  }

  /**
   * Given a coordinate position, find what grid element contains it.
   * Only use if isContiguous() == false
   * This algorithm does a linear search in the bound1[] amd bound2[] array.
   * <p>
   * This means that
   * <pre>
   * edge[i] <= pos < edge[i+1] (if values are ascending)
   * edge[i] > pos >= edge[i+1] (if values are descending)
   * </pre>
   *
   * @param target  The value to search for
   * @param bounded if false, and not in range, return -1, else nearest index
   * @return the index of the element in values whose value is closest to target,
   * or -1 if the target is out of range
   */
  private int findCoordElementNonContiguous(double target, boolean bounded) {

    double[] bounds1 = getBound1();
    double[] bounds2 = getBound2();
    int n = bounds1.length;

    if (isAscending) {
      // Check that the point is within range
      if (target < bounds1[0])
        return bounded ? 0 : -1;
      else if (target > bounds2[n - 1])
        return bounded ? n - 1 : -1;

      int[] idx = findSingleHit(bounds1, bounds2, target);
      if (idx[0] == 0 && !bounded) return -1;  // no hits
      if (idx[0] == 1) return idx[1];          // one hit

      // multiple hits = choose closest to the midpoint i guess
      return findClosest(coords, target);

    } else {

      // Check that the point is within range
      if (target > bounds1[0])
        return bounded ? 0 : -1;
      else if (target < bounds2[n - 1])
        return bounded ? n - 1 : -1;

      int[] idx = findSingleHit(bounds2, bounds1, target);
      if (idx[0] == 0 && !bounded) return -1;  // no hits
      if (idx[0] == 1) return idx[1];

      // multiple hits = choose closest to the midpoint i guess
      return findClosest(getCoordValues(), target);
    }
  }

  // return index if only one match, else -1
  private int[] findSingleHit(double[] low, double[] high, double target) {
    int hits = 0;
    int idxFound = -1;
    int n = low.length;
    for (int i = 0; i < n; i++) {
      if ((low[i] <= target) && (target <= high[i])) {
        hits++;
        idxFound = i;
      }
    }
    return new int[] {hits, idxFound};
  }

  // return index of closest value to target
  private int findClosest(double[] values, double target) {
    double minDiff =  Double.MAX_VALUE;
    int idxFound = -1;
    int n = values.length;
    for (int i = 0; i < n; i++) {
      double diff =  Math.abs(values[i]-target);
      if (diff < minDiff) {
        minDiff = diff;
        idxFound = i;
      }
    }
    return idxFound;
  }

  ///////////////////////////////////////////////////////////////////////////////
  // check if Regular

  /**
   * Get starting value if isRegular()
   *
   * @return starting value if isRegular()
   */
  public double getStart() {
    calcIsRegular();
    return start;
  }

  /**
   * Get increment value if isRegular()
   *
   * @return increment value if isRegular()
   */
  public double getIncrement() {
    calcIsRegular();
    return increment;
  }

  /**
   * If true, then value(i) = <i>getStart()</i> + i * <i>getIncrement()</i>.
   *
   * @return if evenly spaced.
   */
  public boolean isRegular() {
    calcIsRegular();
    return isRegular;
  }

  private void calcIsRegular() {
    if (wasCalcRegular) return;
    if (!wasRead) doRead();

    if (!isNumeric())
      isRegular = false;
    else if (getSize() < 2)
      isRegular = true;
    else {
      start = getCoordValue(0);
      int n = (int) getSize();
      increment = (getCoordValue(n - 1) - getCoordValue(0)) / (n - 1);
      isRegular = true;
      for (int i = 1; i < getSize(); i++)
        if (!ucar.nc2.util.Misc.nearlyEquals(getCoordValue(i) - getCoordValue(i - 1), increment, 5.0e-3)) {
          isRegular = false;
          break;
        }
    }
    wasCalcRegular = true;
  }

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


  private void doRead() {
    if (isNumeric()) {
      readValues();
      wasRead = true;

      if (getSize() < 2)
        isAscending = true;
      else
        isAscending = getCoordValue(0) < getCoordValue(1);
      //  calcIsRegular(); */
    } else if (getDataType() == DataType.STRING) {
      readStringValues();
      wasRead = true;
    } else {
      readCharValues();
      wasRead = true;
    }
  }

  // turns longitude coordinate into monotonic, dealing with possible wrap.
  public void correctLongitudeWrap() {
    // correct non-monotonic longitude coords
    if (axisType != AxisType.Lon) {
      return;
    }

    if (!wasRead) doRead();
    if (!wasBoundsDone) makeBounds();

    boolean monotonic = true;
    for (int i = 0; i < coords.length - 1; i++)
      monotonic &= isAscending ? coords[i] < coords[i + 1] : coords[i] > coords[i + 1];

    if (!monotonic) {
      boolean cross = false;
      if (isAscending) {
        for (int i = 0; i < coords.length; i++) {
          if (cross) coords[i] += 360;
          if (!cross && (i < coords.length - 1) && (coords[i] > coords[i + 1]))
            cross = true;
        }
      } else {
        for (int i = 0; i < coords.length; i++) {
          if (cross) coords[i] -= 360;
          if (!cross && (i < coords.length - 1) && (coords[i] < coords[i + 1]))
            cross = true;
        }
      }

      // LOOK - need to make sure we get stuff from the cache
      Array cachedData = Array.factory(DataType.DOUBLE, getShape(), coords);
      if (getDataType() != DataType.DOUBLE)
        cachedData = MAMath.convert(cachedData, getDataType());
      setCachedData(cachedData);

      if (!isInterval) {
        makeEdges();
      }
    }

  }

  // only used if String

  private void readStringValues() {
    int count = 0;
    Array data;
    try {
      data = read();
    } catch (IOException ioe) {
      log.error("Error reading string coordinate values ", ioe);
      throw new IllegalStateException(ioe);
    }

    names = new String[(int) data.getSize()];
    IndexIterator ii = data.getIndexIterator();
    while (ii.hasNext())
      names[count++] = (String) ii.getObjectNext();
  }

  private void readCharValues() {
    int count = 0;
    ArrayChar data;
    try {
      data = (ArrayChar) read();
    } catch (IOException ioe) {
      log.error("Error reading char coordinate values ", ioe);
      throw new IllegalStateException(ioe);
    }
    ArrayChar.StringIterator iter = data.getStringIterator();
    names = new String[iter.getNumElems()];
    while (iter.hasNext())
      names[count++] = iter.next();
  }

  private void readValues() {
    Array data;
    try {
      // setUseNaNs(false); // missing values not allowed LOOK not true for point data !!
      data = read();
      // if (!hasCachedData()) setCachedData(data, false); //cache data for subsequent reading
    } catch (IOException ioe) {
      log.error("Error reading coordinate values ", ioe);
      throw new IllegalStateException(ioe);
    }

    coords = (double []) data.get1DJavaArray(DataType.DOUBLE);
    //IndexIterator iter = data.getIndexIterator();
    //while (iter.hasNext())
    //  coords[count++] = iter.getDoubleNext();
  }

  /**
   * Calculate bounds, set isInterval, isContiguous
   */
  private void makeBounds() {
    if (!wasRead) doRead();
    if (isNumeric()) {
      if (!makeBoundsFromAux()) {
        makeEdges();
      }
    }
    wasBoundsDone = true;
  }

  private boolean makeBoundsFromAux() {
    Attribute boundsAtt = findAttributeIgnoreCase(CF.BOUNDS);
    if ((null == boundsAtt) || !boundsAtt.isString()) return false;
    String boundsVarName = boundsAtt.getStringValue();
    VariableDS boundsVar = (VariableDS) ncd.findVariable(getParentGroup(), boundsVarName);
    if (null == boundsVar) return false;
    if (2 != boundsVar.getRank()) return false;

    if (getDimension(0) != boundsVar.getDimension(0)) return false;
    if (2 != boundsVar.getDimension(1).getLength()) return false;

    Array data;
    try {
      boundsVar.removeEnhancement(NetcdfDataset.Enhance.ConvertMissing);  // Don't convert missing values to NaN.
      data = boundsVar.read();
    } catch (IOException e) {
      log.warn("CoordinateAxis1D.hasBounds read failed ", e);
      return false;
    }

    assert (data.getRank() == 2) && (data.getShape()[1] == 2) : "incorrect shape data for variable " + boundsVar;

    // extract the bounds
    int n = shape[0];
    double[] value1 = new double[n];
    double[] value2 = new double[n];
    Index ima = data.getIndex();
    for (int i = 0; i < n; i++) {
      ima.set0(i);
      value1[i] = data.getDouble(ima.set1(0));
      value2[i] = data.getDouble(ima.set1(1));
    }

    /* flip if needed
    boolean firstLower = true; // in the first interval, is lower < upper ?
    for (int i = 0; i < value1.length; i++) {
      if (Misc.nearlyEquals(value1[i], value2[i])) continue; // skip when lower == upper
      firstLower = value1[i] < value2[i];
      break;
    }
    // check first against last : lower, unless all lower equal then upper
    boolean goesUp = (n < 2) || value1[n - 1] > value1[0] || (Misc.nearlyEquals(value1[n - 1], value2[0]) && value2[n - 1] > value2[0]);
    if (goesUp != firstLower) {
      double[] temp = value1;
      value1 = value2;
      value2 = temp;
    } */

    // decide if they are contiguous
    boolean contig = true;
    for (int i = 0; i < n - 1; i++) {
      if (!ucar.nc2.util.Misc.nearlyEquals(value1[i + 1], value2[i]))
        contig = false;
    }

    if (contig) {
      edge = new double[n + 1];
      edge[0] = value1[0];
      for (int i = 1; i < n + 1; i++)
        edge[i] = value2[i - 1];
    } else {                           // what does edge mean when not contiguous ??
      edge = new double[n + 1];
      edge[0] = value1[0];
      for (int i = 1; i < n; i++)
        edge[i] = (value1[i] + value2[i - 1]) / 2;
      edge[n] = value2[n - 1];
      isContiguous = false;
    }

    bound1 = value1;
    bound2 = value2;
    isInterval = true;

    return true;
  }

  private void makeEdges() {
    int size = (int) getSize();
    edge = new double[size + 1];
    if (size < 1) return;
    for (int i = 1; i < size; i++)
      edge[i] = (coords[i - 1] + coords[i]) / 2;
    edge[0] = coords[0] - (edge[1] - coords[0]);
    edge[size] = coords[size - 1] + (coords[size - 1] - edge[size - 1]);
    isContiguous = true;
  }

  private void makeBoundsFromEdges() {
    int size = (int) getSize();
    if (size == 0) return;

    bound1 = new double[size];
    bound2 = new double[size];
    for (int i = 0; i < size; i++) {
      bound1[i] = edge[i];
      bound2[i] = edge[i + 1];
    }

    // flip if needed
    if (bound1[0] > bound2[0]) {
      double[] temp = bound1;
      bound1 = bound2;
      bound2 = temp;
    }
  }

}
public List < NamedObject > getNames ( ) { int n = getDimension ( 0 ) . getLength ( ) ; List < NamedObject > names = new ArrayList <> ( n ) ; for ( int i = 0 ; i < n ; i ++ ) names . ( new ucar . nc2 . util . NamedAnything ( getCoordName ( i ) , getShortName ( ) + " " + getUnitsString ( ) ) ) ; return names ; } /**
   * The "name" of the ith coordinate. If nominal, this is all there is to a coordinate.
   * If numeric, this will return a String representation of the coordinate.
   *
   * @param index which one ?
   * @return the ith coordinate value as a String
   */ public String getCoordName ( int index ) { if ( ! wasRead ) doRead ( ) ; if ( isNumeric ( ) ) return Format . d ( getCoordValue ( index ) , 5 , 8 ) ; else return names [ index ] ; } /**
   * Get the ith coordinate value. This is the value of the coordinate axis at which
   * the data value is associated. These must be strictly monotonic.
   *
   * @param index which coordinate. Between 0 and getNumElements()-1 inclusive.
   * @return coordinate value.
   * @throws UnsupportedOperationException if !isNumeric()
   */ public double getCoordValue ( int index ) { if ( ! isNumeric ( ) ) throw new UnsupportedOperationException ( "CoordinateAxis1D.getCoordValue() on non-numeric" ) ; if ( ! wasRead ) doRead ( ) ; return coords [ index ] ; } @ Override public double getMinValue ( ) { if ( ! isNumeric ( ) ) throw new UnsupportedOperationException ( "CoordinateAxis1D.getCoordValue() on non-numeric" ) ; if ( ! wasRead ) doRead ( ) ; return Math . min ( coords [ 0 ] , coords [ coords . length - 1 ] ) ; } @ Override public double getMaxValue ( ) { if ( ! isNumeric ( ) ) throw new UnsupportedOperationException ( "CoordinateAxis1D.getCoordValue() on non-numeric" ) ; if ( ! wasRead ) doRead ( ) ; return Math . max ( coords [ 0 ] , coords [ coords . length - 1 ] ) ; } public double getMinEdgeValue ( ) { if ( edge == null ) return getMinValue ( ) ; if ( ! isNumeric ( ) ) throw new UnsupportedOperationException ( "CoordinateAxis1D.getCoordValue() on non-numeric" ) ; if ( ! wasRead ) doRead ( ) ; return Math . min ( edge [ 0 ] , edge [ edge . length - 1 ] ) ; } public double getMaxEdgeValue ( ) { if ( edge == null ) return getMaxValue ( ) ; if ( ! isNumeric ( ) ) throw new UnsupportedOperationException ( "CoordinateAxis1D.getCoordValue() on non-numeric" ) ; if ( ! wasRead ) doRead ( ) ; return Math . max ( edge [ 0 ] , edge [ edge . length - 1 ] ) ; } /**
   * Get the ith coordinate edge. Exact only if isContiguous() is true, otherwise use getBound1() and getBound2().
   * This is the value where the underlying grid element switches
   * from "belonging to" coordinate value i-1 to "belonging to" coordinate value i.
   * In some grids, this may not be well defined, and so should be considered an
   * approximation or a visualization hint.
   * <p><pre>
   *  Coordinate edges must be strictly monotonic:
   *    coordEdge(0) < coordValue(0) < coordEdge(1) < coordValue(1) ...
   *    ... coordEdge(i) < coordValue(i) < coordEdge(i+1) < coordValue(i+1) ...
   *    ... coordEdge(n-1) < coordValue(n-1) < coordEdge(n)
   *  </pre>
   *
   * @param index which coordinate. Between 0 and getNumElements() inclusive.
   * @return coordinate edge.
   * @throws UnsupportedOperationException if !isNumeric()
   */ public double getCoordEdge ( int index ) { if ( ! isNumeric ( ) ) throw new UnsupportedOperationException ( "CoordinateAxis1D.getCoordEdge() on non-numeric" ) ; if ( ! wasBoundsDone ) makeBounds ( ) ; return edge [ index ] ; } /**
   * Get the coordinate values as a double array.
   *
   * @return coordinate value.
   * @throws UnsupportedOperationException if !isNumeric()
   */ public double [ ] getCoordValues ( ) { if ( ! isNumeric ( ) ) throw new UnsupportedOperationException ( "CoordinateAxis1D.getCoordValues() on non-numeric" ) ; if ( ! wasRead ) doRead ( ) ; return coords . clone ( ) ; } /**
   * Get the coordinate edges as a double array.
   * Exact only if isContiguous() is true, otherwise use getBound1() and getBound2().
   *
   * @return coordinate edges.
   * @throws UnsupportedOperationException if !isNumeric()
   */ public double [ ] getCoordEdges ( ) { if ( ! isNumeric ( ) ) throw new UnsupportedOperationException ( "CoordinateAxis1D.getCoordEdges() on non-numeric" ) ; if ( ! wasBoundsDone ) makeBounds ( ) ; return edge . clone ( ) ; } @ Override public boolean isContiguous ( ) { if ( ! wasBoundsDone ) makeBounds ( ) ; // this sets isContiguous return isContiguous ; } /////////////////////////////////////////////// /**
   * If this coordinate has interval values.
   * If so, then one should use getBound1, getBound2, and not getCoordEdges()
   *
   * @return true if coordinate has interval values
   */ public boolean isInterval ( ) { if ( ! wasBoundsDone ) makeBounds ( ) ; // this sets isInterval return isInterval ; } /**
   * Get the coordinate bound1 as a double array.
   * bound1[i] # coordValue[i] # bound2[i], where # is < if increasing (bound1[i] < bound1[i+1])
   * else < if decreasing.
   *
   * @return coordinate bound1.
   * @throws UnsupportedOperationException if !isNumeric()
   */ public double [ ] getBound1 ( ) { if ( ! isNumeric ( ) ) throw new UnsupportedOperationException ( "CoordinateAxis1D.getBound1() on non-numeric" ) ; if ( ! wasBoundsDone ) makeBounds ( ) ; if ( bound1 == null ) makeBoundsFromEdges ( ) ; assert bound1 != null ; return bound1 . clone ( ) ; } /**
   * Get the coordinate bound1 as a double array.
   * bound1[i] # coordValue[i] # bound2[i],  where # is < if increasing (bound1[i] < bound1[i+1])
   * else < if decreasing.
   *
   * @return coordinate bound2.
   * @throws UnsupportedOperationException if !isNumeric()
   */ public double [ ] getBound2 ( ) { if ( ! isNumeric ( ) ) throw new UnsupportedOperationException ( "CoordinateAxis1D.getBound2() on non-numeric" ) ; if ( ! wasBoundsDone ) makeBounds ( ) ; if ( bound2 == null ) makeBoundsFromEdges ( ) ; assert bound2 != null ; return bound2 . clone ( ) ; } /**
   * Get the coordinate bounds for the ith coordinate.
   * Can use this for isContiguous() true or false.
   *
   * @param i coordinate index
   * @return double[2] edges for ith coordinate
   */ public double [ ] getCoordBounds ( int i ) { if ( ! wasBoundsDone ) makeBounds ( ) ; double [ ] e = new double [ 2 ] ; if ( isContiguous ( ) ) { e [ 0 ] = getCoordEdge ( i ) ; e [ 1 ] = getCoordEdge ( i + 1 ) ; } else { e [ 0 ] = bound1 [ i ] ; e [ 1 ] = bound2 [ i ] ; } return e ; } public double getCoordBoundsMidpoint ( int i ) { double [ ] bounds = getCoordBounds ( i ) ; return ( bounds [ 0 ] + bounds [ 1 ] ) / 2 ; } /**
   * Given a coordinate value, find what grid element contains it.
   * This means that
   * <pre>
   * edge[i] <= value < edge[i+1] (if values are ascending)
   * edge[i] > value >= edge[i+1] (if values are descending)
   * </pre>
   *
   * @param coordVal position in this coordinate system
   * @return index of grid point containing it, or -1 if outside grid area
   */ public int findCoordElement ( double coordVal ) { if ( ! isNumeric ( ) ) throw new UnsupportedOperationException ( "CoordinateAxis.findCoordElement() on non-numeric" ) ; if ( isRegular ( ) ) return findCoordElementRegular ( coordVal , false ) ; if ( isContiguous ( ) ) return findCoordElementIrregular ( coordVal , false ) ; else return findCoordElementNonContiguous ( coordVal , false ) ; } /**
   * Given a coordinate position, find what grid element contains it, or is closest to it.
   *
   * @param coordVal position in this coordinate system
   * @return index of grid point containing it, or best estimate of closest grid interval.
   */ public int findCoordElementBounded ( double coordVal ) { if ( ! isNumeric ( ) ) throw new UnsupportedOperationException ( "CoordinateAxis.findCoordElementBounded() on non-numeric" ) ; // the scalar or len-1 case: if ( this . getSize ( ) == 1 ) return 0 ; if ( isRegular ( ) ) return findCoordElementRegular ( coordVal , true ) ; if ( isContiguous ( ) ) return findCoordElementIrregular ( coordVal , true ) ; else return findCoordElementNonContiguous ( coordVal , true ) ; } /**
   * @deprecated use findCoordElement(coordVal)
   */ public int findCoordElement ( double coordVal , int lastIndex ) { return findCoordElement ( coordVal ) ; } ////////////////////////////////////////////////////////////////// // following is from Jon Blower's ncWMS // faster routines for coordValue -> index search // significantly modified /**
   * Optimize the regular case
   * Gets the index of the given point. Uses index = (value - start) / stride,
   * hence this is faster than an exhaustive search.
   * from jon blower's ncWMS.
   *
   * @param coordValue The value along this coordinate axis
   * @param bounded    if false and not in range, return -1, else nearest index
   * @return the index that is nearest to this point, or -1 if the point is
   * out of range for the axis
   */ private int findCoordElementRegular ( double coordValue , boolean bounded ) { int n = ( int ) this . getSize ( ) ; // the scalar or len-1 case: if ( this . getSize ( ) == 1 ) { return 0 ; } /*  if (axisType == AxisType.Lon) {
      double maxValue = this.start + this.increment * n;
      if (betweenLon(coordValue, this.start, maxValue)) {
        double distance = LatLonPointImpl.getClockwiseDistanceTo(this.start, coordValue);
        double exactNumSteps = distance / this.increment;
        // This axis might wrap, so we make sure that the returned index is within range
        return ((int) Math.round(exactNumSteps)) % (int) this.getSize();

      } else if (coordValue < this.start) {
        return bounded ? 0 : -1;
      } else {
        return bounded ? n - 1 : -1;
      }
    } */ double distance = coordValue - this . start ; double exactNumSteps = distance / this . increment ; int index = ( int ) Math . round ( exactNumSteps ) ; if ( index < 0 ) return bounded ? 0 : - 1 ; else if ( index >= n ) return bounded ? n - 1 : - 1 ; return index ; } private boolean betweenLon ( double lon , double lonBeg , double lonEnd ) { while ( lon < lonBeg ) lon += 360 ; return ( lon >= lonBeg ) && ( lon <= lonEnd ) ; } /**
   * Performs a binary search to find the index of the element of the array
   * whose value is contained in the interval, so must be contiguous.
   *
   * @param target  The value to search for
   * @param bounded if false, and not in range, return -1, else nearest index
   * @return the index of the element in values whose value is closest to target,
   * or -1 if the target is out of range
   */ private int findCoordElementIrregular ( double target , boolean bounded ) { int n = ( int ) this . getSize ( ) ; int low = 0 ; int high = n ; if ( isAscending ) { // Check that the point is within range if ( target < this . edge [ low ] ) return bounded ? 0 : - 1 ; else if ( target > this . edge [ high ] ) return bounded ? n - 1 : - 1 ; // do a binary search to find the nearest index int mid = low ; while ( high > low + 1 ) { mid = ( low + high ) / 2 ; double midVal = this . edge [ mid ] ; if ( midVal == target ) return mid ; else if ( midVal < target ) low = mid ; else high = mid ; } return low ; } else { // Check that the point is within range if ( target > this . edge [ low ] ) return bounded ? 0 : - 1 ; else if ( target < this . edge [ high ] ) return bounded ? n - 1 : - 1 ; // do a binary search to find the nearest index int mid = low ; while ( high > low + 1 ) { mid = ( low + high ) / 2 ; double midVal = this . edge [ mid ] ; if ( midVal == target ) return mid ; else if ( midVal < target ) high = mid ; else low = mid ; } return high - 1 ; } } /**
   * Given a coordinate position, find what grid element contains it.
   * Only use if isContiguous() == false
   * This algorithm does a linear search in the bound1[] amd bound2[] array.
   * <p>
   * This means that
   * <pre>
   * edge[i] <= pos < edge[i+1] (if values are ascending)
   * edge[i] > pos >= edge[i+1] (if values are descending)
   * </pre>
   *
   * @param target  The value to search for
   * @param bounded if false, and not in range, return -1, else nearest index
   * @return the index of the element in values whose value is closest to target,
   * or -1 if the target is out of range
   */ private int findCoordElementNonContiguous ( double target , boolean bounded ) { double [ ] bounds1 = getBound1 ( ) ; double [ ] bounds2 = getBound2 ( ) ; int n = bounds1 . length ; if ( isAscending ) { // Check that the point is within range if ( target < bounds1 [ 0 ] ) return bounded ? 0 : - 1 ; else if ( target > bounds2 [ n - 1 ] ) return bounded ? n - 1 : - 1 ; int [ ] idx = findSingleHit ( bounds1 , bounds2 , target ) ; if ( idx [ 0 ] == 0 && ! bounded ) return - 1 ; // no hits if ( idx [ 0 ] == 1 ) return idx [ 1 ] ; // one hit // multiple hits = choose closest to the midpoint i guess return findClosest ( coords , target ) ; } else { // Check that the point is within range if ( target > bounds1 [ 0 ] ) return bounded ? 0 : - 1 ; else if ( target < bounds2 [ n - 1 ] ) return bounded ? n - 1 : - 1 ; int [ ] idx = findSingleHit ( bounds2 , bounds1 , target ) ; if ( idx [ 0 ] == 0 && ! bounded ) return - 1 ; // no hits if ( idx [ 0 ] == 1 ) return idx [ 1 ] ; // multiple hits = choose closest to the midpoint i guess return findClosest ( getCoordValues ( ) , target ) ; } } // return index if only one match, else -1 private int [ ] findSingleHit ( double [ ] low , double [ ] high , double target ) { int hits = 0 ; int idxFound = - 1 ; int n = low . length ; for ( int i = 0 ; i < n ; i ++ ) { if ( ( low [ i ] <= target ) && ( target <= high [ i ] ) ) { hits ++ ; idxFound = i ; } } return new int [ ] { hits , idxFound } ; } // return index of closest value to target private int findClosest ( double [ ] values , double target ) { double minDiff = Double . MAX_VALUE ; int idxFound = - 1 ; int n = values . length ; for ( int i = 0 ; i < n ; i ++ ) { double diff = Math . abs ( values [ i ] - target ) ; if ( diff < minDiff ) { minDiff = diff ; idxFound = i ; } } return idxFound ; } /////////////////////////////////////////////////////////////////////////////// // check if Regular /**
   * Get starting value if isRegular()
   *
   * @return starting value if isRegular()
   */ public double getStart ( ) { calcIsRegular ( ) ; return start ; } /**
   * Get increment value if isRegular()
   *
   * @return increment value if isRegular()
   */ public double getIncrement ( ) { calcIsRegular ( ) ; return increment ; } /**
   * If true, then value(i) = <i>getStart()</i> + i * <i>getIncrement()</i>.
   *
   * @return if evenly spaced.
   */ public boolean isRegular ( ) { calcIsRegular ( ) ; return isRegular ; } private void calcIsRegular ( ) { if ( wasCalcRegular ) return ; if ( ! wasRead ) doRead ( ) ; if ( ! isNumeric ( ) ) isRegular = false ; else if ( getSize ( ) < 2 ) isRegular = true ; else { start = getCoordValue ( 0 ) ; int n = ( int ) getSize ( ) ; increment = ( getCoordValue ( n - 1 ) - getCoordValue ( 0 ) ) / ( n - 1 ) ; isRegular = true ; for ( int i = 1 ; i < getSize ( ) ; i ++ ) if ( ! ucar . nc2 . util . Misc . nearlyEquals ( getCoordValue ( i ) - getCoordValue ( i - 1 ) , increment , 5.0e-3 ) ) { isRegular = false ; break ; } } wasCalcRegular = true ; } /////////////////////////////////////////////////////////////////////////////// private void doRead ( ) { if ( isNumeric ( ) ) { readValues ( ) ; wasRead = true ; if ( getSize ( ) < 2 ) isAscending = true ; else isAscending = getCoordValue ( 0 ) < getCoordValue ( 1 ) ; //  calcIsRegular(); */ } else if ( getDataType ( ) == DataType . STRING ) { readStringValues ( ) ; wasRead = true ; } else { readCharValues ( ) ; wasRead = true ; } } // turns longitude coordinate into monotonic, dealing with possible wrap. public void correctLongitudeWrap ( ) { // correct non-monotonic longitude coords if ( axisType != AxisType . Lon ) { return ; } if ( ! wasRead ) doRead ( ) ; if ( ! wasBoundsDone ) makeBounds ( ) ; boolean monotonic = true ; for ( int i = 0 ; i < coords . length - 1 ; i ++ ) monotonic &= isAscending ? coords [ i ] < coords [ i + 1 ] : coords [ i ] > coords [ i + 1 ] ; if ( ! monotonic ) { boolean cross = false ; if ( isAscending ) { for ( int i = 0 ; i < coords . length ; i ++ ) { if ( cross ) coords [ i ] += 360 ; if ( ! cross && ( i < coords . length - 1 ) && ( coords [ i ] > coords [ i + 1 ] ) ) cross = true ; } } else { for ( int i = 0 ; i < coords . length ; i ++ ) { if ( cross ) coords [ i ] -= 360 ; if ( ! cross && ( i < coords . length - 1 ) && ( coords [ i ] < coords [ i + 1 ] ) ) cross = true ; } } // LOOK - need to make sure we get stuff from the cache Array cachedData = Array . factory ( DataType . DOUBLE , getShape ( ) , coords ) ; if ( getDataType ( ) != DataType . DOUBLE ) cachedData = MAMath . convert ( cachedData , getDataType ( ) ) ; setCachedData ( cachedData ) ; if ( ! isInterval ) { makeEdges ( ) ; } } } // only used if String private void readStringValues ( ) { int count = 0 ; Array data ; try { data = read ( ) ; } catch ( IOException ioe ) { log . error ( "Error reading string coordinate values " , ioe ) ; throw new IllegalStateException ( ioe ) ; } names = new String [ ( int ) data . getSize ( ) ] ; IndexIterator ii = data . getIndexIterator ( ) ; while ( ii . hasNext ( ) ) names [ count ++ ] = ( String ) ii . getObjectNext ( ) ; } private void readCharValues ( ) { int count = 0 ; ArrayChar data ; try { data = ( ArrayChar ) read ( ) ; } catch ( IOException ioe ) { log . error ( "Error reading char coordinate values " , ioe ) ; throw new IllegalStateException ( ioe ) ; } ArrayChar . StringIterator iter = data . getStringIterator ( ) ; names = new String [ iter . getNumElems ( ) ] ; while ( iter . hasNext ( ) ) names [ count ++ ] = iter . next ( ) ; } private void readValues ( ) { Array data ; try { // setUseNaNs(false); // missing values not allowed LOOK not true for point data !! data = read ( ) ; // if (!hasCachedData()) setCachedData(data, false); //cache data for subsequent reading } catch ( IOException ioe ) { log . error ( "Error reading coordinate values " , ioe ) ; throw new IllegalStateException ( ioe ) ; } coords = ( double [ ] ) data . get1DJavaArray ( DataType . DOUBLE ) ; //IndexIterator iter = data.getIndexIterator(); //while (iter.hasNext()) //  coords[count++] = iter.getDoubleNext(); } /**
   * Calculate bounds, set isInterval, isContiguous
   */ private void makeBounds ( ) { if ( ! wasRead ) doRead ( ) ; if ( isNumeric ( ) ) { if ( ! makeBoundsFromAux ( ) ) { makeEdges ( ) ; } } wasBoundsDone = true ; } private boolean makeBoundsFromAux ( ) { Attribute boundsAtt = findAttributeIgnoreCase ( CF . BOUNDS ) ; if ( ( null == boundsAtt ) || ! boundsAtt . isString ( ) ) return false ; String boundsVarName = boundsAtt . getStringValue ( ) ; VariableDS boundsVar = ( VariableDS ) ncd . findVariable ( getParentGroup ( ) , boundsVarName ) ; if ( null == boundsVar ) return false ; if ( 2 != boundsVar . getRank ( ) ) return false ; if ( getDimension ( 0 ) != boundsVar . getDimension ( 0 ) ) return false ; if ( 2 != boundsVar . getDimension ( 1 ) . getLength ( ) ) return false ; Array data ; try { boundsVar . removeEnhancement ( NetcdfDataset . Enhance . ConvertMissing ) ; // Don't convert missing values to NaN. data = boundsVar . read ( ) ; } catch ( IOException e ) { log . warn ( "CoordinateAxis1D.hasBounds read failed " , e ) ; return false ; } assert ( data . getRank ( ) == 2 ) && ( data . getShape ( ) [ 1 ] == 2 ) : "incorrect shape data for variable " + boundsVar ; // extract the bounds int n = shape [ 0 ] ; double [ ] value1 = new double [ n ] ; double [ ] value2 = new double [ n ] ; Index ima = data . getIndex ( ) ; for ( int i = 0 ; i < n ; i ++ ) { ima . set0 ( i ) ; value1 [ i ] = data . getDouble ( ima . set1 ( 0 ) ) ; value2 [ i ] = data . getDouble ( ima . set1 ( 1 ) ) ; } /* flip if needed
    boolean firstLower = true; // in the first interval, is lower < upper ?
    for (int i = 0; i < value1.length; i++) {
      if (Misc.nearlyEquals(value1[i], value2[i])) continue; // skip when lower == upper
      firstLower = value1[i] < value2[i];
      break;
    }
    // check first against last : lower, unless all lower equal then upper
    boolean goesUp = (n < 2) || value1[n - 1] > value1[0] || (Misc.nearlyEquals(value1[n - 1], value2[0]) && value2[n - 1] > value2[0]);
    if (goesUp != firstLower) {
      double[] temp = value1;
      value1 = value2;
      value2 = temp;
    } */ // decide if they are contiguous boolean contig = true ; for ( int i = 0 ; i < n - 1 ; i ++ ) { if ( ! ucar . nc2 . util . Misc . nearlyEquals ( value1 [ i + 1 ] , value2 [ i ] ) ) contig = false ; } if ( contig ) { edge = new double [ n + 1 ] ; edge [ 0 ] = value1 [ 0 ] ; for ( int i = 1 ; i < n + 1 ; i ++ ) edge [ i ] = value2 [ i - 1 ] ; } else { // what does edge mean when not contiguous ?? edge = new double [ n + 1 ] ; edge [ 0 ] = value1 [ 0 ] ; for ( int i = 1 ; i < n ; i ++ ) edge [ i ] = ( value1 [ i ] + value2 [ i - 1 ] ) / 2 ; edge [ n ] = value2 [ n - 1 ] ; isContiguous = false ; } bound1 = value1 ; bound2 = value2 ; isInterval = true ; return true ; } private void makeEdges ( ) { int size = ( int ) getSize ( ) ; edge = new double [ size + 1 ] ; if ( size < 1 ) return ; for ( int i = 1 ; i < size ; i ++ ) edge [ i ] = ( coords [ i - 1 ] + coords [ i ] ) / 2 ; edge [ 0 ] = coords [ 0 ] - ( edge [ 1 ] - coords [ 0 ] ) ; edge [ size ] = coords [ size - 1 ] + ( coords [ size - 1 ] - edge [ size - 1 ] ) ; isContiguous = true ; } private void makeBoundsFromEdges ( ) { int size = ( int ) getSize ( ) ; if ( size == 0 ) return ; bound1 = new double [ size ] ; bound2 = new double [ size ] ; for ( int i = 0 ; i < size ; i ++ ) { bound1 [ i ] = edge [ i ] ; bound2 [ i ] = edge [ i + 1 ] ; } // flip if needed if ( bound1 [ 0 ] > bound2 [ 0 ] ) { double [ ] temp = bound1 ; bound1 = bound2 ; bound2 = temp ; } } }
Get the list of names, to be used for user selection.
The ith one refers to the ith coordinate.

@return List of ucar.nc2.util.NamedObject, or empty list.
Get the list of names to be used for user selection . The ith one refers to the ith coordinate .