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

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.GregorianCalendar;

/**
 * This represents an LMOD operation
 *
 * @author jrf
 */
public class LMODOperation implements Comparable<LMODOperation> {

  public enum RecStatus {
    MISSING, OK, NOPROCESSES
  };
  float fuel = 0;
  float stir = 0;

  String date;
  String opName;
  String tranOpName;
  RecStatus opStatus;
  String vegName;
  String tranVegName;
  String resName;
  String tranResName;
  RecStatus vegStatus;
  RecStatus resStatus;
  String vegKey;
  String resKey;
  String resCropKey;
  String harVegKey;
  String opBlob;
  String vegBlob;
  String resBlob;
  List<String> processes;
  List<String> tillageKeys;
  List<String> vegKeys;
  String errors;
  boolean cropGrowing;
  boolean forceKillCrop;
  boolean deletePlantProcess;
  boolean interval;
  WEPPModel wm;

  LMODWeppData dbs;

  float yield;
  float resAmount;
  float targetyield;
  boolean use_CRLMOD;


  /**
   * This constructs the data require of WEPP for an LMOD operation. This can
   * reference other databases to get the detail parameters.
   *
   * @param wm WEPP model for lower level functions
   * @param date date of operation
   * @param op name of operation
   * @param veg vegetation name in operation (can be empty)
   * @param res residue name in operation (can be empty)
   * @param resA residue amount in operation (can be empty)
   * @param yld yield amount in operation (can be empty)
   * @param dbs details of WEPP data loaded so far.
   * @param useR2Names true if using RUSLE2 names would require translation
   * @param use_CRLMOD true if using CRLMOD database
   */
  LMODOperation(WEPPModel wm, String date, String op, String veg, String res, String resA, String yld, LMODWeppData dbs, boolean useR2Names, boolean use_CRLMOD, boolean isintv) {
    this.date = date;
    this.wm = wm;
    this.use_CRLMOD = use_CRLMOD;
    opName = op;
    vegName = veg;
    resName = res;
    interval = isintv;
    errors = "";
    cropGrowing = false;
    forceKillCrop = false;
    if (resA == null) {
      resAmount = -999;
    } else if (resA.equals("") || (resA.equalsIgnoreCase("null"))) {
      resAmount = -999;
    } else {
      resAmount = Float.parseFloat(resA);
    }
    if (yld.equals("") || (yld.equalsIgnoreCase("null"))) {
      yield = -999;
      targetyield = yield;
    } else {
      yield = Float.parseFloat(yld);
      targetyield = yield;
    }
    tranOpName = opName;
    tranVegName = vegName;
    tranResName = resName;
    if (opName != null) {
      if (opName.equals("")) {
        tranOpName = null;
      }
    }
    if (vegName != null) {
      if (vegName.equals("")) {
        tranVegName = null;
      }
    }
    if (resName != null) {
      if (resName.equals("")) {
        tranResName = null;
      }
    }

    processes = new ArrayList<>();
    deletePlantProcess = false;
    this.dbs = dbs;
    readDBBlobs();

    try {
      processes = dbs.getProcesses(opBlob, opName, date);
      fuel = dbs.getLastFuel();
      stir = dbs.getLastSTIR();
      vegKey = dbs.getVegs(vegBlob);
      resCropKey = dbs.getVegs(resBlob);  // this is really the crop key for the residue
      if (resCropKey != null) {
        resKey = dbs.addResidueRec(true, resCropKey, resAmount);
      } else {
        // check if remove is needed
        double amount = checkRemResidue();
        if (amount > 0) {
          resKey = dbs.addResidueRecRem(false, "remove", (float) amount);
        }
      }

      String nkey = dbs.attachYield(vegKey, yield);
      if (nkey != null) {
        vegKey = nkey;
      }
      modifyForRCC(vegKey);
    } catch (Exception e) {
      e.printStackTrace(System.err);
//      throw new RuntimeException(e);
    }
  }


  /**
   * Updates the release canopy cover (rcc) for a certain vegetation.
   *
   * @param key vegetation key to update
   */
  void modifyForRCC(String key) {
    double amount = -999;
    for (int i = 0; i < processes.size(); i++) {
      String pname = processes.get(i);
      String parts[] = pname.split("\\|");
      String proc = parts[0];
      switch (proc) {
        case "plant":
          if (parts.length > 1) {
            amount = Double.parseDouble(parts[1]);
            if (amount > 0) {
              dbs.updateVegsRCC(key, amount);
            }
            amount = Double.parseDouble(parts[2]);
            if (amount > 0) {
              dbs.updateVegsRowWidth(key, amount);
            }
            amount = Double.parseDouble(parts[3]);
            if (amount > 0) {
              dbs.updateVegsCanopyCoverCoef(key, amount);
            }
          }
      }
    }
  }


