AoA.java [src/soils] 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;

import csip.SessionLogger;
import csip.api.server.ServiceException;
import csip.api.server.ServiceResources;
import csip.utils.JSONUtils;
import gisobjects.GISObject;
import gisobjects.GISObjectException;
import static gisobjects.GISObjectFactory.createGISObject;
import static gisobjects.GISObjectFactory.setFixBadGeometries;
import gisobjects.db.GISEngine;
import static gisobjects.db.GISEngineFactory.createGISEngine;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import static soils.db.DBResources.SDM;
import static soils.db.DBResources.SDM_REST;
import soils.db.SOILS_DATA;
import soils.db.SOILS_DB_Factory;
import soils.db.tables.TableComponent;
import soils.db.tables.TableComponentCalculations;
import soils.db.tables.TableMapUnit;
import soils.db.tables.TableMapUnitCalculations;
import soils.filters.DataFilter;
import soils.utils.EvalResult;

/**
 * AoA - This class is meant to be extended to meet your service needs. It
 * contains the basics necessary to create an AoA geometry and find its
 * associated mapunits, components, horizons, and frags. This class is coupled
 * with the SOILS_DATA db object that you wish to use for your implementation.
 * If you need to just store soils information, such as coming from an input
 * JSON object, then you may use or extend this class or the SoilsData class
 * contained in this package instead; it is not coupled with any database.
 *
 * @author <a href="mailto:shaun.case@colostate.edu">Shaun Case</a>
 */
public class AoA extends SoilsData {

  //Constants
  public static final String AOA_ID = "AoAId";
  public static final String AOA_GEOMETRY = "aoa_geometry";
  public static final String NSLP = "aoa_nslp";
  public static final String SRP = "aoa_srp";
  public static final String AOA_DRAINED = "aoa_comp_drained";
  public static final String EXCLUDED_LIST = "Excluded_Soil_Components";
  public static final String CORRECTED_GEOMETRY = "corrected_geometry_WKT";
  public static final String PSLP = "aoa_pslp";
  public static final String PSSRP = "aoa_ssrp";
  public static final String PSARP = "aoa_sarp";

  protected boolean FIX_GEOMETRIES = true;

  protected SessionLogger LOG;
  protected double area = Double.NaN;
  protected ArrayList<String> mukeyList = new ArrayList<>();
  protected GISObject shape;
  protected SOILS_DATA soilsDb;

  protected static ArrayList<String> aoaRequiredInputs = new ArrayList<>();

  protected TableAoA tableAoA = new TableAoA();

  public AoA(ServiceResources resources, boolean useRemoteRest, SessionLogger Log) throws ServiceException, SQLException {
    soilsDb = SOILS_DB_Factory.createEngine(resources.getJDBC(((useRemoteRest) ? SDM_REST : SDM)), Log);
    LOG = Log;
  }

  public AoA(ServiceResources resources, boolean useRemoteRest) throws ServiceException, SQLException {
    soilsDb = SOILS_DB_Factory.createEngine(resources.getJDBC(((useRemoteRest) ? SDM_REST : SDM)), null);
    LOG = null;
  }

  public AoA(Map<String, JSONObject> params) throws ServiceException, JSONException {
    super(JSONUtils.getJSONArrayParam(params, soils.SoilsData.MAP_UNIT_LIST));
    tableAoA.setRequiredColumns(aoaRequiredInputs);
    tableAoA.readValuesFromJSON(params);
  }

  public AoA(ServiceResources resources, boolean useRemoteRest, Connection gisDb, JSONObject aoa_geometry, SessionLogger Log) throws ServiceException, JSONException, GISObjectException, SQLException, IOException {
    tableAoA.setRequiredColumns(aoaRequiredInputs);
    this.LOG = Log;
    this.soilsDb = SOILS_DB_Factory.createEngine(resources.getJDBC(((useRemoteRest) ? SDM_REST : SDM)), Log);
    if ((null != soilsDb) && (null != gisDb)) {
      setFixBadGeometries(FIX_GEOMETRIES);
    }

    if (null != aoa_geometry && (null != gisDb)) {
      GISEngine tEngine = createGISEngine(gisDb);
      shape = createGISObject(aoa_geometry, tEngine);
      shape.makeValid(GISObject.UsePurpose.all_purposes, GISObject.GISType.all_types);
      setArea(shape.areaInAcres());
    } else {
      throw new ServiceException(((null == gisDb) ? ("No valid GIS database connection provided, but input geometry shape exists.  Cannot create shape geometry.") : ("Missing " + AOA_GEOMETRY + " parameter in input JSON.")));
    }
  }
  
