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

package ucar.unidata.util;

/**
 * A parameter has a name and a value that is String, a double, or an array of doubles.
 * A substitute for ucar.nc2.Attribute, to prevent dependencies of the ucar.unidata packages on ucar.nc2.
 *
 * @author caron
 */

public class Parameter implements java.io.Serializable {

  private String name;
  private String valueS;
  private double[] valueD;
  private boolean isString;

  /**
   * Get the name of this Parameter.
   *
   * @return name
   */
  public String getName() {
    return name;
  }

  /**
   * True if value is a String.
   *
   * @return if its String valued
   */
  public boolean isString() {
    return isString;
  }

  /**
   * Retrieve String value; only call if isString() is true.
   *
   * @return String if this is a String valued attribute, else null.
   */
  public String getStringValue() {
    if (valueS == null) {
      StringBuilder sbuff = new StringBuilder();
      for (double v : valueD) {
        sbuff.append(v).append(" ");
      }
      valueS = sbuff.toString();
    }
    return valueS;
  }

  /**
   * Retrieve numeric value, use if isString() is false.
   * Equivalent to getNumericValue(0)
   *
   * @return the first element of the value array, or null if its a String.
   */
  public double getNumericValue() {
    return valueD[0];
  }

  /**
   * Get the ith numeric value.
   *
   * @param i index
   * @return ith numeric value
   */
  public double getNumericValue(int i) {
    return valueD[i];
  }

  /**
   * Get the number of values.
   *
   * @return the number of values.
   */
  public int getLength() {
    return valueD.length;
  }

  /**
   * Get array of numeric values as doubles.
   * Do not modify unless you own this object!
   *
   * @return array of numeric values.
   */
  public double[] getNumericValues() {
    return valueD;
  }

  /**
   * Instances which have same content are equal.
   *
   * @param oo compare to this Parameter.
   * @return true if equal.
   */
  public boolean equals(Object oo) {
    if (this == oo) {
      return true;
    }
    if (!(oo instanceof Parameter)) {
      return false;
    }
    return hashCode() == oo.hashCode();
  }

  /**
   * Override Object.hashCode() to implement equals.
   *
   * @return haschcode
   */
  public int hashCode() {
    if (hashCode == 0) {
      int result = 17;
      result = 37 * result + getName().hashCode();
      if (valueS != null) {
        result = 37 * result + getStringValue().hashCode();
      }
      if (valueD != null) {
        for (int i = 0; i < valueD.length; i++) {
          result += 1000 * valueD[i];
        }
      }
      hashCode = result;
    }
    return hashCode;
  }

  private volatile int hashCode = 0;

  /**
   * String representation
   *
   * @return nice String
   */
  public String toString() {
    StringBuilder buff = new StringBuilder();
    buff.append(getName());
    if (isString()) {
      buff.append(" = ");
      buff.append(valueS);
    } else {
      buff.append(" = ");
      for (int i = 0; i < getLength(); i++) {
        if (i != 0) {
          buff.append(", ");
        }
        buff.append(getNumericValue(i));
      }
    }
    return buff.toString();
  }

  ///////////////////////////////////////////////////////////////////////////////

  /**
   * Copy constructor, with new name.
   *
   * @param name name of new Parameter.
   * @param from copy values from here.
   */
  public Parameter(String name, Parameter from) {
    this.name = name;
    this.valueS = from.valueS;
    this.valueD = from.valueD;
    this.isString = from.isString;
  }

  /**
   * Create a String-valued param.
   *
   * @param name name of new Parameter.
   * @param val  value of Parameter
   */
  public Parameter(String name, String val) {
    this.name = name;
    valueS = val;
    this.isString = true;
  }

  /**
   * Create a scalar double-valued param.
   *
   * @param name  name of new Parameter.
   * @param value value of Parameter
   */
  public Parameter(String name, double value) {
    this.name = name;
    valueD = new double[1];
    valueD[0] = value;
  }

  /**
   * Create a array double-valued param.
   *
   * @param name  name of new Parameter.
   * @param value value of Parameter
   */
  public Parameter(String name, double[] value) {
    this.name = name;
    valueD = value.clone();
  }

}


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

package ucar.nc2.iosp.nexrad2;

import ucar.unidata.io.RandomAccessFile;
import ucar.ma2.IndexIterator;
import ucar.ma2.Range;

import java.io.IOException;
import java.io.PrintStream;
import java.util.Date;

/**
 * This class reads one record (radial) in an NEXRAD level II file.
 * File must be uncompressed.
 * Not handling messages yet, only data.
 *

 * 10/16/05: Now returns data as a byte, so use scale and offset.
 *

 * Adapted with permission from the Java Iras software developed by David Priegnitz at NSSL.
 *
 * @author caron
 * @author David Priegnitz
 */
public class Level2Record {
  static private org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Level2Record.class);

  /**
   * Reflectivity moment identifier
   */
  public static final int REFLECTIVITY = 1;

  /**
   * Radial Velocity moment identifier
   */
  public static final int VELOCITY_HI = 2;

  /**
   * Radial Velocity moment identifier
   */
  public static final int VELOCITY_LOW = 4;

  /**
   * Sprectrum Width moment identifier
   */
  public static final int SPECTRUM_WIDTH = 3;

  /**
   * Low doppler resolution code
   */
  public static final int DOPPLER_RESOLUTION_LOW_CODE = 4;

  /**
   * High doppler resolution code
   */
  public static final int DOPPLER_RESOLUTION_HIGH_CODE = 2;

  /**
   * Horizontal beam width
   */
  public static final float HORIZONTAL_BEAM_WIDTH = (float) 1.5;         // LOOK  always true ??

 /* added for high resolution message type 31 */

  public static final int REFLECTIVITY_HIGH = 5;

  /**
   * High Resolution Radial Velocity moment identifier
   */
  public static final int VELOCITY_HIGH = 6;

  /**
   * High Resolution Sprectrum Width moment identifier
   */
  public static final int SPECTRUM_WIDTH_HIGH = 7;

  /**
   * High Resolution Radial Velocity moment identifier
   */
  public static final int DIFF_REFLECTIVITY_HIGH = 8;

  /**
   * High Resolution Radial Velocity moment identifier
   */
  public static final int DIFF_PHASE = 9;

  /**
   * High Resolution Sprectrum Width moment identifier
   */
  public static final int CORRELATION_COEFFICIENT = 10;

  // Lookup Tables

  /**
   * Initialization flag for lookup tables
   * public static int data_lut_init_flag = 0;
   *

   * /** Reflectivity look up table
   * public static float[] Reflectivity_LUT = new float[256];
   *

   * /** 1 km Velocity look up table
   * public static float[] Velocity_1km_LUT = new float[256];
   *

   * /** 1/2 km Velocity look up table
   * public static float[] Velocity_hkm_LUT = new float[256];
   *

   * static {
   * Reflectivity_LUT[0] = 0.0f; // Float.NaN;  //(float) SIGNAL_BELOW_THRESHOLD;
   * Reflectivity_LUT[1] = Float.NaN;  //(float) SIGNAL_OVERLAID;
   * Velocity_1km_LUT[0] = 0.0f; // Float.NaN;  //(float) SIGNAL_BELOW_THRESHOLD;
   * Velocity_1km_LUT[1] = Float.NaN;  //(float) SIGNAL_OVERLAID;
   * Velocity_hkm_LUT[0] = 0.0f; // Float.NaN;  //(float) SIGNAL_BELOW_THRESHOLD;
   * Velocity_hkm_LUT[1] = Float.NaN;  //(float) SIGNAL_OVERLAID;
   *

   * for (int i = 2; i < 256; i++) {
   * Reflectivity_LUT[i] = (float) (i / 2.0 - 33.0);
   * Velocity_1km_LUT[i] = (float) (i - 129.0);
   * Velocity_hkm_LUT[i] = (float) (i / 2.0 - 64.5); // also spectrum width
   * }
   * }
   */

  public static final byte MISSING_DATA = (byte) 1;
  public static final byte BELOW_THRESHOLD = (byte) 0;

  /**
   * Size of the file header, aka title
   */
  static final int FILE_HEADER_SIZE = 24;

  /**
   * Size of the CTM record header
   */
  private static final int CTM_HEADER_SIZE = 12;

  /**
   * Size of the the message header, to start of the data message
   */
  private static final int MESSAGE_HEADER_SIZE = 28;

  /**
   * Size of the entire message, if its a radar data message
   */
  private static final int RADAR_DATA_SIZE = 2432;

  static public String getDatatypeName(int datatype) {
    switch (datatype) {
      case REFLECTIVITY:
        return "Reflectivity";
      case VELOCITY_HI:
      case VELOCITY_LOW:
        return "RadialVelocity";
      case SPECTRUM_WIDTH:
        return "SpectrumWidth";
      case REFLECTIVITY_HIGH:
        return "Reflectivity_HI";
      case VELOCITY_HIGH:
        return "RadialVelocity_HI";
      case SPECTRUM_WIDTH_HIGH:
        return "SpectrumWidth_HI";
      case DIFF_REFLECTIVITY_HIGH:
        return "Reflectivity_DIFF";
      case DIFF_PHASE:
        return "Phase";
      case CORRELATION_COEFFICIENT:
        return "RHO";

      default:
        throw new IllegalArgumentException();
    }
  }

  static public String getDatatypeUnits(int datatype) {
    switch (datatype) {
      case REFLECTIVITY:
        return "dBz";

      case VELOCITY_HI:
      case VELOCITY_LOW:
      case SPECTRUM_WIDTH:
        return "m/s";

      case REFLECTIVITY_HIGH:
        return "dBz";
      case DIFF_REFLECTIVITY_HIGH:
        return "dBz";

      case VELOCITY_HIGH:
      case SPECTRUM_WIDTH_HIGH:
        return "m/s";

      case DIFF_PHASE:
        return "deg";

      case CORRELATION_COEFFICIENT:
        return "N/A";
    }
    throw new IllegalArgumentException();
  }

  public short getDatatypeSNRThreshhold(int datatype) {
    switch (datatype) {
      case REFLECTIVITY_HIGH:
        return ref_snr_threshold;
      case VELOCITY_HIGH:
        return vel_snr_threshold;
      case SPECTRUM_WIDTH_HIGH:
        return sw_snr_threshold;
      case DIFF_REFLECTIVITY_HIGH:
        return zdrHR_snr_threshold;
      case DIFF_PHASE:
        return phiHR_snr_threshold;
      case CORRELATION_COEFFICIENT:
        return rhoHR_snr_threshold;
      default:
        throw new IllegalArgumentException();
    }
  }

  public short getDatatypeRangeFoldingThreshhold(int datatype) {
    switch (datatype) {
      case REFLECTIVITY_HIGH:
        return ref_rf_threshold;
      case VELOCITY_HIGH:
        return vel_rf_threshold;
      case SPECTRUM_WIDTH_HIGH:
        return sw_rf_threshold;
      case REFLECTIVITY:
      case VELOCITY_LOW:
      case VELOCITY_HI:
      case SPECTRUM_WIDTH:
        return threshhold;
      case DIFF_REFLECTIVITY_HIGH:
        return zdrHR_rf_threshold;
      case DIFF_PHASE:
        return phiHR_rf_threshold;
      case CORRELATION_COEFFICIENT:
        return rhoHR_rf_threshold;

      default:
        throw new IllegalArgumentException();
    }
  }

  public float getDatatypeScaleFactor(int datatype) {
    switch (datatype) {
      case REFLECTIVITY:
        return 0.5f;
      case VELOCITY_LOW:
        return 1.0f;
      case VELOCITY_HI:
      case SPECTRUM_WIDTH:
        return 0.5f;
      case REFLECTIVITY_HIGH:
        return 1 / reflectHR_scale;
      case VELOCITY_HIGH:
        return 1 / velocityHR_scale;
      case SPECTRUM_WIDTH_HIGH:
        return 1 / spectrumHR_scale;
      case DIFF_REFLECTIVITY_HIGH:
        return 1.0f / zdrHR_scale;
      case DIFF_PHASE:
        return 1.0f / phiHR_scale;
      case CORRELATION_COEFFICIENT:
        return 1.0f / rhoHR_scale;

      default:
        throw new IllegalArgumentException();
    }
  }

  public float getDatatypeAddOffset(int datatype) {
    switch (datatype) {
      case REFLECTIVITY:
        return -33.0f;
      case VELOCITY_LOW:
        return -129.0f;
      case VELOCITY_HI:
      case SPECTRUM_WIDTH:
        return -64.5f;
      case REFLECTIVITY_HIGH:
        return reflectHR_addoffset * (-1) / reflectHR_scale;
      case VELOCITY_HIGH:
        return velocityHR_addoffset * (-1) / velocityHR_scale;
      case SPECTRUM_WIDTH_HIGH:
        return spectrumHR_addoffset * (-1) / spectrumHR_scale;
      case DIFF_REFLECTIVITY_HIGH:
        return zdrHR_addoffset * (-1) / zdrHR_scale;
      case DIFF_PHASE:
        return phiHR_addoffset * (-1) / phiHR_scale;
      case CORRELATION_COEFFICIENT:
        return rhoHR_addoffset * (-1) / rhoHR_scale;

      default:
        throw new IllegalArgumentException();
    }
  }

  static public String getMessageTypeName(int code) {
    switch (code) {
      case 1:
        return "digital radar data";
      case 2:
        return "RDA status data";
      case 3:
        return "performance/maintainence data";
      case 4:
        return "console message - RDA to RPG";
      case 5:
        return "maintainence log data";
      case 6:
        return "RDA control ocmmands";
      case 7:
        return "volume coverage pattern";
      case 8:
        return "clutter censor zones";
      case 9:
        return "request for data";
      case 10:
        return "console message - RPG to RDA";
      case 11:
        return "loop back test - RDA to RPG";
      case 12:
        return "loop back test - RPG to RDA";
      case 13:
        return "clutter filter bypass map - RDA to RPG";
      case 14:
        return "edited clutter filter bypass map - RDA to RPG";
      case 15:
        return "Notchwidth Map";
      case 18:
        return "RDA Adaptation data";
      case 31:
        return "Digitail Radar Data Generic Format";
      default:
        return "unknown " + code;
    }
  }

  static public String getRadialStatusName(int code) {
    switch (code) {
      case 0:
        return "start of new elevation";
      case 1:
        return "intermediate radial";
      case 2:
        return "end of elevation";
      case 3:
        return "begin volume scan";
      case 4:
        return "end volume scan";
      default:
        return "unknown " + code;
    }
  }

  static public String getVolumeCoveragePatternName(int code) {
    switch (code) {
      case 11:
        return "16 elevation scans every 5 mins";
      case 12:
        return "14 elevation scan every 4.1 mins";
      case 21:
        return "11 elevation scans every 6 mins";
      case 31:
        return "8 elevation scans every 10 mins";
      case 32:
        return "7 elevation scans every 10 mins";
      case 121:
        return "9 elevations, 20 scans every 5 minutes";
      case 211:
        return "14 elevations, 16 scans every 5 mins";
      case 212:
        return "14 elevations, 17 scans every 4 mins";
      case 221:
        return "9 elevations, 11 scans every 5 minutes";
      default:
        return "unknown " + code;
    }
  }

  static public java.util.Date getDate(int julianDays, int msecs) {
    long total = ((long) (julianDays - 1)) * 24 * 3600 * 1000 + msecs;
    return new Date(total);
  }

  /////////////////////////////////////////////////////////////////////////////////////////////////////////////

  int recno;        //  record number within the file
  long message_offset; // offset of start of message

  boolean hasReflectData, hasDopplerData;
  boolean hasHighResREFData;
  boolean hasHighResVELData;
  boolean hasHighResSWData;
  boolean hasHighResZDRData;
  boolean hasHighResPHIData;
  boolean hasHighResRHOData;
  // message header
  short message_size = 0;
  byte id_channel = 0;
  public byte message_type = 0;
  short id_sequence = 0;
  short mess_julian_date = 0;
  int mess_msecs = 0;
  short seg_count = 0;
  short seg_number = 0;

  // radar data header
  int data_msecs = 0;
  short data_julian_date = 0;
  short unamb_range = 0;
  int azimuth_ang = 0;
  short radial_num = 0; // radial number within the elevation : starts with one
  short radial_status = 0;
  short elevation_ang = 0;
  short elevation_num = 0;

  short reflect_first_gate = 0; // distance to first reflectivity gate (m)
  short reflect_gate_size = 0; //  reflectivity gate size (m)
  short reflect_gate_count = 0; //  number of reflectivity gates

  short doppler_first_gate = 0; // distance to first reflectivity gate (m)
  short doppler_gate_size = 0; //  reflectivity gate size (m)
  short doppler_gate_count = 0; //  number of reflectivity gates

  short cut = 0;
  float calibration = 0; // system gain calibration constant (db biased)
  short resolution = 0; // dopplar velocity resolution
  short vcp = 0;        // volume coverage pattern

  short nyquist_vel; // nyquist velocity
  short attenuation; // atmospheric attenuation factor
  short threshhold; // threshhold paramter for minimum difference
  short ref_snr_threshold; // reflectivity signal to noise threshhold
  short vel_snr_threshold;
  short sw_snr_threshold;
  short zdrHR_snr_threshold;
  short phiHR_snr_threshold;
  short rhoHR_snr_threshold;
  short ref_rf_threshold; // reflectivity range folding threshhold
  short vel_rf_threshold;
  short sw_rf_threshold;
  short zdrHR_rf_threshold;
  short phiHR_rf_threshold;
  short rhoHR_rf_threshold;

  private short reflect_offset; // reflectivity data pointer (byte number from start of message)
  private short velocity_offset; // velocity data pointer (byte number from start of message)
  private short spectWidth_offset; // spectrum-width data pointer (byte number from start of message)
  // new addition for message type 31
  short rlength = 0;
  String id;
  float azimuth;
  byte compressIdx;
  byte sp;
  byte ars;
  byte rs;
  float elevation;
  byte rsbs;
  byte aim;
  short dcount;

  int dbp1;
  int dbp2;
  int dbp3;
  int dbp4;
  int dbp5;
  int dbp6;
  int dbp7;
  int dbp8;
  int dbp9;
  short reflectHR_gate_count = 0;
  short velocityHR_gate_count = 0;
  short spectrumHR_gate_count = 0;
  float reflectHR_scale = 0;
  float velocityHR_scale = 0;
  float spectrumHR_scale = 0;
  float zdrHR_scale = 0;
  float phiHR_scale = 0;
  float rhoHR_scale = 0;
  float reflectHR_addoffset = 0;
  float velocityHR_addoffset = 0;
  float spectrumHR_addoffset = 0;
  float zdrHR_addoffset = 0;
  float phiHR_addoffset = 0;
  float rhoHR_addoffset = 0;
  short reflectHR_offset = 0;
  short velocityHR_offset = 0;
  short spectrumHR_offset = 0;
  short zdrHR_offset = 0;
  short phiHR_offset = 0;
  short rhoHR_offset = 0;
  short zdrHR_gate_count = 0;
  short phiHR_gate_count = 0;
  short rhoHR_gate_count = 0;
  short reflectHR_gate_size = 0;
  short velocityHR_gate_size = 0;
  short spectrumHR_gate_size = 0;
  short zdrHR_gate_size = 0;
  short phiHR_gate_size = 0;
  short rhoHR_gate_size = 0;
  short reflectHR_first_gate = 0;
  short velocityHR_first_gate = 0;
  short spectrumHR_first_gate = 0;
  short zdrHR_first_gate = 0;
  short phiHR_first_gate = 0;
  short rhoHR_first_gate = 0;

  public static Level2Record factory(RandomAccessFile din, int record, long message_offset31) throws IOException {
    long offset = record * RADAR_DATA_SIZE + FILE_HEADER_SIZE + message_offset31;
    if (offset >= din.length())
      return null;
    else
      return new Level2Record(din, record, message_offset31);
  }

  public Level2Record(RandomAccessFile din, int record, long message_offset31) throws IOException {

    this.recno = record;
    message_offset = record * RADAR_DATA_SIZE + FILE_HEADER_SIZE + message_offset31;
    din.seek(message_offset);
    din.skipBytes(CTM_HEADER_SIZE);

    // Message Header
    // int size = din.readInt();
    message_size = din.readShort(); // size in "halfwords" = 2 bytes
    id_channel = din.readByte(); // channel id
    message_type = din.readByte();
    id_sequence = din.readShort();
    mess_julian_date = din.readShort(); // from 1/1/70; prob "message generation time"
    mess_msecs = din.readInt();   // message generation time
    seg_count = din.readShort(); // number of message segments
    seg_number = din.readShort(); // this segment

    // if (message_type != 1 ) return;
    if (message_type == 1) {
      // data header
      data_msecs = din.readInt();   // collection time for this radial, msecs since midnight
      data_julian_date = din.readShort(); // prob "collection time"
      unamb_range = din.readShort(); // unambiguous range
      azimuth_ang = din.readUnsignedShort(); // LOOK why unsigned ??
      radial_num = din.readShort(); // radial number within the elevation
      radial_status = din.readShort();
      elevation_ang = din.readShort();
      elevation_num = din.readShort(); // RDA elevation number
      reflect_first_gate = din.readShort(); // range to first gate of reflectivity (m) may be negetive
      doppler_first_gate = din.readShort(); // range to first gate of dopplar (m) may be negetive
      reflect_gate_size = din.readShort(); // reflectivity data gate size (m)
      doppler_gate_size = din.readShort(); // dopplar data gate size (m)
      reflect_gate_count = din.readShort(); // number of reflectivity gates
      doppler_gate_count = din.readShort(); // number of velocity or spectrum width gates
      cut = din.readShort(); // sector number within cut
      calibration = din.readFloat(); // system gain calibration constant (db biased)
      reflect_offset = din.readShort(); // reflectivity data pointer (byte number from start of message)
      velocity_offset = din.readShort(); // velocity data pointer (byte number from start of message)
      spectWidth_offset = din.readShort(); // spectrum-width data pointer (byte number from start of message)
      resolution = din.readShort(); // dopplar velocity resolution
      vcp = din.readShort(); // volume coverage pattern

      din.skipBytes(14);

      nyquist_vel = din.readShort(); // nyquist velocity
      attenuation = din.readShort(); // atmospheric attenuation factor
      threshhold = din.readShort(); // threshhold paramter for minimum difference

      hasReflectData = (reflect_gate_count > 0);
      hasDopplerData = (doppler_gate_count > 0);

    } else if (message_type == 31) {
      // data header
      id = din.readString(4);
      data_msecs = din.readInt();   // collection time for this radial, msecs since midnight
      data_julian_date = din.readShort(); // prob "collection time"
      radial_num = din.readShort(); // radial number within the elevation
      azimuth = din.readFloat(); // LOOK why unsigned ??
      compressIdx = din.readByte();
      sp = din.readByte();
      rlength = din.readShort();
      ars = din.readByte();
      rs = din.readByte();
      elevation_num = din.readByte(); // RDA elevation number
      cut = din.readByte(); // sector number within cut
      elevation = din.readFloat();
      rsbs = din.readByte();
      aim = din.readByte();
      dcount = din.readShort();

      dbp1 = din.readInt();
      dbp2 = din.readInt();
      dbp3 = din.readInt();
      dbp4 = din.readInt();
      dbp5 = din.readInt();
      dbp6 = din.readInt();
      dbp7 = din.readInt();
      dbp8 = din.readInt();
      dbp9 = din.readInt();

      vcp = getDataBlockValue(din, (short) dbp1, 40);
      int dbpp4 = 0;
      int dbpp5 = 0;
      int dbpp6 = 0;
      int dbpp7 = 0;
      int dbpp8 = 0;
      int dbpp9 = 0;

      if (dbp4 > 0) {
        String tname = getDataBlockStringValue(din, (short) dbp4, 1, 3);
        if (tname.startsWith("REF")) {
          hasHighResREFData = true;
          dbpp4 = dbp4;
        } else if (tname.startsWith("VEL")) {
          hasHighResVELData = true;
          dbpp5 = dbp4;
        } else if (tname.startsWith("SW")) {
          hasHighResSWData = true;
          dbpp6 = dbp4;
        } else if (tname.startsWith("ZDR")) {
          hasHighResZDRData = true;
          dbpp7 = dbp4;
        } else if (tname.startsWith("PHI")) {
          hasHighResPHIData = true;
          dbpp8 = dbp4;
        } else if (tname.startsWith("RHO")) {
          hasHighResRHOData = true;
          dbpp9 = dbp4;
        } else {
          logger.warn("Missing radial product dbp4={} tname={}", dbp4, tname);
        }

      }
      if (dbp5 > 0) {

        String tname = getDataBlockStringValue(din, (short) dbp5, 1, 3);
        if (tname.startsWith("REF")) {
          hasHighResREFData = true;
          dbpp4 = dbp5;
        } else if (tname.startsWith("VEL")) {
          hasHighResVELData = true;
          dbpp5 = dbp5;
        } else if (tname.startsWith("SW")) {
          hasHighResSWData = true;
          dbpp6 = dbp5;
        } else if (tname.startsWith("ZDR")) {
          hasHighResZDRData = true;
          dbpp7 = dbp5;
        } else if (tname.startsWith("PHI")) {
          hasHighResPHIData = true;
          dbpp8 = dbp5;
        } else if (tname.startsWith("RHO")) {
          hasHighResRHOData = true;
          dbpp9 = dbp5;
        } else {
          logger.warn("Missing radial product dbp5={} tname={}", dbp5, tname);
        }
      }
      if (dbp6 > 0) {

        String tname = getDataBlockStringValue(din, (short) dbp6, 1, 3);
        if (tname.startsWith("REF")) {
          hasHighResREFData = true;
          dbpp4 = dbp6;
        } else if (tname.startsWith("VEL")) {
          hasHighResVELData = true;
          dbpp5 = dbp6;
        } else if (tname.startsWith("SW")) {
          hasHighResSWData = true;
          dbpp6 = dbp6;
        } else if (tname.startsWith("ZDR")) {
          hasHighResZDRData = true;
          dbpp7 = dbp6;
        } else if (tname.startsWith("PHI")) {
          hasHighResPHIData = true;
          dbpp8 = dbp6;
        } else if (tname.startsWith("RHO")) {
          hasHighResRHOData = true;
          dbpp9 = dbp6;
        } else {
          logger.warn("Missing radial product dbp6={} tname={}", dbp6, tname);
        }
      }

      if (dbp7 > 0) {

        String tname = getDataBlockStringValue(din, (short) dbp7, 1, 3);
        if (tname.startsWith("REF")) {
          hasHighResREFData = true;
          dbpp4 = dbp7;
        } else if (tname.startsWith("VEL")) {
          hasHighResVELData = true;
          dbpp5 = dbp7;
        } else if (tname.startsWith("SW")) {
          hasHighResSWData = true;
          dbpp6 = dbp7;
        } else if (tname.startsWith("ZDR")) {
          hasHighResZDRData = true;
          dbpp7 = dbp7;
        } else if (tname.startsWith("PHI")) {
          hasHighResPHIData = true;
          dbpp8 = dbp7;
        } else if (tname.startsWith("RHO")) {
          hasHighResRHOData = true;
          dbpp9 = dbp7;
        } else {
          logger.warn("Missing radial product dbp7={} tname={}", dbp7, tname);
        }
      }

      if (dbp8 > 0) {

        String tname = getDataBlockStringValue(din, (short) dbp8, 1, 3);
        if (tname.startsWith("REF")) {
          hasHighResREFData = true;
          dbpp4 = dbp8;
        } else if (tname.startsWith("VEL")) {
          hasHighResVELData = true;
          dbpp5 = dbp8;
        } else if (tname.startsWith("SW")) {
          hasHighResSWData = true;
          dbpp6 = dbp8;
        } else if (tname.startsWith("ZDR")) {
          hasHighResZDRData = true;
          dbpp7 = dbp8;
        } else if (tname.startsWith("PHI")) {
          hasHighResPHIData = true;
          dbpp8 = dbp8;
        } else if (tname.startsWith("RHO")) {
          hasHighResRHOData = true;
          dbpp9 = dbp8;
        } else {
          logger.warn("Missing radial product dbp8={} tname={}", dbp8, tname);
        }
      }

      if (dbp9 > 0) {

        String tname = getDataBlockStringValue(din, (short) dbp9, 1, 3);
        if (tname.startsWith("REF")) {
          hasHighResREFData = true;
          dbpp4 = dbp9;
        } else if (tname.startsWith("VEL")) {
          hasHighResVELData = true;
          dbpp5 = dbp9;
        } else if (tname.startsWith("SW")) {
          hasHighResSWData = true;
          dbpp6 = dbp9;
        } else if (tname.startsWith("ZDR")) {
          hasHighResZDRData = true;
          dbpp7 = dbp9;
        } else if (tname.startsWith("PHI")) {
          hasHighResPHIData = true;
          dbpp8 = dbp9;
        } else if (tname.startsWith("RHO")) {
          hasHighResRHOData = true;
          dbpp9 = dbp9;
        } else {
          logger.warn("Missing radial product dbp9={} tname={}", dbp9, tname);
        }
      }
      //hasHighResREFData = (dbp4 > 0);

      if (hasHighResREFData) {
        reflectHR_gate_count = getDataBlockValue(din, (short) dbpp4, 8);
        reflectHR_first_gate = getDataBlockValue(din, (short) dbpp4, 10);
        reflectHR_gate_size = getDataBlockValue(din, (short) dbpp4, 12);
        ref_rf_threshold = getDataBlockValue(din, (short) dbpp4, 14);
        ref_snr_threshold = getDataBlockValue(din, (short) dbpp4, 16);
        reflectHR_scale = getDataBlockValue1(din, (short) dbpp4, 20);
        reflectHR_addoffset = getDataBlockValue1(din, (short) dbpp4, 24);
        reflectHR_offset = (short) (dbpp4 + 28);

      }
      //hasHighResVELData = (dbp5 > 0);
      if (hasHighResVELData) {
        velocityHR_gate_count = getDataBlockValue(din, (short) dbpp5, 8);
        velocityHR_first_gate = getDataBlockValue(din, (short) dbpp5, 10);
        velocityHR_gate_size = getDataBlockValue(din, (short) dbpp5, 12);
        vel_rf_threshold = getDataBlockValue(din, (short) dbpp5, 14);
        vel_snr_threshold = getDataBlockValue(din, (short) dbpp5, 16);
        velocityHR_scale = getDataBlockValue1(din, (short) dbpp5, 20);
        velocityHR_addoffset = getDataBlockValue1(din, (short) dbpp5, 24);
        velocityHR_offset = (short) (dbpp5 + 28);

      }
      // hasHighResSWData  = (dbp6 > 0);
      if (hasHighResSWData) {
        spectrumHR_gate_count = getDataBlockValue(din, (short) dbpp6, 8);
        spectrumHR_first_gate = getDataBlockValue(din, (short) dbpp6, 10);
        spectrumHR_gate_size = getDataBlockValue(din, (short) dbpp6, 12);
        sw_rf_threshold = getDataBlockValue(din, (short) dbpp6, 14);
        sw_snr_threshold = getDataBlockValue(din, (short) dbpp6, 16);
        spectrumHR_scale = getDataBlockValue1(din, (short) dbpp6, 20);
        spectrumHR_addoffset = getDataBlockValue1(din, (short) dbpp6, 24);
        spectrumHR_offset = (short) (dbpp6 + 28);

      }
      // hasHighResZDRData = (dbp7 > 0);
      if (hasHighResZDRData) {
        zdrHR_gate_count = getDataBlockValue(din, (short) dbpp7, 8);
        zdrHR_first_gate = getDataBlockValue(din, (short) dbpp7, 10);
        zdrHR_gate_size = getDataBlockValue(din, (short) dbpp7, 12);
        zdrHR_rf_threshold = getDataBlockValue(din, (short) dbpp7, 14);
        zdrHR_snr_threshold = getDataBlockValue(din, (short) dbpp7, 16);
        zdrHR_scale = getDataBlockValue1(din, (short) dbpp7, 20);
        zdrHR_addoffset = getDataBlockValue1(din, (short) dbpp7, 24);
        zdrHR_offset = (short) (dbpp7 + 28);
      }
      //hasHighResPHIData = (dbp8 > 0);
      if (hasHighResPHIData) {
        phiHR_gate_count = getDataBlockValue(din, (short) dbpp8, 8);
        phiHR_first_gate = getDataBlockValue(din, (short) dbpp8, 10);
        phiHR_gate_size = getDataBlockValue(din, (short) dbpp8, 12);
        phiHR_rf_threshold = getDataBlockValue(din, (short) dbpp8, 14);
        phiHR_snr_threshold = getDataBlockValue(din, (short) dbpp8, 16);
        phiHR_scale = getDataBlockValue1(din, (short) dbpp8, 20);
        phiHR_addoffset = getDataBlockValue1(din, (short) dbpp8, 24);
        phiHR_offset = (short) (dbpp8 + 28);
      }
      //hasHighResRHOData = (dbp9 > 0);
      if (hasHighResRHOData) {
        rhoHR_gate_count = getDataBlockValue(din, (short) dbpp9, 8);
        rhoHR_first_gate = getDataBlockValue(din, (short) dbpp9, 10);
        rhoHR_gate_size = getDataBlockValue(din, (short) dbpp9, 12);
        rhoHR_rf_threshold = getDataBlockValue(din, (short) dbpp9, 14);
        rhoHR_snr_threshold = getDataBlockValue(din, (short) dbpp9, 16);
        rhoHR_scale = getDataBlockValue1(din, (short) dbpp9, 20);
        rhoHR_addoffset = getDataBlockValue1(din, (short) dbpp9, 24);
        rhoHR_offset = (short) (dbpp9 + 28);
      }

    }

  }

  public void dumpMessage(PrintStream out) {
    out.println(recno + " ---------------------");
    out.println(" message type = " + getMessageTypeName(message_type) + " (" + message_type + ")");
    out.println(" message size = " + message_size + " segment=" + seg_number + "/" + seg_count);
  }

  public void dump(PrintStream out) {
    out.println(recno + " ------------------------------------------" + message_offset);
    out.println(" message type = " + getMessageTypeName(message_type));
    out.println(" data date = " + data_julian_date + " : " + data_msecs);
    out.println(" elevation = " + getElevation() + " (" + elevation_num + ")");
    out.println(" azimuth = " + getAzimuth());
    out.println(" radial = " + radial_num + " status= " + getRadialStatusName(radial_status) +
            " ratio = " + getAzimuth() / radial_num);
    out.println(" reflectivity first= " + reflect_first_gate + " size= " + reflect_gate_size + " count= " + reflect_gate_count);
    out.println(" doppler first= " + doppler_first_gate + " size= " + doppler_gate_size + " count= " + doppler_gate_count);
    out.println(" offset: reflect= " + reflect_offset + " velocity= " + velocity_offset + " spWidth= " + spectWidth_offset);
    out.println(" pattern = " + vcp + " cut= " + cut);
  }

  public void dump2(PrintStream out) {
    out.println("recno= " + recno + " massType= " + message_type + " massSize = " + message_size);
  }

  public boolean checkOk() {
    boolean ok = true;

    if (Float.isNaN(getAzimuth())) {
      logger.warn("****" + recno + " HAS bad azimuth value = " + azimuth_ang);
      ok = false;
    }

    if (message_type != 1) return ok;

    if ((seg_count != 1) || (seg_number != 1)) {
      logger.warn("*** segment = " + seg_number + "/" + seg_count + who());
    }

    if ((reflect_offset < 0) || (reflect_offset > RADAR_DATA_SIZE)) {
      logger.warn("****" + recno + " HAS bad reflect offset= " + reflect_offset + who());
      ok = false;
    }

    if ((velocity_offset < 0) || (velocity_offset > RADAR_DATA_SIZE)) {
      logger.warn("****" + recno + " HAS bad velocity offset= " + velocity_offset + who());
      ok = false;
    }

    if ((spectWidth_offset < 0) || (spectWidth_offset > RADAR_DATA_SIZE)) {
      logger.warn("****" + recno + " HAS bad spwidth offset= " +
              spectWidth_offset + who());
      ok = false;
    }

    if ((velocity_offset > 0) && (spectWidth_offset <= 0)) {
      logger.warn("****" + recno + " HAS velocity NOT spectWidth!!" + who());
      ok = false;
    }

    if ((velocity_offset <= 0) && (spectWidth_offset > 0)) {
      logger.warn("****" + recno + " HAS spectWidth AND NOT velocity!!" + who());
      ok = false;
    }

    if (mess_julian_date != data_julian_date) {
      logger.warn("*** message date = " + mess_julian_date + " : " + mess_msecs + who() + "\n" +
              " data date = " + data_julian_date + " : " + data_msecs);
      ok = false;
    }

    if (!hasReflectData && !hasDopplerData) {
      logger.info("*** no reflect or dopplar = " + who());
    }

    return ok;
  }

  private String who() {
    return " message(" + recno + " " + message_offset + ")";
  }

  /**
   * Get the azimuth in degrees
   *
   * @return azimuth angle in degrees 0 = true north, 90 = east
   */
  public float getAzimuth() {
    if (message_type == 31)
      return azimuth;
    else if (message_type == 1)
      return 180.0f * azimuth_ang / 32768.0f;
    else
      return -1.0f;
  }

  /**
   * Get the elevation angle in degrees
   *
   * @return elevation angle in degrees 0 = parellel to pedestal base, 90 = perpendicular
   */
  public float getElevation() {
    if (message_type == 31)
      return elevation;
    else if (message_type == 1)
      return 180.0f * elevation_ang / 32768.0f;
    else
      return -1.0f;
  }

  /**
   * This method returns the gate size in meters
   *
   * @param datatype which type of data : REFLECTIVITY, VELOCITY_HI, VELOCITY_LO, SPECTRUM_WIDTH
   * @return the gate size in meters
   */
  public int getGateSize(int datatype) {
    switch (datatype) {
      case REFLECTIVITY:
        return ((int) reflect_gate_size);

      case VELOCITY_HI:
      case VELOCITY_LOW:
      case SPECTRUM_WIDTH:
        return ((int) doppler_gate_size);
      //high resolution
      case REFLECTIVITY_HIGH:
        return ((int) reflectHR_gate_size);
      case VELOCITY_HIGH:
        return ((int) velocityHR_gate_size);
      case SPECTRUM_WIDTH_HIGH:
        return ((int) spectrumHR_gate_size);
      case DIFF_REFLECTIVITY_HIGH:
        return ((int) zdrHR_gate_size);
      case DIFF_PHASE:
        return ((int) phiHR_gate_size);
      case CORRELATION_COEFFICIENT:
        return ((int) rhoHR_gate_size);

    }
    return -1;
  }

  /**
   * This method returns the starting gate in meters
   *
   * @param datatype which type of data : REFLECTIVITY, VELOCITY_HI, VELOCITY_LO, SPECTRUM_WIDTH
   * @return the starting gate in meters
   */
  public int getGateStart(int datatype) {
    switch (datatype) {
      case REFLECTIVITY:
        return ((int) reflect_first_gate);

      case VELOCITY_HI:
      case VELOCITY_LOW:
      case SPECTRUM_WIDTH:
        return ((int) doppler_first_gate);
      //high resolution
      case REFLECTIVITY_HIGH:
        return ((int) reflectHR_first_gate);
      case VELOCITY_HIGH:
        return ((int) velocityHR_first_gate);
      case SPECTRUM_WIDTH_HIGH:
        return ((int) spectrumHR_first_gate);
      case DIFF_REFLECTIVITY_HIGH:
        return ((int) zdrHR_first_gate);
      case DIFF_PHASE:
        return ((int) phiHR_first_gate);
      case CORRELATION_COEFFICIENT:
        return ((int) rhoHR_first_gate);

    }
    return -1;
  }

  /**
   * This method returns the number of gates
   *
   * @param datatype which type of data : REFLECTIVITY, VELOCITY_HI, VELOCITY_LO, SPECTRUM_WIDTH
   * @return the number of gates
   */
  public int getGateCount(int datatype) {
    switch (datatype) {
      case REFLECTIVITY:
        return ((int) reflect_gate_count);

      case VELOCITY_HI:
      case VELOCITY_LOW:
      case SPECTRUM_WIDTH:
        return ((int) doppler_gate_count);
      // hight resolution
      case REFLECTIVITY_HIGH:
        return ((int) reflectHR_gate_count);
      case VELOCITY_HIGH:
        return ((int) velocityHR_gate_count);
      case SPECTRUM_WIDTH_HIGH:
        return ((int) spectrumHR_gate_count);
      case DIFF_REFLECTIVITY_HIGH:
        return ((int) zdrHR_gate_count);
      case DIFF_PHASE:
        return ((int) phiHR_gate_count);
      case CORRELATION_COEFFICIENT:
        return ((int) rhoHR_gate_count);
    }
    return 0;
  }

  private short getDataOffset(int datatype) {
    switch (datatype) {
      case REFLECTIVITY:
        return reflect_offset;
      case VELOCITY_HI:
      case VELOCITY_LOW:
        return velocity_offset;
      case SPECTRUM_WIDTH:
        return spectWidth_offset;
      case REFLECTIVITY_HIGH:
        return reflectHR_offset;
      case VELOCITY_HIGH:
        return velocityHR_offset;
      case SPECTRUM_WIDTH_HIGH:
        return spectrumHR_offset;
      case DIFF_REFLECTIVITY_HIGH:
        return zdrHR_offset;
      case DIFF_PHASE:
        return phiHR_offset;
      case CORRELATION_COEFFICIENT:
        return rhoHR_offset;
    }
    return Short.MIN_VALUE;
  }

  private short getDataBlockValue(RandomAccessFile raf, short offset, int skip) throws IOException {
    long off = offset + message_offset + MESSAGE_HEADER_SIZE;
    raf.seek(off);
    raf.skipBytes(skip);
    return raf.readShort();
  }

  private String getDataBlockStringValue(RandomAccessFile raf, short offset, int skip, int size) throws IOException {
    long off = offset + message_offset + MESSAGE_HEADER_SIZE;
    raf.seek(off);
    raf.skipBytes(skip);
    return raf.readString(size);
  }

  private float getDataBlockValue1(RandomAccessFile raf, short offset, int skip) throws IOException {
    long off = offset + message_offset + MESSAGE_HEADER_SIZE;
    raf.seek(off);
    raf.skipBytes(skip);
    return raf.readFloat();
  }

  public java.util.Date getDate() {
    return getDate(data_julian_date, data_msecs);
  }

  /**
   * Read data from this record.
   *
   * @param raf       read from this file
   * @param datatype  which type of data : REFLECTIVITY, VELOCITY_HI, VELOCITY_LO, SPECTRUM_WIDTH
   * @param gateRange handles the possible subset of data to return
   * @param ii        put the data here
   * @throws IOException on read error
   */
  public void readData(RandomAccessFile raf, int datatype, Range gateRange, IndexIterator ii) throws IOException {
    long offset = message_offset;
    offset += MESSAGE_HEADER_SIZE; // offset is from "start of digital radar data message header"
    offset += getDataOffset(datatype);
    raf.seek(offset);
    if (logger.isDebugEnabled()) {
      logger.debug("  read recno " + recno + " at offset " + offset + " count= " + getGateCount(datatype));
      logger.debug("   offset: reflect= " + reflect_offset + " velocity= " + velocity_offset + " spWidth= " + spectWidth_offset);
    }

    int dataCount = getGateCount(datatype);
    if (datatype == DIFF_PHASE) {
      short[] data = new short[dataCount];
      raf.readShort(data, 0, dataCount);

      for (int gateIdx : gateRange) {
        if (gateIdx >= dataCount)
          ii.setShortNext(MISSING_DATA);
        else
          ii.setShortNext(data[gateIdx]);
      }
    } else {

      byte[] data = new byte[dataCount];
      raf.readFully(data);
      //short [] ds = convertunsignedByte2Short(data);
      for (int gateIdx : gateRange) {
        if (gateIdx >= dataCount)
          ii.setByteNext(MISSING_DATA);
        else
          ii.setByteNext(data[gateIdx]);
      }

    }
  }

  /**
   * Instances which have same content are equal.
   *

   * public boolean equals(Object oo) {
   * if (this == oo) return true;
   * if ( !(oo instanceof Level2Record)) return false;
   * return hashCode() == oo.hashCode();
   * }
   *

   * /** Override Object.hashCode() to implement equals. *
   * public int hashCode() {
   * if (hashCode == 0) {
   * int result = 17;
   * result = 37*result + elevation_num;
   * //result = 37*result + cut;
   * //result = 37*result + datatype;
   * hashCode = result;
   * }
   * return hashCode;
   * }
   * private volatile int hashCode = 0;
   */
  public short[] convertunsignedByte2Short(byte[] inb) {
    int len = inb.length;
    short[] outs = new short[len];
    int i = 0;
    for (byte b : inb) {
      outs[i++] = convertunsignedByte2Short(b);
    }
    return outs;
  }

  public short convertunsignedByte2Short(byte b) {
    return (short) ((b < 0) ? (short) b + 256 : (short) b);
  }

  public String toString() {
    return "elev= " + elevation_num + " radial_num = " + radial_num;
  }

}


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

