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

ucar.nc2.dataset.CoordinateAxis2D Maven / Gradle / Ivy

Go to download

The NetCDF-Java Library is a Java interface to NetCDF files, as well as to many other types of scientific data formats.

The newest version!
/*
 * Copyright 1998-2014 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.Attribute;
import ucar.nc2.Variable;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants.CF;

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

/**
 * A 2-dimensional numeric Coordinate Axis. Must be invertible meaning, roughly, that
 *   if you draw lines connecting the points, none would cross.
 *
 * @see CoordinateAxis#factory
 * @author john caron
 */

public class CoordinateAxis2D extends CoordinateAxis {
  static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CoordinateAxis2D.class);
  static private final boolean debug = false;

  private ArrayDouble.D2 coords = null;  // LOOK maybe optional for large arrays, or maybe eliminate all together, and read each time ??

  /**
   * Create a 2D coordinate axis from an existing VariableDS
   *
   * @param ncd the containing dataset
   * @param vds create it from here
   */
  public CoordinateAxis2D(NetcdfDataset ncd, VariableDS vds) {
    super(ncd, vds);
  }

  // for section and slice
  @Override
  protected Variable copy() {
    return new CoordinateAxis2D(this.ncd, this);
  }

  /**
   * Get the coordinate value at the i, j index.
   *
   * @param i index 0 (fastest varying, right-most)
   * @param j index 1
   * @return midpoint.get(j, i).
   */
  public double getCoordValue(int j, int i) {
    if (coords == null) doRead();
    return coords.get(j, i);
  }

  private void doRead() {
    Array data;
    try {
      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);
    }

    if (data.getRank() != 2)
      throw new IllegalArgumentException("must be 2D");
    if (debug)
      System.out.printf("Coordinate2D read%n");

    coords = (ArrayDouble.D2) Array.factory(double.class, data.getShape(), data.get1DJavaArray(double.class));

    if (this.axisType == AxisType.Lon)
      makeConnectedLon(coords);
  }

  private boolean isInterval;
  private boolean intervalWasComputed;

  public boolean isInterval() {
    if (!intervalWasComputed)
      isInterval = computeIsInterval();
    return isInterval;
  }

  private void makeConnectedLon(ArrayDouble.D2 mid) {

    int[] shape = mid.getShape();
    int ny = shape[0];
    int nx = shape[1];

    // first row
    double connect = mid.get(0, 0);
    for (int i = 1; i < nx; i++) {
      connect = connectLon(connect, mid.get(0, i));
      mid.set(0, i, connect);
    }

    // other rows
    for (int j = 1; j < ny; j++) {
      connect = mid.get(j - 1, 0);
      for (int i = 0; i < nx; i++) {
        connect = connectLon(connect, mid.get(j, i));
        mid.set(j, i, connect);
      }
    }

  }

  private static final double MAX_JUMP = 100.0; // larger than you would ever expect

  static private double connectLon(double connect, double val) {
    if (Double.isNaN(connect)) return val;
    if (Double.isNaN(val)) return val;

    double diff = val - connect;
    if (Math.abs(diff) < MAX_JUMP) return val; // common case fast
    // we have to add or subtract 360
    double result = diff > 0 ? val - 360 : val + 360;
    double diff2 = connect - result;
    if ((Math.abs(diff2)) < Math.abs(diff))
      val = result;
    return val;
  }


  /**
   * Get the coordinate values as a 1D double array, in canonical order.
   *
   * @return coordinate values
   * @throws UnsupportedOperationException if !isNumeric()
   */
  public double[] getCoordValues() {
    if (coords == null) doRead();
    if (!isNumeric())
      throw new UnsupportedOperationException("CoordinateAxis2D.getCoordValues() on non-numeric");
    return (double[]) coords.get1DJavaArray(double.class);
  }

  /**
   * Create a new CoordinateAxis2D as a section of this CoordinateAxis2D.
   *
   * @param r1 the section on the first index
   * @param r2 the section on the second index
   * @return a section of this CoordinateAxis2D
   * @throws InvalidRangeException if specified Ranges are invalid
   */
  public CoordinateAxis2D section(Range r1, Range r2) throws InvalidRangeException {
    List section = new ArrayList<>();
    section.add(r1);
    section.add(r2);
    return (CoordinateAxis2D) section(section);
  }

  public ArrayDouble.D2 getCoordValuesArray() {
    if (coords == null) doRead();
    return coords;
  }

  /**
   * Only call if isInterval()
   *
   * @return bounds array pr null if not an interval
   */
  public ArrayDouble.D3 getCoordBoundsArray() {
    if (coords == null) doRead();
    return makeBoundsFromAux();
  }

  /**
   * @deprecated use getCoordValuesArray
   */
  public ArrayDouble.D2 getMidpoints() {
    return getCoordValuesArray();
  }

  public ArrayDouble.D2 getXEdges() {
    ArrayDouble.D2 mids = getCoordValuesArray();
    return makeXEdges(mids);
  }

  public ArrayDouble.D2 getYEdges() {
    ArrayDouble.D2 mids = getCoordValuesArray();
    return makeYEdges(mids);
  }

  /**
   * Normal case: do something reasonable in deciding on the edges when we have the midpoints of a 2D coordinate.
   *
   * @param midx x coordinates of midpoints
   * @return x coordinates of edges with shape (ny+1, nx+1)
   */
  static public ArrayDouble.D2 makeXEdges(ArrayDouble.D2 midx) {
    int[] shape = midx.getShape();
    int ny = shape[0];
    int nx = shape[1];
    ArrayDouble.D2 edgex = new ArrayDouble.D2(ny + 1, nx + 1);

    for (int y = 0; y < ny - 1; y++) {
      for (int x = 0; x < nx - 1; x++) {
        // the interior edges are the average of the 4 surrounding midpoints
        double xval = (midx.get(y, x) + midx.get(y, x + 1) + midx.get(y + 1, x) + midx.get(y + 1, x + 1)) / 4;
        edgex.set(y + 1, x + 1, xval);
      }
      // extrapolate to exterior points
      edgex.set(y + 1, 0, edgex.get(y + 1, 1) - (edgex.get(y + 1, 2) - edgex.get(y + 1, 1)));
      edgex.set(y + 1, nx, edgex.get(y + 1, nx - 1) + (edgex.get(y + 1, nx - 1) - edgex.get(y + 1, nx - 2)));
    }

    // extrapolate to the first and last row
    for (int x = 0; x < nx + 1; x++) {
      edgex.set(0, x, edgex.get(1, x) - (edgex.get(2, x) - edgex.get(1, x)));
      edgex.set(ny, x, edgex.get(ny - 1, x) + (edgex.get(ny - 1, x) - edgex.get(ny - 2, x)));
    }

    return edgex;
  }

  /**
   * Normal case: do something reasonable in deciding on the edges when we have the midpoints of a 2D coordinate.
   *
   * @param midy y coordinates of midpoints
   * @return y coordinates of edges with shape (ny+1, nx+1)
   */
  static public ArrayDouble.D2 makeYEdges(ArrayDouble.D2 midy) {
    int[] shape = midy.getShape();
    int ny = shape[0];
    int nx = shape[1];
    ArrayDouble.D2 edgey = new ArrayDouble.D2(ny + 1, nx + 1);

    for (int y = 0; y < ny - 1; y++) {
      for (int x = 0; x < nx - 1; x++) {
        // the interior edges are the average of the 4 surrounding midpoints
        double xval = (midy.get(y, x) + midy.get(y, x + 1) + midy.get(y + 1, x) + midy.get(y + 1, x + 1)) / 4;
        edgey.set(y + 1, x + 1, xval);
      }
      // extrapolate to exterior points
      edgey.set(y + 1, 0, edgey.get(y + 1, 1) - (edgey.get(y + 1, 2) - edgey.get(y + 1, 1)));
      edgey.set(y + 1, nx, edgey.get(y + 1, nx - 1) + (edgey.get(y + 1, nx - 1) - edgey.get(y + 1, nx - 2)));
    }

    // extrapolate to the first and last row
    for (int x = 0; x < nx + 1; x++) {
      edgey.set(0, x, edgey.get(1, x) - (edgey.get(2, x) - edgey.get(1, x)));
      edgey.set(ny, x, edgey.get(ny - 1, x) + (edgey.get(ny - 1, x) - edgey.get(ny - 2, x)));
    }

    return edgey;
  }


  /**
   * Experimental: for WRF rotated (NMM "E") Grids
   *
   * @param midx x coordinates of midpoints
   * @return x coordinates of edges with shape (ny+2, nx+1)
   */
  static public ArrayDouble.D2 makeXEdgesRotated(ArrayDouble.D2 midx) {
    int[] shape = midx.getShape();
    int ny = shape[0];
    int nx = shape[1];
    ArrayDouble.D2 edgex = new ArrayDouble.D2(ny + 2, nx + 1);

    // compute the interior rows
    for (int y = 0; y < ny; y++) {
      for (int x = 1; x < nx; x++) {
        double xval = (midx.get(y, x - 1) + midx.get(y, x)) / 2;
        edgex.set(y + 1, x, xval);
      }
      edgex.set(y + 1, 0, midx.get(y, 0) - (edgex.get(y + 1, 1) - midx.get(y, 0)));
      edgex.set(y + 1, nx, midx.get(y, nx - 1) - (edgex.get(y + 1, nx - 1) - midx.get(y, nx - 1)));
    }

    // compute the first row
    for (int x = 0; x < nx; x++) {
      edgex.set(0, x, midx.get(0, x));
    }

    // compute the last row
    for (int x = 0; x < nx - 1; x++) {
      edgex.set(ny + 1, x, midx.get(ny - 1, x));
    }

    return edgex;
  }

  /**
   * Experimental: for WRF rotated (NMM "E") Grids
   *
   * @param midy y coordinates of midpoints
   * @return y coordinates of edges with shape (ny+2, nx+1)
   */
  static public ArrayDouble.D2 makeYEdgesRotated(ArrayDouble.D2 midy) {
    int[] shape = midy.getShape();
    int ny = shape[0];
    int nx = shape[1];
    ArrayDouble.D2 edgey = new ArrayDouble.D2(ny + 2, nx + 1);

    // compute the interior rows
    for (int y = 0; y < ny; y++) {
      for (int x = 1; x < nx; x++) {
        double yval = (midy.get(y, x - 1) + midy.get(y, x)) / 2;
        edgey.set(y + 1, x, yval);
      }
      edgey.set(y + 1, 0, midy.get(y, 0) - (edgey.get(y + 1, 1) - midy.get(y, 0)));
      edgey.set(y + 1, nx, midy.get(y, nx - 1) - (edgey.get(y + 1, nx - 1) - midy.get(y, nx - 1)));
    }

    // compute the first row
    for (int x = 0; x < nx; x++) {
      double pt0 = midy.get(0, x);
      double pt = edgey.get(2, x);

      double diff = pt0 - pt;
      edgey.set(0, x, pt0 + diff);
    }

    // compute the last row
    for (int x = 0; x < nx - 1; x++) {
      double pt0 = midy.get(ny - 1, x);
      double pt = edgey.get(ny - 1, x);

      double diff = pt0 - pt;
      edgey.set(ny + 1, x, pt0 + diff);
    }

    return edgey;
  }

  ///////////////////////////////////////////////////////////////////////////////
  // bounds calculations


  private ArrayDouble.D3 makeBoundsFromAux() {
    if (!computeIsInterval()) return null;

    Attribute boundsAtt = findAttributeIgnoreCase(CF.BOUNDS);
    if (boundsAtt == null) return null;

    String boundsVarName = boundsAtt.getStringValue();
    VariableDS boundsVar = (VariableDS) ncd.findVariable(getParentGroup(), boundsVarName);

    Array data;
    try {
      //boundsVar.setUseNaNs(false); // missing values not allowed
      data = boundsVar.read();
    } catch (IOException e) {
      log.warn("CoordinateAxis2D.makeBoundsFromAux read failed ", e);
      return null;
    }

    ArrayDouble.D3 bounds;
    assert (data.getRank() == 3) && (data.getShape()[2] == 2) : "incorrect shape data for variable " + boundsVar;
    if (data instanceof ArrayDouble.D3) {
      bounds = (ArrayDouble.D3) data;
    } else {
      bounds = (ArrayDouble.D3) Array.factory(DataType.DOUBLE, data.getShape());
      MAMath.copy(data, bounds);
    }

    return bounds;
  }

  private boolean computeIsInterval() {
    intervalWasComputed = true;

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

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

  ///////////////////////////////////////
  // time

  public CoordinateAxisTimeHelper getCoordinateAxisTimeHelper() {
    return new CoordinateAxisTimeHelper(getCalendarFromAttribute(), getUnitsString());
  }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy