NOTICE: This version of the NSF Unidata web site (archive.unidata.ucar.edu) is no longer being updated.
Current content can be found at unidata.ucar.edu.

To learn about what's going on, see About the Archive Site.

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: