LMODWeppData.java [src/java/m/wepp] Revision:   Date:
package m.wepp;

import csip.Config;
import csip.api.client.ModelDataServiceCall;
import csip.SessionLogger;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.GregorianCalendar;
import java.util.logging.Level;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONObject;

/**
 * This class manages the individual WEPP parameters.
 *
 * @author jrf
 */
public class LMODWeppData {

  List<TillageRec> tillages;
  List<VegetationRec> vegs;
  List<ResidueRec> residues;
  List<IrrigationRec> irrigations;
  List<ContourRec> contours;
  JSONArray woodyvegs;

  JSONObject vegBlobsCRLMOD;
  JSONObject opBlobsCRLMOD;
  JSONObject resBlobsCRLMOD;

  WEPSTags lastProcess;

  JSONObject debugData;
  SessionLogger LOG;

  boolean crlmod31_format;
  String weppOpParamSetName;
  String weppResParamSetName;
  String weppCropParamSetName;

  String cropsURL;
  String operationsURL;
  String residueURL;

  String dataversion;

  static boolean lmodCache = Config.getBoolean("lmod.cache", false);


  /**
   * This is constructed once for each management rotation and holds all the
   * WEPP data used in that management.
   *
   * @param debugData JSON data that can overwrite crop database parameters.
   * @param log CSIP logging pointer
   */
  LMODWeppData(JSONObject debugData, SessionLogger log, String cropsURL, String operationsURL, String residueURL, String version) {
    this.LOG = log;
    this.cropsURL = cropsURL;
    this.operationsURL = operationsURL;
    this.residueURL = residueURL;
    this.dataversion = version;

    lastProcess = new WEPSTags();
    tillages = new ArrayList<>();
    vegs = new ArrayList<>();
    residues = new ArrayList<>();
    irrigations = new ArrayList<>();
    contours = new ArrayList<>();
    woodyvegs = new JSONArray();

    vegBlobsCRLMOD = new JSONObject();
    opBlobsCRLMOD = new JSONObject();
    resBlobsCRLMOD = new JSONObject();

    this.debugData = debugData;
    crlmod31_format = true;

    if (cropsURL.endsWith("/2.0") || cropsURL.endsWith("/3.0") || cropsURL.endsWith("/4.0")) {
      crlmod31_format = false;
    }

    if (crlmod31_format) {
      weppOpParamSetName = "weppOpFile";
      weppResParamSetName = "weppResFile";
      weppCropParamSetName = "weppCropFile";
    } else {
      weppOpParamSetName = "weppOpParamSet";
      weppResParamSetName = "weppResParamSet";
      weppCropParamSetName = "weppCropParamSet";
    }
  }

  /**
   * These are from the WEPS portion of an operation
   *
   */
  private static class WEPSTags {

    float fuel;
    float stir;
  };

  /**
   * WEPP parameters for contouring
   */
  private static class ContourRec {

    String weppkey;
    float slope;
    float height;
    float length;
    float spacing;


    /**
     * Converts data into a string that is used in management.
     *
     * @return contour string for .rot file
     */
    public String toDBString() {
      String rec = weppkey + "   1\n";
      rec = rec + "Contour " + weppkey + "\n";
      rec = rec + "comment...\ncomment...\ncomment...\n";
      rec = rec + String.format("%f %f %f %f\n", slope, height, length, spacing);
      return rec;
    }


    /**
     * Constructs a contour record with parameters.
     *
     * @param key contour key name
     * @param slp slope of contour
     * @param hgt height of contour
     * @param len length of contour
     * @param sp spacing of contours
     */
    ContourRec(String key, float slp, float hgt, float len, float sp) {
      weppkey = key;
      slope = slp;
      height = hgt;
      length = len;
      spacing = sp;
    }
  };

  /**
   * WEPP parameters for residues.
   *
   */
  private static class ResidueRec {

    String cropKey;
    float amount;
    boolean isAddOp;
    String weppkey;
    int type;


    /**
     * Construct generic residue
     *
     * @param ty true if add residue, false for remove
     */
    ResidueRec(boolean ty) {
      isAddOp = ty;
      if (isAddOp == true) {
        amount = (float) 250.0;  // add is in pounds
      } else {
        amount = (float) 1.0;  // remove is a fraction
      }
    }


    /**
     * Construct specific residue
     *
     * @param ty true if add residue, false for remove
     * @param key key to assign to this residue
     * @param ckey crop/veg key used by the residue
     * @param amt amount added or removed. Add is kg, remove is fraction.
     */
    ResidueRec(boolean ty, String key, String ckey, float amt) {
      isAddOp = ty;
      weppkey = key;
      cropKey = ckey;
      amount = amt;
    }


    /**
     * Formats the residue record as a string to use in management file.
     *
     * @return string representation of residue record.
     */
    public String toDBString() {
      String rec = weppkey + "   1\n";
      rec = rec + "Residue " + weppkey + "\n";
      rec = rec + "comment...\ncomment...\ncomment...\n";
      if (isAddOp) {
        rec = rec + "1\n";
        double kgamt = (amount * 1.12085) / 10000;
        rec = rec + String.format("0 %f %s\n", kgamt, cropKey);
      } else {
        // amount is fraction 0-1
        rec = rec + "2\n";
        rec = rec + String.format("0 %f\n", amount);
      }
      return rec;
    }
  };

  /**
   * WEPP parameters for irrigation.
   *
   */
  private static class IrrigationRec {

    String weppkey;
    String name;
    int type;  // fixed-stationary(1) or depletion-stationary(0)
    float rate;
    float mindepth;
    float maxdepth;
    float depth;
    float ratio;
    float maxratio;
    float nozzle;
    int startDate, endDate;


    /**
     * Convert the data into a string that can be written to the management
     * file.
     *
     * @return string to include in management file
     */
    public String toDBString() {
      String rec = weppkey + "   1\n";
      float ratemsec = (float) ((rate / 3600) / 1000.0);
      rec = rec + " Irrigation " + weppkey + "\n";
      rec = rec + name + "\n";
      rec = rec + "comment..\ncomment...\n";
      if (type == 0) { // depletion
        rec = rec + "1 1 1\n";
        rec = rec + "0 0 0\n";
        rec = rec + String.format("%f %f # mindepth maxdepth\n", mindepth / 1000.0, maxdepth / 1000.0);
        rec = rec + String.format("   %f %f %f %f %d %d\n", ratemsec, ratio, maxratio, nozzle, startDate, endDate);
      } else {
        rec = rec + "1\n";
        rec = rec + String.format("%f %f %f # rate depth nozzle\n", ratemsec, depth, nozzle);
      }
      return rec;
    }


    /**
     * Construct an irrigation record starting from a specific date to the end
     * of the year.
     *
     * @param date starting date
     */
    IrrigationRec(String date) {
      String mdate[] = date.split("-");
      int yr = Integer.parseInt(mdate[0]);
      int mon = Integer.parseInt(mdate[1]) - 1;
      int day = Integer.parseInt(mdate[2]);

      GregorianCalendar gc = new GregorianCalendar();
      gc.set(GregorianCalendar.DAY_OF_MONTH, day);
      gc.set(GregorianCalendar.MONTH, mon);
      gc.set(GregorianCalendar.YEAR, 2001);
      startDate = gc.get(GregorianCalendar.DAY_OF_YEAR);
      endDate = 365;
    }


    /**
     * Construct an irrigation record with all parameters.
     *
     * @param ty type of irrigation
     * @param rt rate
     * @param mndepth minimum depth
     * @param mxdepth maximum depth
     * @param dep depth
     * @param rat rate
     * @param mrat maximum ratio
     * @param noz nozzle energy
     * @param date date of irrigation
     */
    IrrigationRec(int ty, float rt, float mndepth, float mxdepth, float dep, float rat, float mrat, float noz, String date) {
      type = ty;
      rate = rt;
      mindepth = mndepth;
      maxdepth = mxdepth;
      depth = dep;
      ratio = rat;
      maxratio = mrat;
      nozzle = noz;
      startDate = 1;
      endDate = 365;
    }


    /**
     * Get irrigation parameters from JSON test in database operations record.
     *
     * @param nirr XML node of irrigation data
     * @return true if parsed ok
     */
    public boolean parseIrrigation(Node nirr) {
      NodeList parms = nirr.getChildNodes();
      for (int k = 0; k < parms.getLength(); k++) {
        Node n = parms.item(k);
        if (n.getNodeType() == Node.ELEMENT_NODE) {
          String elem = n.getNodeName();
          String value = n.getTextContent();
          switch (elem) {
            case "name":
              name = value;
              break;
            case "type":
              List<String> irrCodes = Arrays.asList(new String[]{
                "depletion-stationary", "fixed-stationary"
              });
              type = irrCodes.indexOf(value.toLowerCase());
              break;
            case "rate":
              rate = Float.parseFloat(value);
              break;
            case "mindepth":
              mindepth = Float.parseFloat(value);
              break;
            case "maxdepth":
              maxdepth = Float.parseFloat(value);
              break;
            case "depth":
              depth = Float.parseFloat(value);
              break;
            case "ratio":
              ratio = Float.parseFloat(value);
              break;
            case "maxratio":
              maxratio = Float.parseFloat(value);
              break;
            case "nozzle":
              nozzle = Float.parseFloat(value);
              break;
            default:
              break;
          }
        }
      }
      if ((weppkey == null) || (weppkey.isEmpty())) {
        String key;
        if (name.length() > 6) {
          key = name.substring(0, 6);
        } else {
          key = name;
        }
        key = key.toUpperCase();
        key = key.trim();
        String uniqueId = Integer.toUnsignedString(name.hashCode());
        key += "_" + uniqueId.substring(0, Math.min(5, uniqueId.length()));
        key = key.replace(' ', '_');
        key = key.replace(',', '_');
        weppkey = key;
      }
      return true;
    }


    /**
     * Generate a key based on the name. Used to identify records.
     *
     * @param idx index
     */
    void adjustKey(int idx) {
      String key;
      if (name.length() > 6) {
        key = name.substring(0, 6);
      } else {
        key = name;
      }
      key = key.toUpperCase();
      key = key.trim();
      int random = name.hashCode() + idx;
      String uniqueId = Integer.toUnsignedString(random);
      key += "_" + uniqueId.substring(0, Math.min(5, uniqueId.length()));
      key = key.replace(' ', '_');
      key = key.replace(',', '_');
      weppkey = key;
    }
  };

  /**
   * WEPP parameters for tillage
   */
  private class TillageRec {

    float mfo1, mfo2, rho, rint, rmfo1, rmfo2, rro, surdis, tdmean;
    int numof, pcode, cltpos;
    float rfrag, rnfrag;
    String weppkey;
    String path, name, comment;
    float weps_rfrag, weps_rnfrag;


    /**
     * Creates a string formatted to the what the WEPP management file needs.
     *
     * @return string of formatted tillage operation
     */
    public String toDBString() {
      String rec = weppkey + "   1\n";
      rec = rec + name + "\n";
      //if (comment.length() > 80) {
      //rec = rec + comment.substring(0,80) + "...\n";
      //} else {
      //rec = rec + comment + "\n";
      //}
      if (name.length() > 65) {
        rec = rec + "comment: " + name.substring(0, 65) + "\n";
      } else {
        rec = rec + "comment: " + name + "\n";
      }
      rec = rec + "(from WEPP LMOD service)\n";
      rec = rec + "comment...\n";
      rec = rec + "1 #landuse\n";
      rec = rec + String.format("%f %f %d\n%d", mfo1, mfo2, numof, pcode);
      if (pcode == 3) {
        rec = rec + String.format(" %d", cltpos);
      }
      rec = rec + String.format("\n%f %f %f %f %f %f %f %f %f\n", rho, rint, rmfo1, rmfo2, rro, surdis, tdmean, rfrag, rnfrag);
      return rec;
    }


    /**
     * Gets the XML parameters from a database record and saves them in this
     * class.
     *
     * @param ntill XML node with tillage parameters
     * @return true if parameters read ok
     */
    public boolean parseTillage(Node ntill, String opName, int tillSeq) {
      NodeList parms = ntill.getChildNodes();
      for (int k = 0; k < parms.getLength(); k++) {
        Node n = parms.item(k);
        if (n.getNodeType() == Node.ELEMENT_NODE) {
          String elem = n.getNodeName();
          String value = n.getTextContent();
          switch (elem) {
            case "path":
              path = value;
              break;
            case "name":
              name = value;
              break;
            case "comment":
              comment = value;
              if (comment.length() > 240) {
                comment = comment.substring(0, 240);
              }
              break;
            case "mfo1":
              mfo1 = Float.parseFloat(value);
              break;
            case "mfo2":
              mfo2 = Float.parseFloat(value);
              break;
            case "numof":
              numof = Integer.parseInt(value);
              break;
            case "pcode":
              List<String> implementCodes = Arrays.asList(
                  "pcodeMissing", "planter", "drill", "cultivator", "other"
              );
              pcode = implementCodes.indexOf(value.toLowerCase());
              break;
            case "cltpos":
              List<String> cltCodes = Arrays.asList("front mounted", "rear mounted");
              cltpos = cltCodes.indexOf(value.toLowerCase());
              break;
            case "rho":
              rho = Float.parseFloat(value);
              break;
            case "rint":
              rint = Float.parseFloat(value);
              break;
            case "rmfo1":
              rmfo1 = Float.parseFloat(value);
              break;
            case "rmfo2":
              rmfo2 = Float.parseFloat(value);
              break;
            case "rro":
              rro = Float.parseFloat(value);
              break;
            case "surdis":
              surdis = Float.parseFloat(value);
              break;
            case "tdmean":
              tdmean = Float.parseFloat(value);
              break;
            case "rfrag":
              rfrag = Float.parseFloat(value);
              break;
            case "rnfrag":
              rnfrag = Float.parseFloat(value);
              break;
            case "weppkey":
              weppkey = value;
              break;
            default:
              break;
          }
        }
      }
      if ((weppkey == null) || (weppkey.isEmpty())) {
        String key;
        if (opName.length() > 6) {
          key = opName.substring(0, 6);
        } else {
          key = opName;
        }
        key = key.toUpperCase();
        key = key.trim();
        String uniqueId = Integer.toUnsignedString(opName.hashCode() + tillSeq);
        if (uniqueId.length() <= 6) {
          key += "_" + uniqueId;
        } else {
          //key += "_" + uniqueId.substring(0, Math.min(5, uniqueId.length()));
          key += "_" + uniqueId.substring(uniqueId.length() - 6);
        }
        key = key.replace(' ', '_');
        key = key.replace(',', '_');
        LOG.info(key + "*" + uniqueId);
        weppkey = key;
      } else {
        weppkey = weppkey.replace(' ', '_');
        weppkey = weppkey.replace(',', '_');
      }
      return true;
    }
  };

  /**
   * WEPP parameters for crops.
   */
  private class VegetationRec implements Cloneable {

    String path, name, comment;
    String weppkey;
    int iplant, mfocod, spriod;
    String crunit;
    float bb, bbb, beinp, btemp, cf, crit, critvm, cuthgt, decfct, diam, dlai, dropfc;
    float extnct, fact, flivmx, gddmax, hi, hmax, oratea, orater, otemp, pltol;
    float pltsp, rdmax, rsr, rtmmax, tmpmax, tmpmin, xmxlai, yld, rcc, rowWidth, bbAdjust;
    // these are from WEPS
    String weps_hyldunits;
    float weps_tgtyield, weps_grf;
    int weps_hyldflag, weps_cbaflag, weps_idc;
    float weps_hmx, weps_rdmx, weps_topt, weps_hyconfact, weps_stemdia, weps_hyldwater;

    // other
    float targetYield, defaultYield;
    int sens;

    boolean hasHarvest;
    boolean rcc_mod;


    /**
     * Construct an empty vegetation record
     */
    VegetationRec() {
      targetYield = -999;
      weps_hyconfact = 0;
      hasHarvest = false;
      rcc = 0;
      rowWidth = 0;
      bbAdjust = 1;
      weps_tgtyield = 0;
      defaultYield = 0;
      sens = 0;
      rcc_mod = false;
    }


    public VegetationRec attachYield(float val) {
      float tempYieldDry = val / weps_hyconfact;
      float ty = tempYieldDry * (float) (1.0 - (weps_hyldwater / 100.0));

      if (targetYield <= 0) {
        targetYield = ty;
        if (weps_tgtyield > 0) {
          defaultYield = weps_tgtyield / weps_hyconfact;
          defaultYield = defaultYield * (float) (1.0 - (weps_hyldwater / 100.0));
        } else {
          defaultYield = targetYield;
        }
        LOG.info("First yield: " + weppkey);
      } else {
        // yield already set, check if this is a different yield
        // this is the same crop with a different yield, need to create a new
        // record.
        LOG.info("Second yield: " + weppkey);
        try {
          VegetationRec vr = (VegetationRec) this.clone();
          vr.targetYield = ty;
          vr.weppkey = vr.weppkey + "_" + String.valueOf((int) val);
          LOG.info("New Record added: " + vr.weppkey);
          return vr;
        } catch (Exception e) {
          LOG.info("Error: Could not duplicate veg record " + weppkey);
        }
      }
      return null;
    }


    /**
     * Get WEPP key associated with this crop record.
     *
     * @return key as a string
     */
    public String getWEPPKey() {
      return weppkey;
    }


    /**
     * Check if this crop is considered a perennial.
     *
     * @return true if the crop is a perennial
     */
    boolean isPerennial() {
      // type 7 is ambiguous for wepp - Bi-annuals or Perennials with Tuber Dormancy
      // default type 7 to an annual
      if ((weps_idc == 3) || (weps_idc == 6) || (weps_idc == 8) || (weps_idc == 10) || (weps_idc == 11) || (weps_idc == 12) || (weps_idc == 9)) {
        return true;
      }
      return false;
    }


    /**
     * Formats the vegetation record for the WEPP .rot file.
     *
     * @return formatted string of parameters.
     */
    public String toDBString() {
      int doCalibrate = 0;
      double adjhi = hi;

      // check for WEPS below ground mass used for yield
      if (weps_hyldflag == 5) {
        adjhi = hi * (-1.0);
      }

      // hasHarvest - some crops are not harvested so should not be calibrated
      if ((weps_cbaflag > 0) && (hasHarvest)) {
        if (weps_hyconfact > 0) {
          doCalibrate = 1;
        }
      }

      String rec = String.format("%s   1 %d %f %f\n", weppkey, doCalibrate, targetYield, defaultYield);
      rec = rec + name + "\n";
      if (comment.length() > 80) {
        rec = rec + comment.substring(0, 80) + "...\n";
      } else {
        rec = rec + comment + "\n";
      }
      rec = rec + "(from WEPP LMOD service)\n";
      rec = rec + "comment...\n";
      rec = rec + "1\n";
      rec = rec + crunit + "\n";
      float bbFinal = bb * bbAdjust;
      rec = rec + String.format("%f %f %f %f %f %f %f %f %f %f\n", bbFinal, bbb, beinp, btemp, cf, crit, critvm, cuthgt, decfct, diam);
      rec = rec + String.format("%f %f %f %f %f %f %f %f\n", dlai, dropfc, extnct, fact, flivmx, gddmax, adjhi, hmax);
      rec = rec + String.format("%d\n", mfocod);
      rec = rec + String.format("%f %f %f %f %f %f %f %f %d %f\n", oratea, orater, otemp, pltol, pltsp, rdmax, rsr, rtmmax, spriod, tmpmax);
      rec = rec + String.format("%f %f %f %f\n", tmpmin, xmxlai, yld, rcc);
      return rec;
    }


    /**
     * Parses the database XML for a crop record for the WEPS section. These are
     * WEPS parameters that are used by the WEPP interface and service code but
     * not directly by the WEPP model.
     *
     * @param n XML node to parse
     * @return true
     */
    private boolean parseVegWepsTags(Node n) {
      NodeList nl2 = n.getChildNodes();
      for (int j = 0; j < nl2.getLength(); j++) {
        if (nl2.item(j).getNodeType() == Node.ELEMENT_NODE) {
          Node chld = nl2.item(j);
          String elem = chld.getNodeName();
          String value = chld.getTextContent();
          switch (elem) {
            case "hyldunits":
              weps_hyldunits = value;
              break;
            case "tgtyield":
              weps_tgtyield = Float.parseFloat(value);
              break;
            case "grf":
              weps_grf = Float.parseFloat(value);
              break;
            case "hyldflag":
              weps_hyldflag = Integer.parseInt(value);
              break;
            case "hmx":
              weps_hmx = Float.parseFloat(value);
              break;
            case "rdmx":
              weps_rdmx = Float.parseFloat(value);
              break;
            case "topt":
              weps_topt = Float.parseFloat(value);
              break;
            case "cbaflag":
              weps_cbaflag = Integer.parseInt(value);
              break;
            case "hyconfact":
              try {
                weps_hyconfact = Float.parseFloat(value);
              } catch (Exception e) {
                weps_hyconfact = -999;
              }
              break;
            case "stemdia":
              weps_stemdia = Float.parseFloat(value);
              break;
            case "hyldwater":
              weps_hyldwater = Float.parseFloat(value);
              break;
            case "idc":
              // perennial crops are codes 3,6,7,8
              weps_idc = Integer.parseInt(value);
              break;
            default:
              break;
          }
        }
      }
      return true;
    }


    /**
     * parse the XML from a crop database record into WEPP parameters.
     *
     * @param nveg XML crop node
     * @return true
     */
    public boolean parseVeg(Node nveg) {
      NodeList parms = nveg.getChildNodes();
      for (int k = 0; k < parms.getLength(); k++) {
        Node n = parms.item(k);
        if (n.getNodeType() == Node.ELEMENT_NODE) {
          String elem = n.getNodeName();
          String value = n.getTextContent();
          switch (elem) {
            case "wepstags":
              parseVegWepsTags(n);
              break;
            case "path":
              path = value;
              break;
            case "name":
              name = value;
              break;
            case "comments":
              comment = value;
              if (comment.length() > 240) {
                comment = comment.substring(0, 240);
              }
              break;
            case "weppkey":
              weppkey = value;
              break;
            case "iplant":
              iplant = 1;  // always use cropland which is 1
              break;
            case "crunit":
              crunit = value;
              break;
            case "bb":
              bb = Float.parseFloat(value);
              break;
            case "bbb":
              bbb = Float.parseFloat(value);
              break;
            case "beinp":
              beinp = Float.parseFloat(value);
              break;
            case "btemp":
              btemp = Float.parseFloat(value);
              break;
            case "cf":
              cf = Float.parseFloat(value);
              break;
            case "crit":
              crit = Float.parseFloat(value);
              break;
            case "critvm":
              critvm = Float.parseFloat(value);
              break;
            case "cuthgt":
              cuthgt = Float.parseFloat(value);
              break;
            case "decfct":
              decfct = Float.parseFloat(value);
              break;
            case "diam":
              diam = Float.parseFloat(value);
              break;
            case "dlai":
              dlai = Float.parseFloat(value);
              break;
            case "dropfc":
              dropfc = Float.parseFloat(value);
              break;
            case "extnct":
              extnct = Float.parseFloat(value);
              break;
            case "fact":
              fact = Float.parseFloat(value);
              break;
            case "flivmx":
              flivmx = Float.parseFloat(value);
              break;
            case "gddmax":
              gddmax = Float.parseFloat(value);
              break;
            case "hi":
              hi = Float.parseFloat(value);
              break;
            case "hmax":
              hmax = Float.parseFloat(value);
              break;
            case "mfocod":
              List<String> mfoCodes = Arrays.asList(new String[]{
                "???", "fragile", "non-fragile"
              });
              mfocod = mfoCodes.indexOf(value.toLowerCase());
              break;
            case "oratea":
              oratea = Float.parseFloat(value);
              break;
            case "orater":
              orater = Float.parseFloat(value);
              break;
            case "otemp":
              otemp = Float.parseFloat(value);
              break;
            case "pltol":
              pltol = Float.parseFloat(value);
              break;
            case "pltsp":
              pltsp = Float.parseFloat(value);
              break;
            case "rdmax":
              rdmax = Float.parseFloat(value);
              break;
            case "rsr":
              rsr = Float.parseFloat(value);
              break;
            case "rtmmax":
              rtmmax = Float.parseFloat(value);
              break;
            case "spriod":
              spriod = Integer.parseInt(value);
              break;
            case "tmpmax":
              tmpmax = Float.parseFloat(value);
              break;
            case "tmpmin":
              tmpmin = Float.parseFloat(value);
              break;
            case "xmxlai":
              xmxlai = Float.parseFloat(value);
              break;
            case "yld":
              yld = Float.parseFloat(value);
              break;
            case "rcc":
              rcc = Float.parseFloat(value);
              break;
            case "hyldunits":
              weps_hyldunits = value;
              break;
            case "tgtyield":
              weps_tgtyield = Float.parseFloat(value);
              break;
            case "hyldflag":
              weps_hyldflag = Integer.parseInt(value);
              break;
            case "cbaflag":
              weps_cbaflag = Integer.parseInt(value);
              break;
            case "hyconfact":
              try {
                weps_hyconfact = Float.parseFloat(value);
              } catch (Exception e) {
                weps_hyconfact = -999;
              }
              break;
            case "hyldwater":
              weps_hyldwater = Float.parseFloat(value);
              break;
            case "idc":
              // perennial crops are codes 3,6,7,8
              weps_idc = Integer.parseInt(value);
              break;
            case "sene":
              // senesence day for perennial
              sens = Integer.parseInt(value);
              break;
          }
        }
      }
      if ((weppkey == null) || (weppkey == "")) {
        String key;
        if (name.length() > 6) {
          key = name.substring(0, 6);
        } else {
          key = name;
        }
        key = key.toUpperCase();
        key = key.trim();
        String uniqueId = Integer.toUnsignedString(name.hashCode());
        key += "_" + uniqueId.substring(0, Math.min(5, uniqueId.length()));
        key = key.replace(' ', '_');
        key = key.replace(',', '_');
        weppkey = key;
      } else {
        weppkey = weppkey.replace(' ', '_');
        weppkey = weppkey.replace(',', '_');
      }
      return true;
    }
  }


  /**
   * Create a WEPP formatted string for a .rot file of the vegetation records.
   *
   * @return string of vegetation records
   */
  public String printVegs() {
    String allVegs = "";
    if (!vegs.isEmpty()) {
      for (VegetationRec vr : vegs) {
        String rec = vr.toDBString();
        allVegs = allVegs + rec + "\n\n";
      }
    }
    return allVegs;
  }


  /**
   * For a given vegetation record update the release canopy cover (rcc)
   * parameter to the value passed which overwrites the value in the crop
   * database.
   *
   * @param key vegetation rcc to update
   * @param amount new value for rcc
   */
  public void updateVegsRCC(String key, double amount) {
    if (!vegs.isEmpty()) {
      for (VegetationRec vr : vegs) {
        if (vr.getWEPPKey().equals(key)) {
          if (vr.rcc_mod == false) {
             vr.rcc = (float) amount;
          }
          return;
        }
      }
    }
  }


  public void updateVegsRowWidth(String key, double amount) {
    if (!vegs.isEmpty()) {
      for (VegetationRec vr : vegs) {
        if (vr.getWEPPKey().equals(key)) {
          vr.rowWidth = (float) amount;
          return;
        }
      }
    }
  }


  public void updateVegsCanopyCoverCoef(String key, double amount) {
    if (!vegs.isEmpty()) {
      for (VegetationRec vr : vegs) {
        if (vr.getWEPPKey().equals(key)) {
          vr.bbAdjust = (float) amount;
          return;
        }
      }
    }
  }


  /**
   * Generate a list of those crops need to be calibrated.
   *
   * @return list of vegetation keys and names to calibrate.
   */
  public String getVegsToCal() {
    String calVegs = "";
    if (!vegs.isEmpty()) {
      for (VegetationRec vr : vegs) {
        if ((vr.weps_cbaflag > 0) && (vr.hasHarvest)) {
          //calVegs = calVegs + vr.weppkey + " " + vr.targetYield + "\n";
          calVegs = calVegs + vr.weppkey + "\t" + vr.name + "\n";
        }
      }
    }
    return calVegs;
  }


  /**
   * Get a string representing if the crop was calibrated.
   *
   * @param key vegetation key
   * @return yes or no if the crop is calibrated
   */
  public String getVegCalibStatus(String key) {
    String rc = "?";
    if (!vegs.isEmpty()) {
      for (VegetationRec vr : vegs) {
        if (vr.weppkey.equals(key)) {
          if (vr.weps_cbaflag == 0) {
            rc = "No";
          } else {
            rc = "Yes";
          }
        }
      }
    }
    return rc;
  }


  /**
   * Get a string representing all of the operation section in the .rot file for
   * this management.
   *
   * @return string of operations to output to .rot file
   */
  public String printOps() {
    String allOps = "";
    if (!tillages.isEmpty()) {
      for (TillageRec tr : tillages) {
        String rec = tr.toDBString();
        allOps = allOps + rec + "\n\n";
      }
    }
    return allOps;
  }


  /**
   * Get a string representation of the irrigation section in the .rot file for
   * this management.
   *
   * @param ty type of irrigation to output
   * @return string of irrigations matching the type
   */
  public String printIrrs(int ty) {
    String allIrrs = "";
    if (!irrigations.isEmpty()) {
      for (IrrigationRec ir : irrigations) {
        if (ir.type == ty) {
          String rec = ir.toDBString();
          allIrrs = allIrrs + rec + "\n\n";
        }
      }
    }
    return allIrrs;
  }


  /**
   * Get the number of irrigation processes of a certain type for this
   * management.
   *
   * @param ty type of irrigation
   * @return number of irrigation records matching the type
   */
  public int getIrrCount(int ty) {
    int count = 0;
    if (!irrigations.isEmpty()) {
      for (IrrigationRec ir : irrigations) {
        if (ir.type == ty) {
          count++;
        }
      }
    }
    return count;
  }


  /**
   * Get a string of all the residues to output in the .rot file
   *
   * @return string of residue records.
   */
  public String printResidues() {
    String allRes = "";
    if (!residues.isEmpty()) {
      for (ResidueRec vr : residues) {
        String rec = vr.toDBString();
        allRes = allRes + rec + "\n";
      }
    }
    return allRes;
  }


  /**
   * Get a string of all the contours to output in the .rot file.
   *
   * @return string of contour records
   */
  public String printContours() {
    String allCont = "";
    if (!contours.isEmpty()) {
      for (ContourRec cr : contours) {
        String rec = cr.toDBString();
        allCont = allCont + rec + "\n";
      }
    }
    return allCont;
  }


  /**
   * Parse the WEPS section of the operation record. This contains two
   * parameters.
   *
   * @param nweps XML node to start with
   * @return true
   */
  boolean parseWepsTags(Node nweps) {
    NodeList nl2 = nweps.getChildNodes();
    for (int j = 0; j < nl2.getLength(); j++) {
      if (nl2.item(j).getNodeType() == Node.ELEMENT_NODE) {
        Node chld = nl2.item(j);
        String pname = chld.getNodeName();
        String value = chld.getTextContent();
        if (pname.equals("oenergyarea")) {
          lastProcess.fuel = Float.parseFloat(value);
        } else if (pname.equals("ostir")) {
          lastProcess.stir = Float.parseFloat(value);
        }
      }
    }
    return true;
  }


  /**
   * Parse residue removal parameters from XML.
   *
   * @param nres XML node to start with.
   *
   * @return array with flat and standing removal amounts.
   */
  double[] parseResRemoval(Node nres) {
    double[] amounts = new double[2];

    double standamount = 0.0;
    double flatamount = 0.0;

    NodeList nl2 = nres.getChildNodes();
    for (int j = 0; j < nl2.getLength(); j++) {
      if (nl2.item(j).getNodeType() == Node.ELEMENT_NODE) {
        Node chld = nl2.item(j);
        String pname = chld.getNodeName();
        String value = chld.getTextContent();
        if (pname.equals("standamount")) {
          standamount = Float.parseFloat(value);
        }
        if (pname.equals("flatamount")) {
          flatamount = Float.parseFloat(value);
        }
      }
    }
    amounts[0] = standamount;
    amounts[1] = flatamount;
    return amounts;
  }


  /**
   * Gets a value from an XML tag.
   *
   * @param nres Starting XML mode
   * @param tag tag to search for
   * @return value attached to tag
   */
  double parseAmount(Node nres, String tag) {
    double amount = -999;

    NodeList nl2 = nres.getChildNodes();
    for (int j = 0; j < nl2.getLength(); j++) {
      if (nl2.item(j).getNodeType() == Node.ELEMENT_NODE) {
        Node chld = nl2.item(j);
        String pname = chld.getNodeName();
        String value = chld.getTextContent();
        if (pname.equals(tag)) {
          amount = Float.parseFloat(value);
        }
      }
    }
    return amount;
  }


  /**
   * Sets up a base residue record, adds it to list.
   *
   * @param nres XML node to start parsing.
   * @param isadd true if this is an add residue process.
   *
   * @return true
   */
  boolean parseResidue(Node nres, boolean isadd) {
    NodeList nl2 = nres.getChildNodes();
    ResidueRec r = new ResidueRec(isadd);
    r.amount = (float) 1.0;

    for (int j = 0; j < nl2.getLength(); j++) {
      if (nl2.item(j).getNodeType() == Node.ELEMENT_NODE) {
        Node chld = nl2.item(j);
        String pname = chld.getNodeName();
        String value = chld.getTextContent();
        if (pname.equals("name")) {
          // need to translate this, default for now, will be replaced with actual later
          r.cropKey = "Corn";
        } else if (pname.equals("flatamount")) {
          r.amount = Float.parseFloat(value);
        }
      }
    }
    // Nothing to do because the information is real parms are specified
    // in the other tables.
    Integer sz = residues.size() + 1;
    String key = "RES_" + Integer.toString(sz);
    r.weppkey = key;
    r.isAddOp = isadd;
    addResidue(r);
    return true;
  }


  /**
   * Get the key of the last residue in the list.
   *
   * @return key of last residue or null if none.
   */
  String getLastResidue() {
    String name = null;
    if (residues != null && !residues.isEmpty()) {
      name = residues.get(residues.size() - 1).weppkey;
    }
    return name;
  }


  /**
   * Decodes the vegetation XML into something WEPP can understand.
   *
   * @param vegBlob string of XML data
   * @return key of vegetation record added
   *
   * @throws IOException if any
   * @throws SAXException if any
   * @throws ParserConfigurationException if any
   */
  public String getVegs(String vegBlob) throws IOException, SAXException, ParserConfigurationException {
    String vkey = null;
    if ((vegBlob != null) && (vegBlob != "")) {
      DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
      InputStream inputStream = new ByteArrayInputStream(vegBlob.getBytes(Charset.forName("UTF-8")));
      Document doc = dBuilder.parse(inputStream);
      doc.getDocumentElement().normalize();
      NodeList nl = doc.getElementsByTagName("weppcrop");
      for (int i = 0; i < nl.getLength(); i++) {
        Node n = nl.item(i);
        String elem = n.getNodeName();
        if (elem.equals("weppcrop")) {
          VegetationRec v = new VegetationRec();
          v.parseVeg(n);
          addCrop(v);
          vkey = v.weppkey;
          applyTempDatabase();
        }
      }
    }

    return vkey;
  }


  /**
   * This builds a list of WEPP detail records that need to be included to
   * support this LMOD operation.
   *
   * @param opBlob string of operation XML date
   * @param date date of operation
   * @return list of WEPP process and parameters
   *
   * @throws IOException if any
   * @throws SAXException if any
   * @throws ParserConfigurationException if any
   */
  public List<String> getProcesses(String opBlob, String opName, String date) throws IOException, SAXException, ParserConfigurationException {
    List<String> processes = new ArrayList<>();
    if ((opBlob != null) && (opBlob != "")) {
      DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
      InputStream inputStream = new ByteArrayInputStream(opBlob.getBytes(Charset.forName("UTF-8")));
      Document doc = dBuilder.parse(inputStream);
      doc.getDocumentElement().normalize();

      NodeList nl = doc.getElementsByTagName("process");
      Node n;
      String elem;
      String value;
      int tillSeq = 0;
      for (int i = 0; i < nl.getLength(); i++) {
        n = nl.item(i);
        elem = n.getNodeName();
        if (elem.equals("process")) {
          NodeList n2 = n.getChildNodes();
          for (int j = 0; j < n2.getLength(); j++) {
            n = n2.item(j);
            if (n.getNodeType() == Node.ELEMENT_NODE) {
              elem = n.getNodeName();
              if (elem.equals("tillage")) {
                TillageRec tr = new TillageRec();
                tr.parseTillage(n, opName, tillSeq);
                tillSeq = tillSeq + 1;
                addTillage(tr);
                processes.add(elem + "|" + tr.weppkey);
              } else if (elem.equals("add-residue")) {
                parseResidue(n, true);
                processes.add(elem);
              } else if (elem.equals("remove-residue")) {
                double[] lost = parseResRemoval(n);
                if ((lost[0] > 0.0) && (lost[1] > 0.0)) {
                  // this may need to be restricted to removing flat residue if this is a perennial
                  parseResidue(n, false);
                  // need to do burning instead
                  processes.add("burning" + "|" + Double.toString(lost[0]) + "|" + Double.toString(lost[1]));
                } else {
                  // do regular remove of flat residue
                  parseResidue(n, false);
                  processes.add(elem + "|" + Double.toString(lost[1]));
                }
              } else if (elem.equals("plant")) {
                double amount = parseAmount(n, "rcc");
                if (amount < 0.0) {
                  amount = 0.0;
                }
                double rowWidth = parseAmount(n, "rowWidth");
                if (rowWidth < 0.0) {
                  rowWidth = -1.0;
                }
                double canopyAdjust = parseAmount(n, "canopyCoverAdjust");
                if (canopyAdjust < 0.0) {
                  canopyAdjust = 1.0;
                }
                processes.add(elem + "|" + Double.toString(amount) + "|" + Double.toString(rowWidth) + "|" + Double.toString(canopyAdjust));
              } else if (elem.equals("harvest")) {
                // notthing to do
                processes.add(elem);
              } else if (elem.equals("kill-crop")) {
                // nothing to do
                processes.add(elem);
                /*
			    } else if (elem.equals("burning")) {
				double []lost = parseBurning(n);
				processes.add(elem + "|" + Double.toString(lost[0]) + "|" + Double.toString(lost[1]));
                 */
              } else if( elem.equals( "kill-annual-crop" ) ) {
                // nothing to do
                processes.add( elem );
              } else if (elem.equals("flatten-residue")) {
                double amount = parseAmount(n, "amount");
                if (amount < 0.0) {
                  amount = 0.0;
                }
                processes.add(elem + "|" + Double.toString(amount));
              } else if (elem.equals("silage")) {
                // nothing to do
                processes.add(elem);
              } else if (elem.equals("initialplant")) {
                //nothing to do
                processes.add(elem);
              } else if (elem.equals("remove-live") || elem.equals("remove-live-not-yield")) {
                double amount = parseAmount(n, "amount");
                if (amount > 0.0) {
                  processes.add(elem + "|" + Double.toString(amount));
                } else {
                  processes.add(elem + "|");
                }
             } else if (elem.equals("remove-live-leave") || elem.equals("remove-live-leave-not-yield")) {
                double amount = parseAmount(n, "amount");
                if (amount > 0.0) {
                  processes.add(elem + "|" + Double.toString(amount));
                } else {
                  processes.add(elem + "|");
                }
              } else if (elem.equals("irrigation")) {
                IrrigationRec ir = new IrrigationRec(date);
                ir.parseIrrigation(n);
                addIrrigation(ir);
                if (ir.type == 0) {
                  processes.add(elem + "|" + ir.weppkey);
                } else {
                  processes.add("fixed-" + elem + "|" + ir.weppkey);
                }
              } else if (elem.equals("irrigation-stop")) {
                // nothing to do
                stopIrrigation(date);
                processes.add(elem);
              } else if (elem.equals("next-day")) {
                processes.add(elem);
              }
            }
          }
        }
      }

      nl = doc.getElementsByTagName("ostir");
      if (nl.getLength() > 0) {
        n = nl.item(0);
        if (n.getNodeType() == Node.ELEMENT_NODE) {
          value = n.getTextContent();
          lastProcess.stir = Float.parseFloat(value);
        }
      }
      nl = doc.getElementsByTagName("oenergyarea");
      if (nl.getLength() > 0) {
        n = nl.item(0);
        if (n.getNodeType() == Node.ELEMENT_NODE) {
          value = n.getTextContent();
          lastProcess.fuel = Float.parseFloat(value);
        }
      }
      nl = doc.getElementsByTagName("wepstags");
      for (int i = 0; i < nl.getLength(); i++) {
        n = nl.item(i);
        elem = n.getNodeName();
        if (elem.equals("wepstags")) {
          parseWepsTags(n);
        }
      }
    }
    return processes;
  }


  /**
   * Add a tillage record to the list if it does not exist.
   *
   * @param t tillage record to add
   */
  private void addTillage(TillageRec t) {
    for (TillageRec tr : tillages) {
      if (t.weppkey.equals(tr.weppkey)) {
        return;
      }
    }
    // need to add
    tillages.add(t);
  }


  /**
   * Adds an irrigation. Will change the key to make it unique if it is already
   * in the list.
   *
   * @param irr Irrigation record to add
   */
  private void addIrrigation(IrrigationRec irr) {
    boolean tryAgain = true;
    int idx = 1;
    while (tryAgain == true) {
      boolean present = false;
      for (IrrigationRec ir : irrigations) {
        if (irr.weppkey.equals(ir.weppkey)) {
          // same key, need to change
          irr.adjustKey(idx++);
          present = true;
        }
      }
      if (present == false) {
        tryAgain = false;
      }
    }
    // need to add
    irrigations.add(irr);
  }


  /**
   * Add a stop date to the last irrigation record.
   *
   * @param date stop date
   */
  private void stopIrrigation(String date) {
    // this applies to the last irrigation added
    int last = irrigations.size() - 1;
    if (last >= 0) {
      IrrigationRec ir = irrigations.get(last);
      // date to Julian
      String mdate[] = date.split("-");
      int yr = Integer.parseInt(mdate[0]);
      int mon = Integer.parseInt(mdate[1]) - 1;
      int day = Integer.parseInt(mdate[2]);

      GregorianCalendar gc = new GregorianCalendar();
      gc.set(GregorianCalendar.DAY_OF_MONTH, day);
      gc.set(GregorianCalendar.MONTH, mon);
      gc.set(GregorianCalendar.YEAR, 2001);
      ir.endDate = gc.get(GregorianCalendar.DAY_OF_YEAR);
    }
  }


  /**
   * Add a crop to the list if it does not already exist.
   *
   * @param v vegetation record to add.
   */
  private void addCrop(VegetationRec v) {
    for (VegetationRec vr : vegs) {
      if (v.weppkey.equals(vr.weppkey)) {
        return;
      }
    }
    // need to add
    vegs.add(v);
  }


  /**
   * Set yield for the specific crop. The conversion factor saves the units in
   * kg/m^2.
   *
   * @param key vegetation key yield to update
   * @param val new yield in user units.
   */
  public String attachYield(String key, float val) {
//    char[] exts = new char[]{
//      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
//    };
    for (VegetationRec vr : vegs) {
      if (key.equals(vr.weppkey)) {
        if (vr.weps_hyconfact > 0) {
          VegetationRec temp = vr.attachYield(val);
          if (temp != null) {
            // this indicates a new record was created because it is
            // the same crop with a different yield so add to list
            // make sure it is not already present.
            if (getVegName(temp.weppkey).equals("")) {
              // does not exist, add it
              vegs.add(temp);
              return temp.weppkey;
            } else {
              return temp.weppkey;
            }
            /* This logic was to test when the same crop with the same yield is
			// used in multiple places in the management schedule which would
			// probably require a different calibration factor. This
			// is commented out now because it could lead to a lot of
			// different crop records that may cause other problems.
			int tries = 1;
			String iniKey = temp.weppkey;
			while (tries < exts.length) {
			    if (getVegName(temp.weppkey).equals("")) {
				// does not exist, add it
				vegs.add(temp);
				return temp.weppkey;
			    } else {
				// already exists, change key to be unique
				temp.weppkey = iniKey + exts[tries];
				tries = tries + 1;
			    }
			}
             */
          }
        }
        return null;
      }
    }
    return null;
  }


  /**
   * Add a residue record to the list if it does not exist.
   *
   * @param v Residue record to add
   */
  private void addResidue(ResidueRec v) {
    for (ResidueRec vr : residues) {
      if (v.weppkey.equals(vr.weppkey)) {
        return;
      }
    }
    // need to add
    residues.add(v);
  }


  /**
   * Create a new residue record and add it to the list.
   *
   * @param ty type, add or remove
   * @param ckey vegetation key
   * @param amt amount to add or remove
   * @return key of new residue record.
   */
  public String addResidueRec(boolean ty, String ckey, float amt) {
    Integer sz = residues.size() + 1;
    String key = "RES_" + Integer.toString(sz);
    ResidueRec rr = new ResidueRec(ty, key, ckey, amt);
    addResidue(rr);
    return key;
  }


  /**
   * Adds a remove residue record if it does not exist. If there is already an
   * existing record it will use that.
   *
   * @param ty type (remove=false)
   * @param ckey vegetation key with this residue
   * @param amt amount of residue to add.
   * @return new residue key added
   */
  public String addResidueRecRem(boolean ty, String ckey, float amt) {
    for (ResidueRec vr : residues) {
      // seems like crop key should also be compared?
      if ((vr.isAddOp == false) && (vr.amount == amt)) {
        return vr.weppkey;
      }
    }
    // not found, add record
    String key = addResidueRec(ty, ckey, amt);
    return key;
  }


  /**
   * Add a contour record to the list. There would only be one contour record
   * per management.
   *
   * @param slp slope of contour in %
   * @param len length of contour
   * @return key of new contour record
   */
  public String addContour(float slp, float len) {
    String key = "CONT1";
    float spacing = (float) 0.97537;
    float hgt = (float) 0.03048;
    float grade = (float) (slp / 100.0);
    ContourRec cr = new ContourRec(key, grade, hgt, len, spacing);
    contours.add(cr);
    return key;
  }


  /**
   * Update a vegetation record whether it has a harvest or not.
   *
   * @param key vegetation key to update
   * @param state true if has a harvest, or false
   */
  public void setVegHarvest(String key, boolean state) {
    for (VegetationRec vr : vegs) {
      if (vr.weppkey.equals(key)) {
        vr.hasHarvest = state;
      }
    }
  }


  /**
   * Get the user yield units string from a vegetation.
   *
   * @param key vegetation key to look at
   * @return yield units.
   */
  public String getVegYieldUnits(String key) {
    String rstr = "";
    for (VegetationRec vr : vegs) {
      if (vr.weppkey.equals(key)) {
        rstr = vr.weps_hyldunits;
      }
    }
    return rstr;
  }


  /**
   * Get the yield moisture from a vegetation.
   *
   * @param key vegetation key to look at.
   * @return moisture content for yields
   */
  public float getVegYieldMoisture(String key) {
    float val = 0;
    for (VegetationRec vr : vegs) {
      if (vr.weppkey.equals(key)) {
        val = vr.weps_hyldwater;
      }
    }
    return val;
  }


  /**
   * Get the conversion factor to convert from kg/m^2 of yield to the user
   * defined units.
   *
   * @param key vegetation key to look at.
   * @return yield conversion factor
   */
  public float getVegYieldConvFactor(String key) {
    float val = 0;
    for (VegetationRec vr : vegs) {
      if (vr.weppkey.equals(key)) {
        val = vr.weps_hyconfact;
      }
    }
    return val;
  }