  /**
   * Get amount of residue removed by either a remove-residue operation or a
   * burning operation.
   *
   * @return get amount of residue removed
   */
  double checkRemResidue() {
    double amount = -999;
    for (int i = 0; i < processes.size(); i++) {
      String pname = processes.get(i);
      String parts[] = pname.split("\\|");
      String proc = parts[0];
      switch (proc) {
        case "remove-residue":
          if (parts.length > 1) {
            amount = Double.parseDouble(parts[1]);
          } else {
            amount = 1.0;
          }
          return amount;
        case "burning":
          if (parts.length > 1) {
            amount = Double.parseDouble(parts[2]);
          } else {
            amount = 1.0;
          }
          return amount;
      }
    }
    return amount;
  }


  /**
   * Get residue added name from operation, null if no residue added in
   * operation.
   *
   * @return residue added name
   */
  String checkEmbeddedResidue() {
    String name = null;
    for (int i = 0; i < processes.size(); i++) {
      String pname = processes.get(i);
      String parts[] = pname.split("\\|");
      String proc = parts[0];
      switch (proc) {
        case "add-residue":
          // return last key added
          name = dbs.getLastResidue();
          break;
      }
    }

    return name;
  }


  /**
   * Get any errors associated with this operation.
   *
   * @return string of error messages if any
   */
  String getErrors() {
    return errors;
  }


  /**
   * Change the year of this operation by +1
   */
  void adjustYears() {
    int yr = getYear();
    setYear(yr + 1);
  }


  /**
   * Read all the detail database parameters for crop, operation and residue
   * tied to this operation. May add to error messages.
   *
   * @return true if database parameters are read.
   */
  // Read the op and veg records
  public boolean readDBBlobs() {
    if (tranOpName != null) {
      try {
        opBlob = getBlobCRLMOD(tranOpName, "operations");
        //if (use_CRLMOD == false) {
          //opBlob = getBlob(tranOpName, "operations");
        //} else {
        //  opBlob = getBlobCRLMOD(tranOpName, "operations");
        //}
        if (opBlob.equals("")) {
          opStatus = RecStatus.MISSING;
          errors = errors + "Error, no operation data for: " + tranOpName + "\n";
        } else {
          opStatus = RecStatus.OK;
        }
      } catch (Exception e) {
        opBlob = null;
        opStatus = RecStatus.MISSING;
        errors = errors + "Error, no operation data for: " + tranOpName + "\n";
      }
    }
    if (tranVegName != null) {
      try {
        vegBlob = getBlobCRLMOD(tranVegName, "vegetations");
        //if (use_CRLMOD == false) {
          //vegBlob = getBlob(tranVegName, "vegetations");
        //} else {
        //  vegBlob = getBlobCRLMOD(tranVegName, "vegetations");
        //}
        if (vegBlob.equals("")) {
          vegStatus = RecStatus.MISSING;
          errors = errors + "Error, no vegetation data for: " + tranVegName + "\n";
        } else {
          vegStatus = RecStatus.OK;
        }
      } catch (Exception e) {
        vegBlob = null;
        vegStatus = RecStatus.MISSING;
        errors = errors + "Error, no vegetation data for: " + tranVegName + "\n";
      }
    }
    // operation either has a vegetation or residue or neither,
    if (tranResName != null) {
      try {
        resBlob = getBlobCRLMOD(tranResName, "residues");
        //if (use_CRLMOD == false) {
          //resBlob = getBlob(tranResName, "vegetations");
        //} else {
        //  resBlob = getBlobCRLMOD(tranResName, "residues");
        //}
        if (resBlob.equals("")) {
          resStatus = RecStatus.MISSING;
          errors = errors + "Error, no vegetation/residue data for: " + tranResName + "\n";
        } else {
          resStatus = RecStatus.OK;
        }
      } catch (Exception e) {
        resBlob = null;
        resStatus = RecStatus.MISSING;
        errors = errors + "Error, no vegetation/residue data for: " + tranResName + "\n";
      }
    }
    return true;
  }


  /**
   * Get detail database parameters for either crops, operation or residue.
   *
   * @param name name of record to retrieve
   * @param table whether name is a crop, operation or residue
   * @return data blob from that record.
   */
  private String getBlobCRLMOD(String name, String table) {
    String data = "";
    if (table == "operations") {
      data = dbs.getOpBlob(name);
    } else if (table == "vegetations") {
      data = dbs.getVegBlob(name);
    } else if (table == "residues") {
      data = dbs.getResBlob(name);
    }
    return data;
  }


  /**
   * Adjust operation date string to the specified year
   *
   * @param yr year to set in the date string
   */
  public void setYear(int yr) {
    String mdate[] = date.split("-");
    mdate[0] = String.format("%04d", yr);
    date = mdate[0] + "-" + mdate[1] + "-" + mdate[2];
  }


  /**
   * Get year portion of operation date string.
   *
   * @return year part of date string
   */
  public int getYear() {
    String mdate[] = date.split("-");
    return Integer.parseInt(mdate[0]);
  }


  /**
   * Get month portion of operation date string.
   *
   * @return month part of date string
   */
  public int getMonth() {
    String mdate[] = date.split("-");
    return Integer.parseInt(mdate[1]);
  }


  /**
   * Get day portion of operation date string
   *
   * @return day part of date string.
   */
  public int getDay() {
    String mdate[] = date.split("-");
    return Integer.parseInt(mdate[2]);
  }
  
