/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.dataset.conv;

import ucar.ma2.*;

import ucar.nc2.Attribute;
import ucar.nc2.Dimension;

import ucar.nc2.NetcdfFile;
import ucar.nc2.Variable;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants._Coordinate;
import ucar.nc2.dataset.*;
import ucar.nc2.util.CancelTask;

import java.io.IOException;

/**
 * Cosmic data - version 1.
 * Add time coordinate from global atts start_time, stop_time, assuming its linear along the vertical dimension.
 *
 * @author caron
 * @since Jul 29, 2009
 */
public class Cosmic1Convention extends CoordSysBuilder {

  /**
   * @param ncfile the NetcdfFile to test
   * @return true if we think this is a Zebra file.
   */
  public static boolean isMine(NetcdfFile ncfile) {

    //   :start_time = 9.17028312E8; // double
    // :stop_time = 9.170284104681826E8; // double

    if ((null == ncfile.findDimension("MSL_alt"))
            && (null == ncfile.findDimension("time"))) {
      return false;
    }
    // if (null == ncfile.findGlobalAttribute( "start_time")) return false;
    // if (null == ncfile.findGlobalAttribute( "stop_time")) return false;

    String center = ncfile.findAttValueIgnoreCase(null, "center", null);
    return (center != null) && center.equals("UCAR/CDAAC");
  }

  /**
   * _more_
   */
  public Cosmic1Convention() {
    this.conventionName = "Cosmic1";
  }

  /**
   * _more_
   *
   * @param ds         _more_
   * @param cancelTask _more_
   * @throws IOException _more_
   */
  public void augmentDataset(NetcdfDataset ds,
                             CancelTask cancelTask) throws IOException {

    Attribute leoAtt = ds.findGlobalAttribute("leoId");

    if (leoAtt == null) {
      if (ds.findVariable("time") == null) {
        // create a time variable - assume its linear along the vertical dimension
        double start = ds.readAttributeDouble(null, "start_time",
                Double.NaN);
        double stop = ds.readAttributeDouble(null, "stop_time",
                Double.NaN);

        if (Double.isNaN(start) && Double.isNaN(stop)) {
          double top = ds.readAttributeDouble(null, "toptime",
                  Double.NaN);
          double bot = ds.readAttributeDouble(null, "bottime",
                  Double.NaN);

          this.conventionName = "Cosmic2";
          if (top > bot) {
            stop = top;
            start = bot;
          } else {
            stop = bot;
            start = top;
          }

        }

        Dimension dim = ds.findDimension("MSL_alt");
        Variable dimV = ds.findVariable("MSL_alt");
        Array dimU = dimV.read();
        int inscr = (dimU.getFloat(1) - dimU.getFloat(0)) > 0
                ? 1
                : 0;
        int n = dim.getLength();
        double incr = (stop - start) / n;

        String timeUnits = "seconds since 1980-01-06 00:00:00";
        Variable timeVar = new VariableDS(ds, null, null, "time",
                DataType.DOUBLE, dim.getShortName(),
                timeUnits, null);
        ds.addVariable(null, timeVar);
        timeVar.addAttribute(new Attribute(CDM.UNITS, timeUnits));
        timeVar.addAttribute(new Attribute(_Coordinate.AxisType,
                AxisType.Time.toString()));
        int dir = ds.readAttributeInteger(null, "irs", 1);
        ArrayDouble.D1 data =
                (ArrayDouble.D1) Array.factory(DataType.DOUBLE,
                        new int[]{n});
        if (inscr == 0) {
          if (dir == 1) {
            for (int i = 0; i < n; i++) {
              data.set(i, start + i * incr);
            }
          } else {
            for (int i = 0; i < n; i++) {
              data.set(i, stop - i * incr);
            }
          }
        } else {
          for (int i = 0; i < n; i++) {
            data.set(i, stop - i * incr);
          }
        }
        timeVar.setCachedData(data, false);
      }

      Variable v = ds.findVariable("Lat");
      if (v == null) {
        v = ds.findVariable("GEO_lat");
      }
      v.addAttribute(new Attribute(_Coordinate.AxisType,
              AxisType.Lat.toString()));
      Variable v1 = ds.findVariable("Lon");
      if (v1 == null) {
        v1 = ds.findVariable("GEO_lon");
      }
      v1.addAttribute(new Attribute(_Coordinate.AxisType,
              AxisType.Lon.toString()));
    } else {
      Dimension dim = ds.findDimension("time");
      int n = dim.getLength();
      Variable latVar = new VariableDS(ds, null, null, "Lat",
              DataType.FLOAT, dim.getShortName(),
              "degree", null);
      latVar.addAttribute(new Attribute(_Coordinate.AxisType,
              AxisType.Lat.toString()));
      ds.addVariable(null, latVar);
      Variable lonVar = new VariableDS(ds, null, null, "Lon",
              DataType.FLOAT, dim.getShortName(),
              "degree", null);
      lonVar.addAttribute(new Attribute(_Coordinate.AxisType,
              AxisType.Lon.toString()));
      ds.addVariable(null, lonVar);
      Variable altVar = new VariableDS(ds, null, null, "MSL_alt",
              DataType.FLOAT, dim.getShortName(),
              "meter", null);
      altVar.addAttribute(new Attribute(_Coordinate.AxisType,
              AxisType.Height.toString()));
      ds.addVariable(null, altVar);

      // cal data array
      ArrayFloat.D1 latData =
              (ArrayFloat.D1) Array.factory(DataType.FLOAT,
                      new int[]{n});
      ArrayFloat.D1 lonData =
              (ArrayFloat.D1) Array.factory(DataType.FLOAT,
                      new int[]{n});
      ArrayFloat.D1 altData =
              (ArrayFloat.D1) Array.factory(DataType.FLOAT,
                      new int[]{n});
      ArrayDouble.D1 timeData =
              (ArrayDouble.D1) Array.factory(DataType.DOUBLE,
                      new int[]{n});
      this.conventionName = "Cosmic3";

      int iyr = ds.readAttributeInteger(null, "year", 2009);
      int mon = ds.readAttributeInteger(null, "month", 0);
      int iday = ds.readAttributeInteger(null, "day", 0);
      int ihr = ds.readAttributeInteger(null, "hour", 0);
      int min = ds.readAttributeInteger(null, "minute", 0);
      int sec = ds.readAttributeInteger(null, "second", 0);

      double start = ds.readAttributeDouble(null, "startTime",
              Double.NaN);
      double stop = ds.readAttributeDouble(null, "stopTime",
              Double.NaN);
      double incr = (stop - start) / n;
      int t = 0;
      // double julian = juday(mon, iday, iyr);
      // cal the dtheta based pm attributes
      double dtheta = gast(iyr, mon, iday, ihr, min, sec, t);

      Variable tVar = ds.findVariable("time");
      String timeUnits = "seconds since 1980-01-06 00:00:00";  //dtime.getUnit().toString();
      tVar.removeAttributeIgnoreCase(CDM.VALID_RANGE);
      tVar.removeAttributeIgnoreCase(CDM.UNITS);
      tVar.addAttribute(new Attribute(CDM.UNITS, timeUnits));
      tVar.addAttribute(new Attribute(_Coordinate.AxisType,
              AxisType.Time.toString()));

      Variable v = ds.findVariable("xLeo");
      Array xLeo = v.read();
      v = ds.findVariable("yLeo");
      Array yLeo = v.read();
      v = ds.findVariable("zLeo");
      Array zLeo = v.read();
      double a = 6378.1370;
      double b = 6356.7523142;
      IndexIterator iiter0 = xLeo.getIndexIterator();
      IndexIterator iiter1 = yLeo.getIndexIterator();
      IndexIterator iiter2 = zLeo.getIndexIterator();
      int i = 0;

      while (iiter0.hasNext()) {

        double[] v_inertial = new double[3];
        v_inertial[0] = iiter0.getDoubleNext();  //.getDouble(i); //.nextDouble();
        v_inertial[1] = iiter1.getDoubleNext();  //.getDouble(i); //.nextDouble();
        v_inertial[2] = iiter2.getDoubleNext();  //.getDouble(i); //.nextDouble();
        double[] uvz = new double[3];
        uvz[0] = 0.0;
        uvz[1] = 0.0;
        uvz[2] = 1.0;
        // v_ecef should be in the (approximate) ECEF frame

        // double[] v_ecf = execute(v_inertial, julian);
        double[] v_ecf = spin(v_inertial, uvz, -1 * dtheta);

        // cal lat/lon here
        // double [] llh = ECFtoLLA(v_ecf[0]*1000, v_ecf[1]*1000, v_ecf[2]*1000, a,  b);
        double[] llh = xyzell(a, b, v_ecf);
        latData.set(i, (float) llh[0]);
        lonData.set(i, (float) llh[1]);
        altData.set(i, (float) llh[2]);
        timeData.set(i, start + i * incr);
        i++;
      }

      latVar.setCachedData(latData, false);
      lonVar.setCachedData(lonData, false);
      altVar.setCachedData(altData, false);
      tVar.setCachedData(timeData, false);
    }
    ds.finish();

  }

  /* private DateTime getDateTime(int year, int month, int day, int hour,
                               int min, int sec) throws Exception {
    GregorianCalendar convertCal =
            new GregorianCalendar(DateUtil.TIMEZONE_GMT);
    convertCal.clear();
    convertCal.set(Calendar.YEAR, year);
    //The MONTH is 0 based. The incoming month is 1 based
    convertCal.set(Calendar.MONTH, month - 1);
    convertCal.set(Calendar.DAY_OF_MONTH, day);
    convertCal.set(Calendar.HOUR_OF_DAY, hour);
    convertCal.set(Calendar.MINUTE, min);
    convertCal.set(Calendar.SECOND, sec);
    return new DateTime(convertCal.getTime());
  }  */

  /**
   * _more_
   *
   * @param ncDataset _more_
   * @param v         _more_
   * @return _more_
   */
  protected AxisType getAxisType(NetcdfDataset ncDataset,
                                 VariableEnhanced v) {
    String name = v.getShortName();
    if (name.equals("time")) {
      return AxisType.Time;
    }
    if (name.equals("Lat") || name.equals("GEO_lat")) {
      return AxisType.Lat;
    }
    if (name.equals("Lon") || name.equals("GEO_lon")) {
      return AxisType.Lon;
    }
    // if (name.equals("xLeo") ) return AxisType.GeoX;
    // if (name.equals("yLeo") ) return AxisType.GeoY;
    if (name.equals("MSL_alt")) {
      return AxisType.Height;
    }
    return null;
  }

