Re: [netcdf-java] BUGS: ucar.nc2.dataset.CoordinateAxis1D.findCoordElement()

  • To: Christian D Ward-Garrison <cwardgar@xxxxxxxx>
  • Subject: Re: [netcdf-java] BUGS: ucar.nc2.dataset.CoordinateAxis1D.findCoordElement()
  • From: John Caron <caron@xxxxxxxxxxxxxxxx>
  • Date: Fri, 09 Jul 2010 09:54:16 -0600
Hi Christian:

Ok, thats a bit of a rats nest there. Ive refactored it, I _think_ I have it 
right. Im attaching the new CoordinateAxis1D class and a unit test. If you see 
any problems, Id appreciate it.

John

Christian D Ward-Garrison wrote:
Hello all,

Test cases follow.

NcML:

<?xml version="1.0" encoding="UTF-8"?>
<netcdf xmlns="http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2";>
    <dimension name="lat"  length="2" />
    <dimension name="lon"  length="2" />
    <dimension name="bnds" length="2" />
<variable name="lat" shape="lat" type="double">
        <attribute name="units" type="String" value="degrees_north" />
        <attribute name="bounds" type="String" value="lat_bnds" />
        <values>-45 45</values>
    </variable>
<variable name="lat_bnds" shape="lat bnds" type="double">
        <values>-90 0 0 90</values>
    </variable>

    <variable name="lon" shape="lon" type="double">
        <attribute name="units" type="String" value="degrees_east" />
        <attribute name="bounds" type="String" value="lon_bnds" />
        <values>90 270</values>
    </variable>
<variable name="lon_bnds" shape="lon bnds" type="double">
        <values>0 180 180 360</values>
    </variable>
</netcdf>

Java:

import java.io.File;
import java.io.IOException;
import ucar.nc2.dataset.CoordinateAxis1D;
import ucar.nc2.dataset.NetcdfDataset;

public class Foo {
    public static void main(String[] args) throws IOException {
        File ncmlFile = new File("test.ncml");
NetcdfDataset dataset = NetcdfDataset.openDataset(ncmlFile.getAbsolutePath());

        try {
CoordinateAxis1D latAxis = (CoordinateAxis1D) dataset.findVariable("lat"); double[] lats = new double[] { -91, -90, -67.5, -45, -22.5, 0, 22.5, 45, 67.5, 90, 91 };
            for (double lat : lats) {
System.out.printf("lat: %-5.1f, 1-arg: %-2d, 2-arg: %-2d %n", lat, latAxis.findCoordElement(lat), latAxis.findCoordElement(lat, -1));
            }

            System.out.println();

CoordinateAxis1D lonAxis = (CoordinateAxis1D) dataset.findVariable("lon"); double[] lons = new double[] { -91, -90, 0, 45, 90, 135, 180, 225, 270, 315, 360, 450, 451 };
            for (double lon : lons) {
System.out.printf("lon: %-5.1f, 1-arg: %-2d, 2-arg: %-2d %n", lon, lonAxis.findCoordElement(lon), lonAxis.findCoordElement(lon, -1));
            }
        } finally {
            dataset.close();
        }
    }
}

Output:

lat: -91.0, 1-arg: -1, 2-arg: -1
lat: -90.0, 1-arg: 0 , 2-arg: 0 lat: -67.5, 1-arg: 0 , 2-arg: 0 lat: -45.0, 1-arg: 0 , 2-arg: 0 lat: -22.5, 1-arg: 0 , 2-arg: 0 lat: 0.0 , 1-arg: 1 , 2-arg: 1 lat: 22.5 , 1-arg: 1 , 2-arg: 1 lat: 45.0 , 1-arg: 1 , 2-arg: 1 lat: 67.5 , 1-arg: 1 , 2-arg: 1 lat: 90.0 , 1-arg: -1, 2-arg: 1 lat: 91.0 , 1-arg: -1, 2-arg: -1

lon: -91.0, 1-arg: 1 , 2-arg: 1 lon: -90.0, 1-arg: 1 , 2-arg: 1 lon: 0.0 , 1-arg: 0 , 2-arg: 0 lon: 45.0 , 1-arg: 0 , 2-arg: 0 lon: 90.0 , 1-arg: 0 , 2-arg: 0 lon: 135.0, 1-arg: 0 , 2-arg: 0 lon: 180.0, 1-arg: 1 , 2-arg: 0 lon: 225.0, 1-arg: 1 , 2-arg: 1 lon: 270.0, 1-arg: 1 , 2-arg: 1 lon: 315.0, 1-arg: 1 , 2-arg: 1 lon: 360.0, 1-arg: 0 , 2-arg: 1 lon: 450.0, 1-arg: 0 , 2-arg: -1
lon: 451.0, 1-arg: -1, 2-arg: -1


