SOILS_DATA.java [src/soils/db] Revision: default  Date:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package soils.db;

import csip.api.server.ServiceException;
import gisobjects.GISObject;
import gisobjects.GISObjectException;
import gisobjects.db.GISEngine;
import static java.lang.Double.NaN;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import soils.Component;
import soils.Fragments;
import soils.Horizon;
import soils.MapUnit;
import soils.db.tables.TableComponent;
import soils.db.tables.TableFragments;
import soils.db.tables.TableHorizon;
import soils.db.tables.TableHorizonCalculations;
import soils.db.tables.TableMapUnit;
import soils.exceptions.SDMException;
import soils.exceptions.WEPPException;
import soils.exceptions.WEPSException;
import soils.utils.EvalResult;
import soils.utils.SoilUtils;

/**
 *
 * @author <a href="mailto:shaun.case@colostate.edu">Shaun Case</a>
 */
public interface SOILS_DATA extends AutoCloseable {

  /*    This value, DEFAULT_GEOG_SRID,  should be used for converting intersection
    *  polygon results, and area calculations from an SQL statement to the appropriate
    *  Coordinate Reference System (CRS) default for all csip services,
    *  which is WGS-84 -> SRID: 4326
   */
  public static final String DEFAULT_GEOG_SRID = "4326";

  static String BAD_KFFACT_LAYERS = "No kffact could be located or generated for this component.";
  static String BAD_SAND_LAYERS = "No suitable sand layer could be found or generated for this component.";

  //  The functions below fill in database records without any kind of filtering.
  public HashMap<String, MapUnit> findAllByShape(GISObject shape, boolean returnIntersectionShape) throws SDMException, GISObjectException;

  public boolean findCoCropYieldsByCropNameOrUnit(HashMap<String, MapUnit> mapUnits, ArrayList<String> cropNames, ArrayList<String> yldUnits) throws ServiceException;

  public boolean findMuCropYieldsByCropNameOrUnit(HashMap<String, MapUnit> mapUnits, ArrayList<String> cropNames, ArrayList<String> yldUnits) throws ServiceException;

  public ArrayList<String> findAreaSymbolsByCounty(String countyWKT) throws ServiceException;

  public ArrayList<String> findAreaSymbolsByState(String stateAbbrev) throws ServiceException;

  public boolean findAllComonthData(LinkedHashMap<String, MapUnit> map_units) throws ServiceException;

  public boolean findAllTextureData(HashMap<String, MapUnit> mapUnits) throws ServiceException;

  public boolean findCoCropYldForMukeyList(HashMap<String, MapUnit> mapUnits) throws ServiceException;

  /*
    public HashMap<String, MapUnit> findAllMapUnitComponentHorizonFragmentRecordsByShape(GISObject shape);

    public boolean findAllMapUnitComponentHorizonFragmentRecordsByMukey(MapUnit mapUnit);

    public MapUnit findAllMapUnitComponentHorizonFragmentRecordsByCokey(Component component);
   */
  //  The functions below apply filtering logic to finding records in the database.
  public boolean findComponentSoilMoisture(HashMap<String, MapUnit> mapUnits) throws ServiceException;

  public boolean findComponentsForMapUnit(MapUnit mapUnit) throws ServiceException;

  public MapUnit findComponentsForMukey(String mukey) throws ServiceException;

  public boolean findComponentsForMukeyList(HashMap<String, MapUnit> mapUnits, boolean computeLightleWeesieSlope) throws ServiceException;

  public Boolean findComponentsByMukey(MapUnit mapUnit) throws ServiceException;

  public Boolean findComponentsHorizonsFragsByMukey(MapUnit mapUnit, boolean computeLightleWeesieSlope, String filter) throws ServiceException;

  public boolean findAllBasicNoFilterComponentHorizonFragTextureData(HashMap<String, MapUnit> mapUnits) throws ServiceException;

  public boolean findAllBasicComponentHorizonFragTextureData(HashMap<String, MapUnit> mapUnits) throws ServiceException;

  public boolean findAllBasicComponentHorizonFragTextureData(HashMap<String, MapUnit> mapUnits, boolean includeNonMajComp) throws ServiceException;

  //Orders results by mukey, cokey, horizon depth, and kffact.  Returned results meet the following criteria:
  //Hydrologic group, hydgrp, must not be null/empty, and Component Percentage of the Map Unit, comppct_r, must not be null/empty
  public boolean findComponentsHorizonsFragsForMukeyList(HashMap<String, MapUnit> mapUnits, boolean computeLightleWeesieSlope, String filter) throws ServiceException;

  public ArrayList<ComponentsHorizonsFrags> findComponentsHorizonsFragsForMukeyList(ArrayList<String> mukeys, boolean computeLightleWeesieSlope, String filter) throws ServiceException;

  public boolean findEcoClassForMukeyList(HashMap<String, MapUnit> mapUnits) throws ServiceException;

  public boolean findEcoClassForMukeyList(HashMap<String, MapUnit> mapUnits, String addWhere) throws ServiceException;

  public boolean findHorizonsForCokey(MapUnit mapUnit, Component component) throws ServiceException;

  public HashMap<String, MapUnit> findMapUnitsForGISObject(GISObject gisObject, boolean computeIntersectionArea, boolean returnIntersectionShape) throws ServiceException, GISObjectException;

  public HashMap<String, MapUnit> findMapUnitsForGISObject(GISObject gisObject) throws ServiceException, GISObjectException;

  public HashMap<String, MapUnit> findMapUnitsForWKT(String wkt, boolean computeIntersectinArea, boolean returnIntersectionShape) throws ServiceException;

  public HashMap<String, MapUnit> findMapUnitsForWKT(String wkt) throws ServiceException;

  public boolean findMuCropYldForMukeyList(HashMap<String, MapUnit> mapUnits) throws ServiceException;

  public MapUnit findMukeyData(String mukey) throws ServiceException;

  public boolean findMukeyData(MapUnit mapUnit) throws ServiceException;

  public MapUnit findBasicNoFilterMukeyDataByCokey(Component component) throws ServiceException;

  public MapUnit findMukeyDataByCokey(Component component) throws ServiceException;

  public MapUnit findMapUnitsByMupolygonKey(String mupolygonkey, GISEngine gisEngine, boolean computeIntersectionArea, boolean returnIntersectionShape) throws ServiceException, GISObjectException;

  public HashMap<String, MapUnit> findMukeyDataByAreasymbol(String areaSymbol) throws ServiceException;

  public void findMukeyDataAndShapeByAreasymbol(ArrayList<String> areaSymbols, GISEngine gisEngine, HashMap<String, MapUnit> mapUnits) throws ServiceException;

  @Deprecated
  public boolean findWEPSHorizonsForCokey(MapUnit mapUnit, Component component) throws ServiceException;

  public MapUnit findBasicNoFilterDataByCokey(Component component) throws ServiceException;

  public MapUnit findWEPPDataByCokey(Component component) throws ServiceException, WEPPException;

  public MapUnit findWEPSDataByCokey(Component component, boolean adjustStratified, boolean organicChecks, boolean estimateNulls) throws ServiceException, WEPSException;

  ///////////////////////////////////////////////////////////////////////////
  //  Interface member functions
  ///////////////////////////////
  public Connection getConnection();

  default public String getMapUnitFieldList() {
    TableMapUnit mapUnit = new TableMapUnit();
    String tableName = TableMapUnit.TABLE_NAME;
    String ret_val = "";
    int i = 0;
    synchronized (this) {
      for (String key : mapUnit.getColumnList()) {
        if (i > 0) {
          ret_val += ", " + tableName + "." + key;
        } else {
          ret_val += tableName + "." + key;
        }
        i++;
      }
    }
    return ret_val;
  }

  default public String getComponentFieldList() {
    TableComponent component = new TableComponent();
    String tableName = TableComponent.TABLE_NAME;
    String ret_val = "";
    int i = 0;
    synchronized (this) {
      Component.setDefaultUsedColumns(component.getColumnList());
      for (String key : component.getColumnList()) {
        if (i > 0) {
          ret_val += ", " + tableName + "." + key;
        } else {
          ret_val += tableName + "." + key;
        }
        i++;
      }
    }
    return ret_val;
  }

  default public String getHorizonFieldList() {
    TableHorizon horizon = new TableHorizon();
    String tableName = TableHorizon.TABLE_NAME;
    String ret_val = "";
    ret_val = horizon.getFullColumnNames(false);

//        int i = 0;
//        Horizon.setDefaultUsedColumns(horizon.getColumnList());
//        for (String key : horizon.getColumnList()) {
//            if (i > 0) {
//                ret_val += ", " + tableName + "." + key;
//            } else {
//                ret_val += tableName + "." + key;
//            }
//            i++;
//        }
    return ret_val;
  }

  default public String getFragFieldList() {
    TableFragments frag = new TableFragments();
    String tableName = TableFragments.TABLE_NAME;
    String ret_val = "";
    int i = 0;
    Fragments.setDefaultUsedColumns(frag.getColumnList());
    for (String key : frag.getColumnList()) {
      if (i > 0) {
        ret_val += ", " + tableName + "." + key;
      } else {
        ret_val += tableName + "." + key;
      }
      i++;
    }
    return ret_val;
  }

  public boolean isCONUS(double lat, double lon) throws SQLException, ServiceException;

  /**
   * This default member function is vital for continuity among soils service
   * calls. It implements the default behavior desired by NRCS and SME's in the
   * soils sciences regarding how to select proper horizon dbResults for wind
   * and water erosion models.
   *
   * Side Effects: This function will set values in the component objects passed
   * in with the MapUnit object, or will create new ones if not found in the
   * MapUnit object. All Components and Horizons will be altered according to
   * the data retrieved from the database in the ResultSet passed in the
   * dbResults parameter and according to the logic specified herein.
   *
   *
   * Please note: This function is not written to combine wind and water logic
   * all in one-fell-swoop. Thus, it could be made to be a tad more efficient,
   * if it is written to be able to do both when the filter param is set to, for
   * instance, "both". This could be accomplished by resetting the input
   * ResultSet back to its top member and reiterating through again for the next
   * filter value, not previously used. This would prevent a calling function
   * from forcing a requery of the exact same information. (Might trim off a few
   * hundred milliseconds...)
   *
   * @param dbResults
   * @param filterResults
   * @param mapUnit
   * @param filter
   * @param computeLightleWeesieSlope
   * @return Returns a boolean signifying success or failure of finding a valid
   * component, horizon, frag record meeting the logic requirements for the
   * filter specified. Additionally, the input parameter, filterResults,
   * contains result data pertaining to the success or failure of finding a
   * valid kffact and sandlayer value depending on the filter specified.
   * @throws SQLException
   * @throws ServiceException
   */
  @Deprecated
  default boolean filterComponentHorizonFragData(ResultSet dbResults, FilterResults filterResults, MapUnit mapUnit, String filter, boolean computeLightleWeesieSlope) throws SQLException, ServiceException {
    boolean ret_val = false;
    String lastCokey = "";
    double lambda = 0.0;
    LinkedHashMap<String, Component> components = mapUnit.components();
    filterResults.foundKffact = false;
    filterResults.foundSandLayer = false;

    while (dbResults.next()) {
      Component tComponent;

      String cokey = dbResults.getString(TableComponent.COKEY);

      if (components.containsKey(cokey)) {
        tComponent = components.get(cokey);
      } else {
        tComponent = new Component();
        tComponent.cokey(cokey);
        components.put(cokey, tComponent);
      }

      String chkey = dbResults.getString(TableHorizon.CHKEY_NAME);
      Horizon tHorizon;
      if (tComponent.horizons.containsKey(chkey)) {
        tHorizon = tComponent.horizons.get(chkey);
      } else {
        tHorizon = new Horizon();
        tHorizon.chkey(chkey);
      }

      tComponent.setUsedColumns(new ArrayList<>(Arrays.asList(TableComponent.COMPPCT_R_NAME, TableComponent.HYDGRP_NAME, TableComponent.SLOPE_R_NAME,
          TableComponent.TAXORDER_NAME, TableComponent.MUKEY, TableComponent.COKEY, TableComponent.COMPNAME,
          TableComponent.WEI_NAME, TableComponent.SLOPELENUSLE_R, TableComponent.TFACT_NAME, TableComponent.HYDRICRATING)));

      tHorizon.setUsedColumns(new ArrayList<>(Arrays.asList(TableHorizon.COKEY, TableHorizon.CHKEY_NAME, TableHorizon.OM_R_NAME,
          TableHorizon.HZTHK_R_NAME, TableHorizon.HZDEPT_R_NAME, TableHorizon.HZDEPB_R_NAME, TableHorizon.KWFACT_NAME,
          TableHorizon.KFFACT_NAME, TableHorizon.SANDTOTAL_R_NAME, TableHorizon.SILTTOTAL_R, TableHorizon.CLAYTOTAL_R,
          TableHorizon.PH1TO1H2O_L, TableHorizon.PH1TO1H2O_R, TableHorizon.PH1TO1H2O_H, TableHorizon.SAR_R, TableHorizon.EC_R,
          TableHorizonCalculations.FRAGVOL_R_NAME)));

      tComponent.readFromSQL(dbResults);
      tHorizon.readFromSQL(dbResults);

      double slope_r = tComponent.slope_r();
      tComponent.slope_r(slope_r);
      tComponent.slopegr15((slope_r > 15));

      if (mapUnit.aoaArea() > 0.0) {
        tComponent.area_pct((tComponent.comppct_r() / 100.0) * (mapUnit.area() / mapUnit.aoaArea()) * 100.0);
      } else {
        tComponent.area_pct(EvalResult.getDefaultDouble());
      }
      if (mapUnit.area() > 0.0) {
        tComponent.calculated_area(tComponent.comppct_r() / 100.0 * mapUnit.area());
      } else {
        tComponent.calculated_area(EvalResult.getDefaultDouble());
      }

      if (filter.equalsIgnoreCase("WATER")) { // Water Erodibility needed.
        String taxorder = tComponent.taxorder();

        if (!lastCokey.equalsIgnoreCase(tComponent.cokey())) {
          if (!filterResults.foundKffact) {
            if (!lastCokey.isEmpty()) {
              if (components.containsKey(lastCokey)) {
                components.get(lastCokey).setExclude(true, BAD_KFFACT_LAYERS);
              }
            }
          }
          lastCokey = tComponent.cokey();
          filterResults.foundKffact = false;
        }
        if (!filterResults.foundKffact) {
          if (!computeLightleWeesieSlope) {
            lambda = tComponent.slopelenusle_r();
          } else {
            lambda = SoilUtils.calcLightleWeesieSlopeSlopeLength(slope_r, mapUnit.isPalouse());
          }

          tHorizon.lsfact(SoilUtils.calcLsFactor(slope_r, lambda));
          tComponent.length_r(lambda);

          if (EvalResult.testDefaultDouble(tHorizon.kffact())) {

            if (EvalResult.testDefaultDouble(tHorizon.kwfact())) {
              if (taxorder.equalsIgnoreCase("histosols")) {
                tHorizon.kffact(0.02);
                filterResults.foundKffact = true;
                tHorizon.selected(true);
                tHorizon.selectedReason("WATER");
                tComponent.calculated_kffact(tHorizon.kffact());
                tComponent.calculated_lsfact(tHorizon.lsfact());
                tComponent.calculated_claytotal_r(tHorizon.claytotal_r());
                ret_val = true;
              }
            } else {
              filterResults.foundKffact = true;
              tHorizon.selected(true);
              tHorizon.selectedReason("WATER");
              tComponent.calculated_kffact(tHorizon.kffact());
              tComponent.calculated_lsfact(tHorizon.lsfact());
              tComponent.calculated_claytotal_r(tHorizon.claytotal_r());
              ret_val = true;
            }
          } else {
            filterResults.foundKffact = true;
            tHorizon.selected(true);
            tHorizon.selectedReason("WATER");
            tComponent.calculated_kffact(tHorizon.kffact());
            tComponent.calculated_lsfact(tHorizon.lsfact());
            tComponent.calculated_claytotal_r(tHorizon.claytotal_r());
            ret_val = true;
          }
        }

        tComponent.horizons.put(chkey, tHorizon);

      } else if (filter.equalsIgnoreCase("WIND")) {  //Wind Erodability needed

        if (!lastCokey.equalsIgnoreCase(tComponent.cokey())) {
          if (!filterResults.foundSandLayer) {
            if (!lastCokey.isEmpty()) {
              if (components.containsKey(lastCokey)) {
                components.get(lastCokey).setExclude(true, BAD_SAND_LAYERS);
              }
            }
          }
          lastCokey = tComponent.cokey();
          filterResults.foundSandLayer = false;
        }

        if (!filterResults.foundSandLayer) {
          if (tComponent.taxorder().equalsIgnoreCase("histosols") || (!tComponent.taxorder().equalsIgnoreCase("histosols") && (tHorizon.hzdepb_r() > 10) && (tHorizon.om_r() > 15))) {
            tHorizon.sandtotal_r(50);
          }
          if (tHorizon.sandtotal_r() >= 0.0) {
            tComponent.calculated_sandtotal_r(tHorizon.sandtotal_r());
            tHorizon.selected(true);
            tHorizon.selectedReason("WIND");
            tComponent.calculated_om_r(tHorizon.om_r());
            tComponent.calculated_hzdepb_r(tHorizon.hzdepb_r());
            tComponent.calculated_claytotal_r(tHorizon.claytotal_r());
            filterResults.foundSandLayer = true;
            ret_val = true;
          }
        }

        tComponent.horizons.put(chkey, tHorizon);

      } else {
        throw new ServiceException("Invalid filter specified to component lookup. Not implemented.");
      }
    }

    return ret_val;
  }

  default boolean filterRusle2ComponentHorizonData(ResultSet dbResults, FilterResults filterResults, MapUnit mapUnit, Component tComponent) throws SQLException, ServiceException {
    boolean ret_val = false;
    String lastCokey = tComponent.cokey();
    double lambda = 0.0;
    filterResults.foundKffact = false;
    filterResults.foundSandLayer = false;

    while (dbResults.next()) {
      String cokey = dbResults.getString(TableComponent.COKEY);

      if (tComponent.cokey().equals(cokey)) {

        String chkey = dbResults.getString(TableHorizon.CHKEY_NAME);
        Horizon tHorizon;
        if (tComponent.horizons.containsKey(chkey)) {
          tHorizon = tComponent.horizons.get(chkey);
        } else {
          tHorizon = new Horizon();
          tHorizon.chkey(chkey);
        }

        mapUnit.setUsedColumns(new ArrayList<>(Arrays.asList(TableMapUnit.MUKEY, TableMapUnit.MUNAME, TableMapUnit.MUSYM, TableMapUnit.AREA_NAME)));
        tComponent.setUsedColumns(new ArrayList<>(Arrays.asList(TableComponent.TAXORDER_NAME, TableComponent.TAXSUBGRP, TableComponent.TFACT_NAME,
            TableComponent.COMPNAME, TableComponent.COKEY, TableComponent.SLOPE_R_NAME, TableComponent.SLOPE_L, TableComponent.SLOPE_H,
            TableComponent.SLOPELENUSLE_R, TableComponent.SLOPELENUSLE_L, TableComponent.SLOPELENUSLE_H,
            TableComponent.MUKEY, TableComponent.HYDGRP_NAME)));
        tHorizon.setUsedColumns(new ArrayList<>(Arrays.asList(TableHorizon.CEC7_H, TableHorizon.CEC7_L, TableHorizon.CEC7_R,
            TableHorizon.SANDTOTAL_R_NAME, TableHorizon.SILTTOTAL_R, TableHorizon.CLAYTOTAL_R,
            TableHorizon.WTHIRDBAR_R, TableHorizon.WTHIRDBAR_L, TableHorizon.WTHIRDBAR_H,
            TableHorizon.OM_R_NAME, TableHorizon.OM_L, TableHorizon.OM_H,
            TableHorizon.WFIFTEENBAR_R, TableHorizon.WFIFTEENBAR_L, TableHorizon.WFIFTEENBAR_H,
            TableHorizon.DBTHIRDBAR_R, TableHorizon.DBTHIRDBAR_L, TableHorizon.DBTHIRDBAR_H,
            TableHorizon.PH1TO1H2O_R, TableHorizon.PH1TO1H2O_L, TableHorizon.PH1TO1H2O_H)));

        mapUnit.readFromSQL(dbResults);
        tComponent.readFromSQL(dbResults);
        tHorizon.readFromSQL(dbResults);

        tComponent.area_pct(EvalResult.getDefaultDouble());
        tComponent.calculated_area(EvalResult.getDefaultDouble());

        // Water Erodibility needed.
        String taxorder = tComponent.taxorder();
        String taxsubgrp = tComponent.taxsubgrp();

        if (!lastCokey.equalsIgnoreCase(tComponent.cokey())) {
          throw new ServiceException("Invalid component key encountered in results from database.");
        }

        if (!filterResults.foundKffact) {

          tHorizon.kffact(dbResults.getDouble(TableHorizon.KFFACT_NAME));
          if (dbResults.wasNull()) {
            tHorizon.kffact(dbResults.getDouble(TableHorizon.KWFACT_NAME));
            if (dbResults.wasNull()) {
              if (taxorder.equalsIgnoreCase("histosols") || (taxsubgrp.contains("Histic"))) {
                tHorizon.kffact(0.02);
                filterResults.foundKffact = true;
                tHorizon.selected(true);
                tHorizon.selectedReason("WATER");
                tComponent.calculated_kffact(tHorizon.kffact());
                tComponent.calculated_sandtotal_r(0.0);
                tComponent.calculated_silttotal_r(0.0);
                tComponent.calculated_claytotal_r(100.0);

                ret_val = true;
              }

            } else {
              if (!EvalResult.testDefaultDouble(tHorizon.sandtotal_r())
                  && !EvalResult.testDefaultDouble(tHorizon.silttotal_r())
                  && !EvalResult.testDefaultDouble(tHorizon.claytotal_r())) {

                filterResults.foundKffact = true;
                tHorizon.selected(true);
                tHorizon.selectedReason("WATER");

                //  Recall, from above, we copied KWFACT into the KFFACT location of this 
                //horizon when we couldn't find a kffact value, so copy to component from 
                //kffact, even though it was a kwfact value.                                       
                tComponent.calculated_kffact(tHorizon.kffact());
                tComponent.calculated_sandtotal_r(tHorizon.sandtotal_r());
                tComponent.calculated_silttotal_r(tHorizon.silttotal_r());
                tComponent.calculated_claytotal_r(tHorizon.claytotal_r());

                ret_val = true;
              }

            }
          } else {
            if (!EvalResult.testDefaultDouble(tHorizon.sandtotal_r())
                && !EvalResult.testDefaultDouble(tHorizon.silttotal_r())
                && !EvalResult.testDefaultDouble(tHorizon.claytotal_r())) {

              filterResults.foundKffact = true;
              tHorizon.selected(true);
              tHorizon.selectedReason("WATER");
              tComponent.calculated_kffact(tHorizon.kffact());
              tComponent.calculated_sandtotal_r(tHorizon.sandtotal_r());
              tComponent.calculated_silttotal_r(tHorizon.silttotal_r());
              tComponent.calculated_claytotal_r(tHorizon.claytotal_r());

              ret_val = true;
            }
          }
        }

        tComponent.horizons.put(chkey, tHorizon);

      } else {
        break;
      }
    }
    if (!filterResults.foundKffact) {
      tComponent.setExclude(true, BAD_KFFACT_LAYERS);
    }

    return ret_val;
  }

  /**
   * This function filters ONE single component only. Consequently, the excluded
   * flag will not ever be set, by this function, for any component of interest.
   *
   * @param dbResults
   * @param filterResults
   * @param mapUnit
   * @param tComponent
   * @param computeLightleWeesieSlope
   * @return
   * @throws SQLException
   * @throws ServiceException
   */
  @Deprecated
  default boolean filterWEPSComponentHorizonFragData(ResultSet dbResults, FilterResults filterResults, MapUnit mapUnit, Component tComponent, boolean computeLightleWeesieSlope) throws SQLException, ServiceException {
    boolean ret_val = false;
    String lastCokey = tComponent.cokey();
    double lambda = 0.0;
    LinkedHashMap<String, Component> components = mapUnit.components();
    filterResults.foundKffact = false;
    filterResults.foundSandLayer = false;

    while (dbResults.next()) {
      String cokey = dbResults.getString(TableComponent.COKEY);

      if (tComponent.cokey().equals(cokey)) {

        String chkey = dbResults.getString(TableHorizon.CHKEY_NAME);
        Horizon tHorizon;
        if (tComponent.horizons.containsKey(chkey)) {
          tHorizon = tComponent.horizons.get(chkey);
        } else {
          tHorizon = new Horizon();
          tHorizon.chkey(chkey);
        }

        tComponent.setUsedColumns(new ArrayList<>(Arrays.asList(TableComponent.MUKEY, TableComponent.COKEY, TableComponent.COMPNAME,
            TableComponent.COMPPCT_R_NAME, TableComponent.HYDGRP_NAME, TableComponent.SLOPE_R_NAME, TableComponent.SLOPE_L, TableComponent.SLOPE_H,
            TableComponent.TAXORDER_NAME, TableComponent.TAXSUBGRP,
            TableComponent.WEI_NAME, TableComponent.SLOPELENUSLE_R, TableComponent.SLOPELENUSLE_L, TableComponent.SLOPELENUSLE_H,
            TableComponent.TFACT_NAME,
            TableComponent.ALBEDODRY_R, TableComponent.ALBEDODRY_L, TableComponent.ALBEDODRY_H
        )));

        tHorizon.setUsedColumns(new ArrayList<>(Arrays.asList(TableHorizon.COKEY, TableHorizon.CHKEY_NAME, TableHorizon.OM_R_NAME,
            TableHorizon.HZDEPT_R_NAME, TableHorizon.HZDEPB_R_NAME, TableHorizon.OM_L, TableHorizon.OM_H,
            TableHorizon.HZTHK_R_NAME, TableHorizon.KWFACT_NAME, TableHorizon.KFFACT_NAME,
            TableHorizon.CLAYTOTAL_R, TableHorizon.CLAYTOTAL_L, TableHorizon.CLAYTOTAL_H,
            TableHorizon.SANDVC_R, TableHorizon.SANDVC_L, TableHorizon.SANDVC_H,
            TableHorizon.SANDCO_R, TableHorizon.SANDCO_L, TableHorizon.SANDCO_H,
            TableHorizon.CACO3_R, TableHorizon.CACO3_L, TableHorizon.CACO3_H,
            TableHorizon.SANDMED_R, TableHorizon.SANDMED_L, TableHorizon.SANDMED_H,
            TableHorizon.SANDFINE_R, TableHorizon.SANDFINE_L, TableHorizon.SANDFINE_H,
            TableHorizon.SANDVF_R, TableHorizon.SANDVF_L, TableHorizon.SANDVF_H,
            TableHorizon.SANDTOTAL_R_NAME, TableHorizon.SANDTOTAL_L, TableHorizon.SANDTOTAL_H,
            TableHorizon.SILTTOTAL_R, TableHorizon.SILTTOTAL_L, TableHorizon.SILTTOTAL_H,
            TableHorizon.CEC7_R, TableHorizon.CEC7_L, TableHorizon.CEC7_H,
            TableHorizon.WTHIRDBAR_R, TableHorizon.WTHIRDBAR_L, TableHorizon.WTHIRDBAR_H,
            TableHorizon.WFIFTEENBAR_R, TableHorizon.WFIFTEENBAR_L, TableHorizon.WFIFTEENBAR_H,
            TableHorizon.DBTHIRDBAR_R, TableHorizon.DBTHIRDBAR_L, TableHorizon.DBTHIRDBAR_H,
            TableHorizon.PH1TO1H2O_R, TableHorizon.PH1TO1H2O_L, TableHorizon.PH1TO1H2O_H,
            TableHorizon.ECEC_R, TableHorizon.ECEC_L, TableHorizon.ECEC_H,
            TableHorizon.LEP_R, TableHorizon.LEP_L, TableHorizon.LEP_H,
            TableHorizon.KSAT_R, TableHorizon.KSAT_R, TableHorizon.KSAT_H
        )));

        tComponent.readFromSQL(dbResults);
        tHorizon.readFromSQL(dbResults);

        double slope_r = tComponent.slope_r();
        tComponent.slope_r(slope_r);
        tComponent.slopegr15((slope_r > 15));

        if (mapUnit.aoaArea() > 0.0) {
          tComponent.area_pct((tComponent.comppct_r() / 100.0) * (mapUnit.area() / mapUnit.aoaArea()) * 100.0);
        } else {
          tComponent.area_pct(EvalResult.getDefaultDouble());
        }
        if (mapUnit.area() > 0.0) {
          tComponent.calculated_area(tComponent.comppct_r() / 100.0 * mapUnit.area());
        } else {
          tComponent.calculated_area(EvalResult.getDefaultDouble());
        }

        //Wind Erodability filtering
        if (!lastCokey.equalsIgnoreCase(tComponent.cokey())) {
          throw new ServiceException("Invalid component key encountered in results from database.");
        }

        if (!filterResults.foundSandLayer) {
          if (tComponent.taxorder().equalsIgnoreCase("histosols") || (!tComponent.taxorder().equalsIgnoreCase("histosols") && (tHorizon.hzdepb_r() > 10) && (tHorizon.om_r() > 15))) {
            tHorizon.sandtotal_r(50);
          }
          if (tHorizon.sandtotal_r() >= 0.0) {
            tComponent.calculated_sandtotal_r(tHorizon.sandtotal_r());
            tHorizon.selected(true);
            tHorizon.selectedReason("WIND");
            tComponent.calculated_om_r(tHorizon.om_r());
            tComponent.calculated_hzdepb_r(tHorizon.hzdepb_r());
            filterResults.foundSandLayer = true;
            ret_val = true;
          }
        }

        tComponent.horizons.put(chkey, tHorizon);

      } else {
        break;
      }
    }

    if (!filterResults.foundSandLayer) {
      tComponent.setExclude(true, BAD_SAND_LAYERS);
    }

    return ret_val;
  }

  /**
   * This function filters ONE single component only. Consequently, the excluded
   * flag will not ever be set, by this function, for any component of interest.
   *
   * @param dbResults
   * @param filterResults
   * @param mapUnit
   * @param tComponent
   * @param computeLightleWeesieSlope
   * @return
   * @throws SQLException
   * @throws ServiceException
   */
  default boolean filterWEPPComponentHorizonFragData(ResultSet dbResults, FilterResults filterResults, MapUnit mapUnit, Component tComponent, boolean computeLightleWeesieSlope) throws SQLException, ServiceException {
    boolean ret_val = false;
    String lastCokey = tComponent.cokey();
    double lambda = 0.0;
    LinkedHashMap<String, Component> components = mapUnit.components();
    filterResults.foundKffact = false;
    filterResults.foundSandLayer = false;

    while (dbResults.next()) {
      String cokey = dbResults.getString(TableComponent.COKEY);

      if (tComponent.cokey().equals(cokey)) {

        String chkey = dbResults.getString(TableHorizon.CHKEY_NAME);
        Horizon tHorizon;
        if (tComponent.horizons.containsKey(chkey)) {
          tHorizon = tComponent.horizons.get(chkey);
        } else {
          tHorizon = new Horizon();
          tHorizon.chkey(chkey);
        }

        tComponent.setUsedColumns(new ArrayList<>(Arrays.asList(TableComponent.MUKEY, TableComponent.COKEY, TableComponent.COMPNAME,
            TableComponent.COMPPCT_R_NAME, TableComponent.HYDGRP_NAME, TableComponent.SLOPE_R_NAME, TableComponent.SLOPE_L, TableComponent.SLOPE_H,
            TableComponent.TAXORDER_NAME, TableComponent.TAXSUBGRP,
            TableComponent.WEI_NAME, TableComponent.SLOPELENUSLE_R, TableComponent.SLOPELENUSLE_L, TableComponent.SLOPELENUSLE_H,
            TableComponent.TFACT_NAME,
            TableComponent.ALBEDODRY_R, TableComponent.ALBEDODRY_L, TableComponent.ALBEDODRY_H
        )));

        tHorizon.setUsedColumns(new ArrayList<>(Arrays.asList(TableHorizon.COKEY, TableHorizon.CHKEY_NAME, TableHorizon.OM_R_NAME,
            TableHorizon.HZDEPT_R_NAME, TableHorizon.HZDEPB_R_NAME, TableHorizon.OM_L, TableHorizon.OM_H,
            TableHorizon.HZTHK_R_NAME, TableHorizon.KWFACT_NAME, TableHorizon.KFFACT_NAME,
            TableHorizon.CLAYTOTAL_R, TableHorizon.CLAYTOTAL_L, TableHorizon.CLAYTOTAL_H,
            TableHorizon.SANDVC_R, TableHorizon.SANDVC_L, TableHorizon.SANDVC_H,
            TableHorizon.SANDCO_R, TableHorizon.SANDCO_L, TableHorizon.SANDCO_H,
            TableHorizon.CACO3_R, TableHorizon.CACO3_L, TableHorizon.CACO3_H,
            TableHorizon.SANDMED_R, TableHorizon.SANDMED_L, TableHorizon.SANDMED_H,
            TableHorizon.SANDFINE_R, TableHorizon.SANDFINE_L, TableHorizon.SANDFINE_H,
            TableHorizon.SANDVF_R, TableHorizon.SANDVF_L, TableHorizon.SANDVF_H,
            TableHorizon.SANDTOTAL_R_NAME, TableHorizon.SANDTOTAL_L, TableHorizon.SANDTOTAL_H,
            TableHorizon.SILTTOTAL_R, TableHorizon.SILTTOTAL_L, TableHorizon.SILTTOTAL_H,
            TableHorizon.CEC7_R, TableHorizon.CEC7_L, TableHorizon.CEC7_H,
            TableHorizon.WTHIRDBAR_R, TableHorizon.WTHIRDBAR_L, TableHorizon.WTHIRDBAR_H,
            TableHorizon.WFIFTEENBAR_R, TableHorizon.WFIFTEENBAR_L, TableHorizon.WFIFTEENBAR_H,
            TableHorizon.DBTHIRDBAR_R, TableHorizon.DBTHIRDBAR_L, TableHorizon.DBTHIRDBAR_H,
            TableHorizon.PH1TO1H2O_R, TableHorizon.PH1TO1H2O_L, TableHorizon.PH1TO1H2O_H,
            TableHorizon.ECEC_R, TableHorizon.ECEC_L, TableHorizon.ECEC_H,
            TableHorizon.LEP_R, TableHorizon.LEP_L, TableHorizon.LEP_H,
            TableHorizon.KSAT_R, TableHorizon.KSAT_R, TableHorizon.KSAT_H
        )));

        tComponent.readFromSQL(dbResults);
        tHorizon.readFromSQL(dbResults);

        double slope_r = tComponent.slope_r();
        tComponent.slope_r(slope_r);
        tComponent.slopegr15((slope_r > 15));

        if (mapUnit.aoaArea() > 0.0) {
          tComponent.area_pct((tComponent.comppct_r() / 100.0) * (mapUnit.area() / mapUnit.aoaArea()) * 100.0);
        } else {
          tComponent.area_pct(EvalResult.getDefaultDouble());
        }
        if (mapUnit.area() > 0.0) {
          tComponent.calculated_area(tComponent.comppct_r() / 100.0 * mapUnit.area());
        } else {
          tComponent.calculated_area(EvalResult.getDefaultDouble());
        }

        //Wind Erodability filtering
        if (!lastCokey.equalsIgnoreCase(tComponent.cokey())) {
          throw new ServiceException("Invalid component key encountered in results from database.");
        }

        String taxorder = tComponent.taxorder();

        if (!lastCokey.equalsIgnoreCase(tComponent.cokey())) {
          if (!filterResults.foundKffact) {
            if (!lastCokey.isEmpty()) {
              if (components.containsKey(lastCokey)) {
                components.get(lastCokey).setExclude(true, BAD_KFFACT_LAYERS);
              }
            }
          }
          lastCokey = tComponent.cokey();
          filterResults.foundKffact = false;
        }
        if (!filterResults.foundKffact) {
          if (!computeLightleWeesieSlope) {
            lambda = tComponent.slopelenusle_r();
          } else {
            lambda = SoilUtils.calcLightleWeesieSlopeSlopeLength(slope_r, mapUnit.isPalouse());
          }

          tHorizon.lsfact(SoilUtils.calcLsFactor(slope_r, lambda));
          tComponent.length_r(lambda);

          if (EvalResult.testDefaultDouble(tHorizon.kffact())) {

            if (EvalResult.testDefaultDouble(tHorizon.kwfact())) {
              if (taxorder.equalsIgnoreCase("histosols")) {
                tHorizon.kffact(0.02);
                filterResults.foundKffact = true;
                tHorizon.selected(true);
                tHorizon.selectedReason("WATER");
                tComponent.calculated_kffact(tHorizon.kffact());
                tComponent.calculated_lsfact(tHorizon.lsfact());
                tComponent.calculated_claytotal_r(tHorizon.claytotal_r());
                ret_val = true;
              }
            } else {
              filterResults.foundKffact = true;
              tHorizon.selected(true);
              tHorizon.selectedReason("WATER");
              tComponent.calculated_kffact(tHorizon.kffact());
              tComponent.calculated_lsfact(tHorizon.lsfact());
              tComponent.calculated_claytotal_r(tHorizon.claytotal_r());
              ret_val = true;
            }
          } else {
            filterResults.foundKffact = true;
            tHorizon.selected(true);
            tHorizon.selectedReason("WATER");
            tComponent.calculated_kffact(tHorizon.kffact());
            tComponent.calculated_lsfact(tHorizon.lsfact());
            tComponent.calculated_claytotal_r(tHorizon.claytotal_r());
            ret_val = true;
          }
        }

        tComponent.horizons.put(chkey, tHorizon);

      } else {
        break;
      }
    }

    return ret_val;
  }

  default boolean noFilterComponentData(ResultSet dbResults, MapUnit mapUnit) throws SQLException, ServiceException {
    boolean ret_val = false;
    LinkedHashMap<String, Component> components = mapUnit.components();

    while (dbResults.next()) {
      ret_val = true;
      Component tComponent;

      String cokey = dbResults.getString(TableComponent.COKEY);

      if (components.containsKey(cokey)) {
        tComponent = components.get(cokey);
      } else {
        tComponent = new Component();
        tComponent.cokey(cokey);
        components.put(cokey, tComponent);
      }

      tComponent.setUsedColumns(new ArrayList<>(Arrays.asList(TableComponent.COMPPCT_R_NAME, TableComponent.HYDGRP_NAME,
          TableComponent.TAXORDER_NAME, TableComponent.MUKEY, TableComponent.COKEY, TableComponent.COMPNAME,
          TableComponent.WEI_NAME, TableComponent.TFACT_NAME,
          TableComponent.RS_PROD_H, TableComponent.RS_PROD_L, TableComponent.RS_PROD_R,
          TableComponent.OTHERPH_NAME, TableComponent.LOCALPHASE_NAME, TableComponent.MAJ_COMP_FLAG)));

      tComponent.readFromSQL(dbResults);

      if (mapUnit.aoaArea() > 0.0) {
        tComponent.area_pct((tComponent.comppct_r() / 100.0) * (mapUnit.area() / mapUnit.aoaArea()) * 100.0);
      } else {
        tComponent.area_pct(EvalResult.getDefaultDouble());
      }
      if (mapUnit.area() > 0.0) {
        tComponent.calculated_area(tComponent.comppct_r() / 100.0 * mapUnit.area());
      } else {
        tComponent.calculated_area(EvalResult.getDefaultDouble());
      }
    }

    return ret_val;
  }

  default boolean noFilterComponentHorizonFragData(ResultSet dbResults, HashMap<String, MapUnit> mapUnits, boolean computeLightleWeesieSlope) throws SQLException, ServiceException {
    boolean ret_val = false;

    TableComponent tableComponent = new TableComponent();
    ArrayList<String> componentColumns = tableComponent.getColumnList();
    TableHorizon tableHorizon = new TableHorizon();
    ArrayList<String> horizonColumns = tableHorizon.getColumnList();
    TableFragments tableFragments = new TableFragments();
    ArrayList<String> fragmentColumns = tableFragments.getColumnList();

    while (dbResults.next()) {
      ret_val = true;
      MapUnit tMapUnit;

      String mukey = dbResults.getString(TableComponent.MUKEY);

      //  Setup default used columns for reading in SQL.
//            TableComponent tComponent = new TableComponent();
//            Component.setDefaultUsedColumns(tComponent.getColumnList());
//            TableHorizon tHorizon = new TableHorizon();
//            Horizon.setDefaultUsedColumns(tHorizon.getColumnList());
//            TableFragments tFragments = new TableFragments();
//            Fragments.setDefaultUsedColumns(tFragments.getColumnList());
      if (mapUnits.containsKey(mukey)) {
        tMapUnit = mapUnits.get(mukey);
        readNoFilterComponentHorizonFragData(dbResults, tMapUnit, true, componentColumns, horizonColumns, fragmentColumns);
      } else {
        throw new ServiceException("Invalid Mukey returned in database result set.  Mukey: " + mukey + " was not requested, but was returned.");
      }

    }

    return ret_val;
  }

  default void readNoFilterComponentHorizonFragData(ResultSet dbResults, MapUnit tMapUnit, boolean computeLightleWeesieSlope,
      ArrayList<String> componentColumns, ArrayList<String> horizonColumns, ArrayList<String> fragmentColumns) throws SQLException, ServiceException {
    String lastCokey = "";
    double lambda = 0.0;

    LinkedHashMap<String, Component> components = tMapUnit.components();
    Component tComponent;

    String cokey = dbResults.getString(TableComponent.COKEY);

    if (!cokey.isEmpty()) {
      if (components.containsKey(cokey)) {
        tComponent = components.get(cokey);
      } else {
        tComponent = new Component(dbResults);
        tComponent.cokey(cokey);
        components.put(cokey, tComponent);
        if (EvalResult.testDefaultEmptyString(tComponent.taxorder()) || (null == tComponent.taxorder())) {
          tComponent.setExclude(true, "Taxorder is missing from this component SDM record");
        }
      }

      tComponent.setUsedColumns(componentColumns);
      tComponent.readFromSQL(dbResults);

      if (tMapUnit.aoaArea() > 0.0) {
        tComponent.area_pct((tComponent.comppct_r() / 100.0) * (tMapUnit.area() / tMapUnit.aoaArea()) * 100.0);
      } else {
        tComponent.area_pct(EvalResult.getDefaultDouble());
      }
      if (tMapUnit.area() > 0.0) {
        tComponent.calculated_area(tComponent.comppct_r() / 100.0 * tMapUnit.area());
      } else {
        tComponent.calculated_area(EvalResult.getDefaultDouble());
      }

      double slope_r = tComponent.slope_r();
      tComponent.slope_r(slope_r);
      tComponent.slopegr15((slope_r > 15));
      if (!computeLightleWeesieSlope) {
        lambda = tComponent.slopelenusle_r();
      } else {
        lambda = SoilUtils.calcLightleWeesieSlopeSlopeLength(slope_r, tMapUnit.isPalouse());
      }

      tComponent.length_r(lambda);

      String chkey = dbResults.getString(TableHorizon.CHKEY_NAME);
      Horizon tHorizon = null;
      if (!chkey.isEmpty()) {
        if (tComponent.horizons.containsKey(chkey)) {
          tHorizon = tComponent.horizons.get(chkey);
        } else {
          tHorizon = new Horizon(dbResults);
          tHorizon.chkey(chkey);

          tComponent.horizons.put(chkey, tHorizon);
        }
        tHorizon.setUsedColumns(horizonColumns);
        tHorizon.readFromSQL(dbResults);
        tHorizon.lsfact(SoilUtils.calcLsFactor(slope_r, lambda));

        String chfragskey = dbResults.getString(TableFragments.CHFRAGSKEY);
        Fragments tFragments = null;
        if ((null != chfragskey) && (!chfragskey.isEmpty())) {
          if (tHorizon.fragments.containsKey(chfragskey)) {
            tFragments = tHorizon.fragments.get(chfragskey);
          } else {
            tFragments = new Fragments(dbResults);
            tFragments.chfragskey(chfragskey);
            tHorizon.fragments.put(chfragskey, tFragments);
          }

          tFragments.setUsedColumns(fragmentColumns);
          tFragments.readFromSQL(dbResults);

        }
      }
    }
  }