  public int getJulianDay() {
      int jdate;
      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);
      jdate = gc.get(GregorianCalendar.DAY_OF_YEAR);
      return jdate;
  }


  /**
   * Get date of operation as a string
   *
   * @return date of operation
   */
  public String getDate() {
    return date;
  }


  /**
   * Get status for operation to include in comments for output file.
   *
   * @return string indicating if this operation is ok
   */
  private String getOpStatus() {
    return (opStatus == RecStatus.MISSING) ? "MISSING" : "OK";
  }


  /**
   * Get status for vegetation to include in comments for output file.
   *
   * @return string indicating if the vegetation is ok
   */
  private String getVegStatus() {
    return (vegStatus == RecStatus.MISSING) ? "MISSING" : "OK";
  }


  /**
   * Get description of this operation to include in comments for output file.
   *
   * @return string representation of the the operation
   */
  public String toString() {
    String st;
    if ((vegName != null) && (vegName != "")) {
      st = date + "|" + opName + "|" + tranOpName + "|" + getOpStatus() + "|" + vegName + "|" + tranVegName + "|" + getVegStatus();
    } else {
      st = date + "|" + opName + "|" + tranOpName + "|" + getOpStatus();
    }
    for (int i = 0; i < processes.size(); i++) {
      if ((processes.get(i) == "kill-crop") && (forceKillCrop == false)) {
        st = st + "\n\t" + processes.get(i) + " (Ignored)";
      } else {
        st = st + "\n\t" + processes.get(i);
      }
    }
    return st;
  }


  /**
   * Get the key associated with the vegetation
   *
   * @param def default string to return if no vegetation
   * @return key to this vegetation in the operation record.
   */
  public String getCurCrop(String def) {
    if ((vegName.equals("")) || (vegStatus == RecStatus.MISSING)) {
      return def;
    } else {
      return vegKey;
    }
  }


  /**
   * Set the vegetation key and harvest yield for this operation.
   *
   * @param key vegetation key to use
   */
  public void setVegIfEmpty(String key) {
    if (harVegKey == null) {
      harVegKey = key;
      targetyield = dbs.getVegTargetYield(harVegKey);
    } else if (harVegKey.equals("")) {
      harVegKey = key;
      targetyield = dbs.getVegTargetYield(harVegKey);
    }
  }


  /**
   * Gets vegetation key associated with this operation
   *
   * @return vegetation key for this operation
   */
  public String getVegKey() {
    return vegKey;
  }


  /**
   * Check this operation has a harvest and plant processing together. WEPP will
   * need to the setup to have the plant operation on the next day because they
   * can not occur on the same day.
   *
   * @return true if operation has both a plant and harvest
   */
  public boolean hasHarvestAndPlant() {
    boolean hasHarvest = false;
    boolean hasPlant = false;
    for (int i = 0; i < processes.size(); i++) {
      String pname = processes.get(i);
      String parts[] = pname.split("\\|");
      String proc = parts[0];
      switch (proc) {
        case "harvest":
          hasHarvest = true;
          break;
        case "plant":
          hasPlant = true;
          break;
      }
    }
    return (hasHarvest && hasPlant);
  }


  /**
   * Converts a numeric month into a 3 character string.
   *
   * @return data string
   */
  public String getAltDate() {
    String[] monstrs = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    int mon = getMonth();
    int day = getDay();
    int yr = getYear();

    return String.format("%s %02d, %02d", monstrs[mon - 1], day, yr);
  }


  public boolean isPerennialMaturePlant() {
    boolean rval = false;

    // if a perennial has a 1-1-1 planting date assume this is
    // handled in the initial conditions. 
    //if ((getMonth() == 1) && (getDay() == 1) && (getYear() == 1)) {
      if (hasProcess("initialplant")) {
        if (dbs.isPerennial(vegKey)) {
          if (dbs.getSenescenceDay(vegKey) > 0) {
            rval = true;
          }
        }
      }
    //}
    return rval;
  }


  /**
   * This converts an operation into a string that may contain zero or more
   * lower level WEPP operations.
   *
   * @param curCrop Current crop that is growing.
   * @return String of lower level operations to output
   */
  public String toOpString(String curCrop) {
    String stl;
    boolean hasHarvest = false;
    boolean advDate = false;
    int[] nextDate;

    String[] dparts = date.split("-");
    int year = Integer.parseInt(dparts[0]);
    int mon = Integer.parseInt(dparts[1]);
    int day = Integer.parseInt(dparts[2]);

    int prisec = 2;
    float rowwid = -999;
    stl = "";
    for (int i = 0; i < processes.size(); i++) {
      String pname = processes.get(i);
      String parts[] = pname.split("\\|");
      String proc = parts[0];
      String weppkey;
      String amount;
      if (advDate == true) {
        nextDate = incDay(mon, day, year);
        // Force all remaining processes to move to the next day
        mon = nextDate[0];
        day = nextDate[1];
        year = nextDate[2];
        advDate = false;
      }
      switch (proc) {
        case "tillage":
          weppkey = parts[1];
          float tdmean = dbs.getTillDepth(weppkey);
          rowwid = dbs.getTillRowWidth(weppkey);
          stl = stl + String.format("%4d  %2d  %2d  1 Tillage\t OpCropDef.%s  {%f, %d}\n", mon, day, year, weppkey, tdmean, prisec);
          break;
        case "initialplant":
          // nothing to do
          break;
        case "plant":
          if (rowwid < 0) {
            rowwid = (float) 0.75;
          }
          if (dbs.isPerennial(vegKey)) {
            rowwid = 0;
            if (deletePlantProcess == false) {
              int senday = dbs.getSenescenceDay(vegKey);
              stl = stl + String.format("%4d  %2d  %2d  1 Plant-Perennial\tCropDef.%s  {%f,%d}\n", mon, day, year, vegKey, rowwid, senday);

            }
          } else {
            float rowwidmod = Float.parseFloat(parts[2]);
            if (rowwidmod > 0) {
              rowwid = rowwidmod;
            }
            stl = stl + String.format("%4d  %2d  %2d  1 Plant-Annual\tCropDef.%s  {%f}\n", mon, day, year, vegKey, rowwid);
          }

          break;
        case "kill-crop":
          if (forceKillCrop) {
            if (dbs.isPerennial(curCrop)) {
              stl = stl + String.format("%4d  %2d  %2d  1 Kill-Perennial\tCropDef.%s {}\n", mon, day, year, curCrop);
            } else {
              stl = stl + String.format("%4d  %2d  %2d  1 Herbicide-App\t {}\n", mon, day, year);
            }
          }
          break;
        case "kill-annual-crop":
            if (forceKillCrop) {
                if (dbs.isPerennial(curCrop)) {
                    stl = stl + String.format("%4d  %2d  %2d  1 Kill-Perennial\tCropDef.%s {}\n", mon, day, year, curCrop);
                } else {
                    stl = stl + String.format("%4d  %2d  %2d  1 Herbicide-App\t {}\n", mon, day, year);
                } 
            }
            break;            
        case "add-residue":
          // this could be that the operation has a residue specified but 
          // it was not passed in via the editor so there is no addtion.
          if (resKey != null) {
            stl = stl + String.format("%4d  %2d  %2d  1 Residue-Addition\t ResidueDef.%s  {}\n", mon, day, year, resKey);
          }
          break;
        case "harvest":
          stl = stl + String.format("%4d  %2d  %2d  1 Harvest-Annual\tCropDef.%s  {}\n", mon, day, year, harVegKey);
          hasHarvest = true;
          break;
        /*
                case "cut-perennial":
                    stl = stl + String.format("%4d  %2d  %2d  1 Cut-Perennial\tCropDef.%s  {}\n", mon, day, year, curCrop);
                    break;
		case "cut-perennial-leave":
                    stl = stl + String.format("%4d  %2d  %2d  1 Cut-Perennial-Leave\tCropDef.%s  {}\n", mon, day, year, curCrop);
                    break;
		case "cut-annual":
		    amount = parts[1];   // fraction of height removed,
                    stl = stl + String.format("%4d  %2d  %2d  1 Cut-Annual\tCropDef.%s  {" + amount + "}\n", mon, day, year, curCrop);
                    break;
		case "cut-annual-leave":
		    amount = parts[1];  // fraction of height removed, leave on field
                    stl = stl + String.format("%4d  %2d  %2d  1 Cut-Annual-Leave\tCropDef.%s  {" + amount + "}\n", mon, day, year, curCrop);
                    break;
         */
        case "irrigation":
          weppkey = parts[1];
          // there are two types of irrigation: depletion and fixed stationary
          stl = stl + String.format("%4d  %2d  %2d  1 Start-Irrigation\tIrrDef.%s  {}\n", mon, day, year, weppkey);
          break;
        case "fixed-irrigation":
          weppkey = parts[1];
          // there are two types of irrigation: depletion and fixed stationary
          stl = stl + String.format("%4d  %2d  %2d  1 Irrigate\tDailyIrrDef.%s  {}\n", mon, day, year, weppkey);
          break;
        case "remove-residue":
          if (resKey != null) {
            stl = stl + String.format("%4d  %2d  %2d  1 Residue-Removal\t ResidueDef.%s  {}\n", mon, day, year, resKey);
          }
          break;
        case "burning":
          String standLost = parts[1];
          String flatLost = parts[2];
          // burning only works for annuals
          if ((dbs.isPerennial(curCrop) == false) || (dbs.isPerennial(curCrop) == true)) {
            stl = stl + String.format("%4d  %2d  %2d  1 Burning {" + standLost + "," + flatLost + "}\n", mon, day, year);
          } else {
            if (resKey != null) {
              stl = stl + String.format("%4d  %2d  %2d  1 Residue-Removal\t ResidueDef.%s  {}\n", mon, day, year, resKey);
            }
          }
          break;
        case "silage":
          // use with caution - only one per crop scenario
          stl = stl + String.format("%4d  %2d  %2d  1 Silage {}\n", mon, day, year);
          hasHarvest = true;
          break;
        case "flatten-residue":
          amount = parts[1];
          stl = stl + String.format("%4d  %2d  %2d  1 Shredding {" + amount + "}\n", mon, day, year);
          break;
        case "remove-live":
        case "remove-live-not-yield":
          if ((curCrop.equals("")) || dbs.isPerennial(curCrop)) {
            if (parts.length >= 2) {
              amount = parts[1];  // % of live biomass removed 
              stl = stl + String.format("%4d  %2d  %2d  1 Graze-Perennial {" + amount + "}\n", mon, day, year);
            } else {
              stl = stl + String.format("%4d  %2d  %2d  1 Cut-Perennial {}\n", mon, day, year);
            }
          } else {
            if (parts.length >= 2) {
              amount = parts[1];  // % of height removed
            } else {
              amount = "1.0";   // if no parameter remove 100%
            }
            stl = stl + String.format("%4d  %2d  %2d  1 Cut-Annual {" + amount + "}\n", mon, day, year);
          }
          break;
        case "remove-live-leave":
        case "remove-live-leave-not-yield":
          if (dbs.isPerennial(curCrop)) {
            if (parts.length >= 2) {
              amount = parts[1];  // % of live biomass removed but left on field
            } else {
              amount = "0";
            }
            stl = stl + String.format("%4d  %2d  %2d  1 Cut-Perennial-Leave {" + amount + "}\n", mon, day, year);
          } else {
            if (parts.length >= 2) {
              amount = parts[1];  // % of height removed but left on field
            } else {
              amount = "1.0";
            }
            stl = stl + String.format("%4d  %2d  %2d  1 Cut-Annual-Leave {" + amount + "}\n", mon, day, year);
          }
          break;
        case "irrigation-stop":
          break;
        case "next-day":
          advDate = true;
          break;
        case "none":
          // has no WEPP equivalent
          stl = stl + String.format("%4d  %2d  %2d  1 NO-OP {}\n", mon, day, year);
          break;
      }

    }
    if (processes.size() == 0) {
        stl = stl + String.format("%4d  %2d  %2d  1 NO-OP {}\n", mon, day, year);
    }
    return stl;
  }


  /**
   * From the data add one day. This is because WEPP can't do some combinations
   * of processes on the same day.
   *
   * @param mon current month
   * @param day current day
   * @param yr current year
   * @return operation date advanced one day
   */
  private int[] incDay(int mon, int day, int yr) {
    int[] pdate = {1, 1, 1};

    GregorianCalendar gc = new GregorianCalendar();
    gc.set(GregorianCalendar.DAY_OF_MONTH, day);
    gc.set(GregorianCalendar.MONTH, mon - 1);
    gc.set(GregorianCalendar.YEAR, 2001);  // use 2001 as a non-leap year
    gc.add(Calendar.DAY_OF_MONTH, 1);

    pdate[1] = gc.get(Calendar.DAY_OF_MONTH);
    pdate[0] = gc.get(Calendar.MONTH) + 1;
    int nyr = gc.get(Calendar.YEAR);
    pdate[2] = yr + (nyr - 2001);

    return pdate;
  }


  /**
   * Get the vegetation key of plant in this operation.
   *
   * @return key for the vegetation, null if the operation has no vegetation
   */
  public String getPlant() {
    return vegName.equals("") ? null : dbs.getVegKey(vegName);
  }


  /**
   * Check if an operation contains a vegetation
   *
   * @return true if operation contains a vegetation
   */
  public boolean hasPlant() {
    return vegName.equals("") ? false : true;
  }


  /**
   * Get residue for this operation.
   *
   * @return residue associated with this operation
   */
  public String getResidue() {
    return resName;
  }


  /**
   * Get fuel usage in l/ha for this operation
   *
   * @return fuel usage
   */
  public float getFuel() {
    return fuel;
  }


  /**
   * Get STIR value for this operation. This value is not computed, it is
   * entered in the data record for the operation.
   *
   * @return STIR for the operation
   */
  public float getSTIR() {
    return stir;
  }


  /**
   * Create a date string for the operation.
   *
   * @param year new year
   * @param mon new month
   * @param day new day
   */
  public void changeDate(int year, int mon, int day) {
    date = String.format("%04d-%02d-%02d", year, mon, day);
  }


  /**
   * Set flag to indicate if a crop is growing during this operation.
   *
   * @param val true if crop is growing
   * @return value passed in
   */
  public boolean setCropGrowing(boolean val) {
    cropGrowing = val;
    return cropGrowing;
  }


  /**
   * Check if there is a crop growing during this operation.
   *
   * @return true if crop growing
   */
  public boolean getCropGrowing() {
    return cropGrowing;
  }


  /**
   * Set if this operation should kill the crop or not.
   *
   * @param val kill flag
   * @return value passed in
   */
  public boolean setIncludeKill(boolean val) {
    forceKillCrop = val;
    return forceKillCrop;
  }


  /**
   * Check if one of the processes has an implied kill-crop.
   *
   * @return true if growth stopped by this operation
   */
  public boolean hasStopGrowthProcess(String vkey) {
    boolean rc = false;
    for (int i = 0; i < processes.size(); i++) {
      String pname = processes.get(i);
      String parts[] = pname.split("\\|");
      String proc = parts[0];
      switch (proc) {
        case "harvest":
        case "kill-crop":
        case "silage":
          rc = true;
          break;
        case "kill-annual-crop":
            if (!dbs.isPerennial(vkey)) {
                rc = true;
            }
            break;
      }
    }
    return rc;
  }


  /**
   * Check if this operation could be the start/end of an interval.
   *
   * @return true if this operation defines an interval date.
   */
  public boolean hasIntervalProcess() {
    boolean rc = false;
    for (int i = 0; i < processes.size(); i++) {
      String pname = processes.get(i);
      String parts[] = pname.split("\\|");
      String proc = parts[0];
      switch (proc) {
        case "harvest":
        case "silage":
          rc = true;
          break;
      }
    }
    return rc;
  }


  /**
   * Check if this operation has any process related to potential crop
   * calibration.
   *
   * @return true if this operation should be considered for calibration
   */
  public boolean hasCalRelatedProcess() {
    boolean rc = false;
    for (int i = 0; i < processes.size(); i++) {
      String pname = processes.get(i);
      String parts[] = pname.split("\\|");
      String proc = parts[0];
      switch (proc) {
        case "harvest":
        case "remove-live":
        case "remove-live-leave":
        case "silage":
          rc = true;
          break;
      }
    }
    return rc;
  }


  /**
   * Check if this operation has a specific process.
   *
   * @param pro name to check for
   * @return true if operation contains the process
   */
  public boolean hasProcess(String pro) {
    boolean rc = false;
    for (int i = 0; i < processes.size(); i++) {
      String pname = processes.get(i);
      String parts[] = pname.split("\\|");
      String proc = parts[0];
      if (proc.equals(pro)) {
        rc = true;
      }
    }
    return rc;
  }


  /**
   * Check if this operation contains irrigation.
   *
   * @return irrigation name if found, else empty string
   */
  public String getIrrigationKey() {
    String key = "";
    for (int i = 0; i < processes.size(); i++) {
      String pname = processes.get(i);
      String parts[] = pname.split("\\|");
      String proc = parts[0];
      if (proc.equals("irrigation")) {
        key = parts[1];
      }
    }
    return key;
  }


  /**
   * Increment the date for this operation.
   *
   * @return new date as a string
   */
  public String incDayString() {
    String[] dparts = date.split("-");
    int year = Integer.parseInt(dparts[0]);
    int mon = Integer.parseInt(dparts[1]);
    int day = Integer.parseInt(dparts[2]);

    int[] nextDate = incDay(mon, day, year);
    mon = nextDate[0];
    day = nextDate[1];
    year = nextDate[2];
    return String.format("%04d-%02d-%02d", year, mon, day);
  }


  /**
   * Check if operation is doing a harvest.
   *
   * @return vegetation that is being harvested.
   */
  public String getHarVegKey() {
    return harVegKey;
  }


  /**
   * Check if dates are the same between operations.
   *
   * @param lm Operation to check
   * @return 0= equal dates
   */
  @Override
  public int compareTo(LMODOperation lm) {
    return date.compareTo(lm.date);
  }


  /**
   * Get yield associated with this operation.
   *
   * @return yield if any
   */
  public float getYield() {
    return yield;
  }


  /**
   * Get target yield for this operation as dry weight.
   *
   * @return target yield if any n kg/m^2
   */
  public float getTargetYield() {
    return targetyield;
  }


  /**
   * Get target yield taking into moisture and converting units to what is in
   * the data record for the vegetation.
   *
   * @return yield in user units
   */
  public float getTargetYieldConv() {
    float targetYieldConv = (float) 0.0001;
    double dryYield = targetyield;
    if (dryYield > 0.0001) {
      double yldConv = getYieldConvFactor();
      double moisture = getMoisture();
      double cyld = yldConv * dryYield;  // this is dry yield
      targetYieldConv = (float) (cyld / (1.0 - (moisture / 100.0)));   // what user sees, account for moisture
      targetYieldConv = targetYieldConv + (float) 0.0001;
    }
    return targetYieldConv;
  }


  /**
   * Get the factor that converts from kg/m^2 to the user defined yield units.
   *
   * @return factor for conversion.
   */
  public float getYieldConvFactor() {
    return dbs.getVegYieldConvFactor(harVegKey);
  }


  /**
   * Get moisture content of yield for this operation.
   *
   * @return typical moisture content from crop record.
   */
  public float getMoisture() {
    return dbs.getVegYieldMoisture(harVegKey);
  }


  /**
   * Get yield units string from crop data record.
   *
   * @return yield units
   */
  public String getYldUnits() {
    return dbs.getVegYieldUnits(harVegKey);
  }


  /**
   * Get vegetation name associated with any harvest in this operation.
   *
   * @return harvest crop name, if any
   */
  public String getHarVegName() {
    return dbs.getVegName(harVegKey);
  }


  /**
   * Get vegetation name associated with this operation
   *
   * @return vegetation name, if any
   */
  public String getVegName() {
    return vegName;
  }


}