import ucar.nc2.*;
import ucar.nc2.Attribute;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.dataset.*;
import ucar.ma2.*;

import org.jdom2.*;
import org.jdom2.output.XMLOutputter;
import org.jdom2.output.Format;

import java.io.*;
import java.util.*;

import ucar.unidata.util.Parameter;

/**
 * Helper class to write NcML-G.
 *
 * @see ucar.nc2.NetcdfFile
 * @see http://zeus.pin.unifi.it/projectsSites/galeon2-ncml-gml/
 * @author caron
 */

public class NcMLGWriter {
  protected static final String schemaLocation = "http://www.unidata.ucar.edu/schemas/netcdf-cs.xsd";

  /**
   * Write a NetcdfDataset as an NcML-G document to the specified stream.
   *
   * @param ncd write this dataset; should have opened with "add coordinates".
   * @param os write to this OutputStream
   * @param showCoords show 1D coordinate values
   * @param uri use this url, if null use getLocation()
   * @throws IOException on io error
   */
  public void writeXML(NetcdfDataset ncd, OutputStream os, boolean showCoords, String uri) throws IOException {

    // Output the document, use standard formatter
    //XMLOutputter fmt = new XMLOutputter("  ", true);
    //fmt.setLineSeparator("\n");
    XMLOutputter fmt = new XMLOutputter( Format.getPrettyFormat());
    fmt.output(makeDocument(ncd, showCoords, uri), os);
  }

  Document makeDocument(NetcdfDataset ncd, boolean showCoords, String uri) {
    Element rootElem = new Element("netcdf", thredds.client.catalog.Catalog.ncmlNS);
    Document doc = new Document(rootElem);

      // namespaces
    rootElem.addNamespaceDeclaration(thredds.client.catalog.Catalog.ncmlNS);
    rootElem.addNamespaceDeclaration(thredds.client.catalog.Catalog.xsiNS);
    rootElem.setAttribute("schemaLocation", thredds.client.catalog.Catalog.ncmlNS.getURI()+" "+schemaLocation, thredds.client.catalog.Catalog.xsiNS);

    if (null != ncd.getId())
      rootElem.setAttribute("id", ncd.getId());

    if (null != uri)
      rootElem.setAttribute("url", uri);
    else
      rootElem.setAttribute("url", ncd.getLocation());

        // dimensions
    for (Dimension dim : ncd.getDimensions()) {
      rootElem.addContent(makeDim(dim));
    }

    // attributes
    for (Attribute att : ncd.getGlobalAttributes()) {
      rootElem.addContent(makeAttribute(att, "attribute"));
    }

    // coordinate axes
    for (Variable var : ncd.getVariables()) {
      if (var instanceof CoordinateAxis)
        rootElem.addContent(makeCoordinateAxis((CoordinateAxis) var, showCoords));
    }
    // regular variables
    for (Variable var : ncd.getVariables()) {
      if (!(var instanceof CoordinateAxis))
        rootElem.addContent(makeVariable( (VariableDS) var));
    }

    ///////////////////////////////////////////////////////////
    // reference systems

      // find common coordinate transforms

      /* ReferenceSystem rs = cs.getReferenceSystem();
      int idx = commonRS.indexOf(rs);
      if (idx >= 0) {
        cs.setReferenceSystem( (ReferenceSystem) commonRS.get(idx)); // point to common one
        continue;
      }
      for (int j=i+1; j<csys.size(); j++) {
        CoordinateSystem cs2 = (CoordinateSystem) csys.get(j);
        ReferenceSystem rs2 = cs2.getReferenceSystem();
        if (rs.equals(rs2)) { // used more than once
          commonRS.add( rs);
          break;
        }
      }
    }

      /* find common reference systems
    ArrayList commonRS = new ArrayList();
    List csys = ncd.getCoordinateSystems();
    for (int i=0; i<csys.size(); i++) {
      CoordinateSystem cs = (CoordinateSystem) csys.get(i);
      ReferenceSystem rs = cs.getReferenceSystem();
      int idx = commonRS.indexOf(rs);
      if (idx >= 0) {
        cs.setReferenceSystem( (ReferenceSystem) commonRS.get(idx)); // point to common one
        continue;
      }
      for (int j=i+1; j<csys.size(); j++) {
        CoordinateSystem cs2 = (CoordinateSystem) csys.get(j);
        ReferenceSystem rs2 = cs2.getReferenceSystem();
        if (rs.equals(rs2)) { // used more than once
          commonRS.add( rs);
          break;
        }
      }
    }

      // find common horiz reference systems
    ArrayList commonHRS = new ArrayList();
    csys = ncd.getCoordinateSystems();
    for (int i=0; i<csys.size(); i++) {
      CoordinateSystem cs = (CoordinateSystem) csys.get(i);
      ReferenceSystem rs = cs.getReferenceSystem();
      ReferenceSystem.Horiz hrs = rs.getHoriz();
      int idx = commonHRS.indexOf(hrs);
      if (idx >= 0) {
        rs.setHoriz( (ReferenceSystem.Horiz) commonHRS.get(idx)); // point to common one
        continue;
      }
      for (int j=i+1; j<csys.size(); j++) {
        CoordinateSystem cs2 = (CoordinateSystem) csys.get(j);
        ReferenceSystem rs2 = cs2.getReferenceSystem();
        ReferenceSystem.Horiz hrs2 = rs2.getHoriz();
        if (hrs.equals(hrs2)) { // used more than once
          commonHRS.add( hrs);
          hrs.setId( hrs.getProjectionName()); //  wrong
          break;
        }
      }
    } */

    // coordinate systems
    for (CoordinateSystem cs : ncd.getCoordinateSystems()) {
      rootElem.addContent(makeCoordSys(cs));
    }

    // look for coordinate transforms, ref systems
    List<CoordinateTransform> coordTrans = new ArrayList<CoordinateTransform>();
    for (CoordinateSystem cs : ncd.getCoordinateSystems()) {
      List<CoordinateTransform> ctList = cs.getCoordinateTransforms();
      if (ctList != null) {
        for (CoordinateTransform ct : ctList) {
          if (!coordTrans.contains(ct))
            coordTrans.add(ct);
        }
      }
    }

    // coordinate transforms
    for (CoordinateTransform coordTran : coordTrans)
      rootElem.addContent(makeCoordTransform(coordTran));

    return doc;
  }

  private Element makeAttribute( ucar.nc2.Attribute att, String elementName) {
    Element attElem = new Element(elementName, thredds.client.catalog.Catalog.ncmlNS);
    attElem.setAttribute("name", att.getShortName());

    DataType dt = att.getDataType();
    if (dt != null)
      attElem.setAttribute("type", dt.toString());

    if (att.isString()) {
      String value = att.getStringValue();
      String err = org.jdom2.Verifier.checkCharacterData(value);
      if (err != null) {
        value = "NcMLWriter invalid attribute value, err= "+err;
        System.out.println(value);
      }
      attElem.setAttribute("value", value);
    } else {

      StringBuffer buff = new StringBuffer();
      for (int i=0; i<att.getLength(); i++) {
        Number val = att.getNumericValue(i);
        if (i > 0) buff.append( " ");
        buff.append( val.toString());
      }
      attElem.setAttribute("value", buff.toString());
    }
    return attElem;
  }

  private Element makeAttribute( ucar.unidata.util.Parameter att, String elementName) {
    Element attElem = new Element(elementName, thredds.client.catalog.Catalog.ncmlNS);
    attElem.setAttribute("name", att.getName());

    if (att.isString()) {
      String value = att.getStringValue();
      String err = org.jdom2.Verifier.checkCharacterData(value);
      if (err != null) {
        value = "NcMLWriter invalid attribute value, err= "+err;
        System.out.println(value);
      }
      attElem.setAttribute("value", value);
    } else {
      attElem.setAttribute("type", "double");

      StringBuffer buff = new StringBuffer();
      for (int i=0; i<att.getLength(); i++) {
        double val = att.getNumericValue(i);
        if (i > 0) buff.append( " ");
        buff.append( Double.toString( val));
      }
      attElem.setAttribute("value", buff.toString());
    }
    return attElem;
  }

  private Element makeCoordinateAxis( CoordinateAxis var, boolean showCoords) {

    Element varElem = new Element("coordinateAxis", thredds.client.catalog.Catalog.ncmlNS);
    varElem.setAttribute("name", var.getFullName());

    StringBuffer buff = new StringBuffer();
    List dims = var.getDimensions();
    for (int i=0; i<dims.size(); i++) {
      Dimension dim = (Dimension) dims.get(i);
      if (i > 0) buff.append( " ");
      buff.append( dim.getShortName());
    }
    if (buff.length() > 0)
      varElem.setAttribute("shape", buff.toString());

    DataType dt = var.getDataType();
    varElem.setAttribute("type", dt.toString());

        // attributes
    for (Attribute att : var.getAttributes()) {
      varElem.addContent(makeAttribute(att, "attribute"));
    }

    if (var.isMetadata() || (showCoords && var.getRank() <= 1))
      varElem.addContent( makeValues( var));

    // coordinate axis
    varElem.setAttribute(CDM.UNITS, var.getUnitsString());
    if (var.getAxisType() != null)
      varElem.setAttribute("axisType", var.getAxisType().toString());

    String positive = var.getPositive();
    if (positive != null)
      varElem.setAttribute(CF.POSITIVE, positive);

    String boundaryRef = var.getBoundaryRef();
    if (boundaryRef != null)
      varElem.setAttribute("boundaryRef", boundaryRef);

    // coordinate systems
    List csys = var.getCoordinateSystems();
    if (csys.size() > 0) {
      buff.setLength(0);
      for (int i=0; i<csys.size(); i++) {
        CoordinateSystem cs = (CoordinateSystem) csys.get(i);
        if (i>0) buff.append(" ");
        buff.append( cs.getName());
      }
      varElem.setAttribute("coordinateSystems", buff.toString());
    }

    return varElem;
  }

  private Element makeCoordSys( CoordinateSystem cs) {
    Element csElem = new Element("coordinateSystem", thredds.client.catalog.Catalog.ncmlNS);
    csElem.setAttribute("name", cs.getName());

    for (CoordinateAxis axis : cs.getCoordinateAxes()) {
      Element axisElem = new Element("coordinateAxisRef", thredds.client.catalog.Catalog.ncmlNS);
      axisElem.setAttribute("ref", axis.getFullName());
      csElem.addContent(axisElem);
    }

    List<CoordinateTransform> transforms = cs.getCoordinateTransforms();
    if (transforms != null)
      for (CoordinateTransform ct : transforms) {
        if (ct == null) continue;
        Element tElem = new Element("coordinateTransformRef", thredds.client.catalog.Catalog.ncmlNS);
        tElem.setAttribute("ref", ct.getName());
        csElem.addContent(tElem);
      }
    return csElem;
  }

  private Element makeCoordTransform( CoordinateTransform coordTransform) {
    Element elem = new Element("coordinateTransform", thredds.client.catalog.Catalog.ncmlNS);
    elem.setAttribute("name", coordTransform.getName());
    elem.setAttribute("authority", coordTransform.getAuthority());
    if (coordTransform.getTransformType() != null)
      elem.setAttribute("transformType", coordTransform.getTransformType().toString());

    List<Parameter> params = coordTransform.getParameters();
    for (Parameter p : params) {
      elem.addContent(makeAttribute(p, "parameter"));
    }
    return elem;
  }

  private Element makeDim( Dimension dim) {
    Element dimElem = new Element("dimension", thredds.client.catalog.Catalog.ncmlNS);
    dimElem.setAttribute("name", dim.getShortName());
    dimElem.setAttribute("length", Integer.toString(dim.getLength()));
    if (dim.isUnlimited())
      dimElem.setAttribute("isUnlimited", "true");
    return dimElem;
  }

  /* private Element makeReferenceSys( ReferenceSystem referenceSystem) {
    Element elem = new Element("referenceCoordinateSystem", thredds.client.catalog.Catalog.ncmlNS);
    elem.setAttribute("name", referenceSystem.getName());
    elem.setAttribute("authority", referenceSystem.getAuthority());
    if (referenceSystem.getReferenceType() != null)
      elem.setAttribute("type", referenceSystem.getReferenceType().toString());

    ArrayList params = referenceSystem.getParameters();
    for (int i=0; i<params.size(); i++) {
      ucar.nc2.Attribute att = (ucar.nc2.Attribute) params.get(i);
      elem.addContent( makeAttribute(att, "parameter"));
    }
    return elem;
  } */

  private Element makeVariable( VariableDS var) {

    Element varElem = new Element("variable", thredds.client.catalog.Catalog.ncmlNS);

    varElem.setAttribute("name", var.getFullName());

    StringBuffer buff = new StringBuffer();
    List dims = var.getDimensions();
    for (int i=0; i<dims.size(); i++) {
      Dimension dim = (Dimension) dims.get(i);
      if (i > 0) buff.append( " ");
      buff.append( dim.getShortName());
    }
    if (buff.length() > 0)
      varElem.setAttribute("shape", buff.toString());

    DataType dt = var.getDataType();
    if (dt != null)
      varElem.setAttribute("type", dt.toString());

        // attributes
    for (Attribute att : var.getAttributes()) {
      varElem.addContent(makeAttribute(att, "attribute"));
    }

    if (var.isMetadata())
      varElem.addContent( makeValues( var));

    // coordinate systems
    List csys = var.getCoordinateSystems();
    if (csys.size() > 0) {
      buff.setLength(0);
      for (int i=0; i<csys.size(); i++) {
        CoordinateSystem cs = (CoordinateSystem) csys.get(i);
        if (i>0) buff.append(" ");
        buff.append( cs.getName());
      }
      varElem.setAttribute("coordinateSystems", buff.toString());
    }

    return varElem;
  }

  private Element makeValues( VariableDS v) {
    Element elem = new Element("values", thredds.client.catalog.Catalog.ncmlNS);

    StringBuffer buff = new StringBuffer();
    Array a;
    try {
      a = v.read();
    } catch (IOException ioe) {
      return elem;
    }

    if (a instanceof ArrayChar) {
       //strings
      ArrayChar dataC = (ArrayChar) a;
      for (int i=0; i<dataC.getShape()[0]; i++) {
        if (i > 0) buff.append(" ");
        buff.append("\"").append(dataC.getString(i)).append("\"");
      }
      elem.setText(buff.toString());
    } else {
       // numbers
      if (v instanceof CoordinateAxis1D && ((CoordinateAxis1D)v).isRegular()) {
        CoordinateAxis1D axis = (CoordinateAxis1D) v;
        elem.setAttribute("start", Double.toString(axis.getStart()));
        elem.setAttribute("increment", Double.toString(axis.getIncrement()));
        elem.setAttribute("npts", Long.toString(v.getSize()));

      } else {
         // numbers
        boolean isDouble = (v.getDataType() == DataType.DOUBLE);
        boolean isFloat = (v.getDataType() == DataType.FLOAT);
        IndexIterator iter = a.getIndexIterator();
        while (iter.hasNext()) {
          if (isDouble)
            buff.append(iter.getDoubleNext());
          else if (isFloat)
            buff.append(iter.getFloatNext());
          else
            buff.append(iter.getIntNext());
          buff.append(" ");
        }
        elem.setText(buff.toString());
      } // not regular

    } // not string

    return elem;
  }

  public static void main( String arg[]){
    //String urls = "C:/data/conventions/coards/cldc.mean.nc";
    String urls = "C:/data/galeon/RUC.nc";
    try {
      NetcdfDataset df = NetcdfDataset.openDataset(urls);
      NcMLGWriter ncFactory = new NcMLGWriter();
      System.out.println("NetcdfDataset = "+urls+"\n"+df);
      System.out.println("-----------");
      ncFactory.writeXML( df, System.out, true, null);
    } catch (Exception ioe) {
      System.out.println("error = "+urls);
      ioe.printStackTrace();
    }
  }

}


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

import ucar.nc2.ft2.coverage.SubsetParams;
import ucar.nc2.time.Calendar;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateRange;
import ucar.nc2.time.CalendarPeriod;
import ucar.unidata.geoloc.LatLonPointImpl;

import java.util.List;

/**
 * Parameters specific to ncss point
 *
 * @author caron
 * @since 4/29/2015
 */
public class NcssPointParamsBean extends NcssParamsBean {

  //// station only
	private List<String> stns;

  public List<String> getStns() {
    return stns;
  }

  public void setStns(List<String> stns) {
    this.stns = stns;
  }

  public boolean hasStations() {
    return stns != null && !stns.isEmpty();
  }

  public NcssPointParamsBean() {}

  public NcssPointParamsBean(NcssParamsBean from) {
    super(from);
  }

  ///////////////////////////////

  public SubsetParams makeSubset() {

    SubsetParams subset = new SubsetParams();

    // vars
    subset.set(SubsetParams.variables, var);

    // horiz
    if (stns != null)
      subset.set(SubsetParams.stations, stns);
    else if (hasLatLonBB())
      subset.set(SubsetParams.latlonBB, getLatLonBoundingBox());
    else if (hasLatLonPoint())
      subset.set(SubsetParams.latlonPoint, new LatLonPointImpl(getLatitude(), getLongitude()));

    // time
    CalendarDate date = getRequestedDate(Calendar.getDefault());
    CalendarDateRange dateRange = getCalendarDateRange(Calendar.getDefault());
    if (isAllTimes()) {
      subset.set(SubsetParams.timeAll, true);

    } else if (date != null) {
      subset.set(SubsetParams.time, date);

    } else if (dateRange != null) {
      subset.set(SubsetParams.timeRange, dateRange);

    } else {
      subset.set(SubsetParams.timePresent, true);
    }

    if (timeWindow != null) {
      CalendarPeriod period = CalendarPeriod.of(timeWindow);
      subset.set(SubsetParams.timeWindow, period);
    }

    return subset;
  }

}


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

/**
 * Extends java.awt.FlowLayout, which has a bug where it cant deal with multiple lines.
 * @author John Caron
 */

public class MFlowLayout extends FlowLayout {

    public MFlowLayout(int align, int hgap, int vgap) {
      super( align, hgap, vgap);
    }

    // deal with having components on more than one line
    public Dimension preferredLayoutSize(Container target) {
      synchronized (target.getTreeLock()) {
            Dimension dim = new Dimension(0, 0);

            for (int i = 0 ; i < target.getComponentCount() ; i++) {
              Component m = target.getComponent(i);
              if (m.isVisible()) {
                Dimension d = m.getPreferredSize();

                // original
                // dim.height = Math.max(dim.height, d.height);
                //if (i > 0) { dim.width += hgap; }
                // dim.width += d.width;

                // new  way
                Point p = m.getLocation();
                dim.width =  Math.max(dim.width,  p.x+d.width);
                dim.height = Math.max(dim.height, p.y+d.height);
              }
            }
        Insets insets = target.getInsets();
        dim.width += insets.left + insets.right + getHgap()*2;
        dim.height += insets.top + insets.bottom + getVgap()*2;
        return dim;
      }
    }
}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
// $Id: CatalogBuilderHelper.java 57 2006-07-12 20:20:43Z edavis $
package thredds.cataloggen;

import thredds.catalog.*;
import thredds.catalog.parser.jdom.InvCatalogFactory10;
import thredds.crawlabledataset.CrawlableDataset;
import thredds.crawlabledataset.CrawlableDatasetFilter;
import thredds.crawlabledataset.CrawlableDatasetFactory;

import java.io.IOException;

import org.jdom2.Document;

/**
 * _more_
 *
 * @author edavis
 * @since Dec 7, 2005 2:04:28 PM
 */
class CatalogBuilderHelper
{

  /**
   * Return the requested dataset if it is the ancestor dataset or an allowed
   * descendant of the ancestor dataset, otherwise return null. The given
   * filter determines whether a dataset is allowed or not.
   *
   * @param ancestorCrDs the dataset from which the requested dataset must be descended (or self).
   * @param path the path of the requested dataset.
   * @param filter the CrawlableDatasetFilter that determines which datasets are allowed.
   * @return the CrawlableDataset that represents the given path or null.
   *
   * @throws NullPointerException if the given path or ancestor dataset are null.
   * @throws IllegalArgumentException if the abstract dataset is not a descendant of the ancestor dataset.
   */
  static CrawlableDataset verifyDescendantDataset( CrawlableDataset ancestorCrDs,
                                                   String path,
                                                   CrawlableDatasetFilter filter )
  {
    // Make sure requested path is descendant of ancestor dataset.
    if ( ! ancestorCrDs.isCollection() )
      throw new IllegalArgumentException( "Ancestor dataset <" + ancestorCrDs.getPath() + "> not a collection." );
    if ( ! path.startsWith( ancestorCrDs.getPath() ) )
      throw new IllegalArgumentException( "Dataset path <" + path + "> not descendant of given dataset <" + ancestorCrDs.getPath() + ">." );

    // If path and ancestor are the same, return ancestor.
    if ( path.length() == ancestorCrDs.getPath().length() )
      return ancestorCrDs;

    // Crawl into the dataset collection through each level of the given path
    // checking that each level is accepted by the given CrawlableDatasetFilter.
    String remainingPath = path.substring( ancestorCrDs.getPath().length() );
    if ( remainingPath.startsWith( "/" ) )
      remainingPath = remainingPath.substring( 1 );

    String[] pathSegments = remainingPath.split( "/" );
    CrawlableDataset curCrDs = ancestorCrDs;
    for ( int i = 0; i < pathSegments.length; i++ )
    {
      curCrDs = curCrDs.getDescendant( pathSegments[i]);
      if ( filter != null )
        if ( ! filter.accept( curCrDs ) )
          return null;
    }
    // Only check complete path for existence since speed of check depends on implementation.
    if ( ! curCrDs.exists() )
      return null;
    return curCrDs;
  }

  static Document convertCatalogToDocument( InvCatalog catalog )
  {
    InvCatalogFactory fac = InvCatalogFactory.getDefaultFactory( false );
    InvCatalogConvertIF converter = fac.getCatalogConverter( XMLEntityResolver.CATALOG_NAMESPACE_10 );
    InvCatalogFactory10 fac10 = (InvCatalogFactory10) converter;

    return fac10.writeCatalog( (InvCatalogImpl) catalog );
  }

  static String convertCatalogToString( InvCatalog catalog )
  {
    InvCatalogFactory fac = InvCatalogFactory.getDefaultFactory( false );
    try
    {
      return fac.writeXML( (InvCatalogImpl) catalog );
    }
    catch ( IOException e )
    {
      return null;
    }
  }
}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.unidata.geoloc.vertical;

import ucar.ma2.*;
import ucar.ma2.ArrayDouble.D1;
import ucar.nc2.*;
import ucar.nc2.constants.CDM;
import ucar.nc2.units.SimpleUnit;
import ucar.unidata.util.Parameter;

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

/**
 * Create a 3D height(z,y,x) array using the netCDF CF convention formula for
 * "Hybrid Sigma Pressure".
 *
pressure(x,y,z) = a(z)*p0 + b(z)*surfacePressure(x,y)
 *   or
 *
pressure(x,y,z) = ap(z) + b(z)*surfacePressure(x,y)
 *
 * @author caron
 * @see http://cf-pcmdi.llnl.gov/
 */
public class HybridSigmaPressure extends VerticalTransformImpl {

  /**
   * P-naught identifier
   */
  public static final String P0 = "P0_variableName";

  /**
   * Surface pressure name identifier
   */
  public static final String PS = "SurfacePressure_variableName";

  /**
   * The "a" variable name identifier
   */
  public static final String A = "A_variableName";

  /**
   * The "a" variable name identifier
   */
  public static final String AP = "AP_variableName";

  /**
   * The "b" variable name identifier
   */
  public static final String B = "B_variableName";

  /**
   * value of p-naught
   */
  private double p0;

  /**
   * ps, a, and b variables
   */
  private Variable psVar, aVar, bVar, p0Var;

  /**
   * If has AP, this is a pressure value and has to be also in the same units as PS
   */
  private String apUnits ="";

  /**
   * a and b Arrays
   */
  private Array aArray = null,
          bArray = null;

  /**
   * Construct a coordinate transform for sigma pressure
   *
   * @param ds      netCDF dataset
   * @param timeDim time dimension
   * @param params  list of transformation Parameters
   */
  public HybridSigmaPressure(NetcdfFile ds, Dimension timeDim, List<Parameter> params) {

    super(timeDim);
    String psName = getParameterStringValue(params, PS);
    String aName = getParameterStringValue(params, A);
    String bName = getParameterStringValue(params, B);
    String p0Name = getParameterStringValue(params, P0);
    String apName = getParameterStringValue(params, AP);

    if (apName != null){
       aVar = ds.findVariable(apName);
    }else
       aVar = ds.findVariable(aName);

    if(aVar.findAttributeIgnoreCase(CDM.UNITS) != null){
    	apUnits = aVar.findAttributeIgnoreCase(CDM.UNITS).getStringValue();
    }

    psVar = ds.findVariable(psName);
    bVar = ds.findVariable(bName);
    units = ds.findAttValueIgnoreCase(psVar, CDM.UNITS, "none");
    if (p0Name != null){
      p0Var = ds.findVariable(p0Name);
      apUnits = units; //Won't need transformation for AP = A * P0 * 1 (in this case)
    }

  }

  /**
   * Get the 3D vertical coordinate array for this time step.
   *
   * @param timeIndex the time index. Ignored if !isTimeDependent().
   * @return vertical coordinate array
   * @throws IOException           problem reading data
   * @throws InvalidRangeException _more_
   */
  public ArrayDouble.D3 getCoordinateArray(int timeIndex)
          throws IOException, InvalidRangeException {
    Array psArray = readArray(psVar, timeIndex);

    if (null == aArray) {
      aArray = aVar.read();
      bArray = bVar.read();
      //p0 = (p0Var == null) ? 1.0 : p0Var.readScalarDouble();
      p0 = computeP0();
    }

    int nz = (int) aArray.getSize();
    Index aIndex = aArray.getIndex();
    Index bIndex = bArray.getIndex();

    // it's possible to have rank 3 because pressure can have a level, usually 1
    // Check if rank 3 and try to reduce
    if( psArray.getRank() == 3)
      psArray = psArray.reduce(0);

    int[] shape2D = psArray.getShape();
    int ny = shape2D[0];
    int nx = shape2D[1];

    Index psIndex = psArray.getIndex();

    ArrayDouble.D3 press = new ArrayDouble.D3(nz, ny, nx);

    double ps;
    for (int z = 0; z < nz; z++) {
      double term1 = aArray.getDouble(aIndex.set(z)) * p0;

      //AP might need unit conversion
      if(!apUnits.equals(units) ){
    	  term1 = convertPressureToPSUnits(apUnits, term1);
      }

      double bz = bArray.getDouble(bIndex.set(z));

      for (int y = 0; y < ny; y++) {
        for (int x = 0; x < nx; x++) {
          ps = psArray.getDouble(psIndex.set( y, x));
          press.set(z, y, x, term1 + bz * ps);
        }
      }
    }

    return press;
  }

  /**
   * Get the 1D vertical coordinate array for this time step and point
   *
   * @param timeIndex the time index. Ignored if !isTimeDependent().
   * @param xIndex    the x index
   * @param yIndex    the y index
   * @return vertical coordinate array
   * @throws java.io.IOException problem reading data
   * @throws ucar.ma2.InvalidRangeException _more_
   */
  public D1 getCoordinateArray1D(int timeIndex, int xIndex, int yIndex)
  		throws IOException, InvalidRangeException {

	    Array psArray = readArray(psVar, timeIndex);

	    if (null == aArray) {
	      aArray = aVar.read();
	      bArray = bVar.read();
	      //p0 = (p0Var == null) ? 1.0 : p0Var.readScalarDouble();
	      p0 = computeP0();
	    }

	    int nz = (int) aArray.getSize();
	    Index aIndex = aArray.getIndex();
	    Index bIndex = bArray.getIndex();

	    // it's possible to have rank 3 because pressure can have a level, usually 1
	    // Check if rank 3 and try to reduce
	    if( psArray.getRank() == 3)
	      psArray = psArray.reduce(0);

	    Index psIndex = psArray.getIndex();

	    ArrayDouble.D1 press = new ArrayDouble.D1(nz);

	    double ps;
	    for (int z = 0; z < nz; z++) {

	      double term1 = aArray.getDouble(aIndex.set(z)) * p0;
	      //AP might need unit conversion
	      if(!apUnits.equals(units) ){
	    	  term1 = convertPressureToPSUnits(apUnits, term1);
	      }

	      double bz = bArray.getDouble(bIndex.set(z));

          ps = psArray.getDouble(psIndex.set( yIndex, xIndex));
          press.set(z, term1 + bz * ps);

	    }

	    return press;

  }

  private double computeP0() throws IOException{

	  if (p0Var == null) return 1.0; //Has AP variable

	  double p0 = p0Var.readScalarDouble();

	  //Units check:
	  // P0 must have same units as PS
	  String p0UnitStr = p0Var.findAttributeIgnoreCase(CDM.UNITS).getStringValue();
    if (p0UnitStr == null) throw new IllegalStateException();
	  if (!units.equalsIgnoreCase(p0UnitStr)) {
		  p0 = convertPressureToPSUnits(p0UnitStr, p0);
	  }

	  return p0;
  }

  private double convertPressureToPSUnits(String unit, double val){
	  SimpleUnit psUnit = SimpleUnit.factory(units);
	  SimpleUnit ptopUnit = SimpleUnit.factory(unit);
	  double factor = ptopUnit.convertTo(1.0, psUnit);
	  return val * factor;

  }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.unidata.geoloc.projection.sat;

import ucar.nc2.constants.CF;
import ucar.unidata.geoloc.*;

/**
 * Port Eumetsat MSG_navigation.c to java.
 * from http://www.eumetsat.int/idcplg?IdcService=GET_FILE&dDocName=zip_tools_msg_nav_c&RevisionSelectionMethod=LatestReleased
 *
 * @author caron
 * @see "http://www.eumetsat.int/idcplg?IdcService=GET_FILE&dDocName=PDF_CGMS_03&RevisionSelectionMethod=LatestReleased"
 * @see "www.itc.nl/library/papers_2005/conf/gieske_pro.pdf"
 * @since Jan 9, 2010
 */

public class MSGnavigation extends ProjectionImpl {

  /**
   * **********************************************************************
   * Introduction:
   * =============
   * The Program "MSG_navigation.c" is an example code provided to give
   * the users guidance for a possible implementation of the equations
   * given in the LRIT/HRIT Global Specification [1] to navigate MSG
   * (METEOSAT 8 onwards) data, i.e. to link the pixel coordinates column
   * and line to the corresponding geographical coordinates latitude and
   * longitude.
   *

   * Users should take note, however, that it does NOT provide software
   * for reading MSG data either in LRIT/HRIT, in native or any other
   * format and that EUMETSAT cannot guarantee the accuracy of this
   * software. The software is for use with MSG data only and will not
   * work in the given implementation for Meteosat first generation data.
   *

   * Two functions/subroutines are provided:
   * pixcoord2geocoord: for conversion of column/line into lat./long.
   * geocoord2pixcoord: for conversion of lat./long. into column/line
   *

   * The main routine gives an example how to utilize these two functions by
   * reading a value for column and line at the start of the program on the
   * command line and convert these values into the corresponding
   * geographical coordinates and back again. The results are then printed
   * out on the screen.
   *

   * To Compile the program use for example:
   *

   * COMMAND PROMPT: gcc MSG_navigation.c -o MSG_navigation -lm
   *

   * Run the program by typing
   *

   * COMMAND PROMPT: ./MSG_navigation <COLUMS> <ROWS>
   *

   * ----------------------------------------------------------------------
   *

   * NOTE: Please be aware, that the program assumes the MSG image is
   * ordered in the operational scanning direction which means from south
   * to north and from east to west. With that the VIS/IR channels contains
   * of 3712 x 3712 pixels, start to count on the most southern line and the
   * most eastern column with pixel number 1,1.
   *

   *

   * NOTE on CFAC/LFAC and COFF/LOFF:
   * The parameters CFAC/LFAC and COFF/LOFF are the scaling coefficients
   * provided by the navigation record of the LRIT/HRIT header and used
   * by the scaling function given in Ref [1], page 28.
   *

   * COFF/LOFF are the offsets for column and line which are basically 1856
   * and 1856 for the VIS/IR channels and refer to the middle of the image
   * (centre pixel). The values regarding the High Resolution Visible Channel
   * (HRVis) will be made available in a later issue of this software.
   *

   * CFAC/LFAC are responsible for the image "spread" in the NS and EW
   * directions. They are calculated as follows:
   * CFAC = LFAC = 2^16 / delta
   * with delta = 83.84333 micro Radian (size of one VIS/IR MSG pixel)
   *

   * CFAC     = LFAC     =  781648343.404  rad^-1 for VIS/IR
   *

   * which should be rounded to the nearest integer as stated in Ref [1].
   *

   * CFAC     = LFAC     =  781648343  rad^-1 for VIS/IR
   *

   * the sign of CFAC/LFAC gives the orientation of the image.
   * Negative sign give data scanned from south to north as in the
   * operational scanning. Positive sign vice versa.
   *

   * The terms "line" and "row" are used interchangeable.
   *

   * PLEASE NOTE that the values of CFAC/LFAC which are given in the
   * Header of the LRIT/HRIT Level 1.5 Data (see [2]) are actually in
   * Degrees and should be converted in Radians for use with these
   * routines (see example and values above).
   *

   * The other parameters are given in Ref [1].
   *

   * Further information may be found in either Ref [1], Ref [2] or
   * Ref [3] or on the Eumetsat website http://www.eumetsat.int/ .
   *

   * REFERENCE:
   * [1] LRIT/HRIT Global Specification
   * (CGMS 03, Issue 2.6, 12.08.1999)
   * for the parameters used in the program.
   * [2] MSG Ground Segment LRIT/HRIT Mission Specific
   * Implementation, EUMETSAT Document,
   * (EUM/MSG/SPE/057, Issue 6, 21. June 2006).
   * [3] MSG Level 1.5 Image Data Format Description
   * (EUM/MSG/ICD/105, Issue v5A, 22. August 2007).
   *

   * Please email the User Service (via
   * http://www.eumetsat.int/Home/Basic/Contact_Us/index.htm) if you have
   * any questions regarding this software.
   *

   * ================================
   * From simon.elliott@eumetsat.int
   * As explained in notes 4 and 6 to the GRIB definition (and evidenced by drawing a sketch of the Earth and satellite
   * in the equatorial plane and looking down from above), we have the following from GRIB:
   *

   * # The distance from the Earth's centre to the satellite is Nr (in units of Earth equatorial radius and multiplied by 10^6.
   * # The distance from the Earth's centre to the tangent point on the equator is the Earths equatorial radius
   * # The sine of the angle subtended by the Earths centre and the tangent point on the equator as seen from the spacecraft = Re / (( Nr * Re) / 10^6) = 10^6 / Nr
   * # The angle subtended by the Earth equator as seen by the spacecraft is, by symmetry twice the inverse sine above, 2 * arcsine (10^6 / Nr)
   * # As the number of pixels occupied by the Earth's equatorial radius is dx, the number of radians scanned by the spacecraft per pixel, Rx, is simply [2 * arcsine (10^6 / Nr)] / dx
   *

   * To calculate CFAC, one needs to know delta, the number radians per pixel which is Rx above.
   *

   * So CFAC = 2^16 / delta
   * = 2^16 / Rx
   * = 2^16 / {[2 * arcsine (10^6 / Nr)] / dx }
   *

   *

   *

   * **********************************************************************
   */

  public static final String HEIGHT_FROM_EARTH_CENTER = "height_from_earth_center";
  public static final String SCALE_X = "scale_x";
  public static final String SCALE_Y = "scale_y";

  // parameters used in the routines as given in Ref. [1]
  private static final double SAT_HEIGHT = 42164.0;     // distance from Earth centre to satellite
  private static final double R_EQ = 6378.169;   // radius from Earth centre to equator
  private static final double R_POL = 6356.5838;  // radius from Earth centre to pol
  private static final double SUB_LON = 0.0;     // longitude of sub-satellite point in radiant

  private double lat0 = 0.0; // always 0
  private double lon0; // longitude of sub-satellite point in radians
  private double major_axis, minor_axis; // ellipsoidal shape
  private double sat_height; // distance from Earth centre to satellite
  private double scale_x, scale_y;

  private double const1, const2, const3;
  private double maxR;

  public MSGnavigation() {
    this(0.0, SUB_LON, R_EQ, R_POL, SAT_HEIGHT, SAT_HEIGHT - R_EQ, SAT_HEIGHT - R_EQ);
  }

  /**
   * Constructor
   * @param lat0 in degrees; geosynch satelite is over this point
   * @param lon0 in degrees; geosynch satelite is over this point
   * @param major_axis in meters
   * @param minor_axis in meters
   * @param sat_height in meters
   * @param scale_x  convert between aperature size in radians and distance in km (xrad = xkm / scale_x)
   * @param scale_y  scale_factor = (nr - 1) * major_axis, nr = altitude of the camera from the Earths centre, measured in units of the Earth (equatorial) radius
   */
  public MSGnavigation(double lat0, double lon0, double major_axis, double minor_axis, double sat_height, double scale_x, double scale_y) {
    super("MSGnavigation", false);

    this.lon0 = Math.toRadians(lon0);
    this.major_axis = .001 * major_axis; // convert to km
    this.minor_axis = .001 * minor_axis;
    this.sat_height = .001 * sat_height;
    this.scale_x = scale_x;
    this.scale_y = scale_y;
    const1 = major_axis / minor_axis;
    const1 *= const1; //  (req * rpol)**2
    const2 = 1.0 - (minor_axis * minor_axis) / (major_axis * major_axis); //  (req**2 - rpol**2) / req**2 = 1 - rpol**2 / req**2
    const3 = this.sat_height * this.sat_height - this.major_axis * this.major_axis;

    // "map limit" circle of this radius from the origin, p 173 (Vertical Perspective Projection)
    double P = sat_height / major_axis;
    maxR = .99 * this.major_axis * Math.sqrt((P - 1) / (P + 1));

    addParameter(CF.GRID_MAPPING_NAME, "MSGnavigation");
    addParameter(CF.LONGITUDE_OF_PROJECTION_ORIGIN, lon0);
    addParameter(CF.LATITUDE_OF_PROJECTION_ORIGIN, lat0);
    addParameter(CF.SEMI_MAJOR_AXIS, major_axis);
    addParameter(CF.SEMI_MINOR_AXIS, minor_axis);
    addParameter(HEIGHT_FROM_EARTH_CENTER, sat_height);
    addParameter(SCALE_X, scale_x);
    addParameter(SCALE_Y, scale_y);

    //System.out.printf("%s %n", this);
  }