  /**
   * NAME       :  XYZELL
   *

   * CALL XYZELL(A,B,XSTAT,XSTELL)
   *

   * PURPOSE    :  COMPUTATION OF ELLIPSOIDAL COORDINATES "XSTELL"
   * GIVEN THE CARTESIAN COORDINATES "XSTAT"
   *

   * PARAMETERS :
   * IN :  A   : SEMI-MAJOR AXIS OF THE REFERENCE    R*8
   * ELLIPSOID IN METERS
   * B   : SEMI-MINOR AXIS OF THE REFERENCE    R*8
   * ELLIPSOID IN METERS
   * DXELL(3): TRANSLATION COMPONENTS FROM THE    R*8
   * ORIGIN OF THE CART. COORD. SYSTEM
   * (X,Y,Z) TO THE CENTER OF THE REF.
   * ELLIPSOID (IN METRES)
   * SCELL  : SCALE FACTOR BETWEEN REF. ELLIPSOID R*8
   * AND WGS-84
   * XSTAT(3): CARTESIAN COORDINATES (M)          R*8
   * OUT :  XSTELL(3): ELLIPSOIDAL COORDINATES           R*8
   * XSTELL(1): ELL. LATITUDE (RADIAN)
   * XSTELL(2): ELL. LONGITUDE (RADIAN)
   * XSTELL(3): ELL. HEIGHT (M)
   *

   * SR CALLED  :  DMLMTV
   *

   * REMARKS    :  ---
   *

   * AUTHOR     :  M. ROTHACHER
   *

   * VERSION    :  3.4  (JAN 93)
   *

   * CREATED    :  87/11/03 12:32        LAST MODIFIED :  88/11/21 17:36
   *

   * COPYRIGHT  :  ASTRONOMICAL INSTITUTE
   * 1987      UNIVERSITY OF BERNE
   * SWITZERLAND
   *
   * @param a     _more_
   * @param b     _more_
   * @param xstat _more_
   * @return _more_
   */
  public double[] xyzell(double a, double b, double[] xstat) {

    double[] xstell = new double[3];
    double e2, s, rlam, zps, h, phi, n, hp, phip;
    int i, niter;

    e2 = (a * a - b * b) / (a * a);
    s = Math.sqrt(xstat[0] * xstat[0] + xstat[1] * xstat[1]);
    rlam = Math.atan2(xstat[1], xstat[0]);
    zps = xstat[2] / s;
    h = Math.sqrt(xstat[0] * xstat[0] + xstat[1] * xstat[1]
            + xstat[2] * xstat[2]) - a;
    phi = Math.atan(zps / (1.0 - e2 * a / (a + h)));

    niter = 0;
    for (i = 1; i <= 10000000; i++) {
      n = a / Math.sqrt(1.0 - e2 * Math.sin(phi) * Math.sin(phi));
      hp = h;
      phip = phi;
      h = s / Math.cos(phi) - n;
      phi = Math.atan(zps / (1.0 - e2 * n / (n + h)));
      niter = niter + 1;
      if ((Math.abs(phip - phi) <= 1.e-11)
              && (Math.abs(hp - h) <= 1.e-5)) {
        break;
      }
      if (niter >= 10) {
        phi = -999.0;
        rlam = -999.0;
        h = -999.0;
        break;
      }

    }

    xstell[0] = phi * 180 / 3.1415926;
    xstell[1] = rlam * 180 / 3.1415926;
    xstell[2] = h;

    return xstell;

  }

  /**
   *    gast.f
   *     Compute hour angle dtheta
   *
   *     ! iyr, mon, iday, ihr, min and sec form a base (epoch) time,
   *     ! t is an offset from the base time in seconds
   *     ! dtheta is the output hour angle in radians
   *
   * Calculation of local time
   *
   * ! glon -- East longitude in degrees, -180 to 180
   *
   *
   *      call vprod.f   spin.f   rnorm.f
   * Calculation of the unit vector normal to the occultation plane
   * (clockwise rotated from GPS to LEO)
   *
   * @param iyr _more_
   * @param imon _more_
   * @param iday _more_
   * @param ihr _more_
   * @param imin _more_
   * @param sec _more_
   * @param dsec _more_
   *
   * @return _more_
   */
  /*
      double dtheta = gast(iyr,mon,iday,ihr,min,sec,t)
      utc=ihr*1.d0+min/60.d0
      timloc=utc+24.d0*glon/360.d0
      if (timloc.gt.24.d0) timloc=timloc-24.d0
      if (timloc.lt.0.d0) timloc=timloc+24.d0
  */

  // In the inertial reference frame
  /*
   v_inertial(1)= ! Inertial GPS position vectors, XYZ
   v_inertial(2)=
   v_inertial(3)=
  */
  // In the Earth-fixed reference frame

  //  Z axis to rotate around (unit vector Z)
  /*
      uvz(1)=0.0;
      uvz(2)=0.0;
      uvz(3)=1.0;

      double [] v_ecf = spin(v_inertial,uvz,-180.d0*dtheta/pi)
  */
  // after this call, v_ecef should be in the (approximate) ECEF frame

  /**
   * ----------------------------------------------------------------------
   * gast.f
   *

   * This subroutine computes the Greenwich Apparent Siderial
   * Time angle given a UTC date and time.
   *

   * parameter  Input parameters:
   * Inputs:
   *
   * @param iyr  integer year 1995
   * @param imon integer month 5
   * @param iday integer day 5
   * @param ihr  integer hour 5
   * @param imin integer minute 5
   * @param sec  double second 31.0
   * @param dsec double second 0.0
   *             Outputs:
   * @return theta GAST angle in radians
   *

   *         author     Bill Schreiner
   * @since May 1995
   *        version    $URL: svn://ursa.cosmic.ucar.edu/trunk/src/roam/gast.f $ $Id: gast.f 10129 2008-07-30 17:10:52Z dhunt $
   *        -----------------------------------------------------------------------
   */

  public double gast(int iyr, int imon, int iday, int ihr, int imin,
                     double sec, double dsec) {
    //
    //    implicit double precision (a-h,o-z)
    //    character(len=*), parameter :: header = '$URL: svn://ursa.cosmic.ucar.edu/trunk/src/roam/gast.f $ $Id: gast.f 10129 2008-07-30 17:10:52Z dhunt $'
    //
    // Coordinate transform from the celestial inertial reference frame to the geo-
    // centered Greenwich reference frame.
    // Call a subroutine to calculate the Julian day "djd":
    double djd = juday(imon, iday, iyr);  //djd=julean day.
    double tu = (djd - 2451545.0) / 36525.0;
    double gmst = 24110.548410 + 8640184.8128660 * tu
            + 0.093104 * tu * tu - 6.2E-6 * Math.pow(tu, 3);  //       !gmst=Greenwich mean...
    double utco = (ihr * 3600) + (imin * 60) + sec;

    return togreenw(dsec, utco, gmst);
  }

  /**
   * JDAY calculates the Julian Day number (JD) from the Gregorian month
   * ,day, and year (M,D,Y). (NOT VALID BEFORE 10/15/1582)
   *
   * @param M _more_
   * @param D _more_
   * @param Y _more_
   * @return _more_
   */
  public double juday(int M, int D, int Y) {
    double JD;

    double IY = Y - (12 - M) / 10;
    double IM = M + 1 + 12 * ((12 - M) / 10);
    double I = IY / 100;
    double J = 2 - I + I / 4 + Math.round(365.25 * IY)
            + Math.round(30.6001 * IM);
    JD = (J + D + 1720994.50);
    return JD;
  }

  /**
   * This subroutine is to transform the locations and velocities of the GPS and
   * LEO satellites from the celestial inertial reference frame to the Earth
   * centered Greenwich reference frame.
   * The dummy arguments iyear, month and iday are the calender year, month and
   * day of the occultation event.  The dummy arguments ihour, minute and sec
   * are the UTC time.
   * Reference: Astronomical Alamanus, 1993
   *

   * Modified subroutine from Dasheng's code.
   *
   * @param rectt _more_
   * @param utco  _more_
   * @param gmst  _more_
   * @return _more_
   */
  public double togreenw(double rectt, double utco, double gmst) {

    double pi = Math.acos(-1.00);
    //
    // For each occultation ID, its TU and GMST are the same.  However, every
    // occultation event takes place at gmst+uts, uts is progressively increasing
    // with every occultation event.
    double utc = (utco + rectt) * 1.0027379093;
    gmst = gmst + utc;  //in seconds, without eoe correction.
    //  gmst may be a positive number or may be a negative number.
    while (gmst < 0.0) {
      gmst = gmst + 86400.00;
    }

    while (gmst > 86400.00) {
      gmst = gmst - 86400.00;
    }

    // gmst = the Greenwich mean sidereal time.
    // This gmst is without the corrections from the equation of equinoxes.  For
    // GPS/MET applications, the corrections from equation of equinoxes is not
    // necessary because of the accurary needed.
    return gmst * 2.0 * pi / 86400.0;  //!*** This is the THETA in radian.
  }

  /**
   * ----------------------------------------------------------------------
   * file       spin.f
   *

   * This subroutine rotates vector V1 around vector VS
   * at angle A. V2 is the vector after the rotation.
   *

   *

   * parameter  Input parameters:
   * v1   - Vector to be rotated
   * vs   - Vector around which to rotate v1
   * a    - angle of rotation
   * Output parameters:
   * v2   - output vector
   *

   * S.V.Sokolovskiy
   * URL: svn://ursa.cosmic.ucar.edu/trunk/src/roam/spin.f $ $Id: spin.f 10129 2008-07-30 17:10:52Z dhunt $
   * -----------------------------------------------------------------------
   *
   * @param v1 - Vector to be rotated
   * @param vs - Vector around which to rotate v1
   * @param a  - angle of rotation
   * @return _more_
   */

  public double[] spin(double[] v1, double[] vs, double a) {

    //     implicit real*8(a-h,o-z)
    //     dimension v1(3),vs(3),vsn(3),v2(3),v3(3),s(3,3)
    //     Calculation of the unit vector around which
    //     the rotation should be done.
    double[] v2 = new double[3];
    double[] vsn = new double[3];
    double[] v3 = new double[3];

    double vsabs = Math.sqrt(vs[0] * vs[0] + vs[1] * vs[1]
            + vs[2] * vs[2]);

    for (int i = 0; i < 3; i++) {
      vsn[i] = vs[i] / vsabs;
    }

    // Calculation of the rotation matrix.

    double a1 = Math.cos(a);
    double a2 = 1.0 - a1;
    double a3 = Math.sin(a);
    double[][] s = new double[3][3];

    s[0][0] = a2 * vsn[0] * vsn[0] + a1;
    s[0][1] = a2 * vsn[0] * vsn[1] - a3 * vsn[2];
    s[0][2] = a2 * vsn[0] * vsn[2] + a3 * vsn[1];
    s[1][0] = a2 * vsn[1] * vsn[0] + a3 * vsn[2];
    s[1][1] = a2 * vsn[1] * vsn[1] + a1;
    s[1][2] = a2 * vsn[1] * vsn[2] - a3 * vsn[0];
    s[2][0] = a2 * vsn[2] * vsn[0] - a3 * vsn[1];
    s[2][1] = a2 * vsn[2] * vsn[1] + a3 * vsn[0];
    s[2][2] = a2 * vsn[2] * vsn[2] + a1;

    // Calculation of the rotated vector.

    for (int i = 0; i < 3; i++) {
      v3[i] = s[i][0] * v1[0] + s[i][1] * v1[1] + s[i][2] * v1[2];
    }

    System.arraycopy(v3, 0, v2, 0, 3);

    return v2;
  }

  /**
   * _more_
   */
  protected final static double RTD = 180. / Math.PI;