  public AoA(ServiceResources resources, boolean useRemoteRest, Connection gisDb, JSONObject aoa_geometry) throws ServiceException, JSONException, GISObjectException, SQLException, IOException {
    tableAoA.setRequiredColumns(aoaRequiredInputs);
    this.LOG = null;
    this.soilsDb = SOILS_DB_Factory.createEngine(resources.getJDBC(((useRemoteRest) ? SDM_REST : SDM)), null);
    if ((null != soilsDb) && (null != gisDb)) {
      setFixBadGeometries(FIX_GEOMETRIES);
    }

    if (null != aoa_geometry && (null != gisDb)) {
      GISEngine tEngine = createGISEngine(gisDb);
      shape = createGISObject(aoa_geometry, tEngine);
      shape.makeValid(GISObject.UsePurpose.all_purposes, GISObject.GISType.all_types);
      setArea(shape.areaInAcres());
    } else {
      throw new ServiceException(((null == gisDb) ? ("No valid GIS database connection provided, but input geometry shape exists.  Cannot create shape geometry.") : ("Missing " + AOA_GEOMETRY + " parameter in input JSON.")));
    }
  }  

  public AoA(SOILS_DATA soilsDb, Connection gisDb, JSONObject aoa_geometry, SessionLogger Log) throws ServiceException, JSONException, GISObjectException, SQLException, IOException {
    tableAoA.setRequiredColumns(aoaRequiredInputs);
    this.LOG = Log;
    this.soilsDb = soilsDb;
    if ((null != soilsDb) && (null != gisDb)) {
      setFixBadGeometries(FIX_GEOMETRIES);
    }

    if (null != aoa_geometry && (null != gisDb)) {
      GISEngine tEngine = createGISEngine(gisDb);
      shape = createGISObject(aoa_geometry, tEngine);
      shape.makeValid(GISObject.UsePurpose.all_purposes, GISObject.GISType.all_types);
      setArea(shape.areaInAcres());
    } else {
      throw new ServiceException(((null == gisDb) ? ("No valid GIS database connection provided, but input geometry shape exists.  Cannot create shape geometry.") : ("Missing " + AOA_GEOMETRY + " parameter in input JSON.")));
    }
  }

  public AoA(SOILS_DATA soilsDb, Connection gisDb, Map<String, JSONObject> params, SessionLogger Log) throws ServiceException, JSONException, GISObjectException, SQLException, IOException {
    super(JSONUtils.getJSONArrayParam(params, soils.SoilsData.MAP_UNIT_LIST));
    tableAoA.setRequiredColumns(aoaRequiredInputs);
    tableAoA.readValuesFromJSON(params);
    this.LOG = Log;
    this.soilsDb = soilsDb;
    if ((null != soilsDb) && (null != gisDb)) {
      setFixBadGeometries(FIX_GEOMETRIES);
    }

    if (params.containsKey(AOA_GEOMETRY) && (null != gisDb)) {
      GISEngine tEngine = createGISEngine(gisDb);
      shape = createGISObject(params.get(AOA_GEOMETRY), tEngine);
      shape.makeValid(GISObject.UsePurpose.all_purposes, GISObject.GISType.all_types);
      setArea(shape.areaInAcres());
    } else {
      throw new ServiceException(((null == gisDb) ? ("No valid GIS database connection provided, but input geometry shape exists.  Cannot create shape geometry.") : ("Missing " + AOA_GEOMETRY + " parameter in input JSON.")));
    }
  }

  public AoA(SOILS_DATA soilsDb, Map<String, JSONObject> params, SessionLogger Log) throws ServiceException, JSONException, GISObjectException, SQLException, IOException {
    super(JSONUtils.getJSONArrayParam(params, soils.SoilsData.MAP_UNIT_LIST));
    tableAoA.setRequiredColumns(aoaRequiredInputs);
    tableAoA.readValuesFromJSON(params);
    this.LOG = Log;
    this.soilsDb = soilsDb;
    if (null != soilsDb) {
      //setGISObjectEngine(createGISEngine(this.soilsDb.getConnection()));
      setFixBadGeometries(FIX_GEOMETRIES);
    }

    if (params.containsKey(AOA_GEOMETRY)) {
      GISEngine tEngine = createGISEngine(soilsDb.getConnection());
      shape = createGISObject(params.get(AOA_GEOMETRY), tEngine);
      shape.makeValid(GISObject.UsePurpose.all_purposes, GISObject.GISType.all_types);
      setArea(shape.areaInAcres());
    } else {
      throw new ServiceException("Missing " + AOA_GEOMETRY + " parameter in input JSON.");
    }
  }

  public AoA(JSONArray mapUnitArray) throws ServiceException {
    super(mapUnitArray);
    area = Double.NaN;
    shape = null;
    soilsDb = null;
    LOG = null;
  }

  public AoA() throws ServiceException {
    area = Double.NaN;
    shape = null;
    soilsDb = null;
    LOG = null;
  }

  public AoA(SOILS_DATA soilsDb, GISObject gisShape, SessionLogger Log) throws GISObjectException, ServiceException, SQLException {
    LOG = Log;
    shape = gisShape;
    setArea(gisShape.areaInAcres());
    this.soilsDb = soilsDb;
    if (null != soilsDb) {
      //setGISObjectEngine(createGISEngine(this.soilsDb.getConnection()));
      setFixBadGeometries(FIX_GEOMETRIES);
    }
  }

  public AoA(SOILS_DATA soilsDb, SessionLogger Log) throws GISObjectException, SQLException {
    LOG = Log;
    area = Double.NaN;
    shape = null;
    this.soilsDb = soilsDb;
    if (null != soilsDb) {
      //setGISObjectEngine(createGISEngine(this.soilsDb.getConnection()));
      setFixBadGeometries(FIX_GEOMETRIES);
    }
  }

  public boolean hasGeomChanged() {
    if (null != shape) {
      return shape.hasChanged();
    }

    return false;
  }

  public String shapeWKT() {
    if (null != shape) {
      return shape.toWKT();
    }

    return "";
  }

  public GISObject shape() {
    return shape;
  }

  public void merge(Map<String, JSONObject> soilComponentMap) throws JSONException, ServiceException {
    TableAoA tempAoATable = new TableAoA();
    tempAoATable.readValuesFromJSON(soilComponentMap);

    this.tableAoA.merge(tempAoATable);

    if (JSONUtils.checkKeyExistsB(soilComponentMap, soils.SoilsData.MAP_UNIT_LIST)) {
      readMapUnitsFromJSON(JSONUtils.getJSONArrayParam(soilComponentMap, soils.SoilsData.MAP_UNIT_LIST), true);
    }
  }

  public synchronized static void setRequiredInputs(ArrayList<String> aoaInputs, ArrayList<String> mapUnitInputs, ArrayList<String> componentInputs, ArrayList<String> horizonInputs) {
    setRequiredInputs(mapUnitInputs, componentInputs, horizonInputs);
    aoaRequiredInputs.clear();
    if (null != aoaRequiredInputs) {
      aoaRequiredInputs.addAll(aoaInputs);
    }
  }

  public void computeTillageLayerClayTotals() {
    for (MapUnit tMapUnit : map_units.values()) {
      tMapUnit.computeTillageLayerClayTotals();
    }
  }

  public String aoa_id() {
    return tableAoA.aoa_id();
  }

  public void nutrient_slp(String nslp) {
    tableAoA.aoa_nslp(nslp);
  }

  public String nutrient_slp() {
    return tableAoA.aoa_nslp();
  }

  public void srp(String srp) {
    tableAoA.aoa_srp(srp);
  }

  public String srp() {
    return tableAoA.aoa_srp();
  }

  public String pslp() {
    return tableAoA.aoa_pslp();
  }

  public void pslp(String pslp) {
    tableAoA.aoa_pslp(pslp);
  }

  public String pssrp() {
    return tableAoA.aoa_pssrp();
  }

  public void pssrp(String pssrp) {
    tableAoA.aoa_pssrp(pssrp);
  }

  public String psarp() {
    return tableAoA.aoa_psarp();
  }

  public void psarp(String psarp) {
    tableAoA.aoa_psarp(psarp);
  }

  public void isDrained(boolean value) {
    tableAoA.aoa_is_drained(value);
  }

  public boolean isDrained() {
    return tableAoA.aoa_is_drained();
  }

  /**
   * Gets all of the comonth table data for each component of each mapunit
   * already existing in the AoA. Be sure to find the mapunits and component
   * data for any given input shape for this class before calling this function.
   * This function also computes the maximum values for the flodfreqcl and
   * pondfreqcl for each component and its associated monthly records in the
   * comonth table.
   *
   * @throws ServiceException
   */
  public void getMaxFloodingPondingData() throws ServiceException {
    if (null != soilsDb) {
      if (!soilsDb.findAllComonthData(map_units)) {
        throw new ServiceException("Unable to query SDM database for flooding and ponding information for these mapunits");
      }
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }

  }

  public void getSalinitySodicity() throws ServiceException {
    if (null != soilsDb) {
      if ((null != map_units) && (!map_units.isEmpty())) {
        for (MapUnit mapUnit : map_units.values()) {
          if (!mapUnit.isExcluded()) {
            for (Component component : mapUnit.components().values()) {
              if (!component.isExcluded()) {
                component.computeSalinityAndSodicity();
              }
            }
          }
        }
      } else {
        throw new ServiceException("Unable to compute soil horizon salinity and sodicity values.  No mapunit data has been collected yet.");
      }

    } else {
      throw new ServiceException("Unable to compute soil horizon salinity and sodicity values.  No soils database connection has been established.");
    }

  }

  public void computePestSARP() {
    double psarp_product = 0.0;
    double calc_area = 0.0;

    for (MapUnit mapUnit : map_units.values()) {
      if (!mapUnit.isExcluded()) {
        for (Component component : mapUnit.components().values()) {
          if (!component.isExcluded()) {
            component.computePestSARP(isDrained());
            psarp_product += (component.psarp_number() * component.calculated_area());
            calc_area += component.calculated_area();
          }
        }
      }
    }

    double aoa_psarp_fract = ((calc_area != 0.0) ? (psarp_product / calc_area) : 0.0);
    if (aoa_psarp_fract <= 1.50) {
      psarp("LOW");
    } else if (aoa_psarp_fract > 1.50 && aoa_psarp_fract <= 2.50) {
      psarp("INTERMEDIATE");
    } else {
      psarp("HIGH");
    }
  }

  public void computePestSSRP() {
    double pssrp_product = 0.0;
    double calc_area = 0.0;

    for (MapUnit mapUnit : map_units.values()) {
      if (!mapUnit.isExcluded()) {
        for (Component component : mapUnit.components().values()) {
          if (!component.isExcluded()) {
            component.computePestSSRP(isDrained());
            pssrp_product += (component.pssrp_number() * component.calculated_area());
            calc_area += component.calculated_area();
          }
        }
      }
    }

    double aoa_pssrp_fract = ((calc_area != 0.0) ? (pssrp_product / calc_area) : 0.0);
    if (aoa_pssrp_fract <= 1.50) {
      pssrp("LOW");
    } else if (aoa_pssrp_fract > 1.50 && aoa_pssrp_fract <= 2.50) {
      pssrp("INTERMEDIATE");
    } else {
      pssrp("HIGH");
    }
  }

  public void computePestSLP() {
    double cum_pslp_product = 0.0;
    double aoa_pslp_fract;
    double calc_area = 0.0;

    for (MapUnit mapUnit : map_units.values()) {
      if (!mapUnit.isExcluded()) {
        for (Component component : mapUnit.components().values()) {
          if (!component.isExcluded()) {
            component.computePestSLP(isDrained());
            cum_pslp_product += (component.pslp_number() * component.calculated_area());
            calc_area += component.calculated_area();
          }
        }
      }
    }

    aoa_pslp_fract = ((calc_area != 0.0) ? (cum_pslp_product / calc_area) : 0.0);

    if (aoa_pslp_fract <= 1.50) {
      pslp("VERY LOW");
    } else if (aoa_pslp_fract > 1.50 && aoa_pslp_fract <= 2.50) {
      pslp("LOW");
    } else if (aoa_pslp_fract > 2.50 && aoa_pslp_fract <= 3.50) {
      pslp("INTERMEDIATE");
    } else {
      pslp("HIGH");
    }

  }

  public void computeComponentNSLP() throws ServiceException {
    for (MapUnit mapUnit : map_units.values()) {
      if (!mapUnit.isExcluded()) {
        for (Component component : mapUnit.components().values()) {
          if (!component.isExcluded()) {
            component.computeNSLP();
          }
        }
      }
    }
  }

  public void computeAoANSLP() {
    double cumNslpProduct = 0;
    double aoaArea = 0;

    for (MapUnit mapUnit : map_units.values()) {
      if (!mapUnit.isExcluded()) {
        for (Component component : mapUnit.components().values()) {
          if (!component.isExcluded()) {
            cumNslpProduct += (component.calculated_nslp_number() * component.calculated_area());
            aoaArea += component.calculated_area();
          }
        }
      }
    }
    double aoaNslpFract = ((aoaArea != 0.0) ? (cumNslpProduct / aoaArea) : 0.0);
    if (aoaNslpFract <= 0.50) {
      nutrient_slp("LOW");
    } else if (aoaNslpFract > 0.50 && aoaNslpFract <= 1.50) {
      nutrient_slp("MODERATE");
    } else if (aoaNslpFract > 1.50 && aoaNslpFract <= 2.50) {
      nutrient_slp("MODERATELY HIGH");
    } else {
      nutrient_slp("HIGH");
    }
  }

  public void computeComponentSedNutSRP(boolean isDrained) throws ServiceException {
    for (MapUnit mapUnit : map_units.values()) {
      if (!mapUnit.isExcluded()) {
        for (Component component : mapUnit.components().values()) {
          if (!component.isExcluded()) {
            component.computeSedNutSRP(isDrained);
          }
        }
      }
    }
  }

//Compute weighted average nutrient soil leaching potential for the AoA
  public void computeAoASRP() {
    double srpProduct = 0;
    double aoaArea = 0;

    for (MapUnit mapUnit : map_units.values()) {
      if (!mapUnit.isExcluded()) {
        for (Component component : mapUnit.components().values()) {
          if (!component.isExcluded()) {
            srpProduct += (component.calculated_srp_number() * component.calculated_area());
            aoaArea += component.calculated_area();
          }
        }
      }
    }
    double aoaSrpFract = (aoaArea > 0.0) ? (srpProduct / aoaArea) : 0.0;

    if (aoaSrpFract <= 0.50) {
      srp("LOW");
    } else if (aoaSrpFract > 0.50 && aoaSrpFract <= 1.50) {
      srp("MODERATE");
    } else if (aoaSrpFract > 1.50 && aoaSrpFract <= 2.50) {
      srp("MODERATELY HIGH");
    } else {
      srp("HIGH");
    }

  }

  protected void setMukeyList() {
    if (!mukeyList.isEmpty()) {
      mukeyList.clear();
    }

    map_units.keySet().stream().forEach((tMukey) -> {
      mukeyList.add(tMukey);
    });
  }

  /**
   * Fills in the entire structures for all mapunits already stored in this AoA
   * object. Consequently, this will complete all Component, Horizon, Fragments,
   * and Texture data objects under each mapunit object stored here.<p>
   * <b>This function does not apply any filtering to the database data. If you
   * want the data filtered by soils SME filtering logic use one of the other
   * functions in this AoA class.</b>
   *
   * @return
   * @throws ServiceException
   */
  public boolean findAllComponentsData_Basic() throws ServiceException {
    boolean ret_val;

    if (null != soilsDb) {
      ret_val = soilsDb.findAllBasicComponentHorizonFragTextureData(map_units);
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }

    return ret_val;
  }

  public boolean findAllComponents() throws ServiceException {
    boolean ret_val;

    if (null != soilsDb) {
      ret_val = soilsDb.findComponentsHorizonsFragsForMukeyList(map_units, true, "NONE");
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }

    return ret_val;
  }

  public boolean findAllComponentsApplyWaterFilter() throws ServiceException {
    boolean ret_val;
    boolean leftEarly = false;

    if (null != soilsDb) {
      for (String mukey : map_units.keySet()) {
        if (!soilsDb.findComponentsHorizonsFragsByMukey(map_units.get(mukey), true, "WATER")) {
          //leftEarly = true;
          map_units.get(mukey).setExclude(true, "No soil components for this mapunit were found having a sufficient set of parameters required for NRCS quality assessment (water)");
        }
      }
      ret_val = !leftEarly;
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }

    return ret_val;
  }

  public boolean findAllComponentsApplyWindFilter() throws ServiceException {
    boolean ret_val;
    boolean leftEarly = false;

    if (null != soilsDb) {
      for (String mukey : map_units.keySet()) {
        if (!soilsDb.findComponentsHorizonsFragsByMukey(map_units.get(mukey), true, "WIND")) {
          //leftEarly = true;                    
          map_units.get(mukey).setExclude(true, "No soil components for this mapunit were found having a sufficient set of parameters required for NRCS quality assessment (wind)");
          //map_units.get(mukey)
        }
      }
      ret_val = !leftEarly;
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }

    return ret_val;
  }

  public boolean findAllComponentsApplyWindWaterFilters() throws ServiceException {
    boolean ret_val;
    boolean leftEarly = false;

    if (null != soilsDb) {
      if (findAllComponentsApplyWaterFilter()) {
        if (!findAllComponentsApplyWindFilter()) {
          leftEarly = true;
        }
      } else {
        leftEarly = true;
      }

      ret_val = !leftEarly;
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }

    return ret_val;
  }

  public boolean findAllComponentCropYieldsByCropNameOrUnit(ArrayList<String> cropName, ArrayList<String> yldUnits) throws ServiceException {
    boolean ret_val;

    if (null != soilsDb) {
      ret_val = soilsDb.findCoCropYieldsByCropNameOrUnit(map_units, cropName, yldUnits);
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }

    return ret_val;
  }

  public boolean findAllMapunitCropYieldsByCropNameOrUnit(ArrayList<String> cropName, ArrayList<String> yldUnits) throws ServiceException {
    boolean ret_val;

    if (null != soilsDb) {
      ret_val = soilsDb.findMuCropYieldsByCropNameOrUnit(map_units, cropName, yldUnits);
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }

    return ret_val;
  }

  public boolean findComponentSoilMoistures() throws ServiceException {
    boolean ret_val = false;

    if (null != soilsDb) {
      ret_val = soilsDb.findComponentSoilMoisture(map_units);
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }

    return ret_val;
  }

  public boolean findIntersectedMapUnits() throws ServiceException, GISObjectException {
    boolean ret_val = false;

    if (null != soilsDb) {
      if (!map_units.isEmpty()) {
        map_units.clear();
      }
      map_units.putAll(soilsDb.findMapUnitsForGISObject(shape));
      setMukeyList();

      if (map_units.isEmpty()) {
        ret_val = true;
      }
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }
    return ret_val;
  }

  public boolean findEcoClasses() throws ServiceException {
    boolean ret_val = false;

    if (null != soilsDb) {
      ret_val = soilsDb.findEcoClassForMukeyList(map_units);
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }

    return ret_val;
  }

  public boolean findIntersectedMapUnitsWithAreas() throws ServiceException, GISObjectException {
    boolean ret_val = false;

    if (null != soilsDb) {
      if (!map_units.isEmpty()) {
        map_units.clear();
      }
      map_units.putAll(soilsDb.findMapUnitsForGISObject(shape, true, false));
      setMukeyList();

      if (!map_units.isEmpty()) {
        ret_val = true;
      }
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }
    return ret_val;
  }

  public boolean findIntersectedMapUnitsWithAreasAndShapes() throws ServiceException, GISObjectException {
    boolean ret_val = false;

    if (null != soilsDb) {
      if (!map_units.isEmpty()) {
        map_units.clear();
      }
      map_units.putAll(soilsDb.findMapUnitsForGISObject(shape, true, true));
      setMukeyList();

      if (!map_units.isEmpty()) {
        ret_val = true;
      }
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }
    return ret_val;
  }

  public boolean findIntersectedSoilsWithAreasAndShapesUsingFilter(DataFilter dataFilter) throws Exception {
    if (null != soilsDb) {
      if (!map_units.isEmpty()) {
        map_units.clear();
      }
      map_units.putAll(soilsDb.findMapUnitsForGISObject(shape, true, true));
      setMukeyList();

      if (!map_units.isEmpty()) {
        findAllComponentsData_Basic();
        dataFilter.filter(map_units);
        return true;
      }
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }
    return false;
  }

  public boolean findMapUnitsWithAreasAndShapesByMuPolygonKey(String mupolygonkey, GISEngine gisEngine) throws ServiceException, GISObjectException {
    boolean ret_val = false;

    if (null != soilsDb) {
      if (!map_units.isEmpty()) {
        map_units.clear();
      }
      MapUnit mapUnit = soilsDb.findMapUnitsByMupolygonKey(mupolygonkey, gisEngine, true, true);
      if (null == mapUnit) {
        throw new ServiceException("Could not find any mapunit information for that mupolygonkey");
      }

      map_units.put(mapUnit.mukey(), mapUnit);
      setMukeyList();

      if (!map_units.isEmpty()) {
        ret_val = true;
      }
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }
    return ret_val;
  }