  default boolean noFilterComponentHorizonFragData(ResultSet dbResults, MapUnit mapUnit, boolean computeLightleWeesieSlope) throws SQLException, ServiceException {
    boolean ret_val = false;
    String lastCokey = "";
    double lambda = 0.0;
    LinkedHashMap<String, Component> components = mapUnit.components();

    while (dbResults.next()) {
      ret_val = true;

      Component tComponent;

      String cokey = dbResults.getString(TableComponent.COKEY);

      if (components.containsKey(cokey)) {
        tComponent = components.get(cokey);
      } else {
        tComponent = new Component();
        tComponent.cokey(cokey);
        components.put(cokey, tComponent);
      }

      String chkey = dbResults.getString(TableHorizon.CHKEY_NAME);
      Horizon tHorizon;
      if (tComponent.horizons.containsKey(chkey)) {
        tHorizon = tComponent.horizons.get(chkey);
      } else {
        tHorizon = new Horizon();
        tHorizon.chkey(chkey);
      }

      tComponent.setUsedColumns(new ArrayList<>(Arrays.asList(TableComponent.COMPPCT_R_NAME, TableComponent.HYDGRP_NAME, TableComponent.SLOPE_R_NAME,
          TableComponent.TAXORDER_NAME, TableComponent.MUKEY, TableComponent.COKEY, TableComponent.COMPNAME,
          TableComponent.WEI_NAME, TableComponent.SLOPELENUSLE_R, TableComponent.TFACT_NAME,
          TableComponent.RS_PROD_H, TableComponent.RS_PROD_L, TableComponent.RS_PROD_R,
          TableComponent.OTHERPH_NAME, TableComponent.LOCALPHASE_NAME, TableComponent.MAJ_COMP_FLAG)));

      tHorizon.setUsedColumns(new ArrayList<>(Arrays.asList(TableHorizon.COKEY, TableHorizon.CHKEY_NAME, TableHorizon.OM_R_NAME,
          TableHorizon.HZTHK_R_NAME, TableHorizon.HZDEPT_R_NAME, TableHorizon.HZDEPB_R_NAME, TableHorizon.KWFACT_NAME,
          TableHorizon.KFFACT_NAME, TableHorizon.SANDTOTAL_R_NAME, TableHorizon.SILTTOTAL_R, TableHorizon.CLAYTOTAL_R,
          TableHorizon.PH1TO1H2O_L, TableHorizon.PH1TO1H2O_R, TableHorizon.PH1TO1H2O_H,
          TableHorizonCalculations.FRAGVOL_R_NAME)));

      tComponent.readFromSQL(dbResults);
      tHorizon.readFromSQL(dbResults);

      double slope_r = tComponent.slope_r();
      tComponent.slope_r(slope_r);
      tComponent.slopegr15((slope_r > 15));

      if (mapUnit.aoaArea() > 0.0) {
        tComponent.area_pct((tComponent.comppct_r() / 100.0) * (mapUnit.area() / mapUnit.aoaArea()) * 100.0);
      } else {
        tComponent.area_pct(EvalResult.getDefaultDouble());
      }
      if (mapUnit.area() > 0.0) {
        tComponent.calculated_area(tComponent.comppct_r() / 100.0 * mapUnit.area());
      } else {
        tComponent.calculated_area(EvalResult.getDefaultDouble());
      }

      if (!computeLightleWeesieSlope) {
        lambda = tComponent.slopelenusle_r();
      } else {
        lambda = SoilUtils.calcLightleWeesieSlopeSlopeLength(slope_r, mapUnit.isPalouse());
      }

      tHorizon.lsfact(SoilUtils.calcLsFactor(slope_r, lambda));
      tComponent.length_r(lambda);

      tComponent.horizons.put(chkey, tHorizon);
    }

    return ret_val;

  }

  public boolean validateComponent(int cokey) throws SQLException;

  ///////////////////////////////
  //  END Interface Functions
  ///////////////////////////////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////////////    
  //  Public inner classes needed for this class or in other code if desired there.
  ///////////////////////////////    
  public class ComponentsHorizonsFrags {

    public String mukey;
    public String cokey;
    public String compname;
    public double comppct_r = NaN;
    public String hydgrp;
    public double slope_r = NaN;
    public double slope_length = NaN;
    public String taxorder;
    public String chkey;
    public double om_r = NaN;
    public double hzthk_r = NaN;
    public boolean hzthk_r_b;  //Set to false if hzthk_r was null/empty
    public double hzdept_r = NaN;
    public double hzdepb_r = NaN;
    public double kwfact = NaN;
    public boolean kwfact_b;  //Set to false if kwfact was null/empty
    public double kffact = NaN;
    public boolean kffact_b;  //Set to false if kffact was null/empty
    public double fragvol_r = NaN;
    public String chfragskey;
  }

  public class FilterResults {

    public boolean foundKffact = false;
    public boolean foundSandLayer = false;
  }
}