  /**
   * _more_
   */
  protected final static double DTR = Math.PI / 180.;

  /**
   * _more_
   *
   * @param eci    _more_
   * @param julian _more_
   * @return _more_
   */
  public double[] execute(double[] eci, double julian) {
    double Xi = eci[0];
    double Yi = eci[1];
    double Zi = eci[2];

    double c, s;
    double GHA;

    double[] ecef = new double[3];
    //Compute GHAD
    /* System generated locals */
    double d__1, d__2, d__3;

    /* Local variables */
    double tsec, tday, gmst, t, omega, tfrac, tu, dat;

    /*     INPUT IS TIME "secondsSince1970" IN SECONDS AND "TDAY" */
    /*     WHICH IS WHOLE DAYS FROM 1970 JAN 1 0H */
    /*     THE OUTPUT IS GREENWICH HOUR ANGLE IN DEGREES */
    /*     XOMEGA IS ROTATION RATE IN DEGREES/SEC */

    /*     FOR COMPATABILITY */

    tday = (double) ((int) (julian / 86400.));
    tsec = julian - tday * 86400;

    /*     THE NUMBER OF DAYS FROM THE J2000 EPOCH */
    /*     TO 1970 JAN 1 0H UT1 IS -10957.5 */
    t = tday - (float) 10957.5;
    tfrac = tsec / 86400.;
    dat = t;
    tu = dat / 36525.;

    /* Computing 2nd power */
    d__1 = tu;

    /* Computing 3rd power */
    d__2 = tu;
    d__3 = d__2;
    gmst = tu * 8640184.812866 + 24110.54841 + d__1 * d__1 * .093104
            - d__3 * (d__2 * d__2) * 6.2e-6;

    /*     COMPUTE THE EARTH'S ROTATION RATE */
    /* Computing 2nd power */
    d__1 = tu;
    omega = tu * 5.098097e-6 + 86636.55536790872 - d__1 * d__1 * 5.09e-10;

    /*     COMPUTE THE GMST AND GHA */
    //  da is earth nutation - currently unused
    double da = 0.0;
    gmst = gmst + omega * tfrac + da * RTD * 86400. / 360.;
    gmst = gmst % 86400;
    if (gmst < 0.) {
      gmst += 86400.;
    }
    gmst = gmst / 86400. * 360.;
    //ghan = gmst;
    //  returns gha in radians
    gmst = gmst * DTR;
    GHA = gmst;

    //RotateZ
    c = Math.cos(GHA);
    s = Math.sin(GHA);
    double X = c * Xi + s * Yi;
    double Y = -s * Xi + c * Yi;

    //Set outputs
    ecef[0] = X;
    ecef[1] = Y;
    ecef[2] = Zi;

    return ecef;
  }

  /**
   * comparing api to others
   *
   * @param x _more_
   * @param y _more_
   * @param z _more_
   * @param a _more_
   * @param b _more_
   * @return _more_
   */
  public static double[] ECFtoLLA(double x, double y, double z,
                                  double a, double b) {

    double longitude = Math.atan2(y, x);
    double ePrimeSquared = (a * a - b * b) / (b * b);
    double p = Math.sqrt(x * x + y * y);
    double theta = Math.atan((z * a) / (p * b));
    double sineTheta = Math.sin(theta);
    double cosTheta = Math.cos(theta);
    double f = 1 / 298.257223563;
    double e2 = 2 * f - f * f;
    double top = z + ePrimeSquared * b * sineTheta * sineTheta
            * sineTheta;
    double bottom = p - e2 * a * cosTheta * cosTheta * cosTheta;
    double geodeticLat = Math.atan(top / bottom);
    double sineLat = Math.sin(geodeticLat);
    double N = a / Math.sqrt(1 - e2 * sineLat * sineLat);
    double altitude = (p / Math.cos(geodeticLat)) - N;

    // maintain longitude btw -PI and PI
    if (longitude > Math.PI) {
      longitude -= 2 * Math.PI;

    } else if (longitude < -Math.PI) {
      longitude += 2 * Math.PI;
    }

    return new double[]{geodeticLat, longitude, altitude};
  }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.ui.gis.worldmap;

import ucar.nc2.ui.gis.AbstractGisFeature;
import ucar.nc2.ui.gis.GisFeatureRenderer;
import ucar.nc2.ui.gis.GisPart;
import ucar.nc2.ui.util.Resource;
import ucar.unidata.geoloc.LatLonPointImpl;
import ucar.unidata.geoloc.LatLonRect;
import ucar.unidata.geoloc.ProjectionImpl;
import ucar.unidata.geoloc.projection.LatLonProjection;

import java.io.EOFException;
import java.util.ArrayList;

/** A simple "default" world map Renderer.
 * @author John Caron
 */

public class WorldMap extends GisFeatureRenderer {
  private static final double SECS_PER_DEG = 3600.0;
  private static ArrayList gisList;
  private static ArrayList partList = null;
  private static int total_pts = 0;
  private static WorldMapFeature worldMapFeature = null;

  private static boolean debug = false, debugTime = false;

      // read in lat/lon points one time for this class
  private static boolean readWorldMap () {
    java.io.DataInputStream dis;
    String filename = "/resources/nj22/ui/maps/cil_100km.mapr";
    java.io.InputStream is = null;
    long secs = System.currentTimeMillis();

    is = Resource.getFileResource( filename);

    if (is == null) {
        System.err.println("WorldMap read failed on resource " + filename);
        return false;
    } else {
        dis = new java.io.DataInputStream(is);
    }

      // need an AbstractGisFeature for visad
    worldMapFeature = new WorldMapFeature();
      // need an ArrayList of AbstractGisFeature's for GisFeatureRenderer
    gisList = new ArrayList();
    gisList.add(worldMapFeature);

    partList = new ArrayList();

    while (true) {
      try {
        int npts = dis.readInt();

        dis.readInt();  // minx -- not used.
        dis.readInt();  // maxx -- not used.
        dis.readInt();  // miny -- not used.
        dis.readInt();  // maxy -- not used.

        MapRun run = new MapRun(npts);
        for (int i=0; i<npts; i++) {
          run.wx[i] = ((double) dis.readInt()) / SECS_PER_DEG;
          run.wy[i] = ((double) dis.readInt()) / SECS_PER_DEG;
        }
        partList.add( run);
        total_pts += npts;

      } catch (EOFException ex) {
        break;
      } catch (Exception ex) {
        System.err.println("WorldMap exception " + ex);
        break;
      }
    }

    try { is.close();
    } catch (Exception ex) { }

    if (debugTime) {
      secs = System.currentTimeMillis() - secs;
      System.out.println("WorldMap read file: " + secs*.001 + " seconds");
    }
    return true;
  }

  private static class WorldMapFeature extends AbstractGisFeature {
    public java.awt.geom.Rectangle2D getBounds2D() { return null; }
    public int getNumPoints() { return total_pts; }
    public int getNumParts() { return partList.size(); }
    public java.util.Iterator getGisParts() { return partList.iterator(); }
  }

  private static class MapRun implements GisPart {
    int npts;
    double [] wx;  // lat/lon coords
    double [] wy;

    // constructor
    MapRun ( int npts) {
        this.npts = npts;

        wx = new double[npts];
        wy = new double[npts];
    }

      // implement GisPart
    public int getNumPoints() { return npts; }
    public double[] getX() { return wx; }
    public double[] getY() { return wy; }
  }

  public static AbstractGisFeature getWorldMap() {
    if (worldMapFeature == null)  // read in world map points the first time
      readWorldMap();
    return worldMapFeature;
  }

  /////////////////////////////////////////////////
  private ProjectionImpl dataProject = new LatLonProjection ("Cylindrical Equidistant");

  // constructor
  public WorldMap () {
    if (partList == null)  // read in world map points the first time
      readWorldMap();

    dataProject = new LatLonProjection ("Cylindrical Equidistant");
  }

  private LatLonRect defaultLLBB = new LatLonRect( new LatLonPointImpl(-180., -90.), 360., 180.);
  public LatLonRect getPreferredArea() { return defaultLLBB; }
  protected java.util.List getFeatures() { return gisList; }
  protected ProjectionImpl getDataProjection() { return dataProject; }

    /**
     * Draws the World Map.
     * @param g the Graphics2D context on which to draw
     * @param pixelAT   use this AffineTransform to draw in constant-pixel coordinates. ignored here.
     *
  public void draw(java.awt.Graphics2D g, java.awt.geom.AffineTransform pixelAT) {
    long  secs = System.currentTimeMillis();
    Rectangle2D clipRect = (Rectangle2D) g.getClip();

    if (debug) System.out.println("WorldMap draw clip "+ clipRect + " "+ g.getClipBounds());

    if ((null == project) || (null == path))
      return;
    g.setColor(color);
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
    g.setStroke(new java.awt.BasicStroke(0.0f));

      // special processing for latlon projection
    /* if (project instanceof LatLonProjection) {
      LatLonProjection llproj = (LatLonProjection) project;
      g.draw( new latlonShape(llproj));
    } else *
      g.draw(path);

    if (debugTime) {
      secs = System.currentTimeMillis() - secs;
      System.out.println("WorldMap redraw: " + secs*.001 + " seconds");
    }
  }

  public void setProjection(ProjectionImpl project) {
    this.project = project;
    long secs = System.currentTimeMillis();

    // recalculate Shape for this projection
    path = new GeneralPath(GeneralPath.WIND_EVEN_ODD, total_pts);

    for (int i=0; i<runs.size(); i++) {
      MAPrun run = (MAPrun) runs.get(i);

      for (int pt=0; pt<run.npts; pt++) {
        workL.setLatitude(run.wy[pt]);
        workL.setLongitude(run.wx[pt]);
        ProjectionPoint ppt = project.latLonToProj( workL);

        if ((pt == 0) || project.crossSeam(ppt, lastW))  // deal with wrapping around the world
          path.moveTo((float)ppt.getX(), (float)ppt.getY());
        else
          path.lineTo((float)ppt.getX(), (float)ppt.getY());

        lastW.setLocation(ppt.getX(), ppt.getY());
      }
    }
    path.closePath();

    if (debugTime) {
      secs = System.currentTimeMillis() - secs;
      System.out.println("WorldMap recompute Projection: " + secs*.001 + " seconds");
    }
  }

/*  private class latlonShape implements Shape {
    private double centerLon;

    latlonShape(LatLonProjection ll) {
      centerLon = ll.getCenterLon();
      //System.out.println("  latlonShape centerLon: "+ centerLon);
    }

    public Rectangle getBounds() { return new Rectangle((int)(centerLon-360), -90, 720, 180); }
    public Rectangle2D getBounds2D(){ return new Rectangle2D.Double(centerLon-360, -90, 720, 180); }
    public boolean contains(double x, double y) { return false; }
    public boolean contains(Point2D p) { return false; }
    public boolean intersects(double x, double y, double w, double h) { return false; }
    public boolean intersects(Rectangle2D r) { return false; }
    public boolean contains(double x, double y, double w, double h) { return false; }
    public boolean contains(Rectangle2D r) { return false; }
    public PathIterator getPathIterator(AffineTransform at) { return new latlonPI(at); }
    public PathIterator getPathIterator(AffineTransform at, double flatness) { return new latlonPI(at); }

    private class latlonPI implements PathIterator {
      int nruns = runs.size();
      int runno = 0;
      MAPrun run = (MAPrun) runs.get(runno);
      int pt = 0;
      boolean newrun = true;
      boolean done = false;
      AffineTransform at;

        // minimize GC
      Point2D src = new Point2D.Double();
      Point2D dst = new Point2D.Double();

      latlonPI(AffineTransform at) {
        this.at = at;
        calcPt();       // get first point ready
      }

      public int getWindingRule() { return WIND_EVEN_ODD; }
      public boolean isDone() { return done; }
      public void next() {
        if (pt < run.npts-1) {
          pt++;
          newrun = false;
        } else if (runno < nruns -1) {
          runno++;
          run = (MAPrun) runs.get(runno);
          pt = 0;
          newrun = true;
        } else {
          done = true;
          return;
        }
        calcPt();
      }

      private void calcPt() {
          // convert to cyl.eq.
        workL.setLatitude(run.wy[pt]);
        workL.setLongitude(run.wx[pt]);
        ProjectionPoint ppt = project.latLonToProj( workL);

            // deal with wrapping around the world
        if (!done && !newrun && project.crossSeam(ppt, lastW))
          newrun = true;
        lastW.setLocation(ppt.getX(), ppt.getY());

          // affine transform if needed
        if (at != null) {
          src.setLocation( ppt.getX(), ppt.getY());
          at.transform(src, dst);
        } else
          dst.setLocation( ppt.getX(), ppt.getY());
      }

      public int currentSegment(float[] coords) {
        coords[0] = (float) dst.getX();
        coords[1] = (float) dst.getY();
        return newrun ? SEG_MOVETO : SEG_LINETO;
      }
      public int currentSegment(double[] coords) {
        coords[0] = dst.getX();
        coords[1] = dst.getY();
        return newrun ? SEG_MOVETO : SEG_LINETO;
      }
    } //inner class latlonPI
  } // inner class latlonShape  */
}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.wmo;

import ucar.unidata.util.StringUtil2;

/**
 * Uitlities common to WMO table parsing
 *
 * @author caron
 * @since 3/1/13
 */
public class Util {

