cdm/src/main/java/ucar/nc2/dt/grid/GridCoordSys.java
public List<NamedObject> getLevels() {
    if (vertZaxis == null)
      return new ArrayList<>(0);

    int n = (int) vertZaxis.getSize();
    List<NamedObject> levels = new ArrayList<>(n);
    for (int i = 0; i < n; i++)
      levels.add(new ucar.nc2.util.NamedAnything(vertZaxis.getCoordName(i), vertZaxis.getUnitsString()));

    return levels;
  }

 /**
   * Get the String name for the ith level(z) coordinate.
   *
   * @param index which level coordinate
   * @return level name
   */
  public String getLevelName(int index) {
    if ((vertZaxis == null) || (index < 0) || (index >= vertZaxis.getSize()))
      throw new IllegalArgumentException("getLevelName = " + index);
    return vertZaxis.getCoordName(index).trim();
  }

  /**
   * Get the index corresponding to the level name.
   *
   * @param name level name
   * @return level index, or -1 if not found
   */
  public int getLevelIndex(String name) {
    if ((vertZaxis == null) || (name == null)) return -1;

    for (int i = 0; i < vertZaxis.getSize(); i++) {
      if (vertZaxis.getCoordName(i).trim().equals(name))
        return i;
    }
    return -1;
  }

  /**
   * Get the list of time names, to be used for user selection.
   * The ith one refers to the ith time coordinate.
   *
   * @return List of ucar.nc2.util.NamedObject, or empty list.
   */
  public List<NamedObject> getTimes() {
    List<CalendarDate> cdates = getCalendarDates();
    List<NamedObject> times = new ArrayList<>( cdates.size());
    for (CalendarDate cd: cdates) {
      times.add(new ucar.nc2.util.NamedAnything(cd.toString(), "calendar date"));
    }
    return times;
  }

   ///////////////////////////////////////////////////////////////////////////
  // deprecated

  /**
   * Given a point in x,y coordinate space, find the x,y index in the coordinate system.
   *
   * @deprecated use findXYindexFromCoord
   */
  public int[] findXYCoordElement(double x_coord, double y_coord, int[] result) {
    return findXYindexFromCoord(x_coord, y_coord, result);
  }

  /**
   * Get the date range
   * @return date range
   * @deprecated  use getCalendarDateRange
   */
  public DateRange getDateRange() {
    Date[] dates = getTimeDates();
    if (dates.length > 0)
      return new DateRange(dates[0], dates[dates.length - 1]);
    return null;
  }

  /**
   * Get the list of times as Dates.
   * If 2D, return list of unique dates.
   *
   * @return array of java.util.Date, or Date[0].
   * @deprecated  use getCalendarDates
   */
  public java.util.Date[] getTimeDates() {
    if ((timeTaxis != null) && (timeTaxis.getSize() > 0)) {
      return timeTaxis.getTimeDates();

    } else if ((tAxis != null) && (tAxis.getSize() > 0))  {
      return makeTimes2D();
    }

    return new Date[0];
  }

  private Date[] makeTimes2D() {
    Set<Date> dates = new HashSet<>();

    try {
      // common case: see if it has a valid udunits unit
      String units = tAxis.getUnitsString();
      if (units != null && SimpleUnit.isDateUnit(units) && tAxis.getDataType().isNumeric()) {
        DateUnit du = new DateUnit(units);
        Array data = tAxis.read();
        data.resetLocalIterator();
        while (data.hasNext()) {
          Date d = du.makeDate(data.nextDouble());
          dates.add(d);
        }

      } else if (tAxis.getDataType() == DataType.STRING) {
        // otherwise, see if its a String or CHAR, and if we can parse the values as an ISO date
        DateFormatter formatter = new DateFormatter();
        Array data = tAxis.read();
        data.resetLocalIterator();
        while (data.hasNext()) {
          Date d = formatter.getISODate((String) data.next());
          dates.add(d);
        }

      } else if (tAxis.getDataType() == DataType.CHAR) {
        DateFormatter formatter = new DateFormatter();
        ArrayChar data = (ArrayChar) tAxis.read();
        ArrayChar.StringIterator iter = data.getStringIterator();
        while (iter.hasNext()) {
          Date d = formatter.getISODate(iter.next());
          dates.add(d);
        }

      } else {
        return new Date[0];
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }

    // sorted list
    int n = dates.size();
    Date[] dd = dates.toArray(new Date[n]);
    List<Date> dateList = Arrays.asList(dd);
    Collections.sort(dateList);
    Date[] timeDates = new Date[n];
    int count=0;
    for (Date d : dateList)
      timeDates[count++] = d;

    return timeDates;
  }

  /* old way
  private boolean makeTimes1D() {
    int n = (int) timeTaxis.getSize();
    timeDates = new Date[n];

    // common case: see if it has a valid udunits unit
    try {
      DateUnit du = null;
      String units = timeTaxis.getUnitsString();
      if (units != null)
        du = new DateUnit(units);
      for (int i = 0; i < n; i++) {
        Date d = du.makeDate(timeTaxis.getCoordValue(i));
        timeDates[i] = d;
      }
      isDate = true;
      return true;
    } catch (Exception e) {
      // ok to fall through
    }

    // otherwise, see if its a String, and if we can parse the values as an ISO date
    if ((timeTaxis.getDataType() == DataType.STRING) || (timeTaxis.getDataType() == DataType.CHAR)) {
      DateFormatter formatter = new DateFormatter();
      for (int i = 0; i < n; i++) {
        String coordValue = timeTaxis.getCoordName(i);
        Date d = formatter.getISODate(coordValue);
        if (d == null) {
          isDate = false;
          return false;
        } else {
          timeDates[i] = d;
        }
      }
      isDate = true;
      return true;
    }

    return false;
  }  */



  /**
   * Get the string name for the ith time coordinate.
   *
   * @param index which time coordinate
   * @return time name.
   * @deprecated
   */
  public String getTimeName(int index) {
    List<CalendarDate> cdates = getCalendarDates();

    if ((index < 0) || (index >= cdates.size()))
      throw new IllegalArgumentException("getTimeName illegal index = " + index);

    return cdates.get(index).toString();
  }

  /**
   * Get the index corresponding to the time name.
   *
   * @param name time name
   * @return time index, or -1 if not found
   * @deprecated
   */
  public int getTimeIndex(String name) {
    List<CalendarDate> cdates = getCalendarDates();
    for (int i=0; i < cdates.size(); i++) {
      if( cdates.get(i).toString().equals(name)) return i;
    }

    return -1;
  }

  /**
   * Only works if coordsys has 1d time axis
   * @deprecated use CoordinateAxis1DTime.findTimeIndexFromDate
   */
  public int findTimeIndexFromDate(java.util.Date d) {
    if (timeTaxis == null) return -1;
    return timeTaxis.findTimeIndexFromDate(d);
  }

  ///////////////////////////////////////////////////////////////////////
  // experimental

  static private double getMinOrMaxLon(double lon1, double lon2, boolean wantMin) {
    double midpoint = (lon1 + lon2) / 2;
    lon1 = LatLonPointImpl.lonNormal(lon1, midpoint);
    lon2 = LatLonPointImpl.lonNormal(lon2, midpoint);

    return wantMin ? Math.min(lon1, lon2) : Math.max(lon1, lon2);
  }

  static public LatLonRect getLatLonBoundingBox(Projection proj, double startx, double starty, double endx, double endy) {

    if (proj instanceof LatLonProjection) {
      double deltaLat = endy - starty;
      double deltaLon = endx - startx;

      LatLonPoint llpt = new LatLonPointImpl(starty, startx);
      return new LatLonRect(llpt, deltaLat, deltaLon);

    }

    ProjectionRect bb = new ProjectionRect(startx, starty, endx, endy);

    // look at all 4 corners of the bounding box
    LatLonPointImpl llpt = (LatLonPointImpl) proj.projToLatLon(bb.getLowerLeftPoint(), new LatLonPointImpl());
    LatLonPointImpl lrpt = (LatLonPointImpl) proj.projToLatLon(bb.getLowerRightPoint(), new LatLonPointImpl());
    LatLonPointImpl urpt = (LatLonPointImpl) proj.projToLatLon(bb.getUpperRightPoint(), new LatLonPointImpl());
    LatLonPointImpl ulpt = (LatLonPointImpl) proj.projToLatLon(bb.getUpperLeftPoint(), new LatLonPointImpl());

    // Check if grid contains poles. LOOK disabled
    boolean includesNorthPole = false;
    /* int[] resultNP = new int[2];
    resultNP = findXYindexFromLatLon(90.0, 0, null);
    if (resultNP[0] != -1 && resultNP[1] != -1)
      includesNorthPole = true;  */

    boolean includesSouthPole = false;
    /* int[] resultSP = new int[2];
    resultSP = findXYindexFromLatLon(-90.0, 0, null);
    if (resultSP[0] != -1 && resultSP[1] != -1)
      includesSouthPole = true;  */

    LatLonRect llbb;

    if (includesNorthPole && !includesSouthPole) {
      llbb = new LatLonRect(llpt, new LatLonPointImpl(90.0, 0.0)); // ??? lon=???
      llbb.extend(lrpt);
      llbb.extend(urpt);
      llbb.extend(ulpt);

    } else if (includesSouthPole && !includesNorthPole) {
      llbb = new LatLonRect(llpt, new LatLonPointImpl(-90.0, -180.0)); // ??? lon=???
      llbb.extend(lrpt);
      llbb.extend(urpt);
      llbb.extend(ulpt);

    } else {
      double latMin = Math.min(llpt.getLatitude(), lrpt.getLatitude());
      double latMax = Math.max(ulpt.getLatitude(), urpt.getLatitude());

      // longitude is a bit tricky as usual
      double lonMin = getMinOrMaxLon(llpt.getLongitude(), ulpt.getLongitude(), true);
      double lonMax = getMinOrMaxLon(lrpt.getLongitude(), urpt.getLongitude(), false);

      llpt.set(latMin, lonMin);
      urpt.set(latMax, lonMax);

      llbb = new LatLonRect(llpt, urpt);
    }
    return llbb;
  }
}
public List<NamedObject> getLevels() {
    if (vertZaxis == null)
      return new ArrayList<>(0);

    int n = (int) vertZaxis.getSize();
    List<NamedObject> levels = new ArrayList<>(n);
    for (int i = 0; i < n; i++)
      levels.add(new ucar.nc2.util.NamedAnything(vertZaxis.getCoordName(i), vertZaxis.getUnitsString()));

    return levels;
  }

 /**
   * Get the String name for the ith level(z) coordinate.
   *
   * @param index which level coordinate
   * @return level name
   */
  public String getLevelName(int index) {
    if ((vertZaxis == null) || (index < 0) || (index >= vertZaxis.getSize()))
      throw new IllegalArgumentException("getLevelName = " + index);
    return vertZaxis.getCoordName(index).trim();
  }

  /**
   * Get the index corresponding to the level name.
   *
   * @param name level name
   * @return level index, or -1 if not found
   */
  public int getLevelIndex(String name) {
    if ((vertZaxis == null) || (name == null)) return -1;

    for (int i = 0; i < vertZaxis.getSize(); i++) {
      if (vertZaxis.getCoordName(i).trim().equals(name))
        return i;
    }
    return -1;
  }

  /**
   * Get the list of time names, to be used for user selection.
   * The ith one refers to the ith time coordinate.
   *
   * @return List of ucar.nc2.util.NamedObject, or empty list.
   */
  public List<NamedObject> getTimes() {
    List<CalendarDate> cdates = getCalendarDates();
    List<NamedObject> times = new ArrayList<>( cdates.size());
    for (CalendarDate cd: cdates) {
      times.add(new ucar.nc2.util.NamedAnything(cd.toString(), "calendar date"));
    }
    return times;
  }

   ///////////////////////////////////////////////////////////////////////////
  // deprecated

  /**
   * Given a point in x,y coordinate space, find the x,y index in the coordinate system.
   *
   * @deprecated use findXYindexFromCoord
   */
  public int[] findXYCoordElement(double x_coord, double y_coord, int[] result) {
    return findXYindexFromCoord(x_coord, y_coord, result);
  }

  /**
   * Get the date range
   * @return date range
   * @deprecated  use getCalendarDateRange
   */
  public DateRange getDateRange() {
    Date[] dates = getTimeDates();
    if (dates.length > 0)
      return new DateRange(dates[0], dates[dates.length - 1]);
    return null;
  }

  /**
   * Get the list of times as Dates.
   * If 2D, return list of unique dates.
   *
   * @return array of java.util.Date, or Date[0].
   * @deprecated  use getCalendarDates
   */
  public java.util.Date[] getTimeDates() {
    if ((timeTaxis != null) && (timeTaxis.getSize() > 0)) {
      return timeTaxis.getTimeDates();

    } else if ((tAxis != null) && (tAxis.getSize() > 0))  {
      return makeTimes2D();
    }

    return new Date[0];
  }

  private Date[] makeTimes2D() {
    Set<Date> dates = new HashSet<>();

    try {
      // common case: see if it has a valid udunits unit
      String units = tAxis.getUnitsString();
      if (units != null && SimpleUnit.isDateUnit(units) && tAxis.getDataType().isNumeric()) {
        DateUnit du = new DateUnit(units);
        Array data = tAxis.read();
        data.resetLocalIterator();
        while (data.hasNext()) {
          Date d = du.makeDate(data.nextDouble());
          dates.add(d);
        }

      } else if (tAxis.getDataType() == DataType.STRING) {
        // otherwise, see if its a String or CHAR, and if we can parse the values as an ISO date
        DateFormatter formatter = new DateFormatter();
        Array data = tAxis.read();
        data.resetLocalIterator();
        while (data.hasNext()) {
          Date d = formatter.getISODate((String) data.next());
          dates.add(d);
        }

      } else if (tAxis.getDataType() == DataType.CHAR) {
        DateFormatter formatter = new DateFormatter();
        ArrayChar data = (ArrayChar) tAxis.read();
        ArrayChar.StringIterator iter = data.getStringIterator();
        while (iter.hasNext()) {
          Date d = formatter.getISODate(iter.next());
          dates.add(d);
        }

      } else {
        return new Date[0];
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }

    // sorted list
    int n = dates.size();
    Date[] dd = dates.toArray(new Date[n]);
    List<Date> dateList = Arrays.asList(dd);
    Collections.sort(dateList);
    Date[] timeDates = new Date[n];
    int count=0;
    for (Date d : dateList)
      timeDates[count++] = d;

    return timeDates;
  }

  /* old way
  private boolean makeTimes1D() {
    int n = (int) timeTaxis.getSize();
    timeDates = new Date[n];

    // common case: see if it has a valid udunits unit
    try {
      DateUnit du = null;
      String units = timeTaxis.getUnitsString();
      if (units != null)
        du = new DateUnit(units);
      for (int i = 0; i < n; i++) {
        Date d = du.makeDate(timeTaxis.getCoordValue(i));
        timeDates[i] = d;
      }
      isDate = true;
      return true;
    } catch (Exception e) {
      // ok to fall through
    }

    // otherwise, see if its a String, and if we can parse the values as an ISO date
    if ((timeTaxis.getDataType() == DataType.STRING) || (timeTaxis.getDataType() == DataType.CHAR)) {
      DateFormatter formatter = new DateFormatter();
      for (int i = 0; i < n; i++) {
        String coordValue = timeTaxis.getCoordName(i);
        Date d = formatter.getISODate(coordValue);
        if (d == null) {
          isDate = false;
          return false;
        } else {
          timeDates[i] = d;
        }
      }
      isDate = true;
      return true;
    }

    return false;
  }  */



  /**
   * Get the string name for the ith time coordinate.
   *
   * @param index which time coordinate
   * @return time name.
   * @deprecated
   */
  public String getTimeName(int index) {
    List<CalendarDate> cdates = getCalendarDates();

    if ((index < 0) || (index >= cdates.size()))
      throw new IllegalArgumentException("getTimeName illegal index = " + index);

    return cdates.get(index).toString();
  }

  /**
   * Get the index corresponding to the time name.
   *
   * @param name time name
   * @return time index, or -1 if not found
   * @deprecated
   */
  public int getTimeIndex(String name) {
    List<CalendarDate> cdates = getCalendarDates();
    for (int i=0; i < cdates.size(); i++) {
      if( cdates.get(i).toString().equals(name)) return i;
    }

    return -1;
  }

  /**
   * Only works if coordsys has 1d time axis
   * @deprecated use CoordinateAxis1DTime.findTimeIndexFromDate
   */
  public int findTimeIndexFromDate(java.util.Date d) {
    if (timeTaxis == null) return -1;
    return timeTaxis.findTimeIndexFromDate(d);
  }

  ///////////////////////////////////////////////////////////////////////
  // experimental

  static private double getMinOrMaxLon(double lon1, double lon2, boolean wantMin) {
    double midpoint = (lon1 + lon2) / 2;
    lon1 = LatLonPointImpl.lonNormal(lon1, midpoint);
    lon2 = LatLonPointImpl.lonNormal(lon2, midpoint);

    return wantMin ? Math.min(lon1, lon2) : Math.max(lon1, lon2);
  }

  static public LatLonRect getLatLonBoundingBox(Projection proj, double startx, double starty, double endx, double endy) {

    if (proj instanceof LatLonProjection) {
      double deltaLat = endy - starty;
      double deltaLon = endx - startx;

      LatLonPoint llpt = new LatLonPointImpl(starty, startx);
      return new LatLonRect(llpt, deltaLat, deltaLon);

    }

    ProjectionRect bb = new ProjectionRect(startx, starty, endx, endy);

    // look at all 4 corners of the bounding box
    LatLonPointImpl llpt = (LatLonPointImpl) proj.projToLatLon(bb.getLowerLeftPoint(), new LatLonPointImpl());
    LatLonPointImpl lrpt = (LatLonPointImpl) proj.projToLatLon(bb.getLowerRightPoint(), new LatLonPointImpl());
    LatLonPointImpl urpt = (LatLonPointImpl) proj.projToLatLon(bb.getUpperRightPoint(), new LatLonPointImpl());
    LatLonPointImpl ulpt = (LatLonPointImpl) proj.projToLatLon(bb.getUpperLeftPoint(), new LatLonPointImpl());

    // Check if grid contains poles. LOOK disabled
    boolean includesNorthPole = false;
    /* int[] resultNP = new int[2];
    resultNP = findXYindexFromLatLon(90.0, 0, null);
    if (resultNP[0] != -1 && resultNP[1] != -1)
      includesNorthPole = true;  */

    boolean includesSouthPole = false;
    /* int[] resultSP = new int[2];
    resultSP = findXYindexFromLatLon(-90.0, 0, null);
    if (resultSP[0] != -1 && resultSP[1] != -1)
      includesSouthPole = true;  */

    LatLonRect llbb;

    if (includesNorthPole && !includesSouthPole) {
      llbb = new LatLonRect(llpt, new LatLonPointImpl(90.0, 0.0)); // ??? lon=???
      llbb.extend(lrpt);
      llbb.extend(urpt);
      llbb.extend(ulpt);

    } else if (includesSouthPole && !includesNorthPole) {
      llbb = new LatLonRect(llpt, new LatLonPointImpl(-90.0, -180.0)); // ??? lon=???
      llbb.extend(lrpt);
      llbb.extend(urpt);
      llbb.extend(ulpt);

    } else {
      double latMin = Math.min(llpt.getLatitude(), lrpt.getLatitude());
      double latMax = Math.max(ulpt.getLatitude(), urpt.getLatitude());

      // longitude is a bit tricky as usual
      double lonMin = getMinOrMaxLon(llpt.getLongitude(), ulpt.getLongitude(), true);
      double lonMax = getMinOrMaxLon(lrpt.getLongitude(), urpt.getLongitude(), false);

      llpt.set(latMin, lonMin);
      urpt.set(latMax, lonMax);

      llbb = new LatLonRect(llpt, urpt);
    }
    return llbb;
  }
}
Get the list of level names, to be used for user selection.
The ith one refers to the ith level coordinate.

@return List of ucar.nc2.util.NamedObject, or empty list.