  /**
   * Gets all components for this mapunit. No filtering is performed, and no
   * components are excluded, therefore.
   *
   * @param mukey
   * @throws ServiceException
   */
  public void findComponentsByMapunit(String mukey) throws ServiceException {
    MapUnit tMapUnit;

    tMapUnit = soilsDb.findMukeyData(mukey);

    if (null != tMapUnit) {
      map_units.put(mukey, tMapUnit);
    } // ELSE:  If the lookup for the mukey fails, an exception will be thrown.  No need to have an else here.

    //  Now get the component information
    if (!soilsDb.findComponentsByMukey(tMapUnit)) {
      throw new ServiceException("Could not find component data for this mapunit, " + mukey);
    }
  }

  public void findMapunitsByAreaSymbol(String areaSymbol) throws ServiceException {
    HashMap<String, MapUnit> tMapUnits;

    tMapUnits = soilsDb.findMukeyDataByAreasymbol(areaSymbol);

    if (null != tMapUnits) {
      for (String key : tMapUnits.keySet()) {
        map_units.put(key, tMapUnits.get(key));
      }
    } // ELSE:  If the lookup for the mukey fails, an exception will be thrown.  No need to have an else here.        
  }

  public void findAllTextureData() throws ServiceException {
    if (!soilsDb.findAllTextureData(map_units)) {
      throw new ServiceException("Could not find any texture data for these mapunits.");
    }
  }

  public double getArea() {
    return area;
  }

  public void setArea(double area) {
    this.area = area;
    tableAoA.aoa_area(area);
  }

  public Connection getConnection() throws ServiceException {
    Connection ret_val;
    if (null != soilsDb) {
      ret_val = soilsDb.getConnection();
    } else {
      throw new ServiceException("No soilsDb connection present in AoA object.");
    }
    return ret_val;
  }

  public void setGISObject(GISObject gisShape) throws ServiceException, GISObjectException {
    if ((null != gisShape) && (null != soilsDb)) {
      shape = gisShape;
      setArea(gisShape.areaInAcres());
    } else {
      throw new ServiceException("Attempt to use a NULL value for " + ((null == soilsDb) ? "soilsDb" : "shape") + " in AoA class.");
    }
  }

  @Override
  public void toJSON(boolean selectedHorizonsOnly, JSONArray outArray) throws JSONException {
    tableAoA.toJSON(outArray);
    JSONArray mapUnitArray = new JSONArray();
    super.toJSON(selectedHorizonsOnly, mapUnitArray);
    outArray.put(JSONUtils.data(AoA.MAP_UNIT_LIST, mapUnitArray));
  }

  /**
   * Use this ONLY to produce output JSON for listing the excluded mapUnits, AND
   * do it last. This function will mark map units as excluded if ANY of their
   * components are excluded, thus, this should only be done after all analysis,
   * and calculations have been completed and the used map unit component
   * combinations have already been output to the result JSON.
   *
   * @return
   * @throws JSONException
   * @throws ServiceException
   */
  public JSONArray getExcluded() throws JSONException, ServiceException {
    //Print out excluded MapUnits here, if neccessary.
    JSONArray excludeds = new JSONArray();
    JSONObject excludedObject = new JSONObject();
    for (MapUnit mapUnit : map_units.values()) {
      if (mapUnit.isExcluded() || mapUnit.hasExcludedComponents()) {
        if (EvalResult.testDefaultDouble(mapUnit.aoaArea()) && mapUnit.aoaArea() > 0.0) {
          mapUnit.area_pct((mapUnit.area() / mapUnit.aoaArea()) * 100.0);
        }
        if (!mapUnit.isExcluded() && mapUnit.hasExcludedComponents()) {
          mapUnit.setExclude(true, "Components of this map unit were excluded.  Please see below for the reasons each was excluded.");
        }

        mapUnit.setOutputColumns(new ArrayList<>(Arrays.asList(TableMapUnit.MUKEY, TableMapUnit.MUNAME, TableMapUnitCalculations.AREA_NAME, TableMapUnitCalculations.AREA_PCT)));
        mapUnit.setComponentOutputColumns(new ArrayList<>(Arrays.asList(TableComponent.COKEY, TableComponent.COMPNAME, TableComponentCalculations.AREA_PCT_NAME)));

        excludeds.put(mapUnit.toJSON(false, true));
      }
    }

    return excludeds;
  }
}