  /**
   * Clean up strings to be used for unit string
   * @param unit original
   * @return cleaned up
   */
  public static String cleanUnit(String unit) {
    if (unit == null) return null;
    // These specific words become dimensionless
    if (unit.equalsIgnoreCase("Proportion") || unit.equalsIgnoreCase("Numeric"))
      unit = "";
    // So does '-'
    else if (unit.equalsIgnoreCase("-")) {
      unit = "";
    // Make sure degree(s) true gets concatenated with '_'
    } else if (unit.startsWith("degree") && unit.endsWith("true")) {
      unit = unit.replace(' ', '_');
    // And only do the rest of the conversion if it's not a "* table *" entry
    } else if (!unit.contains(" table ")) {
      if (unit.startsWith("/")) unit = "1" + unit;
      unit = unit.trim();
      unit = StringUtil2.remove(unit, "**");
      StringBuilder sb = new StringBuilder(unit);
      StringUtil2.remove(sb, "^[]");
      StringUtil2.replace(sb, ' ', ".");
      StringUtil2.replace(sb, '*', ".");
      unit = sb.toString();
    }
    return unit;
  }

  /**
   * Clean up strings to be used in Netcdf Object names
   * @param name original name
   * @return cleaned up name
   */
  public static String cleanName(String name) {
    if (name == null) return null;
    int pos = name.indexOf("(see");
    if (pos < 0) pos = name.indexOf("(See");
    if (pos > 0) name = name.substring(0,pos);

    name = StringUtil2.replace(name, '/', "-");
    StringBuilder sb = new StringBuilder(name);
    StringUtil2.replace(sb, '+', "plus");
    StringUtil2.remove(sb, ".;,=[]()/*\"");
    return StringUtil2.collapseWhitespace(sb.toString().trim());
  }

  public static String cleanupDescription(String desc) {
    if (desc == null) return null;
    int pos = desc.indexOf("(see");
    if (pos > 0) desc = desc.substring(0,pos);

    StringBuilder sb = new StringBuilder(desc.trim());
    StringUtil2.remove(sb, ".;,=[]()/*");
    return sb.toString().trim();
  }

  /**
   * Compare two names from tables, trying to ignore superfulous characters.
   * @return true if these are equivilent
   */
  public static boolean equivilantName(String name1, String name2) {
    if (name1 == null || name2 == null) return (name1 == name2);
    String name1clean = cleanName(name1).toLowerCase();
    String name2clean = cleanName(name2).toLowerCase();
    if (name1.equals(name2)) return true;

    StringBuilder sb1 = new StringBuilder(name1clean);
    StringUtil2.remove(sb1, " -’'");
    StringBuilder sb2 = new StringBuilder(name2clean);
    StringUtil2.remove(sb2, " -’'");
    return sb1.toString().equals(sb2.toString());
  }

  /** The given unit is "unitless". */
  public static boolean isUnitless(String unit) {
    if (unit == null) return true;
    String munge = unit.toLowerCase().trim();
    munge = StringUtil2.remove(munge, '(');
    return munge.length()  == 0 ||
        munge.startsWith("numeric") || munge.startsWith("non-dim") || munge.startsWith("see") ||
        munge.startsWith("proportion") || munge.startsWith("code") || munge.startsWith("0=") ||
        munge.equals("1") ;
  }

  public static void main(String[] args) {
    System.out.printf("clean '/s' = %s%n", cleanUnit("/s"));
  }
}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package thredds.server.config;

import org.springframework.stereotype.Component;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;

@Component
public class HtmlConfigBean {

  private String webappName;
  private String webappVersion;
  private String webappVersionBuildDate;
  private String webappContextPath;

  private String webappUrl;
  private String webappDocsUrl;
  private String webappLogoUrl;
  private String webappLogoAlt;

  private String pageCssUrl;
  private String catalogCssUrl;
  private String datasetCssUrl;
  private String openDapCssUrl;
  private String googleTrackingCode;

  private String folderIconUrl;
  private String folderIconAlt;
  private String datasetIconUrl;
  private String datasetIconAlt;

  private String installName;
  private String installUrl;
  private String installLogoUrl;
  private String installLogoAlt;

  private String hostInstName;
  private String hostInstUrl;
  private String hostInstLogoUrl;
  private String hostInstLogoAlt;

  private boolean useRemoteCatalogService;

  private boolean generateDatasetJsonLD;

  public HtmlConfigBean() {
  }

  public void init(String webappName,
                   String webappVersion,
                   String webappVersionBuildDate,
                   String webappContextPath) {
    this.webappName = webappName;
    this.webappVersion = webappVersion;
    this.webappVersionBuildDate = webappVersionBuildDate;
    this.webappContextPath = webappContextPath;
  }

  public String getWebappName() {
    return this.webappName;
  }

  public String getWebappVersion() {
    return webappVersion;
  }

  public String getWebappVersionBuildDate() {
    return webappVersionBuildDate;
  }

  public String getWebappContextPath() {
    return webappContextPath;
  }

  /**
   * Return the URL to the main web page for the webapp.
   *

   *
Note: A relative URL is considered relative to the webapp context path.
   * That is, it is relative to "http://server:port/thredds/".
   *
   * @return the URL to the main web page for the webapp.
   */
  public String getWebappUrl() {
    return webappUrl;
  }

  public void setWebappUrl(String webappUrl) {
    this.webappUrl = webappUrl;
  }

  /**
   * Return the URL to the webapp documentation page.
   *

   *
Note: A relative URL is considered relative to the webapp context path.
   * That is, it is relative to "http://server:port/thredds/".
   *
   * @return the URL to the webapp documentation page.
   */
  public String getWebappDocsUrl() {
    return webappDocsUrl;
  }

  public void setWebappDocsUrl(String webappDocsUrl) {
    this.webappDocsUrl = webappDocsUrl;
  }

  /**
   * Return the Url to the logo file for the webapp.
   *

   *
Note: A relative URL is considered relative to the webapp context path.
   * That is, it is relative to "http://server:port/thredds/".
   *
   * @return the path to the logo file for the webapp.
   */
  public String getWebappLogoUrl() {
    return webappLogoUrl;
  }

  public void setWebappLogoUrl(String webappLogoUrl) {
    this.webappLogoUrl = webappLogoUrl;
  }

  /**
   * Return the alternate text for the webapp logo.
   *
   * @return the alternate text for the webapp logo.
   */
  public String getWebappLogoAlt() {
    return webappLogoAlt;
  }

  public void setWebappLogoAlt(String webappLogoAlt) {
    this.webappLogoAlt = webappLogoAlt;
  }

  /**
   * Return the URL to the CSS file used for all non-catalog HTML pages.
   *

   *
Note: A relative URL is considered relative to the webapp context path.
   * That is, it is relative to "http://server:port/thredds/".
   *
   * @return the Url to the CSS file used for all non-catalog HTML pages.
   */
  public String getPageCssUrl() {
    return pageCssUrl;
  }

  public void setPageCssUrl(String pageCssUrl) {
    this.pageCssUrl = pageCssUrl;
  }

  /**
   * Return the URL to the CSS file used for the OPeNDAP access pages.
   *

   *
Note: A relative URL is considered relative to the webapp context path.
   * That is, it is relative to "http://server:port/thredds/".
   *
   * @return the URL to the CSS file used for the OPeNDAP access pages.
   */
  public String getOpenDapCssUrl() {
    return openDapCssUrl;
  }

  public void setOpenDapCssUrl(String openDapCssUrl) {
    this.openDapCssUrl = openDapCssUrl;
  }

  /**
   * Return the URL to the CSS file used for catalog HTML pages.
   *

   *
Note: A relative URL is considered relative to the webapp context path.
   * That is, it is relative to "http://server:port/thredds/".
   *
   * @return the URL to the CSS file used for catalog HTML pages.
   */
  public String getCatalogCssUrl() {
    return catalogCssUrl;
  }

  public void setCatalogCssUrl(String catalogCssUrl) {
    this.catalogCssUrl = catalogCssUrl;
  }

  /**
   * Return the URL to the CSS file used for catalog HTML pages.
   *

   *
Note: A relative URL is considered relative to the webapp context path.
   * That is, it is relative to "http://server:port/thredds/".
   *
   * @return the URL to the CSS file used for catalog HTML pages.
   */
  public String getDatasetCssUrl() {
    return datasetCssUrl;
  }

  public void setDatasetCssUrl(String datasetCssUrl) {
    this.datasetCssUrl = datasetCssUrl;
  }

  /**
   * Return the google tracking code for google analytics.
   *
   * @return the google tracking code for google analytics.
   */
  public String getGoogleTrackingCode() {
    return googleTrackingCode;
  }

  public void setGoogleTrackingCode(String googleTrackingCode) {
    this.googleTrackingCode = googleTrackingCode;
  }

  /**
   * Return the URL to the icon document used for folders in HTML catalog views.
   *

   *
Note: A relative URL is considered relative to the webapp context path.
   * That is, it is relative to "http://server:port/thredds/".
   *
   * @return the URL to the icon document used for folders in HTML catalog views.
   */
  public String getFolderIconUrl() {
    return folderIconUrl;
  }

  public void setFolderIconUrl(String folderIconUrl) {
    this.folderIconUrl = folderIconUrl;
  }

  public String getFolderIconAlt() {
    return folderIconAlt;
  }

  public void setFolderIconAlt(String folderIconAlt) {
    this.folderIconAlt = folderIconAlt;
  }

  /**
   * Return the URL to the icon document used for datasets in HTML catalog views.
   *

   *
Note: A relative URL is considered relative to the webapp context path.
   * That is, it is relative to "http://server:port/thredds/".
   *
   * @return the URL to the icon document used for datasets in HTML catalog views.
   */
  public String getDatasetIconUrl() {
    return datasetIconUrl;
  }

  public void setDatasetIconUrl(String datasetIconUrl) {
    this.datasetIconUrl = datasetIconUrl;
  }

  public String getDatasetIconAlt() {
    return datasetIconAlt;
  }

  public void setDatasetIconAlt(String datasetIconAlt) {
    this.datasetIconAlt = datasetIconAlt;
  }

  /**
   * Return the name of this TDS installation.
   *
   * @return the name of this TDS installation.
   */
  public String getInstallName() {
    return installName;
  }

  public void setInstallName(String installName) {
    this.installName = installName;
  }

  /**
   * Return the URL to the top level of this TDS installation.
   *

   *
Note: A relative URL is considered relative to the webapp context path.
   * That is, it is relative to "http://server:port/thredds/".
   *
   * @return the URL to the top level of this installation.
   */
  public String getInstallUrl() {
    return installUrl;
  }

  public void setInstallUrl(String installUrl) {
    this.installUrl = installUrl;
  }

  /**
   * Return the path to the logo file for this TDS installation.
   *

   *
Note: A relative URL is considered relative to the webapp context path.
   * That is, it is relative to "http://server:port/thredds/".
   *
   * @return the path to the logo file for this installation of the webapp.
   */
  public String getInstallLogoUrl() {
    return installLogoUrl;
  }

  public void setInstallLogoUrl(String installLogoUrl) {
    this.installLogoUrl = installLogoUrl;
  }

  /**
   * Return the alternate text for the logo for this TDS installation.
   *
   * @return the alternate text for the logo for this installation.
   */
  public String getInstallLogoAlt() {
    return installLogoAlt;
  }

  public void setInstallLogoAlt(String installLogoAlt) {
    this.installLogoAlt = installLogoAlt;
  }

  /**
   * Return the name of the institution hosting this TDS installation.
   *
   * @return the name of the institution hosting this TDS installation.
   */
  public String getHostInstName() {
    return hostInstName;
  }

  public void setHostInstName(String hostInstName) {
    this.hostInstName = hostInstName;
  }

  /**
   * Return the URL to a web page for the institution hosting this installation.
   *

   *
Note: A relative URL is considered relative to the webapp context path.
   * That is, it is relative to "http://server:port/thredds/".
   *
   * @return the URL to a web page for the institution hosting this installation.
   */
  public String getHostInstUrl() {
    return hostInstUrl;
  }

  public void setHostInstUrl(String hostInstUrl) {
    this.hostInstUrl = hostInstUrl;
  }

  /**
   * Return the path to the logo file for the institution hosting this installation.
   *

   *
Note: A relative URL is considered relative to the webapp context path.
   * That is, it is relative to "http://server:port/thredds/".
   *
   * @return the path to the logo file for the institution hosting this installation.
   */
  public String getHostInstLogoUrl() {
    return hostInstLogoUrl;
  }

  public void setHostInstLogoUrl(String hostInstLogoUrl) {
    this.hostInstLogoUrl = hostInstLogoUrl;
  }

  /**
   * Return the alternate text for the logo for the institution hosting this installation.
   *
   * @return the alternate text for the logo for the institution hosting this installation.
   */
  public String getHostInstLogoAlt() {
    return hostInstLogoAlt;
  }

  public void setHostInstLogoAlt(String hostInstLogoAlt) {
    this.hostInstLogoAlt = hostInstLogoAlt;
  }

  /**
   * Return the config option that sets the default of whether or not to use remoteCatalogService
   * for html representation of catalogRef's in client catalogs.
   *
   * @return true: use remoteCatalogService, false: assume catalogRef's point to a TDS, so simply link
   * the html url of the remote catalog.
   */
  public Boolean getUseRemoteCatalogService() {
    return useRemoteCatalogService;
  }

  public void setUseRemoteCatalogService(Boolean remoteCatalogService) {
    this.useRemoteCatalogService = remoteCatalogService;
  }

  /**
   * Return the config option that determines whether or not to DataSet JSON-LD elements are
   * generated in the head of the html for direct datasets.
   *
   * @return true: generate json-ld, false: do not generate json-ld.
   */
  public Boolean getGenerateDatasetJsonLD() { return generateDatasetJsonLD; }

  public void setGenerateDatasetJsonLD(Boolean generateDatasetJsonLD) {
    this.generateDatasetJsonLD = generateDatasetJsonLD;
  }

  /**
   * Return a URL ready to use in a generated HTML page from a URL that
   * is either absolute or relative to the webapp context path. That is,
   * if relative, it is relative to "http://server:port/thredds/".
   *

   *
For simplicity, all relative URLs are converted to URLs that are
   * absolute paths. For instance, "catalog.xml" becomes "/thredds/catalog.xml".
   *
   * @param url the URL to prepare for use in HTML.
   * @return a URL ready to use in a generated HTML page.
   */
  public String prepareUrlStringForHtml(String url) {
    if (url == null)
      return null;
    URI uri;
    try {
      uri = new URI(url);
    } catch (URISyntaxException e) {
      throw new IllegalArgumentException("Given a bad URL [" + url + "].", e);
    }
    if (uri.isAbsolute())
      return uri.toString();
    if (url.startsWith("/"))
      return url;
    return this.getWebappContextPath() + "/" + url;
  }

  public void addHtmlConfigInfoToModel(Map<String, Object> model) {
    model.put("catalogCssUrl", this.getCatalogCssUrl());
    model.put("standardCssUrl", this.getPageCssUrl());
    model.put("openDapCssUrl", this.getOpenDapCssUrl());
    model.put("googleTrackingCode", this.getGoogleTrackingCode());
    model.put("datasetIconAlt", this.getDatasetIconAlt());
    model.put("datasetIconUrl", this.getDatasetIconUrl());
    model.put("folderIconAlt", this.getFolderIconAlt());
    model.put("folderIconUrl", this.getFolderIconUrl());
    model.put("generateDatasetJsonLD", this.getGenerateDatasetJsonLD());

    model.put("hostInstName", this.getHostInstName());
    model.put("hostInstUrl", this.prepareUrlStringForHtml(this.getHostInstUrl()));
    model.put("hostInstLogoUrl", this.prepareUrlStringForHtml(this.getHostInstLogoUrl()));
    model.put("hostInstLogoAlt", this.getHostInstLogoAlt());

    model.put("installationName", this.getInstallName());
    model.put("installationUrl", this.prepareUrlStringForHtml(this.getInstallUrl()));
    model.put("installationLogoUrl", this.prepareUrlStringForHtml(this.getInstallLogoUrl()));
    model.put("installationLogoAlt", this.getInstallLogoAlt());

    model.put("useRemoteCatalogService", this.getUseRemoteCatalogService());

    model.put("webappName", this.getWebappName());
    model.put("webappVersion", this.getWebappVersion());
    model.put("webappVersionBuildDate", this.getWebappVersionBuildDate());
    model.put("webappUrl", this.prepareUrlStringForHtml(this.getWebappUrl()));
    model.put("webappDocsUrl", this.prepareUrlStringForHtml(this.getWebappDocsUrl()));
    model.put("webappLogoUrl", this.prepareUrlStringForHtml(this.getWebappLogoUrl()));
    model.put("webappLogoAlt", this.getWebappLogoAlt());
  }
}


package thredds.catalog.parser.jdom;

import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import thredds.featurecollection.FeatureCollectionConfig;
import thredds.featurecollection.FeatureCollectionType;
import thredds.inventory.CollectionAbstract;

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

/**
 * Read in the <featureCollection> element
 *
 * @author caron
 * @since 11/11/13
 */
public class FeatureCollectionReader {
  static private final Logger logger = LoggerFactory.getLogger(FeatureCollectionReader.class);

    // input is xml file with just the <featureCollection>
  static public FeatureCollectionConfig getConfigFromSnippet(String filename) {

    org.jdom2.Document doc;
    try {
      SAXBuilder builder = new SAXBuilder();
      doc = builder.build(filename);
    } catch (Exception e) {
      System.out.printf("Error parsing featureCollection %s err = %s", filename, e.getMessage());
      return null;
    }

    return FeatureCollectionReader.readFeatureCollection(doc.getRootElement());
  }

  /**
   * Read a catalog and extract a FeatureCollectionConfig from it
   * @param catalogAndPath catalog filename, or catalog#featureName
   * @return FeatureCollectionConfig or null
   */
  static public FeatureCollectionConfig readFeatureCollection(String catalogAndPath) {
    String catFilename;
    String fcName = null;

    int pos = catalogAndPath.indexOf("#");
    if (pos > 0) {
      catFilename = catalogAndPath.substring(0, pos);
      fcName = catalogAndPath.substring(pos+1);
    } else {
      catFilename = catalogAndPath;
    }

    File cat = new File(catFilename);
    org.jdom2.Document doc;
    try {
      SAXBuilder builder = new SAXBuilder();
      doc = builder.build(cat);
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }

    try {
      List<Element> fcElems = new ArrayList<>();
      findFeatureCollection(doc.getRootElement(), fcName, fcElems);
      if (fcElems.size() > 0)
        return readFeatureCollection(fcElems.get(0));

    } catch (IllegalStateException e) {
      e.printStackTrace();
    }

    return null;
  }

  static private void findFeatureCollection(Element parent, String name, List<Element> fcElems) {
    List<Element> elist = parent.getChildren("featureCollection", InvCatalogFactory10.defNS);
    if (name == null)
      fcElems.addAll(elist);
    else {
      for (Element elem : elist) {
        if (name.equals(elem.getAttributeValue("name")))
          fcElems.add(elem);
      }
    }
    for (Element child : parent.getChildren("dataset", InvCatalogFactory10.defNS))
      findFeatureCollection(child, name, fcElems);
  }

  static public FeatureCollectionConfig readFeatureCollection(Element featureCollectionElement) {
    String name = featureCollectionElement.getAttributeValue("name");
    String path = featureCollectionElement.getAttributeValue("path");
    String fcTypeS = featureCollectionElement.getAttributeValue("featureType");

    FeatureCollectionType fcType = FeatureCollectionType.valueOf(fcTypeS);
    if (fcType == null) {
      logger.error( "featureCollection "+name+" must have a valid FeatureCollectionType attribute, found '"+fcTypeS+"'");
      return null;
    }

    // collection element required
    Element collElem = featureCollectionElement.getChild( "collection", InvCatalogFactory10.defNS );
    if (collElem == null) {
      logger.error( "featureCollection "+name+" must have a <collection> element." );
      return null;
    }
    String collectionName = collElem.getAttributeValue("name");
    collectionName = CollectionAbstract.cleanName(collectionName != null ? collectionName : name);

    String spec = collElem.getAttributeValue("spec");
    // String spec = expandAliasForCollectionSpec(collElem.getAttributeValue("spec")); // now done externally
    String timePartition = collElem.getAttributeValue("timePartition");
    String dateFormatMark = collElem.getAttributeValue("dateFormatMark");
    String olderThan = collElem.getAttributeValue("olderThan");
    String rootDir = collElem.getAttributeValue("rootDir");
    String regExp = collElem.getAttributeValue("regExp");
    if (spec == null && rootDir == null) {
      logger.error( "featureCollection "+name+" must have a spec or rootDir attribute." );
      return null;
    }
    Element innerNcml = featureCollectionElement.getChild( "netcdf", InvCatalogFactory10.ncmlNS );
    FeatureCollectionConfig config = new FeatureCollectionConfig(name, path, fcType, spec, collectionName, dateFormatMark, olderThan,
            timePartition, innerNcml);
    config.setFilter(rootDir, regExp);

    // tds and update elements
    Element tdmElem = featureCollectionElement.getChild( "tdm", InvCatalogFactory10.defNS );
    config.tdmConfig = readUpdateElement( tdmElem);
    Element updateElem = featureCollectionElement.getChild( "update", InvCatalogFactory10.defNS );
    config.updateConfig = readUpdateElement( updateElem);

    // protoDataset element
    Element protoElem = featureCollectionElement.getChild( "protoDataset", InvCatalogFactory10.defNS );
    if (protoElem != null) {
      String choice = protoElem.getAttributeValue("choice");
      String change = protoElem.getAttributeValue("change");
      String param = protoElem.getAttributeValue("param");
      Element ncmlElem = protoElem.getChild( "netcdf", InvCatalogFactory10.ncmlNS );
      config.protoConfig = new FeatureCollectionConfig.ProtoConfig(choice, change, param, ncmlElem);
    }

    // fmrcConfig element
    Element fmrcElem = featureCollectionElement.getChild( "fmrcConfig", InvCatalogFactory10.defNS );
    if (fmrcElem != null) {
      String regularize = fmrcElem.getAttributeValue("regularize");
      config.fmrcConfig = new FeatureCollectionConfig.FmrcConfig(regularize);

      String datasetTypes = fmrcElem.getAttributeValue("datasetTypes");
      if (null != datasetTypes)
        config.fmrcConfig.addDatasetType(datasetTypes);

      List<Element> bestElems = fmrcElem.getChildren( "dataset", InvCatalogFactory10.defNS );
      for (Element best : bestElems) {
        String bestName = best.getAttributeValue("name");
        String offs = best.getAttributeValue("offsetsGreaterEqual");
        double off = Double.parseDouble(offs);
        config.fmrcConfig.addBestDataset(bestName, off);
      }
    }

    // pointConfig element optional
    Element pointElem = featureCollectionElement.getChild( "pointConfig", InvCatalogFactory10.defNS );
    if (pointElem != null) {
      String datasetTypes = pointElem.getAttributeValue("datasetTypes");
      if (null != datasetTypes)
        config.pointConfig.addDatasetType(datasetTypes);
    }

    // gribConfig element optional
    Element gribConfig = featureCollectionElement.getChild( "gribConfig", InvCatalogFactory10.defNS );
    if (gribConfig != null) {
      config.gribConfig.configFromXml(gribConfig, InvCatalogFactory10.defNS);
    }

    // done reading - do anything needed
    config.finish();

    return config;
  }

  static private FeatureCollectionConfig.UpdateConfig readUpdateElement(Element updateElem) {
    if (updateElem == null) {
      return new FeatureCollectionConfig.UpdateConfig(); // default
    }

    String startup = updateElem.getAttributeValue("startup");
    String rewrite = updateElem.getAttributeValue("rewrite");
    String recheckAfter = updateElem.getAttributeValue("recheckAfter");
    String rescan = updateElem.getAttributeValue("rescan");
    String trigger = updateElem.getAttributeValue("trigger");
    String deleteAfter = updateElem.getAttributeValue("deleteAfter");

    return new FeatureCollectionConfig.UpdateConfig(startup, rewrite, recheckAfter, rescan, trigger, deleteAfter);
  }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.ui.gis;

import ucar.unidata.geoloc.*;

import java.awt.geom.GeneralPath;
import java.awt.Shape;

/**
 * Abstract class that implements common methods for concrete
 * implementations of GisFeature.
 *
 * @author Russ Rew
 * @author John Caron
 */

public abstract class AbstractGisFeature implements GisFeature {

  // subclasses must implement these methods
  public abstract java.awt.geom.Rectangle2D getBounds2D();  // may be null

  public abstract int getNumPoints();

  public abstract int getNumParts();

  public abstract java.util.Iterator getGisParts();

  /**
   * Convert this GisFeature to a java.awt.Shape, using the default
   * coordinate system, mapping gisFeature(x,y) -> screen(x,y).
   * LOOK STILL HAVE TO crossSeam()
   *
   * @return shape corresponding to this feature.
   */
  public Shape getShape() {
    int npts = getNumPoints();
    GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD, npts);

    java.util.Iterator pi = getGisParts();
    while (pi.hasNext()) {
      GisPart gp = (GisPart) pi.next();
      double[] xx = gp.getX();
      double[] yy = gp.getY();
      int np = gp.getNumPoints();
      if (np > 0)
        path.moveTo((float) xx[0], (float) yy[0]);
      for (int i = 1; i < np; i++) {
        path.lineTo((float) xx[i], (float) yy[i]);
      }
    }
    return path;
  }

  /**
   * Convert this GisFeature to a java.awt.Shape. The data coordinate system
   * is assumed to be (lat, lon), use the projection to transform points, so
   * project.latLonToProj(gisFeature(x,y)) -> screen(x,y).
   *
   * @param displayProject Projection to use to display
   * @return shape corresponding to this feature
   */
  public Shape getProjectedShape(ProjectionImpl displayProject) {
    LatLonPointImpl workL = new LatLonPointImpl();
    ProjectionPointImpl lastW = new ProjectionPointImpl();
    GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD, getNumPoints());

    boolean showPts = ucar.util.prefs.ui.Debug.isSet("projection/showPoints");

    java.util.Iterator pi = getGisParts();
    while (pi.hasNext()) {
      GisPart gp = (GisPart) pi.next();
      double[] xx = gp.getX();
      double[] yy = gp.getY();
      boolean skipPrev = false;
      int count = 0;
      for (int i = 0; i < gp.getNumPoints(); i++) {
        workL.set(yy[i], xx[i]);
        ProjectionPoint pt = displayProject.latLonToProj(workL);

        if (showPts) {
          System.out.println("AbstractGisFeature getProjectedShape 1 " + xx[i] + " " + yy[i] + " === " + pt.getX() + " " + pt.getY());
          if (displayProject.crossSeam(pt, lastW)) System.out.println("***cross seam");
        }

        // deal with possible NaNs
        if (Double.isNaN(pt.getX()) || Double.isNaN(pt.getY())) {
          skipPrev = true;
          continue;
        }

        if ((count == 0) || skipPrev || displayProject.crossSeam(pt, lastW))
          path.moveTo((float) pt.getX(), (float) pt.getY());
        else
          path.lineTo((float) pt.getX(), (float) pt.getY());

        count++;
        skipPrev = false;

        lastW.setLocation(pt);
      }
    }
    return path;
  }

  /**
   * Convert this GisFeature to a java.awt.Shape. The data coordinate system
   * is in the coordinates of dataProject, and the screen is in the coordinates of
   * displayProject. So:
   * displayProject.latLonToProj( dataProject.projToLatLon(gisFeature(x,y))) -> screen(x,y).
   *
   * @param dataProject    data Projection to use.
   * @param displayProject display Projection to use.
   * @return shape corresponding to this feature
   */
  public Shape getProjectedShape(ProjectionImpl dataProject, ProjectionImpl displayProject) {
    ProjectionPointImpl pt1 = new ProjectionPointImpl();
    ProjectionPointImpl lastW = new ProjectionPointImpl();
    GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD, getNumPoints());

    boolean showPts = ucar.util.prefs.ui.Debug.isSet("projection/showPoints");

    java.util.Iterator pi = getGisParts();
    while (pi.hasNext()) {
      GisPart gp = (GisPart) pi.next();
      double[] xx = gp.getX();
      double[] yy = gp.getY();
      boolean skipPrev = false;
      int count = 0;
      for (int i = 0; i < gp.getNumPoints(); i++) {
        pt1.setLocation(xx[i], yy[i]);
        LatLonPoint llpt = dataProject.projToLatLon(pt1);
        ProjectionPoint pt2 = displayProject.latLonToProj(llpt);

        if (showPts) {
          System.out.println("AbstractGisFeature getProjectedShape 2 " + xx[i] + " " + yy[i] + " === " + pt2.getX() + " " + pt2.getY());
          if (displayProject.crossSeam(pt2, lastW)) System.out.println("***cross seam");
        }

        // deal with possible NaNs
         if (Double.isNaN(pt2.getX()) || Double.isNaN(pt2.getY())) {
           skipPrev = true;
           continue;
         }

        if ((count == 0) || skipPrev || displayProject.crossSeam(pt2, lastW))
          path.moveTo((float) pt2.getX(), (float) pt2.getY());
        else
          path.lineTo((float) pt2.getX(), (float) pt2.getY());
        count++;
        skipPrev = false;

        lastW.setLocation(pt2);
      }
    }
    return path;
  }

} // AbstractGisFeature


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.grib.grib2.table;

import ucar.nc2.grib.grib2.Grib2Parameter;

/**
 * Mrms local tables
 *
 * @see "http://www.nssl.noaa.gov/projects/mrms/operational/tables.php"
 */

public class MrmsLocalTables extends LocalTables {

  MrmsLocalTables(Grib2TableConfig config) {
    super(config);
    init();
  }

  @Override
  public String getTablePath(int discipline, int category, int number) {
    if ((category <= 191) && (number <= 191)) {
      return super.getTablePath(discipline, category, number);
    }
    return this.getClass().getName();
  }