The first block of output gives the results of the 1-arg and 2-arg versions of findCoordElement() for several latitude inputs. They only disagree for lat=90, and in that case, the 2-arg result of "1" seems correct to me.

There is much greater disagreement in the longitude block of the output, and I don't think either version of findCoordElement() gives correct results for all inputs. I think the results should be:

lon: -91.0, result: -1
lon: -90.0, result: -1
lon: 0.0  , result: 0
lon: 45.0 , result: 0
lon: 90.0 , result: 0
lon: 135.0, result: 0
lon: 180.0, result: 1
lon: 225.0, result: 1
lon: 270.0, result: 1
lon: 315.0, result: 1
lon: 360.0, result: 1
lon: 450.0, result: -1
lon: 451.0, result: -1

For longitude ranges that span the globe, as the one in the example does, an argument can be made that no longitudes should give a "-1" result. That's fine (although not as useful as the alternative behavior, in my opinion), but neither version of findCoordElement() currently implements that policy.

A more pressing bug appears when we reverse the order of the longitudes (in the NcML) from ascending to descending:

lon: -91.0, 1-arg: -1, 2-arg: -1
lon: -90.0, 1-arg: -1, 2-arg: -1
lon: 0.0  , 1-arg: -1, 2-arg: -1
lon: 45.0 , 1-arg: -1, 2-arg: -1
lon: 90.0 , 1-arg: -1, 2-arg: -1
lon: 135.0, 1-arg: -1, 2-arg: -1
lon: 180.0, 1-arg: -1, 2-arg: -1
lon: 225.0, 1-arg: -1, 2-arg: -1
lon: 270.0, 1-arg: -1, 2-arg: -1
lon: 315.0, 1-arg: -1, 2-arg: -1
lon: 360.0, 1-arg: -1, 2-arg: -1
lon: 450.0, 1-arg: -1, 2-arg: -1
lon: 451.0, 1-arg: -1, 2-arg: -1

findCoordElement() completely fails in this case because CoordinateAxis1D.betweenLon() assumes ascending order.

Finally, findCoordElement(double, int) is public, so shouldn't findCoordElementBounded(double, int) be public as well?

Regards,
Christian Ward-Garrison


------------------------------------------------------------------------

_______________________________________________
netcdf-java mailing list
netcdf-java@xxxxxxxxxxxxxxxx
For list information or to unsubscribe, visit: http://www.unidata.ucar.edu/mailing_lists/

/*
 * 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.dataset;

import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.constants._Coordinate;
import ucar.nc2.constants.AxisType;
import ucar.nc2.util.NamedObject;
import ucar.unidata.util.Format;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * A 1-dimensional Coordinate Axis. Its values must be monotonic.
 * <p/>
 * If this is char valued, it will have rank 2, otherwise it will have rank 1.
 * <p/>
 * If string or char valued, only <i>getCoordName()</i> can be called.
 * <p/>
 * If the coordinates are regularly spaced, <i>isRegular()</i> is true, and the 
values are equal to
 * <i>getStart()</i> + i * <i>getIncrement()</i>.
 * <p/>
 * This will also set "cell bounds" for this axis. By default, the cell bounds 
are midway between the coordinates values,
 * and are therefore contiguous, and can be accessed though getCoordEdge(i).
 * The only way the bunds can be set is if the coordinate variable has an 
attribute "bounds" that points to another variable
 * bounds(ncoords,2). These contain the cell bounds, and must be ascending or 
descending as the coordinate values are. In
 * this case isContiguous() is true when bounds1(i+1) == bounds2(i) for all i.
 *
 * @author john caron
 * @see CoordinateAxis#factory
 */

public class CoordinateAxis1D extends CoordinateAxis {
  static private org.slf4j.Logger log = 
org.slf4j.LoggerFactory.getLogger(CoordinateAxis1D.class);

  private boolean wasRead = false;
  private boolean hasBounds = false;
  private boolean wasCalc = false;
  private boolean isAscending;

  // read in on doRead()
  private double[] midpoint; // n midpoints
  private String[] names = null; // only set if String or char values

