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

ucar.nc2.ft2.coverage.CoverageCoordAxis1D Maven / Gradle / Ivy

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

import ucar.ma2.*;
import ucar.nc2.constants.AxisType;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateRange;
import ucar.nc2.util.Indent;
import ucar.nc2.util.NamedAnything;
import ucar.nc2.util.NamedObject;
import ucar.nc2.util.Optional;
import ucar.unidata.util.Format;

import javax.annotation.concurrent.Immutable;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.Iterator;
import java.util.List;

/**
 * Coverage CoordAxis 1D case
 *
 * @author caron
 * @since 7/15/2015
 */
@Immutable
public class CoverageCoordAxis1D extends CoverageCoordAxis { // implements Iterable {

  // does this really describe all subset possibilities? what about RangeScatter, composite ??
  protected final Range range;            // for subset, tracks the indexes in the original
  protected final RangeComposite crange;

  public CoverageCoordAxis1D(CoverageCoordAxisBuilder builder) {
    super(builder);

    if (axisType == null && builder.dependenceType == DependenceType.independent)
      throw new IllegalArgumentException("independent axis must have type");

    // make sure range has axisType as the name
    String rangeName = (axisType != null) ? axisType.toString() : null;
    if (builder.range != null) {
      this.range = (rangeName != null) ? builder.range.setName(rangeName) : builder.range;
    } else {
      this.range = Range.make(rangeName, getNcoords());
    }
    this.crange = builder.crange;
  }

  @Override
  public RangeIterator getRangeIterator() {
    return crange != null ? crange : range;
  }

  @Override
  public Range getRange() {
    return range;
  }

  @Override
  public void toString(Formatter f, Indent indent) {
    super.toString(f, indent);
    f.format("%s range=%s isSubset=%s", indent, range, isSubset());
    f.format("%n");
  }

  @Override
  public String getSummary() {
    if (axisType != AxisType.RunTime)
      return super.getSummary();

    if (ncoords < 7) {
      Formatter f = new Formatter();
      for (int i = 0; i < ncoords; i++) {
        CalendarDate cd = makeDate(getCoordMidpoint(i));
        if (i > 0) f.format(", ");
        f.format("%s", cd);
      }
      return f.toString();
    }

    Formatter f = new Formatter();
    CalendarDate start = makeDate(getStartValue());
    f.format("start=%s", start);
    CalendarDate end = makeDate(getEndValue());
    f.format(", end=%s", end);
    f.format(" (npts=%d spacing=%s)", getNcoords(), getSpacing());

    return f.toString();
  }


  ///////////////////////////////////////////////////////////////////
  // Spacing

  public boolean isAscending() {
    loadValuesIfNeeded();
    switch (spacing) {
      case regularInterval:
      case regularPoint:
        return getResolution() > 0;

      case irregularPoint:
        return values[0] <= values[ncoords - 1];

      case contiguousInterval:
        return values[0] <= values[ncoords];

      case discontiguousInterval:
        return values[0] <= values[2 * ncoords - 1];
    }
    throw new IllegalStateException("unknown spacing" + spacing);
  }

  public double getCoordMidpoint(int index) {
    if (index < 0 || index >= getNcoords())
      throw new IllegalArgumentException("Index out of range=" + index);
    loadValuesIfNeeded();

    switch (spacing) {
      case regularPoint:
        return startValue + index * getResolution();

      case irregularPoint:
        return values[index];

      case regularInterval:
        return startValue + (index + .5) * getResolution();

      case contiguousInterval:
      case discontiguousInterval:
        return (getCoordEdge1(index) + getCoordEdge2(index)) / 2;
    }
    throw new IllegalStateException("Unknown spacing=" + spacing);
  }

  public double getCoordEdge1(int index) {
    if (index < 0 || index >= getNcoords())
      throw new IllegalArgumentException("Index out of range=" + index);
    loadValuesIfNeeded();

    switch (spacing) {
      case regularPoint:
        return startValue + (index - .5) * getResolution();

      case regularInterval:
        return startValue + index * getResolution();

      case irregularPoint:
        if (index > 0)
          return (values[index - 1] + values[index]) / 2;
        else
          return values[0] - (values[1] - values[0]) / 2;

      case contiguousInterval:
        return values[index];

      case discontiguousInterval:
        return values[2 * index];
    }
    throw new IllegalStateException("Unknown spacing=" + spacing);
  }

  public double getCoordEdge2(int index) {
    if (index < 0 || index >= getNcoords())
      throw new IllegalArgumentException("Index out of range=" + index);
    loadValuesIfNeeded();

    switch (spacing) {
      case regularPoint:
        if (index < 0 || index >= ncoords) throw new IllegalArgumentException("Index out of range " + index);
        return startValue + (index + .5) * getResolution();

      case regularInterval:
        return startValue + (index+1) * getResolution();

      case irregularPoint:
        if (index < ncoords - 1)
          return (values[index] + values[index + 1]) / 2;
        else
          return values[index] + (values[index] - values[index - 1]) / 2;

      case contiguousInterval:
        return values[index + 1];

      case discontiguousInterval:
        return values[2 * index + 1];
    }
    throw new IllegalStateException("Unknown spacing=" + spacing);
  }

  public double getCoordEdgeFirst() {
    return getCoordEdge1(0);
  }

  public double getCoordEdgeLast() {
    return getCoordEdge2(ncoords - 1);
  }

  @Override
  public Array getCoordsAsArray() {
    Array result;
    switch (dependenceType) {
      case scalar:
        result = Array.factory(getDataType(), new int[0]);
        break;
      default:
        result = Array.factory(getDataType(), new int[]{ncoords});
        break;
    }

    for (int i = 0; i < ncoords; i++)
      result.setDouble(i, getCoordMidpoint(i));
    return result;
  }

  @Override
  public Array getCoordBoundsAsArray() {
    Array result = Array.factory(getDataType(), new int[]{ncoords, 2});

    int count = 0;
    for (int i = 0; i < ncoords; i++) {
      result.setDouble(count++, getCoordEdge1(i));
      result.setDouble(count++, getCoordEdge2(i));
    }
    return result;
  }

  @Override
  public Optional subset(double minValue, double maxValue, int stride) throws InvalidRangeException {
    CoordAxisHelper helper = new CoordAxisHelper(this);
    Optional buildero = helper.subset(minValue, maxValue, stride);
    return !buildero.isPresent() ? Optional.empty(buildero.getErrorMessage()) : Optional.of(new CoverageCoordAxis1D(buildero.get()));
  }

  // CalendarDate, double[2], or Double
  public Object getCoordObject(int index) {
    if (axisType == AxisType.RunTime)
      return makeDate(getCoordMidpoint(index));
    if (isInterval())
      return new double[]{getCoordEdge1(index), getCoordEdge2(index)};
    return getCoordMidpoint(index);
  }

  public List getCoordValueNames() {
    loadValuesIfNeeded();
    if (timeHelper != null)
      return timeHelper.getCoordValueNames(this);

    List result = new ArrayList<>();
    for (int i = 0; i < ncoords; i++) {
      Object value = null;
      switch (spacing) {
        case regularPoint:
        case irregularPoint:
          value = Format.d(getCoordMidpoint(i), 3);
          break;

        case regularInterval:
        case contiguousInterval:
        case discontiguousInterval:
          value = new CoordInterval(getCoordEdge1(i), getCoordEdge2(i), 3);
          break;
      }
      result.add(new NamedAnything(value, value + " " + getUnits()));
    }

    return result;
  }

  @Override
  public CoverageCoordAxis copy() {
    return new CoverageCoordAxis1D(new CoverageCoordAxisBuilder(this));
  }

  @Override
  public Optional subset(SubsetParams params) {
    Optional buildero = subsetBuilder(params);
    return !buildero.isPresent() ? Optional.empty(buildero.getErrorMessage()) : Optional.of(new CoverageCoordAxis1D(buildero.get()));
  }

  // only for longitude, only for regular (do we need a subclass for longitude 1D coords ??
  public Optional subsetByIntervals(List lonIntvs, int stride) {
    if (axisType != AxisType.Lon)
      return Optional.empty("subsetByIntervals only for longitude");
    if (!isRegular())
      return Optional.empty("subsetByIntervals only for regular longitude");

    CoordAxisHelper helper = new CoordAxisHelper(this);

    double start = Double.NaN;
    boolean first = true;
    List ranges = new ArrayList<>();
    for (MAMath.MinMax lonIntv : lonIntvs) {
      if (first) start = lonIntv.min;
      first = false;

      Optional opt = helper.makeRange(lonIntv.min, lonIntv.max, stride);
      if (!opt.isPresent())
        return Optional.empty(opt.getErrorMessage());
      ranges.add(opt.get());
    }

    try {
      RangeComposite compositeRange = new RangeComposite(AxisType.Lon.toString(), ranges);
      int npts = compositeRange.length();
      double end = start + npts * resolution;

      CoverageCoordAxisBuilder builder = new CoverageCoordAxisBuilder(this); // copy
      builder.subset(npts, start, end, resolution, null);
      builder.setRange(null);
      builder.setCompositeRange(compositeRange);

      return Optional.of(new CoverageCoordAxis1D(builder));
    } catch (InvalidRangeException e) {
      return Optional.empty(e.getMessage());
    }
  }

  public Optional subsetByIndex(Range range) {
    try {
      CoordAxisHelper helper = new CoordAxisHelper(this);
      CoverageCoordAxisBuilder builder = helper.subsetByIndex(range);
      return Optional.of(new CoverageCoordAxis1D(builder));
    } catch (InvalidRangeException e) {
      return Optional.empty(e.getMessage());
    }
  }

  // LOOK  incomplete handling of subsetting params
  protected Optional subsetBuilder(SubsetParams params) {
    if (params == null)
      return Optional.of(new CoverageCoordAxisBuilder(this));

    CoordAxisHelper helper = new CoordAxisHelper(this);

    switch (getAxisType()) {
      case GeoZ:
      case Pressure:
      case Height:
        Double dval = params.getVertCoord();
        if (dval != null)
          return Optional.of(helper.subsetClosest(dval));
        // use midpoint of interval LOOK may not always be unique
        double[] intv = params.getVertCoordIntv();
        if (intv != null)
          return Optional.of(helper.subsetClosest((intv[0]+intv[1])/2));

        double[] vertRange = params.getVertRange(); // used by WCS
        if (vertRange != null)
          return helper.subset(vertRange[0], vertRange[1], 1);

        // default is all
        break;

      case Ensemble:
        Double eval = params.getDouble(SubsetParams.ensCoord);
        if (eval != null) {
          return Optional.of(helper.subsetClosest(eval));
        }
        // default is all
        break;

      // x,y get seperately subsetted
      case GeoX:
      case GeoY:
      case Lat:
      case Lon:
        throw new IllegalArgumentException();
        // return null; // LOOK heres a case where null is "correct"

      case Time:
        if (params.isTrue(SubsetParams.timePresent))
          return Optional.of(helper.subsetLatest());

        CalendarDate date = (CalendarDate) params.get(SubsetParams.time);
        if (date != null)
          return Optional.of(helper.subsetClosest(date));

        Integer stride = (Integer) params.get(SubsetParams.timeStride);
        if (stride == null || stride < 0) stride = 1;

        CalendarDateRange dateRange = (CalendarDateRange) params.get(SubsetParams.timeRange);
        if (dateRange != null)
          return helper.subset(dateRange, stride);

        // If no time range or time point, a timeOffset can be used to specify the time point.
        /* CalendarDate timeOffsetDate = params.getTimeOffsetDate();
        if (timeOffsetDate != null) {
          return Optional.of(helper.subsetClosest(timeOffsetDate));
        } */

        // A time offset or time offset interval starts from the rundate of the offset
        Double timeOffset = params.getTimeOffset();
        CalendarDate runtime = params.getRunTime();
        if (timeOffset != null) {
          if (runtime != null) {
            date = makeDateInTimeUnits(runtime, timeOffset);
            return Optional.of(helper.subsetClosest(date));
          } else {
            return Optional.of(helper.subsetClosest(timeOffset));
          }
        }

        // If a time interval is sent, search for match.
        double[] timeOffsetIntv = params.getTimeOffsetIntv();
        if (timeOffsetIntv != null && runtime != null) {
          // double midOffset = (timeOffsetIntv[0] + timeOffsetIntv[1]) / 2;
          CalendarDate[] dateIntv = new CalendarDate[2];
          dateIntv[0] = makeDateInTimeUnits(runtime, timeOffsetIntv[0]);
          dateIntv[1] = makeDateInTimeUnits(runtime, timeOffsetIntv[1]);
          return Optional.of(helper.subsetClosest(dateIntv));
        }

        if (stride != 1)
          try {
            return Optional.of(helper.subsetByIndex(getRange().setStride(stride)));
          } catch (InvalidRangeException e) {
            return Optional.empty(e.getMessage());
          }

        // default is all
        break;

      case RunTime:
        CalendarDate rundate = (CalendarDate) params.get(SubsetParams.runtime);
        if (rundate != null)
          return Optional.of(helper.subsetClosest(rundate));

/*        CalendarDateRange rundateRange = (CalendarDateRange) params.get(SubsetParams.runtimeRange);
        if (rundateRange != null)
          return helper.subset(rundateRange, 1); */

        if (params.isTrue(SubsetParams.runtimeAll))
          break;

        // default is latest
        return Optional.of(helper.subsetLatest());

      case TimeOffset:
        Double oval = params.getDouble(SubsetParams.timeOffset);
        if (oval != null) {
          return Optional.of(helper.subsetClosest(oval));
        }

        // If a time interval is sent, search for match.
        timeOffsetIntv = params.getTimeOffsetIntv();
        if (timeOffsetIntv != null) {
          return Optional.of(helper.subsetClosest((timeOffsetIntv[0]+timeOffsetIntv[1])/2));
        }


        if (params.isTrue(SubsetParams.timeOffsetFirst)) {
          try {
            return Optional.of(helper.subsetByIndex(new Range(1)));
          } catch (InvalidRangeException e) {
            return Optional.empty(e.getMessage());
          }
        }
        // default is all
        break;
    }

    // otherwise return copy the original axis
    return Optional.of(new CoverageCoordAxisBuilder(this));
  }

  @Override
  public Optional subsetDependent(CoverageCoordAxis1D dependsOn) {
    CoverageCoordAxisBuilder builder;
    try {
      builder = new CoordAxisHelper(this).subsetByIndex(dependsOn.getRange()); // LOOK Other possible subsets?
    } catch (InvalidRangeException e) {
      return Optional.empty(e.getMessage());
    }
    return Optional.of(new CoverageCoordAxis1D(builder));
  }

 // @Override
  public Iterator iterator() {
    return new MyIterator();
  }

  // Look what about intervals ??
  private class MyIterator implements java.util.Iterator {
    private int current = 0;
    private int ncoords = getNcoords();

    public boolean hasNext() {
      return current < ncoords;
    }

    public Object next() {
      return getCoordMidpoint(current++);
    }
  }

}