  /*
  taken from http://www.nssl.noaa.gov/projects/mrms/operational/tables.php
  */
  private void init() {
    add(209, 2, 0, "LightningDensityNLDN1min", "CG Lightning Density 1-min - NLDN", "flashes/km^2/min", -3, -1);
    add(209, 2, 1, "LightningDensityNLDN5min", "CG Lightning Density 5-min - NLDN", "flashes/km^2/min", -3, -1);
    add(209, 2, 2, "LightningDensityNLDN15min", "CG Lightning Density 15-min - NLDN", "flashes/km^2/min", -3, -1);
    add(209, 2, 3, "LightningDensityNLDN30min", "CG Lightning Density 30-min - NLDN", "flashes/km^2/min", -3, -1);
    add(209, 2, 4, "LightningProbabilityNext30min", "Lightning Probability 0-30 minutes - NLDN", "%", 0, 0);
    add(209, 3, 0, "MergedAzShear0to2kmAGL", "Azimuth Shear 0-2km AGL", "0.001/s", 0, 0);
    add(209, 3, 1, "MergedAzShear3to6kmAGL", "Azimuth Shear 3-6km AGL", "0.001/s", 0, 0);
    add(209, 3, 2, "RotationTrack30min", "Rotation Track 0-2km AGL 30-min", "0.001/s", 0, 0);
    add(209, 3, 3, "RotationTrack60min", "Rotation Track 0-2km AGL 60-min", "0.001/s", 0, 0);
    add(209, 3, 4, "RotationTrack120min", "Rotation Track 0-2km AGL 120-min", "0.001/s", 0, 0);
    add(209, 3, 5, "RotationTrack240min", "Rotation Track 0-2km AGL 240-min", "0.001/s", 0, 0);
    add(209, 3, 6, "RotationTrack360min", "Rotation Track 0-2km AGL 360-min", "0.001/s", 0, 0);
    add(209, 3, 7, "RotationTrack1440min", "Rotation Track 0-2km AGL 1440-min", "0.001/s", 0, 0);
    add(209, 3, 14, "RotationTrackML30min", "Rotation Track 0-3km AGL 30-min", "0.001/s", 0, 0);
    add(209, 3, 15, "RotationTrackML60min", "Rotation Track 0-3km AGL 60-min", "0.001/s", 0, 0);
    add(209, 3, 16, "RotationTrackML120min", "Rotation Track 0-3km AGL 120-min", "0.001/s", 0, 0);
    add(209, 3, 17, "RotationTrackML240min", "Rotation Track 0-3km AGL 240-min", "0.001/s", 0, 0);
    add(209, 3, 18, "RotationTrackML360min", "Rotation Track 0-3km AGL 360-min", "0.001/s", 0, 0);
    add(209, 3, 19, "RotationTrackML1440min", "Rotation Track 0-3km AGL 1440-min", "0.001/s", 0, 0);
    add(209, 3, 26, "SHI", "Severe Hail Index", "index", -3, -1);
    add(209, 3, 27, "POSH", "Prob of Severe Hail", "%", -3, -1);
    add(209, 3, 28, "MESH", "Maximum Estimated Size of Hail (MESH)", "mm", -3, -1);
    add(209, 3, 29, "MESHMax30min", "MESH Hail Swath 30-min", "mm", -3, -1);
    add(209, 3, 30, "MESHMax60min", "MESH Hail Swath 60-min", "mm", -3, -1);
    add(209, 3, 31, "MESHMax120min", "MESH Hail Swath 120-min", "mm", -3, -1);
    add(209, 3, 32, "MESHMax240min", "MESH Hail Swath 240-min", "mm", -3, -1);
    add(209, 3, 33, "MESHMax360min", "MESH Hail Swath 360-min", "mm", -3, -1);
    add(209, 3, 34, "MESHMax1440min", "MESH Hail Swath 1440-min", "mm", -3, -1);
    add(209, 3, 41, "VIL", "Vertically Integrated Liquid", "kg/m^2", -3, -1);
    add(209, 3, 42, "VILDensity", "Vertically Integrated Liquid Density", "g/m^3", -3, -1);
    add(209, 3, 43, "VII", "Vertically Integrated Ice", "kg/m^2", -3, -1);
    add(209, 3, 44, "EchoTop18", "Echo Top - 18 dBZ MSL", "km", -3, -1);
    add(209, 3, 45, "EchoTop30", "Echo Top - 30 dBZ MSL", "km", -3, -1);
    add(209, 3, 46, "EchoTop50", "Echo Top - 50 dBZ MSL", "km", -3, -1);
    add(209, 3, 47, "EchoTop60", "Echo Top - 60 dBZ MSL", "km", -3, -1);
    add(209, 3, 48, "H50AboveM20C", "Thickness [50 dBZ top - (-20C)]", "km", -999, -99);
    add(209, 3, 49, "H50Above0C", "Thickness [50 dBZ top - 0C]", "km", -999, -99);
    add(209, 3, 50, "H60AboveM20C", "Thickness [60 dBZ top - (-20C)]", "km", -999, -99);
    add(209, 3, 51, "H60Above0C", "Thickness [60 dBZ top - 0C]", "km", -999, -99);
    add(209, 3, 52, "Reflectivity0C", "Isothermal Reflectivity at 0C", "dBZ", -999, -99);
    add(209, 3, 53, "ReflectivityM5C", "Isothermal Reflectivity at -5C", "dBZ", -999, -99);
    add(209, 3, 54, "ReflectivityM10C", "Isothermal Reflectivity at -10C", "dBZ", -999, -99);
    add(209, 3, 55, "ReflectivityM15C", "Isothermal Reflectivity at -15C", "dBZ", -999, -99);
    add(209, 3, 56, "ReflectivityM20C", "Isothermal Reflectivity at -20C", "dBZ", -999, -99);
    add(209, 3, 57, "ReflectivityAtLowestAltitude", "ReflectivityAtLowestAltitude", "dBZ", -999, -99);
    add(209, 3, 58, "MergedReflectivityAtLowestAltitude", "Non Quality Controlled Reflectivity At Lowest Altitude", "dBZ", -999,
        -99);
    add(209, 4, 0, "IRband4", "Infrared (E/W blend)", "K", -999, -99);
    add(209, 4, 1, "Visible", "Visible (E/W blend)", "dimensionless", -3, -1);
    add(209, 4, 2, "WaterVapor", "Water Vapor (E/W blend)", "K", -999, -99);
    add(209, 4, 3, "CloudCover", "Cloud Cover", "K", -999, -99);
    add(209, 6, 0, "PrecipFlag", "Surface Precipitation Type (Convective, Stratiform, Tropical, Hail, Snow)", "dimensionless", -3,
        -1);
    add(209, 6, 1, "PrecipRate", "Radar Precipitation Rate", "mm/hr", -3, -1);
    add(209, 6, 2, "RadarOnlyQPE01H", "Radar precipitation accumulation 1-hour", "mm", -3, -1);
    add(209, 6, 3, "RadarOnlyQPE03H", "Radar precipitation accumulation 3-hour", "mm", -3, -1);
    add(209, 6, 4, "RadarOnlyQPE06H", "Radar precipitation accumulation 6-hour", "mm", -3, -1);
    add(209, 6, 5, "RadarOnlyQPE12H", "Radar precipitation accumulation 12-hour", "mm", -3, -1);
    add(209, 6, 6, "RadarOnlyQPE24H", "Radar precipitation accumulation 24-hour", "mm", -3, -1);
    add(209, 6, 7, "RadarOnlyQPE48H", "Radar precipitation accumulation 48-hour", "mm", -3, -1);
    add(209, 6, 8, "RadarOnlyQPE72H", "Radar precipitation accumulation 72-hour", "mm", -3, -1);
    add(209, 6, 9, "GaugeCorrQPE01H", "Local gauge bias corrected radar precipitation accumulation 1-hour", "mm", -3, -1);
    add(209, 6, 10, "GaugeCorrQPE03H", "Local gauge bias corrected radar precipitation accumulation 3-hour", "mm", -3, -1);
    add(209, 6, 11, "GaugeCorrQPE06H", "Local gauge bias corrected radar precipitation accumulation 6-hour", "mm", -3, -1);
    add(209, 6, 12, "GaugeCorrQPE12H", "Local gauge bias corrected radar precipitation accumulation 12-hour", "mm", -3, -1);
    add(209, 6, 13, "GaugeCorrQPE24H", "Local gauge bias corrected radar precipitation accumulation 24-hour", "mm", -3, -1);
    add(209, 6, 14, "GaugeCorrQPE48H", "Local gauge bias corrected radar precipitation accumulation 48-hour", "mm", -3, -1);
    add(209, 6, 15, "GaugeCorrQPE72H", "Local gauge bias corrected radar precipitation accumulation 72-hour", "mm", -3, -1);
    add(209, 6, 16, "GaugeOnlyQPE01H", "Gauge only precipitation accumulation 1-hour", "mm", -3, -1);
    add(209, 6, 17, "GaugeOnlyQPE03H", "Gauge only precipitation accumulation 3-hour", "mm", -3, -1);
    add(209, 6, 18, "GaugeOnlyQPE06H", "Gauge only precipitation accumulation 6-hour", "mm", -3, -1);
    add(209, 6, 19, "GaugeOnlyQPE12H", "Gauge only precipitation accumulation 12-hour", "mm", -3, -1);
    add(209, 6, 20, "GaugeOnlyQPE24H", "Gauge only precipitation accumulation 24-hour", "mm", -3, -1);
    add(209, 6, 21, "GaugeOnlyQPE48H", "Gauge only precipitation accumulation 48-hour", "mm", -3, -1);
    add(209, 6, 22, "GaugeOnlyQPE72H", "Gauge only precipitation accumulation 72-hour", "mm", -3, -1);
    add(209, 6, 23, "MountainMapperQPE01H", "Mountain Mapper precipitation accumulation 1-hour", "mm", -3, -1);
    add(209, 6, 24, "MountainMapperQPE03H", "Mountain Mapper precipitation accumulation 3-hour", "mm", -3, -1);
    add(209, 6, 25, "MountainMapperQPE06H", "Mountain Mapper precipitation accumulation 6-hour", "mm", -3, -1);
    add(209, 6, 26, "MountainMapperQPE12H", "Mountain Mapper precipitation accumulation 12-hour", "mm", -3, -1);
    add(209, 6, 27, "MountainMapperQPE24H", "Mountain Mapper precipitation accumulation 24-hour", "mm", -3, -1);
    add(209, 6, 28, "MountainMapperQPE48H", "Mountain Mapper precipitation accumulation 48-hour", "mm", -3, -1);
    add(209, 6, 29, "MountainMapperQPE72H", "Mountain Mapper precipitation accumulation 72-hour", "mm", -3, -1);
    add(209, 7, 0, "ModelSurfaceTemp", "Model Surface temperature [RAP 13km]", "C", -999, -99);
    add(209, 7, 1, "ModelWetBulbTemp", "Model Surface wet bulb temperature [RAP 13km]", "C", -999, -99);
    add(209, 7, 2, "WarmRainProbability", "Probability of warm rain [RAP 13km derived]", "%", -3, -1);
    add(209, 7, 3, "ModelHeight0C", "Model Freezing Level Height [RAP 13km] MSL", "m", -3, -1);
    add(209, 7, 4, "BrightBandTopHeight", "Brightband Top Radar [RAP 13km derived] AGL", "m", -3, -1);
    add(209, 7, 5, "BrightBandBottomHeight", "Brightband Bottom Radar [RAP 13km derived] AGL", "m", -3, -1);
    add(209, 8, 0, "RadarQualityIndex", "Radar Quality Index", "dimensionless", -3, -1);
    add(209, 8, 1, "GaugeInflIndex01H", "Gauge Influence Index for 1-hour QPE", "dimensionless", -3, -1);
    add(209, 8, 2, "GaugeInflIndex03H", "Gauge Influence Index for 3-hour QPE", "dimensionless", -3, -1);
    add(209, 8, 3, "GaugeInflIndex06H", "Gauge Influence Index for 6-hour QPE", "dimensionless", -3, -1);
    add(209, 8, 4, "GaugeInflIndex12H", "Gauge Influence Index for 12-hour QPE", "dimensionless", -3, -1);
    add(209, 8, 5, "GaugeInflIndex24H", "Gauge Influence Index for 24-hour QPE", "dimensionless", -3, -1);
    add(209, 8, 6, "GaugeInflIndex48H", "Gauge Influence Index for 48-hour QPE", "dimensionless", -3, -1);
    add(209, 8, 7, "GaugeInflIndex72H", "Gauge Influence Index for 72-hour QPE", "dimensionless", -3, -1);
    add(209, 8, 8, "SeamlessHSR", "Seamless Hybrid Scan Reflectivity with VPR correction", "dBZ", -999, -99);
    add(209, 8, 9, "SeamlessHSRHeight", "Height of Seamless Hybrid Scan Reflectivity AGL", "km", -3, -1);
    add(209, 9, 0, "ConusMergedReflectivityQC", "WSR-88D 3D Reflectivity Mosaic - 33 CAPPIS (500-19000m)", "dBZ", -999, -99);
    add(209, 9, 1, "ConusPlusMergedReflectivityQC", "All Radar 3D Reflectivity Mosaic - 33 CAPPIS (500-19000m)", "dBZ", -999, -99);
    add(209, 10, 0, "MergedReflectivityQCComposite", "Composite Reflectivity Mosaic (optimal method)", "dBZ", -999, -99);
    add(209, 10, 1, "HeightCompositeReflectivity", "Height of Composite Reflectivity Mosaic (optimal method) MSL", "m", -3, -1);
    add(209, 10, 2, "LowLevelCompositeReflectivity", "Low-Level Composite Reflectivity Mosaic (0-4km)", "dBZ", -999, -99);
    add(209, 10, 3, "HeightLowLevelCompositeReflectivity", "Height of Low-Level Composite Reflectivity Mosaic (0-4km) MSL", "m", -3,
        -1);
    add(209, 10, 4, "LayerCompositeReflectivity_Low", "Layer Composite Reflectivity Mosaic 0-24kft (low altitude)", "dBZ", -999,
        -99);
    add(209, 10, 5, "LayerCompositeReflectivity_High", "Layer Composite Reflectivity Mosaic 24-60 kft (highest altitude)", "dBZ",
        -999, -99);
    add(209, 10, 6, "LayerCompositeReflectivity_Super", "Layer Composite Reflectivity Mosaic 33-60 kft (super high altitude)",
        "dBZ", -999, -99);
    add(209, 10, 7, "ReflectivityCompositeHourlyMax", "Composite Reflectivity Hourly Maximum", "dBZ", -999, -99);
    add(209, 10, 8, "ReflectivityMaxAboveM10C", "Maximum Reflectivity at -10 deg C height and above", "dBZ", -999, -99);
    add(209, 11, 0, "MergedBaseReflectivityQC", "Mosaic Base Reflectivity (optimal method)", "dBZ", -999, -99);
    add(209, 11, 1, "MergedReflectivityComposite", "UnQc'd Composite Reflectivity Mosaic (max ref)", "dBZ", -999, -99);
    add(209, 11, 2, "MergedReflectivityQComposite", "Composite Reflectivity Mosaic (max ref)", "dBZ", -999, -99);
  }