  /**
   * Get the target yield for a vegetation.
   *
   * @param key vegetation key to look at.
   * @return target yield
   */
  public float getVegTargetYield(String key) {
    float val = 0;
    for (VegetationRec vr : vegs) {
      if (vr.weppkey.equals(key)) {
        val = vr.targetYield;
      }
    }
    return val;
  }


  /**
   * Get the full vegetation name.
   *
   * @param key vegetation key to look at
   * @return full name of vegetation
   */
  public String getVegName(String key) {
    String rstr = "";
    for (VegetationRec vr : vegs) {
      if (vr.weppkey.equals(key)) {
        rstr = vr.name;
      }
    }
    return rstr;
  }


  /**
   * Get the vegetation key give the full name.
   *
   * @param name full name to look for
   * @return key associated with the name
   */
  public String getVegKey(String name) {
    for (VegetationRec vr : vegs) {
      if (vr.name.equals(name)) {
        return vr.weppkey;
      }
    }
    return null;
  }


  /**
   * Get the tillage depth parameter for a specific tillage key.
   *
   * @param key tillage key to search for
   * @return tillage depth parameter, 0 if not found
   */
  public float getTillDepth(String key) {
    for (TillageRec tr : tillages) {
      if (tr.weppkey.equals(key)) {
        return tr.tdmean;
      }
    }
    return 0;
  }


  /**
   * Get the row width for a specific tillage key.
   *
   * @param key tillage key to search for
   * @return tillage ridge interval parameter, 0 if not found
   */
  public float getTillRowWidth(String key) {
    for (TillageRec tr : tillages) {
      if (tr.weppkey.equals(key)) {
        return tr.rint;
      }
    }
    return 0;
  }


  /**
   * Check if particular vegetation is a perennial.
   *
   * @param key vegetation to check
   * @return true if record is a perennial
   */
  public boolean isPerennial(String key) {
    for (VegetationRec vr : vegs) {
      if (vr.weppkey.equals(key)) {
        return vr.isPerennial();
      }
    }
    return false;
  }


  /**
   * Get senescence day if vegetation is a perennial.
   *
   * @param key vegetation to check
   * @return true if record is a perennial
   */
  public int getSenescenceDay(String key) {
    for (VegetationRec vr : vegs) {
      if (vr.weppkey.equals(key)) {
        return vr.sens;
      }
    }
    return 0;
  }


  public JSONObject getWoodyVeg(String name, File woodyFile) throws Exception {
    // if the file has not been loaded, read it now
    if (woodyvegs.length() == 0) {
      try {
        // read in the JSON data for woody vegs
        String ifile = woodyFile.getAbsolutePath();
        byte[] data = Files.readAllBytes(Paths.get(ifile));
        String woodydata = new String(data, Charset.defaultCharset());
        JSONObject st = new JSONObject(woodydata);
        woodyvegs = st.getJSONArray("woodyinicond");
      } catch (Exception e) {
        LOG.warning("Error: Could not get woody JSON file" + woodyFile.getName());
        return null;
      }
    }
    for (int i = 0; i < woodyvegs.length(); i++) {
      JSONObject veg = woodyvegs.optJSONObject(i);
      if (veg.getString("name").equals(name)) {
        // found it
        JSONObject wr = new JSONObject();
        wr.put("cancov", veg.getDouble("cancov"));
        wr.put("inrcov", veg.getDouble("inrcov"));
        wr.put("rilcov", veg.getDouble("rilcov"));
        wr.put("usrril", veg.getDouble("usrril"));
        wr.put("usrintr", veg.getDouble("usrintr"));
        return wr;
      }
    }
    // did not find it, return null object
    return null;
  }


  /**
   * Get number of vegetation records.
   *
   * @return veg count
   */
  public int getVegCount() {
    return vegs.size();
  }


  /**
   * Get number of residue records.
   *
   * @return residue count
   */
  public int getResCount() {
    return residues.size();
  }


  /**
   * Get number of contour records.
   *
   * @return contour count
   */
  public int getContCount() {
    return contours.size();
  }


  /**
   * Get number of irrigation records
   *
   * @return irrigation count
   */
  public int getIrrCount() {
    return irrigations.size();
  }


  /**
   * Get STIR value of last process.
   *
   * @return STIR
   */
  public float getLastSTIR() {
    return lastProcess.stir;
  }


  /**
   * Get fuel value of last process.
   *
   * @return fuel
   */
  public float getLastFuel() {
    return lastProcess.fuel;
  }


  /**
   * Use the debug JSON to modify any vegetation records. This is done after all
   * the vegetation records have been built.
   *
   * This would allow the default vegetation database records to be modified.
   */
  public void applyTempDatabase() {
    VegetationRec vegRec;
    if (debugData == null) {
        return;
    }
    if (debugData.length() == 0) {
        return;
    }
    try {
      JSONArray crps = debugData.getJSONArray("crops");
      for (int i = 0; i < crps.length(); i++) {
        JSONObject cropObj = crps.getJSONObject(i);
        String cname = cropObj.getString("name");
        vegRec = null;
        if (!vegs.isEmpty()) {
          for (VegetationRec vr : vegs) {
            if (vr.name.equals(cname)) {
              vegRec = vr;
            }
          }
          if (vegRec != null) {
            for (int j = 0; j < cropObj.names().length(); j++) {
              String ckey = cropObj.names().getString(j);
              switch (ckey) {
                case "name":
                  break;
                case "bb":
                  vegRec.bb = (float) cropObj.getDouble("bb");
                  break;
                case "hi":
                  vegRec.hi = (float) cropObj.getDouble("hi");
                  break;
                case "fact":
                  vegRec.fact = (float) cropObj.getDouble("fact");
                  break;
                case "cuthgt":
                  vegRec.cuthgt = (float) cropObj.getDouble("cuthgt");
                  break;
                case "rdmax":
                  vegRec.rdmax = (float) cropObj.getDouble("rdmax");
                  break;
                case "rtmmax":
                  vegRec.rtmmax = (float) cropObj.getDouble("rtmmax");
                  break;
                case "pltol":
                  vegRec.pltol = (float) cropObj.getDouble("pltol");
                  break;
                case "pltsp":
                  vegRec.pltsp = (float) cropObj.getDouble("pltsp");
                  break;
                case "tmpmax":
                  vegRec.tmpmax = (float) cropObj.getDouble("tmpmax");
                  break;
                case "hmax":
                  vegRec.hmax = (float) cropObj.getDouble("hmax");
                  break;
                case "rsr":
                  vegRec.rsr = (float) cropObj.getDouble("rsr");
                  break;
                case "otemp":
                  vegRec.otemp = (float) cropObj.getDouble("otemp");
                  break;
                case "mfocod":
                  vegRec.mfocod = (int) cropObj.getInt("mfocod");
                  break;
                case "rcc":
                  vegRec.rcc = (float) cropObj.getDouble("rcc");
                  vegRec.rcc_mod = true;
                  break;
                case "beinp":
                  vegRec.beinp = (float) cropObj.getDouble("beinp");
                  break;
                case "orater":
                  vegRec.orater = (float) cropObj.getDouble("orater");
                  break;
                case "diam":
                  vegRec.diam = (float) cropObj.getDouble("diam");
                  break;
                case "dlai":
                  vegRec.dlai = (float) cropObj.getDouble("dlai");
                  break;
                case "bbb":
                  vegRec.bbb = (float) cropObj.getDouble("bbb");
                  break;
                case "cf":
                  vegRec.cf = (float) cropObj.getDouble("cf");
                  break;
                case "oratea":
                  vegRec.oratea = (float) cropObj.getDouble("oratea");
                  break;
                case "xmxlai":
                  vegRec.xmxlai = (float) cropObj.getDouble("xmxlai");
                  break;
                case "btemp":
                  vegRec.btemp = (float) cropObj.getDouble("btemp");
                  break;
                case "gddmax":
                  vegRec.gddmax = (float) cropObj.getDouble("gddmax");
                  break;
                case "flivmx":
                  vegRec.flivmx = (float) cropObj.getDouble("flivmx");
                  break;
                case "dropfc":
                  vegRec.dropfc = (float) cropObj.getDouble("dropfc");
                  break;
                case "crit":
                  vegRec.crit = (float) cropObj.getDouble("crit");
                  break;
                case "extnct":
                  vegRec.extnct = (float) cropObj.getDouble("extnct");
                  break;
                case "spriod":
                  vegRec.spriod = (int) cropObj.getInt("spriod");
                  break;
                case "decfct":
                  vegRec.decfct = (float) cropObj.getDouble("decfct");
                  break;
                case "tmpmin":
                  vegRec.tmpmin = (float) cropObj.getDouble("tmpmin");
                  break;
                case "hyldunits":
                  vegRec.weps_hyldunits = cropObj.getString("hyldunits");
                  break;
                case "cbaflag":
                  vegRec.weps_cbaflag = (int) cropObj.getInt("cbaflag");
                  break;
                case "hyldwater":
                  vegRec.weps_hyldwater = (float) cropObj.getDouble("hyldwater");
                  break;
                case "idc":
                  vegRec.weps_idc = (int) cropObj.getInt("idc");
                  break;
                case "tgtyield":
                  vegRec.weps_tgtyield = (float) cropObj.getDouble("tgtyield");
                  break;
                case "hyldflag":
                  vegRec.weps_hyldflag = (int) cropObj.getInt("hyldflag");
                  break;
                case "sene":
                  vegRec.sens = (int) cropObj.getInt("sene");
                  break;
              }
            }
          }
        }
      }
    } catch (Exception e) {
      LOG.log(Level.WARNING, "Ignore: problem with debug syntax -- ignore data settings", e);
    }
  }


