cdm/src/main/java/ucar/nc2/iosp/uamiv/UAMIVServiceProvider.java
public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask) throws IOException {
    /*
     * <b>open</b> initializes the file meta data and creates all variables.
     * The meta-data and variable information is gathered from the UAM-IV
     * header.  The header format is detailed in the CAMx User's 
     * guide and copied here.
     * 
     * Header:
     * name,note,ione,nspec,ibdate,btime,iedate,etime
     * rdum,rdum,iutm,xorg,yorg,delx,dely,nx,ny,nz,idum,idum,rdum,rdum,rdum
     * ione,ione,nx,ny
     * (mspec(l),l=1,nspec)
     *
     * name - Text string (character*4(10) array)
     * note - Text string containing file description (character*4(60) array)
     * ione - Dummy variable = 1
     * nspec - Number of species on file
     * ibdate - Beginning date (YYJJJ)
     * btime - Beginning hour (HHMM)
     * iedate - Ending date (YYJJJ)
     * etime - Ending hour (HHMM)
     * rdum - Dummy real variable
     * iutm - UTM zone (ignored for other projections)
     * xorg - Grid x-origin at southwest corner of domain (m or degrees longitude)
     * yorg - Grid y-origin at southwest corner of domain (m or degrees latitude)
     * delx - Cell size in x-direction (m or degrees longitude)
     * dely - Cell size in y-direction (m or degrees longitude)
     * nx - Number of grid columns
     * ny - Number of grid rows
     * nz - Number of layers
     * idum - Dummy integer variable
     * mspec - Species names for nspec species (character*4(10,nspec) array)
     *
     *
     *   time step is HHMMSS
     *
     *  the projection is:
     *   LCC // >  :GDTYP = 2; // int
     *   First True Latitude (Alpha):  	30N // >  :P_ALP = 30.0; // double
     *   Second True Latitude (Beta): 	60N // >  :P_BET = 60.0; // double
     *   Central Longitude (Gamma): 	100W //>  :XCENT = -100.0; // double
     *   Projection Origin: 	(100W, 40N) //>  :YCENT = 40.0; // double
     *
     */
    // Internalize raf and ncfile
    super.open(raf, ncfile, cancelTask);

    // set raf to big endian and start at the beginning
    raf.order(RandomAccessFile.BIG_ENDIAN);
    raf.seek(0);

    // Read first line of UAM-IV header
    raf.skipBytes(4); // Skip record pad
    String name = raf.readString(40); // read 40 name
    String note = raf.readString(240);
    int itzone = raf.readInt(); // Read the time zone
    int nspec = raf.readInt(); // Read number of species
    int bdate = raf.readInt(); // get file start date
    float btime = raf.readFloat(); // get file start time
    int edate = raf.readInt(); // get file end date
    float etime = raf.readFloat(); // get file end time
    int btimei = (int) btime; // convert btime to an integer

    // CAMx times are sometimes provided as HH or HHMM.
    // IOAPI times are always provided as HHMMSS.
    // CAMx times less than 100 are HH and should be
    // multipled by 100 to get HHMM.  CAMx times less
    // 10000 are HHMM and should be multipled by 100
    // to get HHMMSS.
    if (btimei < 100) btimei = btimei * 100;
    if (btimei < 10000) btimei = btimei * 100;

    /*
    * Dates are YYJJJ and are heuristically converted
    * to YYYYJJJ based on the following assumption:
    * YY < 70 are 2000
    * YY >= 70 are 1900
    *
    */
    if (bdate < 70000) {
      edate = edate + 2000000;
      bdate = bdate + 2000000;
    } else {
      edate = edate + 1900000;
      bdate = bdate + 1900000;
    }

    raf.skipBytes(4); //Skip record pad

    // Read second line of UAM-IV header
    raf.skipBytes(4); //Skip record pad
    float plon = raf.readFloat(); // get polar longitude
    float plat = raf.readFloat(); // get polar latitude
    int iutm = raf.readInt(); // get utm
    float xorg = raf.readFloat(); // get x origin in meters
    float yorg = raf.readFloat(); // get y origin in meters
    float delx = raf.readFloat(); // get x cell size in meters
    float dely = raf.readFloat(); // get y cell size in meters
    int nx = raf.readInt(); // get number of columns
    int ny = raf.readInt(); // get number of rows
    int nz = raf.readInt(); // get number of layers
    // get projection number
    //    (0: lat-lon;
    //     1: Universal Transverse Mercator;
    //     2: Lambert Conic Conformal;
    //     3: Polar stereographic)
    // These translate to IOAPI GDTYP3D values 1, 5, 2, and 6 respectively
    int iproj = raf.readInt(); 
    int istag = raf.readInt(); // Read stagger indicator
    float tlat1 = raf.readFloat(); // Read true latitude 1
    float tlat2 = raf.readFloat(); // Read true latitude 2
    raf.skipBytes(4); //Skip 1 dummies
    raf.skipBytes(4); //Skip record pad

    // Read third line of UAM-IV header
    raf.skipBytes(4); //Skip record pad
    raf.skipBytes(8); //Skip 2 dummies
    int nx2 = raf.readInt(); // duplicate number of columns
    int ny2 = raf.readInt(); // duplicate number of rows
    raf.skipBytes(8); //Skip 2 dummies    
    nz = Math.max(nz, 1); // number of layers; Emissions files occasionally report 0 layers
    /*
     * 1) Read each species name
     * 2) remove white space from the name
     * 3) store the names
     * 4) internalize them
     */
    int count = 0;
    String[] spc_names = new String[nspec];
    while (count < nspec) {
      String spc = raf.readString(40); // 1) read species name
      spc_names[count++] = spc.replace(" ", ""); // 2&3) store name without whitespace
    }
    this.species_names = spc_names; // 4) internalize names
    raf.skipBytes(4); // Skip record pad

    // Note this position; it is the start of the data block
    this.data_start = raf.getFilePointer();

    // Note the number of float equivalents (4 byte chunks) in data block
    int data_length_float_equivalents = ((int) raf.length() - (int) data_start) / 4;

    // Store 2D value size
    this.n2dvals = nx * ny;

    // Store 3D value size
    this.n3dvals = nx * ny * nz;

    // Store 2D binary data block size: include values (nx*ny), 
    // species name (10), a dummy (1) and 2 record pads
    int spc_2D_block = nx * ny + 10 + 2 + 1;

    // Store 3D binary data block size
    this.spc_3D_block = spc_2D_block * nz;

    // Store whole data block size; includes date (6)
    this.data_block = this.spc_3D_block * nspec + 6;

    // Store the number of times
    int ntimes = data_length_float_equivalents / this.data_block;


    // Add dimensions based on header values
    ncfile.addDimension(null, new Dimension("TSTEP", ntimes, true));
    ncfile.addDimension(null, new Dimension("LAY", nz, true));
    ncfile.addDimension(null, new Dimension("ROW", ny, true));
    ncfile.addDimension(null, new Dimension("COL", nx, true));

    // Force sync of dimensions
    ncfile.finish();
    count = 0;

    /*
    * For each species, create a variable with long_name,
    * and var_desc, and units.  long_name and var_desc are
    * simply the species name.  units is heuristically
    * determined from the name
    */
    HashSet<String> AeroSpcs = new HashSet<>(Arrays.asList( "PSO4", "PNO3", "PNH4", "PH2O", "SOPA", "SOPB",  "NA", "PCL", "POA", "PEC", "FPRM", "FCRS", "CPRM", "CCRS"));
    HashSet<String> LULC = new HashSet<>(Arrays.asList("WATER", "ICE", "LAKE", "ENEEDL", "EBROAD", "DNEEDL", "DBROAD", "TBROAD", "DDECID", "ESHRUB", "DSHRUB", "TSHRUB", "SGRASS", "LGRASS", "CROPS", "RICE", "SUGAR", "MAIZE", "COTTON", "ICROPS", "URBAN", "TUNDRA", "SWAMP", "DESERT", "MWOOD", "TFOREST"));
    
    while (count < nspec) {
      String spc = spc_names[count++];
      Variable temp = ncfile.addVariable(null, spc, DataType.FLOAT, "TSTEP LAY ROW COL");
      if (spc.equals(WINDX) || spc.equals(WINDY) ||
              spc.equals(SPEED)) {
        temp.addAttribute(new Attribute(CDM.UNITS, "m/s"));
      } else if (spc.equals(VERTDIFF)) {
        temp.addAttribute(new Attribute(CDM.UNITS, "m**2/s"));
      } else if (spc.equals(TEMP)) {
        temp.addAttribute(new Attribute(CDM.UNITS, "K"));
      } else if (spc.equals(PRESS)) {
        temp.addAttribute(new Attribute(CDM.UNITS, "hPa"));
      } else if (spc.equals(HEIGHT) || spc.equals(PBL)) {
        temp.addAttribute(new Attribute(CDM.UNITS, "m"));
      } else if (spc.equals(CLDWATER) || spc.equals(PRECIP) || spc.equals(RAIN)) {
        temp.addAttribute(new Attribute(CDM.UNITS, "g/m**3"));
      } else if (spc.equals(CLDOD) || spc.equals("CLOUDOD")) {
        temp.addAttribute(new Attribute(CDM.UNITS, "none"));
      } else if (spc.equals("SNOWCOVER")) {
        temp.addAttribute(new Attribute(CDM.UNITS, "yes/no"));        
      } else if (spc.startsWith("SOA") || AeroSpcs.contains(spc)) {
        if (name.equals(EMISSIONS)) {
          temp.addAttribute(new Attribute(CDM.UNITS, "g/time"));
        } else {
          temp.addAttribute(new Attribute(CDM.UNITS, "ug/m**3"));
        }
      } else if (LULC.contains(spc)) {
          temp.addAttribute(new Attribute(CDM.UNITS, "fraction"));
      } else if (spc.lastIndexOf("_") > -1) {
        String tmpunit = spc.substring(spc.lastIndexOf("_") + 1);
        tmpunit = tmpunit.trim();
        switch (tmpunit) {
          case "M2pS":
            tmpunit = "m**2/s";
            break;
          case "MpS":
            tmpunit = "m/s";
            break;
          case "PPM":
            tmpunit = "ppm";
            break;
          case "MB":
            tmpunit = "millibar";
            break;
          case "GpM3":
            tmpunit = "g/m**3";
            break;
          case "M":
            tmpunit = "m";
            break;
        }
        temp.addAttribute(new Attribute(CDM.UNITS, tmpunit));
      } else {
        if (name.equals(EMISSIONS)) {
          temp.addAttribute(new Attribute(CDM.UNITS, "mol/time"));
        } else {
          temp.addAttribute(new Attribute(CDM.UNITS, "ppm"));
        }
      }
      temp.addAttribute(new Attribute(CDM.LONG_NAME, spc));
      temp.addAttribute(new Attribute("var_desc", spc));
    }

    /*
    * Create 1...n array of "sigma" values
    */
    double[] sigma = new double[nz + 1];
    count = 0;
    while (count < nz + 1) {
      sigma[count++] = count;
    }
    int[] size = new int[1];
    size[0] = nz + 1;
    Array sigma_arr = Array.factory(DataType.DOUBLE, size, sigma);

    /*
    * Add meta-data according to the IOAPI conventions
    * http://www.baronams.com/products/ioapi
    */
    ncfile.addAttribute(null, new Attribute("VGLVLS", sigma_arr));
    ncfile.addAttribute(null, new Attribute("SDATE", bdate));
    ncfile.addAttribute(null, new Attribute("STIME", btimei));
    ncfile.addAttribute(null, new Attribute("TSTEP", 10000));
    ncfile.addAttribute(null, new Attribute("NSTEPS", ntimes));
    ncfile.addAttribute(null, new Attribute("NLAYS", nz));
    ncfile.addAttribute(null, new Attribute("NROWS", ny));
    ncfile.addAttribute(null, new Attribute("NCOLS", nx));
    ncfile.addAttribute(null, new Attribute("XORIG", (double) xorg));
    ncfile.addAttribute(null, new Attribute("YORIG", (double) yorg));
    ncfile.addAttribute(null, new Attribute("XCELL", (double) delx));
    ncfile.addAttribute(null, new Attribute("YCELL", (double) dely));

    /*
     * IOAPI Projection parameters are provided by a colocated camxproj.txt file;
     *
     * to do:
     * 1) needs earth radius
     * 2) needs better error checking
    */
    Integer gdtyp = 2;
    Double p_alp = 20.;
    Double p_bet = 60.;
    Double p_gam = 0.;
    Double xcent = -95.;
    Double ycent = 25.;
    if (!((iproj == 0) && (tlat1 == 0) && (tlat2 == 0) && (plon == 0) && (plat == 0))) {
      xcent = (double) plon;
      ycent = (double) plat;
      if (iproj == 0) {
        // Lat-Lon (iproj=0) has no additional information
        gdtyp = 1;
      } else if (iproj == 1){
        // UTM uses only iutm 
        gdtyp = 5;
        p_alp = (double) iutm;
      } else if (iproj == 2){
        gdtyp = 2;
        p_alp = (double) tlat1;
        p_bet = (double) tlat2;
        p_gam = (double) plon;
      } else if (iproj == 3){
        gdtyp = 6;
        if (plat == 90){
          p_alp = 1.;
        } else if (plat == -90) {
          p_alp = -1.;
        }
        p_bet = (double) tlat1;
        p_gam = (double) plon;
      } else {
        gdtyp = 2;
        p_alp = 20.;
        p_bet = 60.;
        p_gam = 0.;
        xcent = -95.;
        ycent = 25.;
      }
    }

    String thisLine;
    String projpath = raf.getLocation();
    Boolean lgdtyp = false;
    Boolean lp_alp = false;
    Boolean lp_bet = false;
    Boolean lp_gam = false;
    Boolean lxcent = false;
    Boolean lycent = false;
    int lastIndex = projpath.lastIndexOf(File.separator);
    if (lastIndex <= 0)
      lastIndex = projpath.lastIndexOf('/');
    if (lastIndex > 0)
      projpath = projpath.substring(0, lastIndex);
    projpath = projpath + File.separator + "camxproj.txt";
    File paramFile = new File(projpath);

    if (paramFile.exists()) {
      try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(paramFile), CDM.UTF8))) {
        while ((thisLine = br.readLine()) != null) {
          if (thisLine.length() == 0) continue;
          if (thisLine.charAt(0) == '#') continue;
          String[] key_value = thisLine.split("=");
          switch (key_value[0]) {
            case "GDTYP":
              gdtyp = Integer.parseInt(key_value[1]);
              lgdtyp = true;
              break;
            case "P_ALP":
              p_alp = Double.parseDouble(key_value[1]);
              lp_alp = true;
              break;
            case "P_BET":
              p_bet = Double.parseDouble(key_value[1]);
              lp_bet = true;
              break;
            case "P_GAM":
              p_gam = Double.parseDouble(key_value[1]);
              lp_gam = true;
              break;
            case "YCENT":
              ycent = Double.parseDouble(key_value[1]);
              lycent = true;
              break;
            case "XCENT":
              xcent = Double.parseDouble(key_value[1]);
              lxcent = true;
              break;
          }
        }
      }
      if (!lgdtyp) log.warn("GDTYP not found; using " + gdtyp.toString());
      if (!lp_alp) log.warn("P_ALP not found; using " + p_alp.toString());
      if (!lp_bet) log.warn("P_BET not found; using " + p_bet.toString());
      if (!lp_gam) log.warn("P_GAM not found; using " + p_gam.toString());
      if (!lxcent) log.warn("XCENT not found; using " + xcent.toString());
      if (!lycent) log.warn("YCENT not found; using " + ycent.toString());

    } else {
      if (log.isDebugEnabled()) log.debug("UAMIVServiceProvider: adding projection file");
      try (FileOutputStream out = new FileOutputStream(paramFile)) {
        OutputStreamWriter fout = new OutputStreamWriter(out, CDM.utf8Charset);
        BufferedWriter bw = new BufferedWriter(fout);

        bw.write("# Projection parameters are based on IOAPI.  For details, see www.baronams.com/products/ioapi/GRIDS.html");
        bw.newLine();
        bw.write("GDTYP=");
        bw.write(gdtyp.toString());
        bw.newLine();
        bw.write("P_ALP=");
        bw.write(p_alp.toString());
        bw.newLine();
        bw.write("P_BET=");
        bw.write(p_bet.toString());
        bw.newLine();
        bw.write("P_GAM=");
        bw.write(p_gam.toString());
        bw.newLine();
        bw.write("XCENT=");
        bw.write(xcent.toString());
        bw.newLine();
        bw.write("YCENT=");
        bw.write(ycent.toString());
        bw.newLine();
        bw.flush();
        bw.close();
      }
    }

    ncfile.addAttribute(null, new Attribute("GDTYP", gdtyp));
    ncfile.addAttribute(null, new Attribute("P_ALP", p_alp));
    ncfile.addAttribute(null, new Attribute("P_BET", p_bet));
    ncfile.addAttribute(null, new Attribute("P_GAM", p_gam));
    ncfile.addAttribute(null, new Attribute("XCENT", xcent));
    ncfile.addAttribute(null, new Attribute("YCENT", ycent));
  }
public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask) throws IOException {
    /*
     * <b>open</b> initializes the file meta data and creates all variables.
     * The meta-data and variable information is gathered from the UAM-IV
     * header.  The header format is detailed in the CAMx User's 
     * guide and copied here.
     * 
     * Header:
     * name,note,ione,nspec,ibdate,btime,iedate,etime
     * rdum,rdum,iutm,xorg,yorg,delx,dely,nx,ny,nz,idum,idum,rdum,rdum,rdum
     * ione,ione,nx,ny
     * (mspec(l),l=1,nspec)
     *
     * name - Text string (character*4(10) array)
     * note - Text string containing file description (character*4(60) array)
     * ione - Dummy variable = 1
     * nspec - Number of species on file
     * ibdate - Beginning date (YYJJJ)
     * btime - Beginning hour (HHMM)
     * iedate - Ending date (YYJJJ)
     * etime - Ending hour (HHMM)
     * rdum - Dummy real variable
     * iutm - UTM zone (ignored for other projections)
     * xorg - Grid x-origin at southwest corner of domain (m or degrees longitude)
     * yorg - Grid y-origin at southwest corner of domain (m or degrees latitude)
     * delx - Cell size in x-direction (m or degrees longitude)
     * dely - Cell size in y-direction (m or degrees longitude)
     * nx - Number of grid columns
     * ny - Number of grid rows
     * nz - Number of layers
     * idum - Dummy integer variable
     * mspec - Species names for nspec species (character*4(10,nspec) array)
     *
     *
     *   time step is HHMMSS
     *
     *  the projection is:
     *   LCC // >  :GDTYP = 2; // int
     *   First True Latitude (Alpha):  	30N // >  :P_ALP = 30.0; // double
     *   Second True Latitude (Beta): 	60N // >  :P_BET = 60.0; // double
     *   Central Longitude (Gamma): 	100W //>  :XCENT = -100.0; // double
     *   Projection Origin: 	(100W, 40N) //>  :YCENT = 40.0; // double
     *
     */
    // Internalize raf and ncfile
    super.open(raf, ncfile, cancelTask);

    // set raf to big endian and start at the beginning
    raf.order(RandomAccessFile.BIG_ENDIAN);
    raf.seek(0);

    // Read first line of UAM-IV header
    raf.skipBytes(4); // Skip record pad
    String name = raf.readString(40); // read 40 name
    String note = raf.readString(240);
    int itzone = raf.readInt(); // Read the time zone
    int nspec = raf.readInt(); // Read number of species
    int bdate = raf.readInt(); // get file start date
    float btime = raf.readFloat(); // get file start time
    int edate = raf.readInt(); // get file end date
    float etime = raf.readFloat(); // get file end time
    int btimei = (int) btime; // convert btime to an integer

    // CAMx times are sometimes provided as HH or HHMM.
    // IOAPI times are always provided as HHMMSS.
    // CAMx times less than 100 are HH and should be
    // multipled by 100 to get HHMM.  CAMx times less
    // 10000 are HHMM and should be multipled by 100
    // to get HHMMSS.
    if (btimei < 100) btimei = btimei * 100;
    if (btimei < 10000) btimei = btimei * 100;

    /*
    * Dates are YYJJJ and are heuristically converted
    * to YYYYJJJ based on the following assumption:
    * YY < 70 are 2000
    * YY >= 70 are 1900
    *
    */
    if (bdate < 70000) {
      edate = edate + 2000000;
      bdate = bdate + 2000000;
    } else {
      edate = edate + 1900000;
      bdate = bdate + 1900000;
    }

    raf.skipBytes(4); //Skip record pad

    // Read second line of UAM-IV header
    raf.skipBytes(4); //Skip record pad
    float plon = raf.readFloat(); // get polar longitude
    float plat = raf.readFloat(); // get polar latitude
    int iutm = raf.readInt(); // get utm
    float xorg = raf.readFloat(); // get x origin in meters
    float yorg = raf.readFloat(); // get y origin in meters
    float delx = raf.readFloat(); // get x cell size in meters
    float dely = raf.readFloat(); // get y cell size in meters
    int nx = raf.readInt(); // get number of columns
    int ny = raf.readInt(); // get number of rows
    int nz = raf.readInt(); // get number of layers
    // get projection number
    //    (0: lat-lon;
    //     1: Universal Transverse Mercator;
    //     2: Lambert Conic Conformal;
    //     3: Polar stereographic)
    // These translate to IOAPI GDTYP3D values 1, 5, 2, and 6 respectively
    int iproj = raf.readInt(); 
    int istag = raf.readInt(); // Read stagger indicator
    float tlat1 = raf.readFloat(); // Read true latitude 1
    float tlat2 = raf.readFloat(); // Read true latitude 2
    raf.skipBytes(4); //Skip 1 dummies
    raf.skipBytes(4); //Skip record pad

    // Read third line of UAM-IV header
    raf.skipBytes(4); //Skip record pad
    raf.skipBytes(8); //Skip 2 dummies
    int nx2 = raf.readInt(); // duplicate number of columns
    int ny2 = raf.readInt(); // duplicate number of rows
    raf.skipBytes(8); //Skip 2 dummies    
    nz = Math.max(nz, 1); // number of layers; Emissions files occasionally report 0 layers
    /*
     * 1) Read each species name
     * 2) remove white space from the name
     * 3) store the names
     * 4) internalize them
     */
    int count = 0;
    String[] spc_names = new String[nspec];
    while (count < nspec) {
      String spc = raf.readString(40); // 1) read species name
      spc_names[count++] = spc.replace(" ", ""); // 2&3) store name without whitespace
    }
    this.species_names = spc_names; // 4) internalize names
    raf.skipBytes(4); // Skip record pad

    // Note this position; it is the start of the data block
    this.data_start = raf.getFilePointer();

    // Note the number of float equivalents (4 byte chunks) in data block
    int data_length_float_equivalents = ((int) raf.length() - (int) data_start) / 4;

    // Store 2D value size
    this.n2dvals = nx * ny;

    // Store 3D value size
    this.n3dvals = nx * ny * nz;

    // Store 2D binary data block size: include values (nx*ny), 
    // species name (10), a dummy (1) and 2 record pads
    int spc_2D_block = nx * ny + 10 + 2 + 1;

    // Store 3D binary data block size
    this.spc_3D_block = spc_2D_block * nz;

    // Store whole data block size; includes date (6)
    this.data_block = this.spc_3D_block * nspec + 6;

    // Store the number of times
    int ntimes = data_length_float_equivalents / this.data_block;


    // Add dimensions based on header values
    ncfile.addDimension(null, new Dimension("TSTEP", ntimes, true));
    ncfile.addDimension(null, new Dimension("LAY", nz, true));
    ncfile.addDimension(null, new Dimension("ROW", ny, true));
    ncfile.addDimension(null, new Dimension("COL", nx, true));

    // Force sync of dimensions
    ncfile.finish();
    count = 0;

    /*
    * For each species, create a variable with long_name,
    * and var_desc, and units.  long_name and var_desc are
    * simply the species name.  units is heuristically
    * determined from the name
    */
    HashSet<String> AeroSpcs = new HashSet<>(Arrays.asList( "PSO4", "PNO3", "PNH4", "PH2O", "SOPA", "SOPB",  "NA", "PCL", "POA", "PEC", "FPRM", "FCRS", "CPRM", "CCRS"));
    HashSet<String> LULC = new HashSet<>(Arrays.asList("WATER", "ICE", "LAKE", "ENEEDL", "EBROAD", "DNEEDL", "DBROAD", "TBROAD", "DDECID", "ESHRUB", "DSHRUB", "TSHRUB", "SGRASS", "LGRASS", "CROPS", "RICE", "SUGAR", "MAIZE", "COTTON", "ICROPS", "URBAN", "TUNDRA", "SWAMP", "DESERT", "MWOOD", "TFOREST"));
    
    while (count < nspec) {
      String spc = spc_names[count++];
      Variable temp = ncfile.addVariable(null, spc, DataType.FLOAT, "TSTEP LAY ROW COL");
      if (spc.equals(WINDX) || spc.equals(WINDY) ||
              spc.equals(SPEED)) {
        temp.addAttribute(new Attribute(CDM.UNITS, "m/s"));
      } else if (spc.equals(VERTDIFF)) {
        temp.addAttribute(new Attribute(CDM.UNITS, "m**2/s"));
      } else if (spc.equals(TEMP)) {
        temp.addAttribute(new Attribute(CDM.UNITS, "K"));
      } else if (spc.equals(PRESS)) {
        temp.addAttribute(new Attribute(CDM.UNITS, "hPa"));
      } else if (spc.equals(HEIGHT) || spc.equals(PBL)) {
        temp.addAttribute(new Attribute(CDM.UNITS, "m"));
      } else if (spc.equals(CLDWATER) || spc.equals(PRECIP) || spc.equals(RAIN)) {
        temp.addAttribute(new Attribute(CDM.UNITS, "g/m**3"));
      } else if (spc.equals(CLDOD) || spc.equals("CLOUDOD")) {
        temp.addAttribute(new Attribute(CDM.UNITS, "none"));
      } else if (spc.equals("SNOWCOVER")) {
        temp.addAttribute(new Attribute(CDM.UNITS, "yes/no"));        
      } else if (spc.startsWith("SOA") || AeroSpcs.contains(spc)) {
        if (name.equals(EMISSIONS)) {
          temp.addAttribute(new Attribute(CDM.UNITS, "g/time"));
        } else {
          temp.addAttribute(new Attribute(CDM.UNITS, "ug/m**3"));
        }
      } else if (LULC.contains(spc)) {
          temp.addAttribute(new Attribute(CDM.UNITS, "fraction"));
      } else if (spc.lastIndexOf("_") > -1) {
        String tmpunit = spc.substring(spc.lastIndexOf("_") + 1);
        tmpunit = tmpunit.trim();
        switch (tmpunit) {
          case "M2pS":
            tmpunit = "m**2/s";
            break;
          case "MpS":
            tmpunit = "m/s";
            break;
          case "PPM":
            tmpunit = "ppm";
            break;
          case "MB":
            tmpunit = "millibar";
            break;
          case "GpM3":
            tmpunit = "g/m**3";
            break;
          case "M":
            tmpunit = "m";
            break;
        }
        temp.addAttribute(new Attribute(CDM.UNITS, tmpunit));
      } else {
        if (name.equals(EMISSIONS)) {
          temp.addAttribute(new Attribute(CDM.UNITS, "mol/time"));
        } else {
          temp.addAttribute(new Attribute(CDM.UNITS, "ppm"));
        }
      }
      temp.addAttribute(new Attribute(CDM.LONG_NAME, spc));
      temp.addAttribute(new Attribute("var_desc", spc));
    }

    /*
    * Create 1...n array of "sigma" values
    */
    double[] sigma = new double[nz + 1];
    count = 0;
    while (count < nz + 1) {
      sigma[count++] = count;
    }
    int[] size = new int[1];
    size[0] = nz + 1;
    Array sigma_arr = Array.factory(DataType.DOUBLE, size, sigma);

    /*
    * Add meta-data according to the IOAPI conventions
    * http://www.baronams.com/products/ioapi
    */
    ncfile.addAttribute(null, new Attribute("VGLVLS", sigma_arr));
    ncfile.addAttribute(null, new Attribute("SDATE", bdate));
    ncfile.addAttribute(null, new Attribute("STIME", btimei));
    ncfile.addAttribute(null, new Attribute("TSTEP", 10000));
    ncfile.addAttribute(null, new Attribute("NSTEPS", ntimes));
    ncfile.addAttribute(null, new Attribute("NLAYS", nz));
    ncfile.addAttribute(null, new Attribute("NROWS", ny));
    ncfile.addAttribute(null, new Attribute("NCOLS", nx));
    ncfile.addAttribute(null, new Attribute("XORIG", (double) xorg));
    ncfile.addAttribute(null, new Attribute("YORIG", (double) yorg));
    ncfile.addAttribute(null, new Attribute("XCELL", (double) delx));
    ncfile.addAttribute(null, new Attribute("YCELL", (double) dely));

    /*
     * IOAPI Projection parameters are provided by a colocated camxproj.txt file;
     *
     * to do:
     * 1) needs earth radius
     * 2) needs better error checking
    */
    Integer gdtyp = 2;
    Double p_alp = 20.;
    Double p_bet = 60.;
    Double p_gam = 0.;
    Double xcent = -95.;
    Double ycent = 25.;
    if (!((iproj == 0) && (tlat1 == 0) && (tlat2 == 0) && (plon == 0) && (plat == 0))) {
      xcent = (double) plon;
      ycent = (double) plat;
      if (iproj == 0) {
        // Lat-Lon (iproj=0) has no additional information
        gdtyp = 1;
      } else if (iproj == 1){
        // UTM uses only iutm 
        gdtyp = 5;
        p_alp = (double) iutm;
      } else if (iproj == 2){
        gdtyp = 2;
        p_alp = (double) tlat1;
        p_bet = (double) tlat2;
        p_gam = (double) plon;
      } else if (iproj == 3){
        gdtyp = 6;
        if (plat == 90){
          p_alp = 1.;
        } else if (plat == -90) {
          p_alp = -1.;
        }
        p_bet = (double) tlat1;
        p_gam = (double) plon;
      } else {
        gdtyp = 2;
        p_alp = 20.;
        p_bet = 60.;
        p_gam = 0.;
        xcent = -95.;
        ycent = 25.;
      }
    }

    String thisLine;
    String projpath = raf.getLocation();
    Boolean lgdtyp = false;
    Boolean lp_alp = false;
    Boolean lp_bet = false;
    Boolean lp_gam = false;
    Boolean lxcent = false;
    Boolean lycent = false;
    int lastIndex = projpath.lastIndexOf(File.separator);
    if (lastIndex <= 0)
      lastIndex = projpath.lastIndexOf('/');
    if (lastIndex > 0)
      projpath = projpath.substring(0, lastIndex);
    projpath = projpath + File.separator + "camxproj.txt";
    File paramFile = new File(projpath);

    if (paramFile.exists()) {
      try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(paramFile), CDM.UTF8))) {
        while ((thisLine = br.readLine()) != null) {
          if (thisLine.length() == 0) continue;
          if (thisLine.charAt(0) == '#') continue;
          String[] key_value = thisLine.split("=");
          switch (key_value[0]) {
            case "GDTYP":
              gdtyp = Integer.parseInt(key_value[1]);
              lgdtyp = true;
              break;
            case "P_ALP":
              p_alp = Double.parseDouble(key_value[1]);
              lp_alp = true;
              break;
            case "P_BET":
              p_bet = Double.parseDouble(key_value[1]);
              lp_bet = true;
              break;
            case "P_GAM":
              p_gam = Double.parseDouble(key_value[1]);
              lp_gam = true;
              break;
            case "YCENT":
              ycent = Double.parseDouble(key_value[1]);
              lycent = true;
              break;
            case "XCENT":
              xcent = Double.parseDouble(key_value[1]);
              lxcent = true;
              break;
          }
        }
      }
      if (!lgdtyp) log.warn("GDTYP not found; using " + gdtyp.toString());
      if (!lp_alp) log.warn("P_ALP not found; using " + p_alp.toString());
      if (!lp_bet) log.warn("P_BET not found; using " + p_bet.toString());
      if (!lp_gam) log.warn("P_GAM not found; using " + p_gam.toString());
      if (!lxcent) log.warn("XCENT not found; using " + xcent.toString());
      if (!lycent) log.warn("YCENT not found; using " + ycent.toString());

    } else {
      if (log.isDebugEnabled()) log.debug("UAMIVServiceProvider: adding projection file");
      try (FileOutputStream out = new FileOutputStream(paramFile)) {
        OutputStreamWriter fout = new OutputStreamWriter(out, CDM.utf8Charset);
        BufferedWriter bw = new BufferedWriter(fout);

        bw.write("# Projection parameters are based on IOAPI.  For details, see www.baronams.com/products/ioapi/GRIDS.html");
        bw.newLine();
        bw.write("GDTYP=");
        bw.write(gdtyp.toString());
        bw.newLine();
        bw.write("P_ALP=");
        bw.write(p_alp.toString());
        bw.newLine();
        bw.write("P_BET=");
        bw.write(p_bet.toString());
        bw.newLine();
        bw.write("P_GAM=");
        bw.write(p_gam.toString());
        bw.newLine();
        bw.write("XCENT=");
        bw.write(xcent.toString());
        bw.newLine();
        bw.write("YCENT=");
        bw.write(ycent.toString());
        bw.newLine();
        bw.flush();
        bw.close();
      }
    }

    ncfile.addAttribute(null, new Attribute("GDTYP", gdtyp));
    ncfile.addAttribute(null, new Attribute("P_ALP", p_alp));
    ncfile.addAttribute(null, new Attribute("P_BET", p_bet));
    ncfile.addAttribute(null, new Attribute("P_GAM", p_gam));
    ncfile.addAttribute(null, new Attribute("XCENT", xcent));
    ncfile.addAttribute(null, new Attribute("YCENT", ycent));
  }
Open existing file, and populate ncfile with it.

@param raf        the file to work on, it has already passed the isValidFile() test.
@param ncfile     add objects to this NetcdfFile
@param cancelTask used to monito user cancellation; may be null.
@throws IOException