  @Override
  public String toString() {
    return "MSGnavigation{" +
            "lat0=" + lat0 +
            ", lon0=" + lon0 +
            ", major_axis=" + major_axis +
            ", minor_axis=" + minor_axis +
            ", sat_height=" + sat_height +
            ", scale_x=" + scale_x +
            ", scale_y=" + scale_y +
            '}';
  }

  private int pixcoord2geocoord(double xkm, double ykm, LatLonPointImpl result) {

    /*  calculate viewing angle of the satellite by use of the equation  */
    /*  on page 28, Ref [1]. */
    //double x = (column - x_off) / cfac;
    //double y = (row - y_off) / lfac;

    // convert to radians
    double xrad = xkm / scale_x;
    double yrad = ykm / scale_y;

    double cosx = Math.cos(xrad);
    double cosy = Math.cos(yrad);
    double siny = Math.sin(yrad);

    /*  now calculate the inverse projection */
    /* first check for visibility, whether the pixel is located on the Earth   */
    /* surface or in space. 						     */
    /* To do this calculate the argument to sqrt of "sd", which is named "sa". */
    /* If it is negative then the sqrt will return NaN and the pixel will be   */
    /* located in space, otherwise all is fine and the pixel is located on the */
    /* Earth surface.                                                          */
    double sa = Math.pow(sat_height * cosx * cosy, 2) - (cosy * cosy + const1 * siny * siny) * const3;

    /* produce error values */
    if (sa <= 0.0) {
      result.set(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
      return (-1);
    }

    /* now calculate the rest of the formulas using equations on */
    /* page 25, Ref. [1]  */

    double sd = Math.sqrt(sa);
    double sn = (sat_height * cosx * cosy - sd) / (cosy * cosy + const1 * siny * siny);

    double s1 = sat_height - sn * cosx * cosy;
    double s2 = sn * Math.sin(xrad) * cosy;
    double s3 = -sn * siny;

    double sxy = Math.sqrt(s1 * s1 + s2 * s2);

    /* using the previous calculations the inverse projection can be  */
    /* calculated now, which means calculating the lat./long. from    */
    /* the pixel row and column by equations on page 25, Ref [1].     */

    double longi = Math.atan(s2 / s1) + lon0;
    double lati = Math.atan(const1 * s3 / sxy);

    /* convert from radians into degrees */
    result.setLatitude(Math.toDegrees(lati));
    result.setLongitude(Math.toDegrees(longi));

    return (0);

  }

  private int geocoord2pixcoord(double latitude, double longitude, ProjectionPointImpl result) {

    /* check if the values are sane, otherwise return error values */
    if (latitude < -90.0 || latitude > 90.0 || longitude < -180.0 || longitude > 180.0) {
      result.setLocation(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
      return (-1);
    }

    /* convert to radians */
    double lat = Math.toRadians(latitude);
    double lon = Math.toRadians(longitude) - lon0;
    double cosLon = Math.cos(lon);

    /* calculate the geocentric latitude from the          */
    /* geographic one using equations on page 24, Ref. [1] */

    double c_lat = Math.atan(Math.tan(lat) / const1);

    /* using c_lat calculate the length from the Earth */
    /* centre to the surface of the Earth ellipsoid    */
    /* equations on page 23, Ref. [1]                  */

    double coscLat = Math.cos(c_lat);
    double re = minor_axis / Math.sqrt(1.0 - const2 * coscLat * coscLat);

    /* calculate the forward projection using equations on */
    /* page 24, Ref. [1]                                        */

    double r1 = sat_height - re * coscLat * cosLon;
    double r2 = -re * coscLat * Math.sin(lon);
    double r3 = re * Math.sin(c_lat);
    double rn = Math.sqrt(r1 * r1 + r2 * r2 + r3 * r3);

    /* check for visibility, whether the point on the Earth given by the */
    /* latitude/longitude pair is visible from the satellte or not. This */
    /* is given by the dot product between the vectors of:               */
    /* 1) the point to the spacecraft,			               */
    /* 2) the point to the centre of the Earth.			       */
    /* If the dot product is positive the point is visible otherwise it  */
    /* is invisible.						       */

    double dotprod = r1 * (re * coscLat * cosLon) - r2 * r2 - r3 * r3 * const1;
    if (dotprod <= 0) {
      //System.out.printf("lat,lon=%f,%f NaN%n",latitude,longitude);
      result.setLocation(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
      return (-1);
    }

    double xx = Math.atan(-r2 / r1);
    double yy = Math.asin(-r3 / rn);

    // convert to pixel column and row using the scaling functions on page 28, Ref. [1].
    //double cc = x_off + xx * cfac;
    //double ll = y_off + yy * lfac;

    //System.out.printf("lat,lon=%f,%f x,y=%f,%f i,j=%f,%f%n",latitude,longitude,xx, yy, cc, ll);
    result.setLocation(scale_x * xx, scale_y * yy);
    return (0);
  }

  @Override
  public ProjectionImpl constructCopy() {
    ProjectionImpl result =  new MSGnavigation(
            lat0, Math.toDegrees(lon0), 1000 * major_axis, 1000 * minor_axis, 1000 * sat_height, scale_x, scale_y);
    result.setDefaultMapArea(defaultMapArea);
    result.setName(name);
    return result;
  }

  @Override
  public String paramsToString() {
    return "";
  }

  @Override
  public ProjectionPoint latLonToProj(LatLonPoint latlon, ProjectionPointImpl destPoint) {
    geocoord2pixcoord(latlon.getLatitude(), latlon.getLongitude(), destPoint);
    return destPoint;
  }

  @Override
  public LatLonPoint projToLatLon(ProjectionPoint ppt, LatLonPointImpl destPoint) {
    pixcoord2geocoord(ppt.getX(), ppt.getY(), destPoint);
    return destPoint;
  }

  @Override
  public boolean crossSeam(ProjectionPoint pt1, ProjectionPoint pt2) {
    // either point is infinite
    if (ProjectionPointImpl.isInfinite(pt1) || ProjectionPointImpl.isInfinite(pt2))
      return true;

    // opposite signed X values, larger then 100 km
    return (pt1.getX() * pt2.getX() < 0) && (Math.abs(pt1.getX() - pt2.getX()) > 100);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    MSGnavigation that = (MSGnavigation) o;

    if (Double.compare(that.lat0, lat0) != 0) return false;
    if (Double.compare(that.lon0, lon0) != 0) return false;
    if (Double.compare(that.major_axis, major_axis) != 0) return false;
    if (Double.compare(that.minor_axis, minor_axis) != 0) return false;
    if (Double.compare(that.sat_height, sat_height) != 0) return false;
    if (Double.compare(that.scale_x, scale_x) != 0) return false;
    return Double.compare(that.scale_y, scale_y) == 0;
  }

  @Override
  public int hashCode() {
    int result;
    long temp;
    temp = Double.doubleToLongBits(lat0);
    result = (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(lon0);
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(major_axis);
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(minor_axis);
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(sat_height);
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(scale_x);
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(scale_y);
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    return result;
  }

  /* public boolean equals2(Object proj) {
      if (this == proj) return true;
      if (proj == null || getClass() != proj.getClass()) return false;

      MSGnavigation that = (MSGnavigation) proj;
      if (Double.compare(that.lat0, lat0) != 0) return false;
      if (Double.compare(that.lon0, lon0) != 0) return false;
      if ((defaultMapArea == null) != (that.defaultMapArea == null)) return false; // common case is that these are null
      if (defaultMapArea != null && !that.defaultMapArea.equals(defaultMapArea)) return false;

      return proj instanceof MSGnavigation;
  }  */

  /**
   * Create a ProjectionRect from the given LatLonRect.
   * Handles lat/lon points that do not intersect the projection panel.
   *
   * @param rect the LatLonRect
   * @return ProjectionRect, or null if no part of the LatLonRect intersects the projection plane
   */
  @Override
  public ProjectionRect latLonToProjBB(LatLonRect rect) {
    BoundingBoxHelper bbhelper = new BoundingBoxHelper(this, maxR);
    return bbhelper.latLonToProjBB(rect);
  }

  public double getLon0() {
    return lon0;  // Exposed for testing in MSGnavigationTest.
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////////////

  static void tryit(double want, double x) {
    System.out.printf("x = %f %f %f %n", x, x / want, want / x);
  }

  static private void doOne(ProjectionImpl proj, double lat, double lon) {
    LatLonPointImpl startL = new LatLonPointImpl(lat, lon);
    ProjectionPoint p = proj.latLonToProj(startL);
    LatLonPointImpl endL = (LatLonPointImpl) proj.projToLatLon(p);

    System.out.println("start  = " + startL.toString(8));
    System.out.println("xy   = " + p.toString());
    System.out.println("end  = " + endL.toString(8));

  }

  static private void doTwo(ProjectionImpl proj, double x, double y) {
    ProjectionPointImpl startL = new ProjectionPointImpl(x, y);
    LatLonPoint p = proj.projToLatLon(startL);
    ProjectionPointImpl endL = (ProjectionPointImpl) proj.latLonToProj(p);

    System.out.println("start  = " + startL.toString());
    System.out.println("lat,lon   = " + p.toString());
    System.out.println("end  = " + endL.toString());

  }

  public static void main(String arg[]) {
    double dx = 1207;
    double dy = 1189;
    double nr = 6610700.0;

    double scanx = 2 * Math.asin(1.e6 / nr) / dx;
    double scany = 2 * Math.asin(1.e6 / nr) / dy;
    System.out.printf("scanx = %g urad %n", scanx * 1e6);
    System.out.printf("scany = %g urad %n", scany * 1e6);

    double scan2 = 2 * Math.asin(1.e6 / nr) / 3566;
    System.out.printf("scan2 = %g urad %n", scan2 * 1e6);

    /* MSGnavigation msg = new MSGnavigation();
   System.out.printf("const1 = %f %n",msg.const1);
   System.out.printf("1/const1 = %f %n",1.0/msg.const1);
   System.out.printf("const2 = %10.8f %n",msg.const2);
   System.out.printf("1/const) = %f %n",1.0/msg.const2);
   System.out.printf("1/(1+const2) = %f %n",1.0/(1+msg.const2));
   System.out.printf("1/(1-const2) = %f %n",1.0/(1-msg.const2));

   double s = Math.sqrt(1737121856.);
   System.out.printf("sqrt = %f %n",s);
   System.out.printf("try = %f %n",SAT_HEIGHT*SAT_HEIGHT - R_EQ * R_EQ);

   /* tryit(s, SAT_HEIGHT/msg.const1);
   tryit(s, SAT_HEIGHT/(1+msg.const2));
   tryit(s, SAT_HEIGHT/(1+msg.const2*msg.const2));
   tryit(s, SAT_HEIGHT/msg.const1*msg.const1);
   tryit(s, SAT_HEIGHT/msg.const1*(1+msg.const2));

   tryit(s, SAT_HEIGHT*(1+msg.const2));

   /*
       :grid_mapping_name = "MSGnavigation";
  :longitude_of_projection_origin = 0.0; // double
  :latitude_of_projection_origin = 0.0; // double
  :semi_major_axis = 6378.14013671875; // double
  :semi_minor_axis = 6356.75537109375; // double
  :height_from_earth_center_km = 42163.97100180664; // double
  :dx = 1207.0; // double
  :dy = 1189.0; // double
    */

    // MSGnavigation m = new MSGnavigation(0, 0, 6378.14013671875, 6356.75537109375, 42163.97100180664);

    //doOne(m, 11, 51);
    //doOne(m, -34, 18);
    /* doTwo(m, 100, 100);
 doTwo(m, 5000, 5000);

 m = new MSGnavigation(0, 0, 6378.14013671875, 6356.75537109375, 42163.97100180664, 0, 0);
 System.out.printf("%ncfac = %f r = %f %n", m.cfac, CFAC/m.cfac);
 System.out.printf("lfac = %f r = %f %n", m.lfac, CFAC/m.lfac);

 doTwo(m, 100, 100);
 doTwo(m, 5000, 5000); */

  }
}