  /**
   * Get the detail WEPP vegetation data from a CSIP service.
   *
   * @param names list of vegetation names to fetch
   */
  public void getVegBlobsCRLMOD(List<String> names) {
    try {      
      ModelDataServiceCall r;
      // needed for testing to use alternate data 
      if(cropsURL.indexOf( "purdue" ) > -1 )  {
         r = new ModelDataServiceCall()
          .put("limit", names.size())
          .put("native_formats", "true")
          .put("name", names)
          .put("dtype","vegetations")
          .put("ver", dataversion)
          .url(cropsURL)
          .withDefaultLogger()
          .call();  
      } else {
          r = new ModelDataServiceCall()
          .put("limit", names.size())
          .put("native_formats", "true")
          .put("name", names)
          .url(cropsURL)
          .withDefaultLogger()
          .withCache(lmodCache)
          .call();
      }

      if (r.serviceFinished()) {
        vegBlobsCRLMOD = r.getJSONObject(r.getNames().get(0));
      } else {
        LOG.log(Level.WARNING, "LMOD Crop Service failed.");
      }
    } catch (Exception ex) {
      LOG.log(Level.WARNING, "Ignored Error: could not get crops CRLMOD", ex);
    }
  }


  /**
   * Get the detail WEPP operation data from a CSIP service.
   *
   * @param names list of operation names to fetch
   */
  public void getOpBlobsCRLMOD(List<String> names) {
    try {
      ModelDataServiceCall r;
      // needed for testing to use alternate data 
      if(operationsURL.indexOf( "purdue" ) > -1 )  {
         r = new ModelDataServiceCall()
          .put("limit", names.size())
          .put("native_formats", "true")
          .put("name", names)
          .put("dtype","operations")
          .put("ver", dataversion)
          .url(operationsURL)
          .withDefaultLogger()
          .call();  
      } else {
          r = new ModelDataServiceCall()
          .put("limit", names.size())
          .put("native_formats", "true")
          .put("name", names)
          .url(operationsURL)
          .withDefaultLogger()
          .withCache(lmodCache)
          .call();
      }
      if (r.serviceFinished()) {
        opBlobsCRLMOD = r.getJSONObject(r.getNames().get(0));
      } else {
        LOG.log(Level.WARNING, "LMOD operations Service failed.");
      }
    } catch (Exception ex) {
      LOG.log(Level.WARNING, "Ignored Error: could not get operations CRLMOD", ex);
    }
  }


  /**
   * Get the WEPP residue data from a CSIP service.
   *
   * @param names list of residue names to fetch
   */
  public void getResBlobsCRLMOD(List<String> names) {
    try {
      ModelDataServiceCall r;
      // needed for testing to use alternate data 
      if(residueURL.indexOf( "purdue" ) > -1 )  {
         r = new ModelDataServiceCall()
          .put("limit", names.size())
          .put("native_formats", "true")
          .put("name", names)
          .put("dtype","residues")
          .put("ver", dataversion)
          .url(residueURL)
          .withDefaultLogger()
          .call();  
      } else {
          r = new ModelDataServiceCall()
          .put("limit", names.size())
          .put("native_formats", "true")
          .put("name", names)
          .url(residueURL)
          .withDefaultLogger()
          .withCache(lmodCache)
          .call();
      }
     
      if (r.serviceFinished()) {
        resBlobsCRLMOD = r.getJSONObject(r.getNames().get(0));
      } else {
        LOG.log(Level.WARNING, "LMOD Residue Service failed.");
      }
    } catch (Exception ex) {
      LOG.log(Level.WARNING, "Ignored Error: could not get CRLMOD Residues", ex);
    }
  }


  /**
   * From the residue data read from CSIP get a specific JSON subset matching
   * the passed in name.
   *
   * @param name residue name to extract from JSON object.
   *
   * @return residue data as a string
   */
  public String getResBlob(String name) {
    String blob = "";
    try {
      JSONArray mans = resBlobsCRLMOD.getJSONArray("residues");
      for (int i = 0; i < mans.length(); i++) {
        JSONObject theOp = mans.getJSONObject(i);
        if (theOp.getString("name").equals(name)) {
          blob = theOp.getString(weppResParamSetName);
          //LOG.info("Got residue blob: " + name);
        }
      }
    } catch (Exception e) {
      LOG.log(Level.WARNING, "Ignored:", e);
    }
    return blob;
  }


  /**
   * From the vegetation data read from CSIP get a specific JSON subset matching
   * the passed in name.
   *
   * @param name vegetation name to extract from JSON object
   *
   * @return vegetation data as a string
   */
  public String getVegBlob(String name) {
    String blob = "";
    try {
      JSONArray mans = vegBlobsCRLMOD.getJSONArray("crops");
      for (int i = 0; i < mans.length(); i++) {
        JSONObject theOp = mans.getJSONObject(i);
        if (theOp.getString("name").equals(name)) {
          blob = theOp.getString(weppCropParamSetName);
          //LOG.info("Got veg blob: " + name);
        }
      }
    } catch (Exception e) {
      LOG.log(Level.WARNING, "Ignored:", e);
    }
    //vegBlobsCRLMOD
    if (blob.equals("")) {
      LOG.info("Failed to get VEG: " + name + "\n");
    }
    return blob;
  }


  /**
   * From the operation data read from CSIP get a specific JSON subset matching
   * the passed in name.
   *
   * @param name operation name to extract from JSON object
   *
   * @return operation data as a string
   */
  public String getOpBlob(String name) {
    String blob = "";
    String tempName;
    try {
      JSONArray mans = opBlobsCRLMOD.getJSONArray("operations");

      //LOG.info("Looking at ops: " + String.valueOf(mans.length()) + "\n");
      for (int i = 0; i < mans.length(); i++) {
        JSONObject theOp = mans.getJSONObject(i);
        //LOG.info("DUMP: " + theOp.getString("name"));
        tempName = theOp.getString("name").trim();
        if (tempName.equals(name)) {
          blob = theOp.getString(weppOpParamSetName);
          if (blob.startsWith("No matching")) {
            blob = "";
            break;
          }
        } else {
          //LOG.info("Miss OP: >" + name + "< " + theOp.getString("name"));
        }
      }
    } catch (Exception e) {
      LOG.log(Level.WARNING, "Ignored:", e);
    }
    if (blob.equals("")) {
      LOG.severe("Failed to get operation data: >" + name + "<\n");
    }
    return blob;
  }
}