  // defer until ask, use makeBounds()
  private double[] edge; // n+1 edges, edge[k] < midpoint[k] < edge[k+1]
  private double[] bound1, bound2; // not contiguous

  /**
   * Create a 1D coordinate axis from an existing Variable
   *
   * @param ncd the containing dataset
   * @param vds wrap this VariableDS, which is not changed.
   */
  public CoordinateAxis1D(NetcdfDataset ncd, VariableDS vds) {
    super(ncd, vds);
    setIsLayer();
  }

  CoordinateAxis1D(NetcdfDataset ncd, CoordinateAxis1D org) {
    //super(ncd, org.getParentGroup(), org.getShortName(), org.getDataType(), 
org.getDimensionsString(),
    //        org.getUnitsString(), org.getDescription());
    super(ncd, org);
    this.orgName = org.orgName;
    this.cache = new Variable.Cache(); // decouple cache
    setIsLayer();
  }

  /**
   * Constructor when theres no underlying variable. You better set the values 
too!
   *
   * @param ds        the containing dataset.
   * @param group     the containing group; if null, use rootGroup
   * @param shortName axis name.
   * @param dataType  data type
   * @param dims      list of dimension names
   * @param units     units of coordinates, preferably udunit compatible.
   * @param desc      long name.
   */
  public CoordinateAxis1D(NetcdfDataset ds, Group group, String shortName,
                          DataType dataType, String dims, String units, String 
desc) {

    super(ds, group, shortName, dataType, dims, units, desc);
    setIsLayer();
  }

  /**
   * Create a new CoordinateAxis1D as a section of this CoordinateAxis1D.
   *
   * @param r the section range
   * @return a new CoordinateAxis1D as a section of this CoordinateAxis1D
   * @throws InvalidRangeException if IllegalRange
   */
  public CoordinateAxis1D section(Range r) throws InvalidRangeException {
    Section section = new Section().appendRange(r);
    return (CoordinateAxis1D) section(section);
  }

  // for section and slice

  @Override
  protected Variable copy() {
    return new CoordinateAxis1D(this.ncd, this);
  }

  @Override
  public CoordinateAxis copyNoCache() {
    CoordinateAxis1D axis = new CoordinateAxis1D(ncd, getParentGroup(), 
getShortName(), getDataType(), getDimensionsString(),
        getUnitsString(), getDescription());

    // other state
    axis.axisType = this.axisType;
    axis.boundaryRef = this.boundaryRef;
    axis.isContiguous = this.isContiguous;
    axis.positive = this.positive;
    setIsLayer();

    axis.cache = new Variable.Cache(); // decouple cache
    return axis;
  }

  /**
   * 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 midpoint[index];
  }

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

    return Math.min(midpoint[0], midpoint[(int) getSize() - 1]);
  }

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

    return Math.max(midpoint[0], midpoint[(int) getSize() - 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 (!wasRead) doRead();
    if (!hasBounds) 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 midpoint.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 (!wasRead) doRead();
    if (!hasBounds) makeBounds();
    return edge.clone();
  }

  @Override
  public boolean isContiguous() {
    if (!wasRead) doRead();
    if (!hasBounds) makeBounds();
    return isContiguous;
  }

  /**
   * 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 (!wasRead) doRead();
    if (!hasBounds) makeBounds();
    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 (!wasRead) doRead();
    if (!hasBounds) makeBounds();
    return bound2.clone();
  }


  /**
   * Get the coordinate edges 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[] getCoordEdges(int i) {
    if (!wasRead) doRead();
    if (!hasBounds) 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;
  }

  /**
   * Given a coordinate position, find what grid element contains it.
   * 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 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");

    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();

  /*  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) {
    if (!wasRead) doRead();
    if (!hasBounds) makeBounds();

    int n = (int) this.getSize();
    int low = 0;
    int high = n;

    /* special case for longitude
    if (axisType == AxisType.Lon) {
      if (target < this.edge[low]) {
        target += 360.0;
        if (target > this.edge[high])
          return bounded ? 0 : -1;

      } else if (target > this.edge[high]) {
        target -= 360.0;
        if (target < this.edge[low])
          return bounded ? n - 1 : -1;
      }
    } */

    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) {
    if (!wasRead) doRead();
    if (!hasBounds) makeBounds();

    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;

      // do a linear search to find the nearest index
      for (int i=0; i<n; i++) {
        if ((bound1[i] <= target) && (target <= bound2[i]))
           return i;
        if (bound1[i] > target) {
          if (!bounded) return -1;
          double d1 = bound1[i] - target;
          double d2 = target - bound1[i-1];
          return (d1 > d2) ? i-1 : i;
        }
      }
      return bounded ? n-1 : -1;

    } 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;

      // do a linear search to find the nearest index
      for (int i=0; i<n; i++) {
        if ((bound2[i] <= target) && (target <= bound1[i]))
           return i;
        if (bound2[i] < target) {
          if (!bounded) return -1;
          double d1 = bound2[i] - target;
          double d2 = target - bound2[i-1];
          return (d1 > d2) ? i-1 : i;
        }
      }
      return bounded ? n-1 : -1;
    }
  }


  
///////////////////////////////////////////////////////////////////////////////
  // checkIsRegular
  private boolean isRegular = false;
  private double start, increment;

  /**
   * 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;
  }


  private boolean isLayer = false;

  /**
   * Caution: many datasets do not explicitly specify this info, this is often 
a guess; default is false.
   *
   * @return true if coordinate lies between a layer, or false if its at a 
point.
   */
  public boolean isLayer() {
    return isLayer;
  }

  /**
   * Set if coordinate lies between a layer, or is at a point.
   *
   * @param isLayer true if coordinate lies between a layer, or false if its at 
a point
   */
  public void setLayer(boolean isLayer) {
    this.isLayer = isLayer;
  }

  private void setIsLayer() {
    Attribute att = findAttribute(_Coordinate.ZisLayer);
    if ((att != null) && att.getStringValue().equalsIgnoreCase("true"))
      this.isLayer = true;
  }

  /**
   * 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 (wasCalc) 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.closeEnough(getCoordValue(i) - getCoordValue(i 
- 1), increment, 5.0e-3)) {
          isRegular = false;
          // double diff = Math.abs(getCoordValue(i) - getCoordValue(i-1) - 
increment);
          //System.out.println(i+" diff= "+getCoordValue(i)+" 
"+getCoordValue(i-1));
          break;
        }
    }
    wasCalc = true;
  }

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


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

      if (getSize() < 2)
        isAscending = true;
      else
        isAscending = getCoordValue(0) < getCoordValue(1);

      // correct non-monotonic longitude coords
      if (axisType == AxisType.Lon) {
        boolean monotonic = true;
        for (int i = 0; i < midpoint.length - 1; i++)
          monotonic &= isAscending ? midpoint[i] < midpoint[i + 1] : 
midpoint[i] > midpoint[i + 1];

        if (!monotonic) {
          boolean cross = false;
          if (isAscending) {
            for (int i = 0; i < midpoint.length; i++) {
              if (cross) midpoint[i] += 360;
              if (!cross && (i < midpoint.length - 1) && (midpoint[i] > 
midpoint[i + 1]))
                cross = true;
            }
          } else {
            for (int i = 0; i < midpoint.length; i++) {
              if (cross) midpoint[i] -= 360;
              if (!cross && (i < midpoint.length - 1) && (midpoint[i] < 
midpoint[i + 1]))
                cross = true;
            }
          }
          // LOOK - need to make sure we get stuff from the cache
          Array cachedData = Array.factory(DataType.DOUBLE, getShape(), 
midpoint);
          if (getDataType() != DataType.DOUBLE)
            cachedData = MAMath.convert(cachedData, getDataType());
          setCachedData(cachedData);
        }
      }

      //  calcIsRegular();
    } else if (getDataType() == DataType.STRING) {
      readStringValues();
      wasRead = true;
    } else {
      readCharValues();
      wasRead = true;
    }
  }

  // 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() {
    midpoint = new double[(int) getSize()];
    int count = 0;
    Array data;
    try {
      setUseNaNs(false); // missing values not allowed
      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);
    }

    IndexIterator iter = data.getIndexIterator();
    while (iter.hasNext())
      midpoint[count++] = iter.getDoubleNext();
  }

  private void makeBounds() {
    if (!makeBoundsFromAux()) {
      makeEdges();
      makeBoundsFromEdges();
    }
    hasBounds = true;
  }

  private boolean makeBoundsFromAux() {
    Attribute boundsAtt = findAttributeIgnoreCase("bounds");
    if ((null == boundsAtt) || !boundsAtt.isString()) return false;
    String boundsVarName = boundsAtt.getStringValue();
    VariableDS boundsVar = (VariableDS) ncd.findVariable(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.setUseNaNs(false); // missing values not allowed
      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 goesUp = (n < 2) || value1[1] > value1[0];
    boolean firstLower = value1[0] < 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.closeEnough(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 {
      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];
      setContiguous(false);
    }

    bound1 = value1;
    bound2 = value2;

    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] = (midpoint[i - 1] + midpoint[i]) / 2;
    edge[0] = midpoint[0] - (edge[1] - midpoint[0]);
    edge[size] = midpoint[size - 1] + (midpoint[size - 1] - edge[size - 1]);
  }

  private void makeMidpoints() {
    int size = (int) getSize();
    midpoint = new double[size];
    for (int i = 0; i < size; i++)
      midpoint[i] = (edge[i] + edge[i + 1]) / 2;
  }

  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.
   */
  public List<NamedObject> getNames() {
    int n = (int) getSize();
    List<NamedObject> names = new ArrayList<NamedObject>(n);
    for (int i = 0; i < n; i++)
      names.add(new ucar.nc2.util.NamedAnything(getCoordName(i), 
getUnitsString()));
    return names;
  }

}
/*
 * 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.dataset;

import junit.framework.TestCase;
import ucar.nc2.ncml.NcMLReader;

import java.io.IOException;
import java.io.StringReader;

/**
 * test CoordinateAxis1D.findCoord()
 *
 * @author caron
 * @since Jul 8, 2010
 */