  private void add(int discipline, int category, int number, String name, String desc, String unit, float fill, float missing) {
    local.put(makeParamId(discipline, category, number),
        new Grib2Parameter(discipline, category, number, name, unit, null, desc, fill, missing));
  }

}


/* Copyright 2012, UCAR/Unidata.
   See the LICENSE file for more information. */

package dap4.dap4lib.netcdf;

import dap4.core.dmr.*;
import dap4.core.util.DapSort;
import dap4.core.util.DapUtil;

/**
 * Note that ideally, this info should be part of the
 * Nc4DMR classes, but that would require multiple inheritance.
 * Hence, we isolate that info here and add it to the instances
 * via annotation
 */

abstract public class Nc4Notes
{
    //////////////////////////////////////////////////
    // Constants

    // Mnemonics
    static public final int NOGROUP = -1;
    static public final int NOID = -1;
    static public final int NOFIELDID = -1;

    //////////////////////////////////////////////////
    // Use a factory so we can debug constructor calls

    static Notes
    factory(NoteSort ns, int g, int id, Nc4DSP dsp)
    {
        Notes note = null;
        switch (ns) {
        case TYPE:
            note = new TypeNotes(g, id, dsp);
            break;
        case VAR:
            note = new VarNotes(g, id, dsp);
            break;
        case DIM:
            note = new DimNotes(g, id, dsp);
            break;
        case GROUP:
            note = new GroupNotes(g, id, dsp);
            break;
        }
        return note;
    }

    //////////////////////////////////////////////////
    //Manage the compound id for variables

    static public long
    getVarId(VarNotes note)
    {
        return getVarId(note.gid, note.id, note.getFieldIndex());
    }

    static public long
    getVarId(int gid, int varid, int ifid)
    {
        long gv = ((long) gid) << 32;
        assert varid < 0x100000;
        gv = gv | ((long) varid) << 20;
        long fid = (long) ifid;
        if(fid >= 0)
            gv |= fid;
        return gv;
    }

    //////////////////////////////////////////////////
    // Type Decls

    static public enum NoteSort
    {
        TYPE, VAR, GROUP, DIM;
    }

    static public class Notes implements Cloneable
    {
        Nc4DSP dsp; // Need a place to store global state
        NoteSort sort;
        int gid;
        int id;
        DapNode node = null;
        protected String name = null;
        protected Notes parent = null;
        protected TypeNotes basetype = null;
        protected long offset = 0;
        // For most types, there is only one size,
        // but for vlen/sequence, there are two sizes:
        //  (1) the record size and (2) the instance size (= |vlen_t|);
        protected long size = 0;
        protected long recordsize = 0;

        protected Notes(NoteSort sort, int gid, int id, Nc4DSP dsp)
        {
            this.sort = sort;
            this.dsp = dsp;
            this.gid = gid;
            this.id = id;
        }

        public NoteSort getSort()
        {
            return this.sort;
        }

        public Notes setName(String name)
        {
            this.name = name;
            return this;
        }

        public Notes set(DapNode node)
        {
            this.node = node;
            if(this.name == null) setName(node.getShortName());
            return this;
        }

        public DapNode get()
        {
            return this.node;
        }

        public Notes setContainer(Notes parent)
        {
            this.parent = parent;
            return this;
        }

        public Notes getContainer()
        {
            return this.parent;
        }

        public long getOffset()
        {
            return this.offset;
        }

        public Notes setOffset(long offset)
        {
            this.offset = offset;
            return this;
        }

        public long getSize()
        {
            return this.size;
        }

        public Notes setSize(long size)
        {
            this.size = size;
            return this;
        }

        public long getRecordSize()
        {
            return this.recordsize;
        }

        public Notes setRecordSize(long size)
        {
            this.recordsize = size;
            return this;
        }

        public Notes setBaseType(TypeNotes t)
        {
            this.basetype = t;
            return this;
        }

        public TypeNotes getBaseType()
        {
            return this.basetype;
        }

        DapGroup group()
        {
            GroupNotes g = (GroupNotes) dsp.find(gid, NoteSort.GROUP);
            return (g == null ? null : g.get());
        }

        public String toString()
        {
            StringBuilder buf = new StringBuilder();
            buf.append(this.getClass().getName());
            buf.append("{");
            if(name != null) {
                buf.append("name=");
                buf.append(name);
            }
            buf.append(" node=");
            buf.append(this.node != null ? this.node.getShortName() : "null");
            if(this.basetype != null)      {
                buf.append(" type=");
                buf.append(this.node.getShortName());
            }
            buf.append("}");
            return buf.toString();
        }
    }

    static public class GroupNotes extends Notes
    {
        protected GroupNotes(int p, int g, Nc4DSP dsp)
        {
            super(NoteSort.GROUP, p, g, dsp);
        }

        public DapGroup get()
        {
            return (DapGroup) super.get();
        }

        public GroupNotes set(DapNode node)
        {
            return (GroupNotes) super.set(node);
        }

    }

    static public class DimNotes extends Notes
    {
        protected DimNotes(int g, int id, Nc4DSP dsp)
        {
            super(NoteSort.DIM, g, id, dsp);
        }

        public DapDimension get()
        {
            return (DapDimension) super.get();
        }

        public DimNotes set(DapNode node)
        {
            return (DimNotes) super.set(node);
        }

    }

    static public class TypeNotes extends Notes
    {
        public int enumbase = -1;
        public boolean isvlen = false;

        protected TypeNotes(int g, int id, Nc4DSP dsp)
        {
            super(NoteSort.TYPE, g, id, dsp);
        }

        public DapType getType()
        {
            DapSort sort = this.node.getSort();
            switch (sort) {
            case ATOMICTYPE:
            case STRUCTURE:
            case SEQUENCE:
                return (DapType) super.get();
            case ENUMERATION:
                return (DapEnumeration) super.get();
            case VARIABLE:
                return ((DapVariable) super.get()).getBaseType();
            default:
                break;
            }
            return null;
        }

        public TypeNotes setOpaque(long len)
        {
            super.setSize(len);
            return this;
        }

        public TypeNotes setEnumBaseType(int bt)
        {
            this.enumbase = bt;
            TypeNotes btt = (TypeNotes)this.dsp.find(bt,NoteSort.TYPE);
            setSize(btt.getSize());
            return this;
        }

        public boolean isOpaque()
        {
            return getType().getTypeSort().isOpaqueType();
        }

        public boolean isEnum()
        {
            return getType().getTypeSort().isEnumType();
        }

        public boolean isSeq()
        {
            return getType().getTypeSort().isSeqType();
        }

        public boolean isCompound()
        {
            return getType().getTypeSort().isCompoundType();
        }

        public boolean isVlen()
        {
            return this.isvlen;
        }

        public TypeNotes markVlen()
        {
            this.isvlen = true;
            return this;
        }

        public DapType get()
        {
            return (DapType) super.get();
        }

        public TypeNotes set(DapNode node)
        {
            return (TypeNotes) super.set(node);
        }

    }

    static public class VarNotes extends Notes
    {
        protected VarNotes(int g, int v, Nc4DSP dsp)
        {
            super(NoteSort.VAR, g, v, dsp);
        }

        public VarNotes setBaseType(TypeNotes ti)
        {
            return (VarNotes) super.setBaseType(ti);
        }

        public DapVariable get()
        {
            return (DapVariable) super.get();
        }

        public VarNotes set(DapNode node)
        {
            return (VarNotes) super.set(node);
        }

        public int getFieldIndex()
        {
            assert this.get() != null;
            return this.get().getFieldIndex();
        }

        @Override
        public long getSize()
        {
            return this.getBaseType().getSize() * DapUtil.dimProduct(get().getDimensions());
        }

    }
}