public class TestFindCoord extends TestCase {

  public TestFindCoord(String name) {
    super(name);
  }

  public void testRegular() throws IOException {
    String ncml =
        "<?xml version='1.0' encoding='UTF-8'?>\n" +
            "<netcdf 
xmlns='http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2'>\n" +
            "    <dimension name='lat'  length='2' />\n" +
            "    <dimension name='lon'  length='2' />\n" +
            "    <dimension name='bnds' length='2' />\n" +
            "    <attribute name='Conventions' value='CF-1.0' />\n" +
            "    <variable name='lat' shape='lat' type='double'>\n" +
            "        <attribute name='units' type='String' 
value='degrees_north' />\n" +
            "        <attribute name='bounds' type='String' value='lat_bnds' 
/>\n" +
            "        <values>-45 45</values>\n" +
            "    </variable>\n" +
            "    <variable name='lat_bnds' shape='lat bnds' type='double'>\n" +
            "        <values>-90 0 0 90</values>\n" +
            "    </variable>\n" +
            "    <variable name='lon' shape='lon' type='double'>\n" +
            "        <attribute name='units' type='String' value='degrees_east' 
/>\n" +
            "        <attribute name='bounds' type='String' value='lon_bnds' 
/>\n" +
            "        <values>90 270</values>\n" +
            "    </variable>\n" +
            "    <variable name='lon_bnds' shape='lon bnds' type='double'>\n" +
            "        <values>0 180 180 360</values>\n" +
            "    </variable>\n" +
            "</netcdf>";

    doTest(ncml, "lat", false,
        new double[]{-91, -90, -67.5, -45, -22.5, 0, 22.5, 45, 67.5, 90, 91},
        new int[]   {-1,    0,     0,   0,     0, 1,    1,  1,    1,  -1, -1});

    doTest(ncml, "lon", false,
        new double[]{-91, -90, 0, 45, 90, 135, 180, 225, 270, 315, 360, 450, 
451},
        new int[]   {-1,    -1,0,  0,  0,    0,  1,   1,  1,    1,  -1, -1,    
-1});

    doTest(ncml, "lat", true,
        new double[]{-91, -90, -67.5, -45, -22.5, 0, 22.5, 45, 67.5, 90, 91},
        new int[]   {0,    0,     0,   0,     0, 1,    1,  1,    1,  1, 1});

    doTest(ncml, "lon", true,
        new double[]{-91, -90, 0, 45, 90, 135, 180, 225, 270, 315, 360, 450, 
451},
        new int[]   {  0,    0,0,  0,  0,    0,  1,   1,  1,    1,   1,   1,   
1});
  }

  public void testRegularDescending() throws IOException {
    String ncml =
        "<?xml version='1.0' encoding='UTF-8'?>\n" +
            "<netcdf 
xmlns='http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2'>\n" +
            "    <dimension name='lat'  length='2' />\n" +
            "    <dimension name='lon'  length='2' />\n" +
            "    <dimension name='bnds' length='2' />\n" +
            "    <attribute name='Conventions' value='CF-1.0' />\n" +
            "    <variable name='lat' shape='lat' type='double'>\n" +
            "        <attribute name='units' type='String' 
value='degrees_north' />\n" +
            "        <attribute name='bounds' type='String' value='lat_bnds' 
/>\n" +
            "        <values>45 -45</values>\n" +
            "    </variable>\n" +
            "    <variable name='lat_bnds' shape='lat bnds' type='double'>\n" +
            "        <values>90 0 0 -90</values>\n" +
            "    </variable>\n" +
            "    <variable name='lon' shape='lon' type='double'>\n" +
            "        <attribute name='units' type='String' value='degrees_east' 
/>\n" +
            "        <attribute name='bounds' type='String' value='lon_bnds' 
/>\n" +
            "        <values>270 90</values>\n" +
            "    </variable>\n" +
            "    <variable name='lon_bnds' shape='lon bnds' type='double'>\n" +
            "        <values>360 180 180 0</values>\n" +
            "    </variable>\n" +
            "</netcdf>";

    doTest(ncml, "lat", false,
        new double[]{-91, -90, -67.5, -45, -22.5, 0, 22.5, 45, 67.5, 90, 91},
        new int[]   {-1,    -1,    1, 1, 1, 1, 0, 0, 0, 0, -1});

    doTest(ncml, "lon", false,
        new double[]{-91, -90, 0, 45, 90, 135, 180, 225, 270, 315, 360, 450, 
451},
        new int[]   {-1,   -1, -1, 1,  1,   1,   1,   0,   0,   0,   0, -1, 
-1});

    doTest(ncml, "lat", true,
        new double[]{-91, -90, -67.5, -45, -22.5, 0, 22.5, 45, 67.5, 90, 91},
        new int[]   {1,    1,  1,    1,  1, 1, 0,    0,     0,   0,     0, });

    doTest(ncml, "lon", true,
        new double[]{-91, -90, 0, 45, 90, 135, 180, 225, 270, 315, 360, 450, 
451},
        new int[]   {  1,   1,  1,    1,   1,   1,   1, 0,    0,0,  0,  0,    
0});
  }

  public void testIrregular() throws IOException {
    String ncml =
        "<?xml version='1.0' encoding='UTF-8'?>\n" +
            "<netcdf 
xmlns='http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2'>\n" +
            "    <dimension name='lat'  length='3' />\n" +
            "    <dimension name='lon'  length='3' />\n" +
            "    <dimension name='bnds' length='2' />\n" +
            "    <attribute name='Conventions' value='CF-1.0' />\n" +
            "    <variable name='lat' shape='lat' type='double'>\n" +
            "        <attribute name='units' type='String' 
value='degrees_north' />\n" +
            "        <attribute name='bounds' type='String' value='lat_bnds' 
/>\n" +
            "        <values>-45 10 45</values>\n" +
            "    </variable>\n" +
            "    <variable name='lat_bnds' shape='lat bnds' type='double'>\n" +
            "        <values>-90 5 5 15 15 90</values>\n" +
            "    </variable>\n" +
            "    <variable name='lon' shape='lon' type='double'>\n" +
            "        <attribute name='units' type='String' value='degrees_east' 
/>\n" +
            "        <attribute name='bounds' type='String' value='lon_bnds' 
/>\n" +
            "        <values>90 200 270</values>\n" +
            "    </variable>\n" +
            "    <variable name='lon_bnds' shape='lon bnds' type='double'>\n" +
            "        <values>0 180 180 220 220 360</values>\n" +
            "    </variable>\n" +
            "</netcdf>";

    doTest(ncml, "lat", false,
        new double[]{-91, -90, -67.5, -45, -22.5, 0, 10, 45, 67.5, 90, 91},
        new int[] {-1,  0,  0,  0,  0,  0,  1,  2,  2,  2, -1});

    doTest(ncml, "lon", false,
        new double[]{-91, -90, 0, 45, 90, 135, 180, 210, 270, 315, 360, 450, 
451},
        new int[] {-1, -1,  0,  0,  0,  0,  1,  1,  2,  2,  2, -1, -1, });

    doTest(ncml, "lat", true,
        new double[]{-91, -90, -67.5, -45, -22.5, 0, 10, 45, 67.5, 90, 91},
        new int[] {0,  0,  0,  0,  0,  0,  1,  2,  2,  2, 2});

    doTest(ncml, "lon", true,
        new double[]{-91, -90, 0, 45, 90, 135, 180, 210, 270, 315, 360, 450, 
451},
        new int[] {0, 0,  0,  0,  0,  0,  1,  1,  2,  2,  2, 2, 2 });
  }

  public void testIrregularDescending() throws IOException {
    String ncml =
        "<?xml version='1.0' encoding='UTF-8'?>\n" +
            "<netcdf 
xmlns='http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2'>\n" +
            "    <dimension name='lat'  length='3' />\n" +
            "    <dimension name='lon'  length='3' />\n" +
            "    <dimension name='bnds' length='2' />\n" +
            "    <attribute name='Conventions' value='CF-1.0' />\n" +
            "    <variable name='lat' shape='lat' type='double'>\n" +
            "        <attribute name='units' type='String' 
value='degrees_north' />\n" +
            "        <attribute name='bounds' type='String' value='lat_bnds' 
/>\n" +
            "        <values>44 40 30</values>\n" +
            "    </variable>\n" +
            "    <variable name='lat_bnds' shape='lat bnds' type='double'>\n" +
            "        <values>50 42 42 35 35 0</values>\n" +
            "    </variable>\n" +
            "    <variable name='lon' shape='lon' type='double'>\n" +
            "        <attribute name='units' type='String' value='degrees_east' 
/>\n" +
            "        <attribute name='bounds' type='String' value='lon_bnds' 
/>\n" +
            "        <values>9 0 -20</values>\n" +
            "    </variable>\n" +
            "    <variable name='lon_bnds' shape='lon bnds' type='double'>\n" +
            "        <values>20 0 0 -10 -10 -90</values>\n" +
            "    </variable>\n" +
            "</netcdf>";

    doTest(ncml, "lat", false,
        new double[]{-90, -0, 0, 10, 33, 44, 55, 90},
        new int[] {-1,  2,  2,  2,  2,  0, -1, -1});

    doTest(ncml, "lon", false,
        new double[]{-91, -90, -12, -2, 0, 2, 22, 90},
        new int[] {-1,  2,  2,  1,  1,  0, -1, -1, });

    doTest(ncml, "lat", true,
        new double[]{-90, -0, 0, 10, 33, 44, 55, 90},
        new int[]{ 2,  2,  2,  2,  2,  0,  0,  0, });

    doTest(ncml, "lon", true,
        new double[]{-91, -90, -12, -2, 0, 2, 22, 90},
        new int[]{ 2,  2,  2,  1,  1,  0,  0,  0, });
  }

  public void testNonContig() throws IOException {
    String ncml =
        "<?xml version='1.0' encoding='UTF-8'?>\n" +
            "<netcdf 
xmlns='http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2'>\n" +
            "    <dimension name='lat'  length='3' />\n" +
            "    <dimension name='lon'  length='3' />\n" +
            "    <dimension name='bnds' length='2' />\n" +
            "    <attribute name='Conventions' value='CF-1.0' />\n" +
            "    <variable name='lat' shape='lat' type='double'>\n" +
            "        <attribute name='units' type='String' 
value='degrees_north' />\n" +
            "        <attribute name='bounds' type='String' value='lat_bnds' 
/>\n" +
            "        <values>10 20 90</values>\n" +
            "    </variable>\n" +
            "    <variable name='lat_bnds' shape='lat bnds' type='double'>\n" +
            "        <values>0 11 18 22 30 90</values>\n" +
            "    </variable>\n" +
            "    <variable name='lon' shape='lon' type='double'>\n" +
            "        <attribute name='units' type='String' value='degrees_east' 
/>\n" +
            "        <attribute name='bounds' type='String' value='lon_bnds' 
/>\n" +
            "        <values>0 10 90</values>\n" +
            "    </variable>\n" +
            "    <variable name='lon_bnds' shape='lon bnds' type='double'>\n" +
            "        <values>0 5 5 10 80 90</values>\n" +
            "    </variable>\n" +
            "</netcdf>";

    doTest(ncml, "lat", false,
        new double[]{90, 50, 48, 41.5, 40, 33, 15, 0, -10},
        new int[] { 2,  2,  2,  2,  2,  2, -1,  0, -1, });

    doTest(ncml, "lon", false,
        new double[]{90, 20, 18, 5, 4, 0, -10, -15, -20, -45, -90, -100},
        new int[] { 2, -1, -1,  0,  0,  0, -1, -1, -1, -1, -1, -1, });

    doTest(ncml, "lat", true,
        new double[]{90, 50, 48, 41.5, 40, 33, 15, 0, -10},
        new int[] { 2,  2,  2,  2,  2,  2,  1,  0,  0, });

    doTest(ncml, "lon", true,
        new double[]{90, 20, 18, 5, 4, 0, -10, -15, -20, -45, -90, -100},
        new int[]{ 2,  1,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0, });
  }


  public void testNonContigDescending() throws IOException {
    String ncml =
        "<?xml version='1.0' encoding='UTF-8'?>\n" +
            "<netcdf 
xmlns='http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2'>\n" +
            "    <dimension name='lat'  length='3' />\n" +
            "    <dimension name='lon'  length='3' />\n" +
            "    <dimension name='bnds' length='2' />\n" +
            "    <attribute name='Conventions' value='CF-1.0' />\n" +
            "    <variable name='lat' shape='lat' type='double'>\n" +
            "        <attribute name='units' type='String' 
value='degrees_north' />\n" +
            "        <attribute name='bounds' type='String' value='lat_bnds' 
/>\n" +
            "        <values>44 40 30</values>\n" +
            "    </variable>\n" +
            "    <variable name='lat_bnds' shape='lat bnds' type='double'>\n" +
            "        <values>50 42 41 35 32 0</values>\n" +
            "    </variable>\n" +
            "    <variable name='lon' shape='lon' type='double'>\n" +
            "        <attribute name='units' type='String' value='degrees_east' 
/>\n" +
            "        <attribute name='bounds' type='String' value='lon_bnds' 
/>\n" +
            "        <values>9 0 -20</values>\n" +
            "    </variable>\n" +
            "    <variable name='lon_bnds' shape='lon bnds' type='double'>\n" +
            "        <values>20 5 0 -10 -20 -90</values>\n" +
            "    </variable>\n" +
            "</netcdf>";

    doTest(ncml, "lat", false,
        new double[]{90, 50, 48, 41.5, 40, 33, 15, 0, -10},
        new int[]{-1,  0,  0, -1,  1, -1,  2,  2, -1, });

    doTest(ncml, "lon", false,
        new double[]{90, 20, 18, 5, 4, 0, -10, -15, -20, -45, -90, -100},
        new int[] {-1,  0,  0,  0, -1,  1,  1, -1,  2,  2,  2, -1, });

    doTest(ncml, "lat", true,
        new double[]{90, 50, 48, 41.5, 40, 33, 15, 0, -10},
         new int[] { 0,  0,  0,  1,  1,  2,  2,  2,  2, });

    doTest(ncml, "lon", true,
        new double[]{90, 20, 18, 5, 4, 0, -10, -15, -20, -45, -90, -100},
        new int[] { 0,  0,  0,  0,  1,  1,  1,  2,  2,  2,  2,  2, });
  }


  public void doTest(String ncml, String varName, boolean bounded, double[] 
vals, int[] expect) throws IOException {
    NetcdfDataset nc = NcMLReader.readNcML(new StringReader(ncml), null);
    NetcdfDataset dataset = new NetcdfDataset(nc, true);

    try {
      CoordinateAxis1D axis1D = (CoordinateAxis1D) 
dataset.findVariable(varName);
      if (axis1D.isContiguous()) {
        double[] edge = axis1D.getCoordEdges();
        System.out.printf("%s =", varName);
        for (int i =0; i<edge.length;i++)
          System.out.printf("%2f, ", edge[i]);
        System.out.printf("%n");

      } else {
        System.out.printf("%s =", varName);
        double[] bound1 =axis1D.getBound1();
        double[] bound2 =axis1D.getBound2();
        for (int i =0; i<axis1D.getSize();i++)
          System.out.printf("(%f,%f) ", bound1[i],bound2[i]);
        System.out.printf("%n");
      }
      
      for (int i =0; i<vals.length;i++) {
        double v = vals[i];
        int index = bounded ? axis1D.findCoordElementBounded(v) :  
axis1D.findCoordElement(v);
        System.out.printf(" %5.1f, index= %2d %n", v, index);
        if (expect != null) assert index == expect[i];
      }
      System.out.printf("{");
      for (int i =0; i<vals.length;i++) {
        double v = vals[i];
        int index = bounded ? axis1D.findCoordElementBounded(v) :  
axis1D.findCoordElement(v);
        System.out.printf("%2d, ", index);
      }
      System.out.printf("}%n%n");
    } finally {
      dataset.close();
    }
  }
}
  • 2010 messages navigation, sorted by:
    1. Thread
    2. Subject
    3. Author
    4. Date
    5. ↑ Table Of Contents
  • Search the netcdf-java archives: